├── 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 | --------------------------------------------------------------------------------