storybookjs / storybook

Storybook is the industry standard workshop for building, documenting, and testing UI components in isolation
https://storybook.js.org
MIT License
83.4k stars 9.12k forks source link

Web components template should be using web components not lit-html #18351

Open itssumitrai opened 2 years ago

itssumitrai commented 2 years ago

Describe the bug

When you try to create a web components storybook, I was surprised to see lit-html in there. While I do understand lit-html helps you in making web components, my expectation would be vanilla web components. Even svelte, and other libs can make web components, but thats not the expectation.

web components !== lit-html

And I don't want to be pulling in lit-html into my project when I am trying to use web components. I would rather expect a separate lit template for such a thing, which makes sense if your project is already using lit.

To Reproduce

Create a storybook project with type web component using storybook version 6.5

System

System:
    OS: macOS 12.3.1
    CPU: (16) x64 Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz
  Binaries:
    Node: 16.14.0 - /usr/local/bin/node
    npm: 8.3.1 - /usr/local/bin/npm
  Browsers:
    Chrome: 101.0.4951.64
    Firefox: 100.0.1
    Safari: 15.4
  npmPackages:
    @storybook/addon-actions: ^6.5.5 => 6.5.5 
    @storybook/addon-essentials: ^6.5.5 => 6.5.5 
    @storybook/addon-links: ^6.5.5 => 6.5.5 
    @storybook/builder-webpack5: ^6.5.5 => 6.5.5 
    @storybook/manager-webpack5: ^6.5.5 => 6.5.5 
    @storybook/web-components: ^6.5.5 => 6.5.5 

Additional context Please use HTMLElement for web component template, thats the standard way of making web components

steveblue commented 1 year ago

I concur with this sentiment. lit-html should not be a dependency of a story which features custom elements coded using only browser spec.

EisenbergEffect commented 1 year ago

Just saw this post and wanted to chime in.

I'm the architecture lead for Microsoft's FAST Web Components. So, we were also a bit surprised to find out that the Web Components setup didn't work for us. We're now building our own custom solution on Storybook for FAST as a result.

It would be great if we could all put our heads together and see if we can maybe decouple things a little bit so that it's easier for folks to build Web Components in whatever way they want. We're happy to share what we've had to work on to get things working on our side and provide feedback to improve the overall approach.

ninofiliu commented 1 month ago

For anyone wondering, using lit is the current default but is not required. This:

// MyCustomElement.ts
export class MyCustomElement extends HTMLElement {
  constructor() {
    super();
  }
}
// MyCustomElement.stories.ts
import type { Meta, StoryObj } from "@storybook/web-components";
import { MyCustomElement } from "./MyCustomElement";

customElements.define("my-custom-element", MyCustomElement);

type Props = {};

export default {
  title: "MyCustomElement",
  component: "my-custom-element",
  render: (args: any) =>
    `<my-custom-element><pre>${JSON.stringify(args)}</pre></my-custom-element>`,
} satisfies Meta<Props>;

type Story = StoryObj<Props>;

export const Default: Story = {
  args: {
    hello: "world",
  },
};

renders as

image

and yes, the "hello" control is interactive (updates reflect in the preview)

I don't know how performant this is, I'm afraid it does an .innerHTML = ... somewhere instead of being clever like lit, but it works for my use case.

ninofiliu commented 1 month ago

I also found that writing the render function as such also works

export default {
  render: (args: any) => {
    const pre = document.createElement('pre');
    pre.innerHTML = JSON.stringify(args);
    return pre;
  }
}
ninofiliu commented 1 month ago

And that the previous element can be retrieved on update so as to be more performant and use possible "update" animations instead of rebuilding everything from scratch

type Props = {
  foo: string;
};

const meta: Story<Props> = {
  render: (props, context) => {
    const elt = context.canvasElement.querySelector<MyCustomElement>('my-custom-element')
                ?? new MyCustomElement();
    elt.foo = props.foo;
    return elt;
  }
};