├── src
├── index.ts
├── vite-env.d.ts
├── my-element.stories.ts
└── my-element.ts
├── types
├── index.d.ts
└── my-element.d.ts
├── tsconfig.node.json
├── .storybook
├── preview.js
└── main.js
├── vite.config.ts
├── .gitignore
├── index.html
├── README.md
├── tsconfig.json
└── package.json
/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './my-element'
2 |
--------------------------------------------------------------------------------
/types/index.d.ts:
--------------------------------------------------------------------------------
1 | export * from './my-element';
2 |
--------------------------------------------------------------------------------
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "esnext",
5 | "moduleResolution": "node"
6 | },
7 | "include": ["vite.config.ts"]
8 | }
9 |
--------------------------------------------------------------------------------
/.storybook/preview.js:
--------------------------------------------------------------------------------
1 | export const parameters = {
2 | actions: { argTypesRegex: "^on[A-Z].*" },
3 | controls: {
4 | matchers: {
5 | color: /(background|color)$/i,
6 | date: /Date$/,
7 | },
8 | },
9 | }
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 |
3 | // https://vitejs.dev/config/
4 | export default defineConfig({
5 | build: {
6 | lib: {
7 | fileName: 'index',
8 | entry: 'src/index.ts',
9 | formats: ['es'],
10 | },
11 | rollupOptions: {
12 | // external: /^lit/
13 | },
14 | },
15 | })
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Vite + Lit App
7 |
8 |
9 |
10 |
11 | This is child content
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Vite, Lit and Storybook for standalone web component development
2 |
3 | This is an article from my blog
4 |
5 | https://leon.id/articles/web-components/2022-02-vite-lit-storybook
6 |
7 | ## Install
8 |
9 | ```bash
10 | npm install
11 | ```
12 |
13 | ## Develop
14 |
15 | ```bash
16 | npm run dev
17 | ```
18 |
19 | ## Storybook
20 |
21 | ```bash
22 | npm run storybook
23 | ```
24 |
25 | ## Build
26 |
27 | ```bash
28 | npm run build
29 | ```
30 |
--------------------------------------------------------------------------------
/src/my-element.stories.ts:
--------------------------------------------------------------------------------
1 | import { Meta, StoryObj } from '@storybook/web-components'
2 | import { html } from 'lit'
3 |
4 | import './my-element'
5 |
6 | export default {
7 | title: 'My Element',
8 | parameters: {
9 | layout: 'centered',
10 | },
11 | argTypes: {
12 | onOpen: { action: 'onClick' },
13 | },
14 | render: (args) => html``,
15 | } as Meta
16 |
17 | export const Default: StoryObj = {
18 | name: 'Default',
19 | args: {
20 | name: 'Lit',
21 | },
22 | }
23 |
--------------------------------------------------------------------------------
/types/my-element.d.ts:
--------------------------------------------------------------------------------
1 | import { LitElement } from 'lit';
2 | /**
3 | * An example element.
4 | *
5 | * @slot - This element has a slot
6 | * @csspart button - The button
7 | */
8 | export declare class MyElement extends LitElement {
9 | static styles: import("lit").CSSResult;
10 | /**
11 | * The name to say "Hello" to.
12 | */
13 | name: string;
14 | /**
15 | * The number of times the button has been clicked.
16 | */
17 | count: number;
18 | render(): import("lit-html").TemplateResult<1>;
19 | private _onClick;
20 | foo(): string;
21 | }
22 | declare global {
23 | interface HTMLElementTagNameMap {
24 | 'my-element': MyElement;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/.storybook/main.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
3 | addons: ['@storybook/addon-links', '@storybook/addon-essentials'],
4 | framework: '@storybook/web-components',
5 | core: {
6 | builder: 'storybook-builder-vite',
7 | },
8 | async viteFinal(config, { configType }) {
9 | // customize the Vite config here
10 | config.optimizeDeps.include = [
11 | ...(config.optimizeDeps?.include ?? []),
12 | '@storybook/web-components',
13 | ]
14 | config.optimizeDeps.exclude = [...(config.optimizeDeps?.exclude ?? []), 'lit', 'lit-html']
15 |
16 | // return the customized config
17 | return config
18 | },
19 | }
20 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "esnext",
4 | "lib": ["es2017", "dom", "dom.iterable"],
5 | "declaration": true,
6 | "emitDeclarationOnly": true,
7 | "outDir": "./types",
8 | "strict": true,
9 | "noUnusedLocals": true,
10 | "noUnusedParameters": true,
11 | "noImplicitReturns": true,
12 | "noFallthroughCasesInSwitch": true,
13 | "moduleResolution": "node",
14 | "allowSyntheticDefaultImports": true,
15 | "experimentalDecorators": true,
16 | "forceConsistentCasingInFileNames": true,
17 | "useDefineForClassFields": false
18 | },
19 | "include": ["src/**/*.ts"],
20 | "exclude": ["src/**/*.stories.ts"],
21 | "references": [{ "path": "./tsconfig.node.json" }]
22 | }
23 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "my-webcomponents",
3 | "version": "1.0.0",
4 | "main": "dist/index.es.js",
5 | "exports": {
6 | ".": "./dist/index.es.js"
7 | },
8 | "types": "types/index.d.ts",
9 | "files": [
10 | "dist",
11 | "types"
12 | ],
13 | "scripts": {
14 | "dev": "vite",
15 | "build": "tsc && vite build",
16 | "storybook": "start-storybook -p 6006",
17 | "build-storybook": "build-storybook"
18 | },
19 | "devDependencies": {
20 | "lit": "^2.0.2",
21 | "@babel/core": "^7.17.2",
22 | "@storybook/addon-actions": "^6.4.19",
23 | "@storybook/addon-essentials": "^6.4.19",
24 | "@storybook/addon-links": "^6.4.19",
25 | "@storybook/web-components": "^6.4.19",
26 | "babel-loader": "^8.2.3",
27 | "storybook-builder-vite": "^0.1.15",
28 | "typescript": "^4.5.4",
29 | "vite": "^2.8.0"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/my-element.ts:
--------------------------------------------------------------------------------
1 | import { html, css, LitElement } from 'lit'
2 | import { customElement, property } from 'lit/decorators.js'
3 |
4 | /**
5 | * An example element.
6 | *
7 | * @slot - This element has a slot
8 | * @csspart button - The button
9 | */
10 | @customElement('my-element')
11 | export class MyElement extends LitElement {
12 | static styles = css`
13 | :host {
14 | display: block;
15 | border: solid 1px gray;
16 | padding: 16px;
17 | max-width: 800px;
18 | }
19 | `
20 |
21 | /**
22 | * The name to say "Hello" to.
23 | */
24 | @property()
25 | name = 'World'
26 |
27 | /**
28 | * The number of times the button has been clicked.
29 | */
30 | @property({ type: Number })
31 | count = 0
32 |
33 | render() {
34 | return html`
35 | Hello, ${this.name}!
36 |
37 |
38 | `
39 | }
40 |
41 | private _onClick() {
42 | this.count++
43 | }
44 |
45 | foo(): string {
46 | return 'foo'
47 | }
48 | }
49 |
50 | declare global {
51 | interface HTMLElementTagNameMap {
52 | 'my-element': MyElement
53 | }
54 | }
55 |
--------------------------------------------------------------------------------