├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .prettierignore ├── .prettierrc ├── .storybook ├── code-bubble-setup.js ├── main.ts ├── preview.ts └── styles.css ├── .vscode └── extensions.json ├── LICENSE ├── README.md ├── custom-elements-manifest.config.js ├── eslint.config.js ├── package.json ├── plop-templates ├── component.definition.ts.hbs ├── component.docs.hbs ├── component.stories.ts.hbs ├── component.styles.ts.hbs ├── component.test.ts.hbs └── component.ts.hbs ├── plopfile.js ├── rollup.config.js ├── src └── components │ └── button │ ├── button.mdx │ ├── button.stories.ts │ ├── button.styles.ts │ ├── button.test.ts │ ├── button.ts │ └── index.ts ├── tsconfig.json └── web-test-runner.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | 132 | # Custom 133 | /cdn 134 | /eslint 135 | /public 136 | /react 137 | /types 138 | custom-elements.json 139 | vscode.css-custom-data.json 140 | vscode.html-custom-data.json 141 | web-types.json -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | cdn 3 | dist 4 | eslint 5 | plop-templates 6 | public 7 | react 8 | types -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "arrowParens": "avoid" 4 | } 5 | -------------------------------------------------------------------------------- /.storybook/code-bubble-setup.js: -------------------------------------------------------------------------------- 1 | import { CodeBubble } from 'code-bubble'; 2 | import packageJson from '../package.json'; 3 | 4 | // This script sets up the CodeBubble configuration for live code examples in Storybook. It fetches the necessary library data and configures the sandbox environment for both HTML and React examples. 5 | // https://www.npmjs.com/package/code-bubble 6 | (async () => { 7 | async function fetchLibData(url) { 8 | const baseUrl = window.location.href.includes('localhost') 9 | ? '' 10 | : window.location.href.split('/iframe')[0]; 11 | 12 | try { 13 | const response = await fetch(baseUrl + url); // Make the request 14 | return await response.text(); // Extract JSON data 15 | } catch (error) { 16 | console.error('Error fetching data:', url, error); 17 | return null; // Handle the error gracefully 18 | } 19 | } 20 | 21 | /** @type { import('code-bubble').CodeBubbleConfig } */ 22 | new CodeBubble({ 23 | sandbox: 'stackblitz', 24 | component: { 25 | frameworkButtons: { 26 | label: framework => 27 | ({ 28 | html: 'HTML', 29 | tsx: 'React', 30 | })[framework] || framework, 31 | }, 32 | }, 33 | sandboxConfig: { 34 | /** StackBlitz sandbox configuration */ 35 | stackBlitz: { 36 | html: { 37 | project: { 38 | title: packageJson.name + ' Demo', 39 | description: 'A live web component demo', 40 | files: { 41 | [`libs/${packageJson.name}/index.js`]: 42 | await fetchLibData('/html/index.js'), 43 | }, 44 | }, 45 | exampleTemplate: { 46 | fileName: 'index.html', 47 | template: ` 48 | 49 | 50 | 51 | ${packageJson.name} Demo 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | %example% 60 | 61 | `, 62 | }, 63 | }, 64 | tsx: { 65 | project: { 66 | title: packageJson.name + ' React Demo', 67 | description: 'A live react/web component demo', 68 | files: { 69 | [`libs/${packageJson.name}/react/index.js`]: 70 | await fetchLibData('/react/index.js'), 71 | [`libs/${packageJson.name}/react/index.d.ts`]: 72 | await fetchLibData('/react/index.d.ts'), 73 | 'src/index.tsx': `import { createRoot } from "react-dom/client"; 74 | import App from "./App"; 75 | 76 | const rootElement = createRoot(document.getElementById("root")); 77 | rootElement.render();`, 78 | [`libs/${packageJson.name}/package.json`]: `{ 79 | "name": "${packageJson.name}", 80 | "version": "${packageJson.version}", 81 | "description": "A live react/web component demo", 82 | "main": "index.js" 83 | }`, 84 | 'package.json': `{ 85 | "name": "react-sandbox", 86 | "version": "1.0.0", 87 | "main": "src/index.tsx", 88 | "dependencies": { 89 | "react": "^18.2.0", 90 | "react-dom": "^18.2.0", 91 | "${packageJson.name}": "file:./libs/${packageJson.name}" 92 | }, 93 | "scripts": { 94 | "dev": "vite", 95 | "build": "vite build", 96 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", 97 | "preview": "vite preview" 98 | }, 99 | "devDependencies": { 100 | "@types/react": "^18.3.3", 101 | "@types/react-dom": "^18.3.0", 102 | "@typescript-eslint/eslint-plugin": "^7.15.0", 103 | "@typescript-eslint/parser": "^7.15.0", 104 | "@vitejs/plugin-react": "^4.3.1", 105 | "eslint": "^8.57.0", 106 | "eslint-plugin-react-hooks": "^4.6.2", 107 | "eslint-plugin-react-refresh": "^0.4.7", 108 | "typescript": "^5.2.2", 109 | "vite": "^5.3.2" 110 | } 111 | }`, 112 | 'tsconfig.json': `{ 113 | "include": ["./src/**/*"], 114 | "compilerOptions": { 115 | "strict": false, 116 | "esModuleInterop": true, 117 | "lib": ["ESNext", "DOM", "DOM.Iterable"], 118 | "jsx": "react-jsx", 119 | "baseUrl": "." 120 | } 121 | }`, 122 | 'vite.config.ts': `import { defineConfig } from 'vite'; 123 | import react from '@vitejs/plugin-react'; 124 | 125 | // https://vitejs.dev/config/ 126 | export default defineConfig({ 127 | plugins: [react()], 128 | });`, 129 | 'index.html': ` 130 | 131 | 132 | 133 | 134 | 135 | React code example 136 | 137 | 138 |
139 | 140 | 141 | `, 142 | }, 143 | }, 144 | exampleTemplate: { 145 | fileName: 'src/App.tsx', 146 | template: `%example%`, 147 | }, 148 | }, 149 | }, 150 | }, 151 | }); 152 | })(); 153 | -------------------------------------------------------------------------------- /.storybook/main.ts: -------------------------------------------------------------------------------- 1 | import type { StorybookConfig } from '@storybook/web-components-vite'; 2 | 3 | const config: StorybookConfig = { 4 | stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], 5 | addons: [ 6 | '@storybook/addon-links', 7 | '@storybook/addon-essentials', 8 | '@storybook/addon-a11y', 9 | ], 10 | framework: { 11 | name: '@storybook/web-components-vite', 12 | options: {}, 13 | }, 14 | staticDirs: ['../public'], 15 | }; 16 | export default config; 17 | -------------------------------------------------------------------------------- /.storybook/preview.ts: -------------------------------------------------------------------------------- 1 | import { setCustomElementsManifest } from '@storybook/web-components'; 2 | import customElements from '../custom-elements.json'; 3 | import packageJson from '../package.json'; 4 | import { setWcStorybookHelpersConfig } from 'wc-storybook-helpers'; 5 | import { withActions } from '@storybook/addon-actions/decorator'; 6 | import { setWcDoxConfig } from 'wc-dox'; 7 | import './code-bubble-setup.js'; 8 | import './styles.css'; 9 | import '../public/html/index.js'; 10 | 11 | import type { Preview } from '@storybook/web-components'; 12 | 13 | setCustomElementsManifest(customElements); 14 | 15 | // This adds additional features to storybook - https://www.npmjs.com/package/wc-storybook-helpers 16 | setWcStorybookHelpersConfig({ typeRef: 'expandedType' }); 17 | 18 | // This configures the documentation for the API documentation - https://www.npmjs.com/package/wc-dox 19 | setWcDoxConfig(customElements, { 20 | imports: { 21 | langOnPreTag: true, 22 | imports: [ 23 | { 24 | label: 'HTML', 25 | lang: 'html', 26 | importTemplate: (tagName) => 27 | ``, 28 | }, 29 | { 30 | label: 'NPM', 31 | lang: 'js', 32 | importTemplate: (tagName) => 33 | `import '${packageJson.name}/dist/components/${tagName}/index.js';`, 34 | }, 35 | { 36 | label: 'React', 37 | lang: 'js', 38 | importTemplate: (tagName, className) => 39 | `import { ${className} } from '${packageJson.name}/react/index.js';`, 40 | }, 41 | ], 42 | }, 43 | }); 44 | 45 | 46 | const preview: Preview = { 47 | parameters: { 48 | controls: { 49 | expanded: true, // provides descriptions and additional info for controls 50 | sort: 'alpha', // sorts controls in alphabetical order 51 | }, 52 | }, 53 | decorators: [withActions], 54 | }; 55 | 56 | export default preview; 57 | -------------------------------------------------------------------------------- /.storybook/styles.css: -------------------------------------------------------------------------------- 1 | :not(:defined) { 2 | opacity: 0; 3 | } 4 | 5 | code-bubble { 6 | margin-bottom: 2rem; 7 | } 8 | 9 | code-bubble[code-bubble] pre[slot] { 10 | margin: 0; 11 | } 12 | 13 | code-bubble[code-bubble] pre button { 14 | display: none; 15 | } 16 | 17 | .docblock-source.sb-unstyled { 18 | margin: 0; 19 | border: 0; 20 | box-shadow: none; 21 | } 22 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "bierner.lit-html", 4 | "dbaeumer.vscode-eslint", 5 | "esbenp.prettier-vscode", 6 | "streetsidesoftware.code-spell-checker", 7 | "unifiedjs.vscode-mdx", 8 | "yzhang.markdown-all-in-one" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Burton Smith 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lit Starter Kit 2 | 3 | Welcome to the Lit Starter Kit. This is not an official kit for the Lit library, but this is a tool to get a component library up and running quickly. 4 | 5 | ## Features 6 | 7 | This repository is designed to be a "batteries included" repo, so you can hit the ground running with what you need to start delivering components. This repo includes: 8 | 9 | - ✅ Library and component scaffolding 10 | - ✅ [Storybook](https://storybook.js.org/docs/get-started/frameworks/web-components-vite?renderer=web-components) integration (with [helpers](https://www.npmjs.com/package/wc-storybook-helpers)) 11 | - ✅ CDN build (in `/cdn`) 12 | - ✅ NPM build (in `/dist`) 13 | - ✅ Testing 14 | - ✅ Documentation 15 | - ✅ [React wrappers](https://www.npmjs.com/-package/custom-element-react-wrappers) (in - `/react`) 16 | - ✅ [JSX integration](https://www.npmjs.com/-package/custom-element-jsx-integration) - (in `/types`) 17 | - ✅ [Vue.js integration](https://www.npmjs.-com/package/-custom-element-vuejs-integration) (in `/- types`) 18 | - ✅ [Svelte integration](https://www.npmjs.-com/package/-custom-element-svelte-integration) (in `/- types`) 19 | - ✅ [SolidJS integration](https://www.npmjs.-com/package/-custom-element-solidjs-integration) (in `/- types`) 20 | - ✅ [HTML linter](https://www.npmjs.com/package/eslint-plugin-custom-element) (in `/eslint`) 21 | 22 | ## Getting Started 23 | 24 | You can choose to fork this repository directly or you can run the following command to create a new project. 25 | 26 | ```bash 27 | npm init lit-starter-kit your-project-name 28 | ``` 29 | 30 | ## Running the Code 31 | 32 | The development environment uses [Storybook](https://storybook.js.org/) to showcase and document the components. The documentation files are written in MDX files to increase portability in case you wan to use a different tool for documenting your components. 33 | 34 | ```bash 35 | npm run dev 36 | ``` 37 | 38 | ### Creating a New Component 39 | 40 | This project leverages [plop](https://www.npmjs.com/package/plop) to generate new components in your library. You can create a new component by running the following command and following the prompts. 41 | 42 | ```bash 43 | npm run new 44 | ``` 45 | 46 | ## Building the Project 47 | 48 | Generating the final build assets will generate the `dist` assets for the NPM package, the content for the CDN located in the `cdn` directory at the root of the project, as well as the meta content for your components like framework integrations like types and react wrappers. 49 | 50 | ```bash 51 | npm run build 52 | ``` 53 | 54 | ## Testing the Components 55 | 56 | Tests are written and executed using [web-test-runner](https://modern-web.dev/docs/test-runner/overview/) which execute your tests in _real_ browsers to validate your APIs are working as expected in the environments you intend to be using them in. 57 | 58 | Tests can be configured in the `web-test-runner.config.js` file located at the root of the project. 59 | 60 | Tests can be run using the following command: 61 | 62 | ```bash 63 | npm test 64 | ``` 65 | -------------------------------------------------------------------------------- /custom-elements-manifest.config.js: -------------------------------------------------------------------------------- 1 | import { getTsProgram, expandTypesPlugin } from 'cem-plugin-expanded-types'; 2 | import { customElementReactWrapperPlugin } from 'custom-element-react-wrappers'; 3 | import { customElementVsCodePlugin } from 'custom-element-vs-code-integration'; 4 | import { customElementJetBrainsPlugin } from 'custom-element-jet-brains-integration'; 5 | import { customElementSolidJsPlugin } from 'custom-element-solidjs-integration'; 6 | import { customElementJsxPlugin } from 'custom-element-jsx-integration'; 7 | import { customElementVuejsPlugin } from 'custom-element-vuejs-integration'; 8 | import { customElementSveltePlugin } from 'custom-element-svelte-integration'; 9 | import { cemInheritancePlugin } from 'custom-elements-manifest-inheritance'; 10 | import { customElementLazyLoaderPlugin } from 'custom-element-lazy-loader'; 11 | import { customJSDocTagsPlugin } from 'cem-plugin-custom-jsdoc-tags'; 12 | import { customEsLintRuleGeneratorPlugin } from 'custom-element-eslint-rule-generator'; 13 | import { cemDeprecatorPlugin } from "custom-elements-manifest-deprecator"; 14 | 15 | export default { 16 | /** Globs to analyze */ 17 | globs: ['src/components/**/*.ts'], 18 | /** Globs to exclude */ 19 | exclude: ['src/**/*.test.ts', 'src/**/*.stories.ts', 'src/**/*.styles.ts'], 20 | /** Enable special handling for litelement */ 21 | litelement: true, 22 | /** Provide custom plugins */ 23 | plugins: [ 24 | expandTypesPlugin(), 25 | cemInheritancePlugin(), 26 | cemDeprecatorPlugin(), 27 | 28 | customElementVsCodePlugin(), 29 | customElementJetBrainsPlugin(), 30 | customElementReactWrapperPlugin({ 31 | outdir: 'react', 32 | modulePath: (_, tagName) => 33 | `../dist/components/${tagName.replace('my-', '')}/index.js`, 34 | }), 35 | customElementSolidJsPlugin({ 36 | outdir: 'types', 37 | fileName: 'custom-element-solidjs.d.ts', 38 | modulePath: (_, tagName) => 39 | `../dist/components/${tagName.replace('my-', '')}/${tagName.replace('my-', '')}.js`, 40 | }), 41 | customElementJsxPlugin({ 42 | outdir: 'types', 43 | modulePath: (_, tagName) => 44 | `../dist/components/${tagName.replace('my-', '')}/${tagName.replace('my-', '')}.js`, 45 | }), 46 | customElementVuejsPlugin({ 47 | outdir: 'types', 48 | fileName: 'custom-element-vuejs.d.ts', 49 | modulePath: (_, tagName) => 50 | `../dist/components/${tagName.replace('my-', '')}/${tagName.replace('my-', '')}.js`, 51 | }), 52 | customElementSveltePlugin({ 53 | outdir: 'types', 54 | fileName: 'custom-element-svelte.d.ts', 55 | modulePath: (_, tagName) => 56 | `../dist/components/${tagName.replace('my-', '')}/${tagName.replace('my-', '')}.js`, 57 | }), 58 | customElementLazyLoaderPlugin({ 59 | outdir: 'cdn', 60 | importPathTemplate: (_, tagName) => 61 | `../dist/components/${tagName.replace('my-', '')}/${tagName.replace('my-', '')}.js`, 62 | }), 63 | 64 | customJSDocTagsPlugin({ 65 | tags: { 66 | status: {}, 67 | since: {}, 68 | dependency: { 69 | mappedName: 'dependencies', 70 | isArray: true, 71 | }, 72 | }, 73 | }), 74 | 75 | customEsLintRuleGeneratorPlugin({ 76 | outdir: 'eslint', 77 | }), 78 | ], 79 | 80 | overrideModuleCreation: ({ ts, globs }) => { 81 | const program = getTsProgram(ts, globs, 'tsconfig.json'); 82 | return program 83 | .getSourceFiles() 84 | .filter(sf => globs.find(glob => sf.fileName.includes(glob))); 85 | }, 86 | }; 87 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import globals from 'globals'; 2 | import js from '@eslint/js'; 3 | import tseslint from 'typescript-eslint'; 4 | import { configs as lit } from 'eslint-plugin-lit'; 5 | import eslintConfigPrettier from 'eslint-config-prettier'; 6 | import json from '@eslint/json'; 7 | import markdown from '@eslint/markdown'; 8 | import storybook from 'eslint-plugin-storybook'; 9 | import litA11y from 'eslint-plugin-lit-a11y'; 10 | 11 | /** @type {import('eslint').Linter.Config[]} */ 12 | export default [ 13 | { languageOptions: { globals: globals.browser } }, 14 | eslintConfigPrettier, 15 | ...storybook.configs['flat/recommended'], 16 | 17 | // lint JSON files 18 | { 19 | files: ['**/*.json'], 20 | language: 'json/json', 21 | ...json.configs.recommended, 22 | rules: { 23 | 'json/no-duplicate-keys': 'error', 24 | 'no-irregular-whitespace': 'off', 25 | }, 26 | }, 27 | 28 | // lint MD files 29 | ...markdown.configs.recommended, 30 | { 31 | files: ['**/*.md'], 32 | rules: { 33 | 'no-irregular-whitespace': 'off', 34 | }, 35 | }, 36 | 37 | // lint JS/TS files 38 | ...tseslint.configs.recommended, 39 | { 40 | files: ['**/*.{js,mjs,cjs,ts}'], 41 | ...lit['flat/recommended'], 42 | ...js.configs.recommended, 43 | rules: {}, 44 | }, 45 | 46 | { 47 | plugins: { 48 | 'lit-a11y': litA11y, 49 | }, 50 | }, 51 | 52 | { 53 | ignores: [ 54 | '.vscode/*', 55 | 'cdn/*', 56 | 'dist/*', 57 | 'eslint/*', 58 | 'plop-templates/*', 59 | 'public/*', 60 | 'react/*', 61 | 'types/*', 62 | 'custom-elements.json', 63 | 'vscode.css-custom-data.json', 64 | 'vscode.html-custom-data.json', 65 | 'web-types.json', 66 | 'tsconfig.json', 67 | ], 68 | }, 69 | ]; 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lit-starter-kit", 3 | "version": "0.0.0", 4 | "description": "A starter kit for creating a Lit-based web component library.", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "analyze": "cem analyze", 9 | "analyze:dev": "cem analyze --watch", 10 | "clean": "git clean -fqdx", 11 | "dev": "concurrently -k -r \"npm run analyze:dev\" \"npm run build:watch\" \"npm run storybook\"", 12 | "test": "web-test-runner --group default", 13 | "build": "npm run analyze && npm run build:cdn", 14 | "build:cdn": "tsc && rollup -c rollup.config.js", 15 | "build:watch": "concurrently -k -r \"tsc --watch\" \"rollup -c rollup.config.js --watch\"", 16 | "new": "plop", 17 | "deploy": "npm run build && npm publish", 18 | "format": "npm run format:eslint && npm run format:prettier", 19 | "format:eslint": "npx eslint --fix", 20 | "format:prettier": "npx prettier . --write", 21 | "lint": "npm run lint:eslint && npm run lint:prettier", 22 | "lint:eslint": "npx eslint", 23 | "lint:prettier": "npx prettier . --check", 24 | "prepare": "husky && npx playwright install-deps", 25 | "storybook": "storybook dev -p 6006", 26 | "build-storybook": "storybook build" 27 | }, 28 | "author": "", 29 | "license": "MIT", 30 | "dependencies": { 31 | "lit": "^3.2.1" 32 | }, 33 | "devDependencies": { 34 | "@custom-elements-manifest/analyzer": "^0.10.3", 35 | "@eslint/js": "^9.16.0", 36 | "@eslint/json": "^0.8.0", 37 | "@eslint/markdown": "^6.2.1", 38 | "@open-wc/testing": "^4.0.0", 39 | "@playwright/test": "^1.46.1", 40 | "@rollup/plugin-multi-entry": "^6.0.1", 41 | "@rollup/plugin-node-resolve": "^15.3.0", 42 | "@rollup/plugin-terser": "^0.4.4", 43 | "@rollup/plugin-typescript": "^12.1.1", 44 | "@storybook/addon-a11y": "^8.4.6", 45 | "@storybook/addon-actions": "^8.1.11", 46 | "@storybook/addon-essentials": "^8.1.11", 47 | "@storybook/addon-links": "^8.1.11", 48 | "@storybook/blocks": "^8.1.11", 49 | "@storybook/test": "^8.1.11", 50 | "@storybook/web-components": "^8.1.11", 51 | "@storybook/web-components-vite": "^8.1.11", 52 | "@types/mocha": "^10.0.2", 53 | "@web/dev-server-esbuild": "^1.0.2", 54 | "@web/test-runner": "^0.19.0", 55 | "@web/test-runner-commands": "^0.9.0", 56 | "@web/test-runner-playwright": "^0.11.0", 57 | "cem-plugin-custom-jsdoc-tags": "^1.1.2", 58 | "cem-plugin-expanded-types": "^1.3.3", 59 | "code-bubble": "^1.2.0", 60 | "concurrently": "^9.1.0", 61 | "custom-element-eslint-rule-generator": "^1.0.1", 62 | "custom-element-jet-brains-integration": "^1.6.2", 63 | "custom-element-jsx-integration": "^1.5.3", 64 | "custom-element-lazy-loader": "^1.3.1", 65 | "custom-element-react-wrappers": "^1.6.8", 66 | "custom-element-solidjs-integration": "^1.8.2", 67 | "custom-element-svelte-integration": "^1.1.2", 68 | "custom-element-vs-code-integration": "^1.4.1", 69 | "custom-element-vuejs-integration": "^1.3.3", 70 | "custom-elements-manifest-deprecator": "^1.1.1", 71 | "custom-elements-manifest-inheritance": "^1.1.1", 72 | "eslint": "^9.16.0", 73 | "eslint-config-prettier": "^9.1.0", 74 | "eslint-plugin-lit": "^1.15.0", 75 | "eslint-plugin-lit-a11y": "^1.1.0-next.1", 76 | "eslint-plugin-require-extensions": "^0.1.3", 77 | "eslint-plugin-storybook": "^0.11.1", 78 | "glob": "^11.0.0", 79 | "globals": "^15.13.0", 80 | "husky": "^9.0.11", 81 | "lint-staged": "^15.2.7", 82 | "plop": "^4.0.1", 83 | "prettier": "^3.3.2", 84 | "rollup": "^4.28.0", 85 | "rollup-plugin-dts": "^6.1.1", 86 | "rollup-plugin-summary": "^3.0.0", 87 | "storybook": "^8.1.11", 88 | "typescript": "^5.5.3", 89 | "typescript-eslint": "^8.17.0", 90 | "wc-dox": "^1.2.0", 91 | "wc-storybook-helpers": "^2.0.4" 92 | }, 93 | "lint-staged": { 94 | "*.js": "eslint --cache --fix", 95 | "*.format:prettier": "prettier --write" 96 | }, 97 | "files": [ 98 | "cdn", 99 | "eslint", 100 | "dist", 101 | "react", 102 | "types", 103 | "index.d.ts", 104 | "index.js", 105 | "package.json", 106 | "custom-elements.json", 107 | "vscode.css-custom-data.json", 108 | "vscode.html-custom-data.json", 109 | "web-types.json" 110 | ], 111 | "keywords": [ 112 | "web-components", 113 | "custom-elements", 114 | "lit-element", 115 | "typescript", 116 | "lit" 117 | ] 118 | } 119 | -------------------------------------------------------------------------------- /plop-templates/component.definition.ts.hbs: -------------------------------------------------------------------------------- 1 | import { {{pascalCase tagName}} } from './{{kebabCase name}}.js'; 2 | 3 | export type * from './{{kebabCase name}}.js'; 4 | 5 | customElements.define('{{kebabCase tagName}}', {{pascalCase tagName}}); -------------------------------------------------------------------------------- /plop-templates/component.docs.hbs: -------------------------------------------------------------------------------- 1 | # {{dashToTitle name}} 2 | 3 | Add examples and descriptions of the component features here. 4 | 5 | 6 | 7 | ```html 8 | <{{kebabCase tagName}}> 9 | ``` 10 | 11 | ```tsx 12 | import { {{pascalCase tagName}} } from 'lit-starter-kit/react'; 13 | 14 | export default () => { 15 | return ( 16 | <> 17 | <{{pascalCase tagName}}> 18 | 19 | ); 20 | }; 21 | ``` 22 | 23 | 24 | 25 | ## API 26 | 27 | -------------------------------------------------------------------------------- /plop-templates/component.stories.ts.hbs: -------------------------------------------------------------------------------- 1 | import { StoryObj } from '@storybook/web-components'; 2 | import { getWcStorybookHelpers } from 'wc-storybook-helpers'; 3 | import type { {{pascalCase tagName}} } from './index.js'; 4 | 5 | const { args, argTypes, events, template } = getWcStorybookHelpers('{{kebabCase tagName}}'); 6 | 7 | export default { 8 | title: 'Components/{{pascalCase name}}', 9 | component: '{{kebabCase tagName}}', 10 | args, 11 | argTypes, 12 | parameters: { 13 | actions: { 14 | handles: events, 15 | }, 16 | }, 17 | }; 18 | 19 | 20 | type Story = StoryObj<{{pascalCase tagName}} & typeof args>; 21 | 22 | export const Default: Story = { 23 | render: args => template(args), 24 | args: {} 25 | }; 26 | -------------------------------------------------------------------------------- /plop-templates/component.styles.ts.hbs: -------------------------------------------------------------------------------- 1 | import { css } from 'lit'; 2 | 3 | export default css` 4 | :host { 5 | } 6 | `; 7 | -------------------------------------------------------------------------------- /plop-templates/component.test.ts.hbs: -------------------------------------------------------------------------------- 1 | import './index.js'; 2 | import { expect, fixture, html } from '@open-wc/testing'; 3 | import type { {{pascalCase tagName}} } from './{{kebabCase name}}.js'; 4 | 5 | describe('{{pascalCase tagName}}', () => { 6 | describe('accessibility', () => { 7 | it('default is accessible', async () => { 8 | const el = await fixture<{{pascalCase tagName}}>(html`<{{kebabCase tagName}}>`); 9 | await expect(el).to.be.accessible(); 10 | }); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /plop-templates/component.ts.hbs: -------------------------------------------------------------------------------- 1 | import { html, LitElement } from 'lit'; 2 | import { property } from 'lit/decorators.js'; 3 | import styles from './{{kebabCase name}}.styles.js'; 4 | 5 | /** 6 | * Add a description here 7 | * 8 | * @tag {{kebabCase tagName}} 9 | * @since 0.0.0 10 | * @status experimental 11 | * 12 | **/ 13 | export class {{pascalCase tagName}} extends LitElement { 14 | static override styles = styles; 15 | 16 | @property() 17 | heading = 'Hello, word!'; 18 | 19 | override render() { 20 | return html` 21 |

${this.heading}

22 | `; 23 | } 24 | } 25 | 26 | export default {{pascalCase tagName}}; 27 | -------------------------------------------------------------------------------- /plopfile.js: -------------------------------------------------------------------------------- 1 | /** @arg {import('plop').NodePlopAPI} plop */ 2 | 3 | export default function (plop) { 4 | plop.setHelper('dashToTitle', text => { 5 | const titleCase = plop.getHelper('titleCase'); 6 | return titleCase(text.replace(/-/g, ' ')); 7 | }); 8 | 9 | plop.setGenerator('component', { 10 | description: 'generate a new component', 11 | prompts: [ 12 | { 13 | type: 'input', 14 | name: 'name', 15 | message: 16 | 'Please enter your component name in kebab-case (e.g. button-group)', 17 | default: 'component', 18 | }, 19 | { 20 | type: 'input', 21 | name: 'prefix', 22 | message: 23 | 'What is the prefix for your component? (e.g. my)', 24 | default: '', 25 | }, 26 | ], 27 | actions: function (data) { 28 | const basename = data?.name; 29 | if ( 30 | // Must only contain alphanumeric characters and dashes 31 | !/[a-z0-9-]+/.test(basename) || 32 | // Must start with a letter 33 | !/^[a-z]/.test(basename) || 34 | // Must not end in a dash 35 | basename.endsWith('-') 36 | ) { 37 | console.log( 38 | 'The name must only contain alphanumeric characters and dashes, start with a letter, and not end in a dash. Please try again.', 39 | ); 40 | return []; 41 | } 42 | 43 | const BASE_PATH = `src/components/{{kebabCase name}}`; 44 | 45 | data.tagName = data?.prefix ? `${data.prefix}-${data.name}` : data.name; 46 | 47 | return [ 48 | { 49 | type: 'add', 50 | skipIfExists: true, 51 | path: `${BASE_PATH}/{{kebabCase name}}.ts`, 52 | templateFile: 'plop-templates/component.ts.hbs', 53 | }, 54 | { 55 | type: 'add', 56 | skipIfExists: true, 57 | path: `${BASE_PATH}/{{kebabCase name}}.styles.ts`, 58 | templateFile: 'plop-templates/component.styles.ts.hbs', 59 | }, 60 | { 61 | type: 'add', 62 | skipIfExists: true, 63 | path: `${BASE_PATH}/{{kebabCase name}}.test.ts`, 64 | templateFile: 'plop-templates/component.test.ts.hbs', 65 | }, 66 | { 67 | type: 'add', 68 | skipIfExists: true, 69 | path: `${BASE_PATH}/{{kebabCase name}}.mdx`, 70 | templateFile: 'plop-templates/component.docs.hbs', 71 | }, 72 | { 73 | type: 'add', 74 | skipIfExists: true, 75 | path: `${BASE_PATH}/{{kebabCase name}}.stories.ts`, 76 | templateFile: 'plop-templates/component.stories.ts.hbs', 77 | }, 78 | { 79 | type: 'add', 80 | skipIfExists: true, 81 | path: `${BASE_PATH}/index.ts`, 82 | templateFile: 'plop-templates/component.definition.ts.hbs', 83 | }, 84 | ]; 85 | }, 86 | }); 87 | } 88 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import { globSync } from 'glob'; 2 | import path from 'node:path'; 3 | import { fileURLToPath } from 'node:url'; 4 | import typescript from '@rollup/plugin-typescript'; 5 | import dts from 'rollup-plugin-dts'; 6 | import resolve from '@rollup/plugin-node-resolve'; 7 | import multi from '@rollup/plugin-multi-entry'; 8 | import terser from '@rollup/plugin-terser'; 9 | import summary from 'rollup-plugin-summary'; 10 | 11 | export default [ 12 | /** bundle components for the CDN */ 13 | { 14 | watch: false, 15 | input: Object.fromEntries( 16 | globSync('src/**/*.ts', { 17 | ignore: [ 18 | 'src/**/*.test.ts', 19 | 'src/**/*.stories.ts', 20 | 'src/**/*.styles.ts', 21 | ], 22 | }).map(file => [ 23 | // This remove `src/` as well as the file extension from each 24 | // file, so e.g. src/nested/foo.js becomes nested/foo 25 | path.relative( 26 | 'src', 27 | file.slice(0, file.length - path.extname(file).length), 28 | ), 29 | // This expands the relative paths to absolute paths, so e.g. 30 | // src/nested/foo becomes /project/src/nested/foo.js 31 | fileURLToPath(new URL(file, import.meta.url)), 32 | ]), 33 | ), 34 | output: { 35 | dir: 'cdn', 36 | format: 'esm', 37 | }, 38 | plugins: [ 39 | typescript({ 40 | tsconfig: 'tsconfig.json', 41 | outDir: 'cdn', 42 | sourceMap: false, 43 | declaration: false, 44 | declarationMap: false, 45 | }), 46 | resolve(), 47 | terser({ 48 | ecma: 2021, 49 | module: true, 50 | warnings: true, 51 | }), 52 | summary(), 53 | ], 54 | }, 55 | // watch version 56 | { 57 | watch: { 58 | buildDelay: 150, 59 | }, 60 | input: Object.fromEntries( 61 | globSync('src/**/*.ts', { 62 | ignore: ['src/**/*.test.ts', 'src/**/*.stories.ts'], 63 | }).map(file => [ 64 | // This remove `src/` as well as the file extension from each 65 | // file, so e.g. src/nested/foo.js becomes nested/foo 66 | path.relative( 67 | 'src', 68 | file.slice(0, file.length - path.extname(file).length), 69 | ), 70 | // This expands the relative paths to absolute paths, so e.g. 71 | // src/nested/foo becomes /project/src/nested/foo.js 72 | fileURLToPath(new URL(file, import.meta.url)), 73 | ]), 74 | ), 75 | output: { 76 | dir: 'cdn', 77 | format: 'esm', 78 | }, 79 | plugins: [ 80 | typescript({ 81 | tsconfig: 'tsconfig.json', 82 | outDir: 'cdn', 83 | sourceMap: false, 84 | declaration: false, 85 | declarationMap: false, 86 | }), 87 | resolve(), 88 | ], 89 | }, 90 | 91 | /** bundle components for sandboxes */ 92 | { 93 | watch: false, 94 | input: globSync('src/**/index.ts'), 95 | output: { 96 | format: 'esm', 97 | dir: 'public/html', 98 | }, 99 | plugins: [ 100 | typescript({ 101 | tsconfig: 'tsconfig.json', 102 | outDir: 'public/html', 103 | sourceMap: false, 104 | declaration: false, 105 | declarationMap: false, 106 | }), 107 | resolve(), 108 | multi({ 109 | entryFileName: 'index.js', 110 | }), 111 | terser({ 112 | ecma: 2021, 113 | module: true, 114 | warnings: true, 115 | }), 116 | summary(), 117 | ], 118 | }, 119 | // watch version 120 | { 121 | watch: { 122 | buildDelay: 150, 123 | }, 124 | input: globSync('src/**/index.ts'), 125 | output: { 126 | format: 'esm', 127 | dir: 'public/html', 128 | }, 129 | plugins: [ 130 | typescript({ 131 | tsconfig: 'tsconfig.json', 132 | outDir: 'public/html', 133 | sourceMap: false, 134 | declaration: false, 135 | declarationMap: false, 136 | }), 137 | resolve(), 138 | multi({ 139 | entryFileName: 'index.js', 140 | }), 141 | ], 142 | }, 143 | 144 | /** bundle react components for sandboxes */ 145 | { 146 | watch: false, 147 | input: 'react/index.js', 148 | output: { 149 | format: 'esm', 150 | file: 'public/react/index.js', 151 | sourcemap: false, 152 | }, 153 | external: ['react'], 154 | plugins: [ 155 | resolve(), 156 | terser({ 157 | ecma: 2021, 158 | module: true, 159 | warnings: true, 160 | }), 161 | summary(), 162 | ], 163 | onwarn(warning) { 164 | if ( 165 | /Could not resolve import/.test(warning.message) || 166 | /'this' keyword is equivalent to 'undefined'/.test(warning.message) 167 | ) { 168 | return; 169 | } 170 | 171 | console.error(warning.message); 172 | }, 173 | }, 174 | // watch version 175 | { 176 | watch: { 177 | buildDelay: 150, 178 | }, 179 | input: 'react/index.js', 180 | output: { 181 | format: 'esm', 182 | file: 'public/react/index.js', 183 | sourcemap: false, 184 | }, 185 | external: ['react'], 186 | plugins: [resolve()], 187 | onwarn(warning) { 188 | if ( 189 | /Could not resolve import/.test(warning.message) || 190 | /'this' keyword is equivalent to 'undefined'/.test(warning.message) 191 | ) { 192 | return; 193 | } 194 | 195 | console.error(warning.message); 196 | }, 197 | }, 198 | // bundle react component types for sandboxes 199 | { 200 | watch: false, 201 | input: './react/index.d.ts', 202 | output: [{ file: 'public/react/index.d.ts', format: 'es' }], 203 | external: ['react'], 204 | plugins: [dts(), resolve(), summary()], 205 | }, 206 | // watch version 207 | { 208 | watch: { 209 | buildDelay: 150, 210 | }, 211 | input: './react/index.d.ts', 212 | output: [{ file: 'public/react/index.d.ts', format: 'es' }], 213 | external: ['react'], 214 | plugins: [dts(), resolve()], 215 | }, 216 | ]; 217 | -------------------------------------------------------------------------------- /src/components/button/button.mdx: -------------------------------------------------------------------------------- 1 | # Button 2 | 3 | This is an example of a button component. 4 | 5 | 6 | 7 | ```html 8 | My Button 9 | ``` 10 | 11 | ```tsx 12 | import { MyButton } from 'lit-starter-kit/react'; 13 | 14 | export default () => { 15 | return My Button; 16 | }; 17 | ``` 18 | 19 | 20 | 21 | ## Variations 22 | 23 | Here are some examples of button variations. 24 | 25 | 26 | 27 | ```html 28 | My Button 29 | My Button 30 | My Button 31 | My Button 32 | ``` 33 | 34 | ```tsx 35 | import { MyButton } from 'lit-starter-kit/react'; 36 | 37 | export default () => { 38 | return <> 39 | My Button 40 | My Button 41 | My Button 42 | My Button 43 | ; 44 | }; 45 | ``` 46 | 47 | 48 | 49 | ## Disabled 50 | 51 | Here is an example of how to disable a button. 52 | 53 | 54 | 55 | ```html 56 | My Button 57 | ``` 58 | 59 | ```tsx 60 | import { MyButton } from 'lit-starter-kit/react'; 61 | 62 | export default () => { 63 | return My Button; 64 | }; 65 | ``` 66 | 67 | 68 | 69 | ## API 70 | 71 | -------------------------------------------------------------------------------- /src/components/button/button.stories.ts: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from '@storybook/web-components'; 2 | import { getWcStorybookHelpers } from 'wc-storybook-helpers'; 3 | import { html } from 'lit'; 4 | 5 | import type { MyButton } from './button.js'; 6 | 7 | const { events, args, argTypes, template } = getWcStorybookHelpers('my-button'); 8 | 9 | const meta: Meta = { 10 | title: 'Components/Button', 11 | component: 'my-button', 12 | args, 13 | argTypes, 14 | parameters: { 15 | actions: { 16 | handles: events, 17 | }, 18 | }, 19 | }; 20 | export default meta; 21 | 22 | /** 23 | * create Story type that will provide autocomplete and docs for `args`, 24 | * but also allow for namespaced args like CSS Shadow Parts and Slots 25 | */ 26 | type Story = StoryObj; 27 | 28 | export const Default: Story = { 29 | render: args => html`${template(args)}`, 30 | args: { 31 | 'default-slot': 'My Button', 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /src/components/button/button.styles.ts: -------------------------------------------------------------------------------- 1 | import { css } from 'lit'; 2 | 3 | export default css` 4 | :host { 5 | --button-bg-color: #f0f0f0; 6 | --button-fg-color: #333; 7 | --button-border-color: transparent; 8 | 9 | display: inline-flex; 10 | } 11 | 12 | button { 13 | cursor: pointer; 14 | background-color: var(--button-bg-color); 15 | border: 1px solid var(--button-border-color); 16 | border-radius: 4px; 17 | color: var(--button-fg-color); 18 | padding: 8px 16px; 19 | } 20 | 21 | button:disabled { 22 | cursor: not-allowed; 23 | opacity: 0.5; 24 | } 25 | 26 | :host([variation='primary']) { 27 | --button-bg-color: #024996; 28 | --button-fg-color: white; 29 | --button-border-color: #024996; 30 | } 31 | 32 | :host([variation='hollow']) { 33 | --button-bg-color: transparent; 34 | --button-fg-color: #024996; 35 | --button-border-color: #024996; 36 | } 37 | 38 | :host([variation='transparent']) { 39 | --button-bg-color: transparent; 40 | --button-fg-color: #024996; 41 | --button-border-color: transparent; 42 | } 43 | `; 44 | -------------------------------------------------------------------------------- /src/components/button/button.test.ts: -------------------------------------------------------------------------------- 1 | import './index.js'; 2 | import { expect, fixture, html } from '@open-wc/testing'; 3 | import { MyButton } from './index.js'; 4 | 5 | describe('MyButton', () => { 6 | describe('accessibility', () => { 7 | it('default is accessible', async () => { 8 | const el = await fixture(html`My Button`); 9 | await expect(el).to.be.accessible(); 10 | }); 11 | 12 | it('variations are accessible', async () => { 13 | const el = await fixture(html` 14 | My Button 15 | My Button 16 | My Button 17 | `); 18 | await expect(el).to.be.accessible(); 19 | }); 20 | 21 | it('disabled is accessible', async () => { 22 | const el = await fixture(html`My Button`); 23 | const button = el.shadowRoot?.querySelector('button'); 24 | 25 | await expect(el).to.be.accessible(); 26 | await expect(button?.hasAttribute('disabled')).to.be.true; 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /src/components/button/button.ts: -------------------------------------------------------------------------------- 1 | import { html, LitElement } from 'lit'; 2 | import { property } from 'lit/decorators.js'; 3 | import styles from './button.styles.js'; 4 | 5 | /** 6 | * An example button component 7 | * 8 | * @tag my-button 9 | * 10 | * @csspart control - The button element 11 | * 12 | * @cssproperty [--button-bg-color=#f0f0f0] - The background color of the button 13 | * @cssproperty [--button-fg-color=#333] - The text color of the button 14 | * @cssproperty [--button-border-color=transparent] - The border color of the button 15 | * 16 | * @slot - The main content for the button 17 | * 18 | */ 19 | export default class MyButton extends LitElement { 20 | static override styles = styles; 21 | 22 | /** Changes the display of the button */ 23 | @property() 24 | variation?: 'default' | 'primary' | 'hollow' | 'transparent'; 25 | 26 | /** Controls the disabled property of the button */ 27 | @property({ type: Boolean }) 28 | disabled = false; 29 | 30 | override render() { 31 | return html` 32 | 35 | `; 36 | } 37 | } 38 | 39 | export { MyButton }; 40 | -------------------------------------------------------------------------------- /src/components/button/index.ts: -------------------------------------------------------------------------------- 1 | import { MyButton } from './button.js'; 2 | 3 | export type * from './button.js'; 4 | 5 | customElements.define('my-button', MyButton); 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "ES2020" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, 15 | "lib": [ 16 | "ES2020", 17 | "DOM", 18 | "DOM.Iterable" 19 | ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */, 20 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 21 | "experimentalDecorators": true /* Enable experimental support for TC39 stage 2 draft decorators. */, 22 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 23 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 24 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 25 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 26 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 27 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 28 | "useDefineForClassFields": false /* Emit ECMAScript-standard-compliant class fields. */, 29 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 30 | 31 | /* Modules */ 32 | "module": "ESNext" /* Specify what module code is generated. */, 33 | "rootDir": "./src" /* Specify the root folder within your source files. */, 34 | "moduleResolution": "Node" /* Specify how TypeScript looks up a file from a given module specifier. */, 35 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 36 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 37 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 38 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 39 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 40 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 41 | "moduleSuffixes": [] /* List of file name suffixes to search when resolving a module. */, 42 | "resolveJsonModule": true /* Enable importing .json files. */, 43 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 44 | 45 | /* JavaScript Support */ 46 | "allowJs": true /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */, 47 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 48 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 49 | 50 | /* Emit */ 51 | "declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */, 52 | "declarationMap": true /* Create sourcemaps for d.ts files. */, 53 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 54 | "sourceMap": true /* Create source map files for emitted JavaScript files. */, 55 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 56 | "outDir": "./dist" /* Specify an output folder for all emitted files. */, 57 | // "removeComments": true, /* Disable emitting comments. */ 58 | // "noEmit": true, /* Disable emitting files from a compilation. */ 59 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 60 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 61 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 62 | // "sourceRoot": "/src", /* Specify the root path for debuggers to find the reference source code. */ 63 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 64 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 65 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 66 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 67 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 68 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 69 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 70 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 71 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 72 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 73 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 74 | 75 | /* Interop Constraints */ 76 | "isolatedModules": true /* Ensure that each file can be safely transpiled without relying on other imports. */, 77 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 78 | "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, 79 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 80 | "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, 81 | 82 | /* Type Checking */ 83 | "strict": true /* Enable all strict type-checking options. */, 84 | "noImplicitAny": true /* Enable error reporting for expressions and declarations with an implied 'any' type. */, 85 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 86 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 87 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 88 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 89 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 90 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 91 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 92 | "noUnusedLocals": true /* Enable error reporting when local variables aren't read. */, 93 | "noUnusedParameters": true /* Raise an error when a function parameter isn't read. */, 94 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 95 | "noImplicitReturns": true /* Enable error reporting for codepaths that do not explicitly return in a function. */, 96 | "noFallthroughCasesInSwitch": true /* Enable error reporting for fallthrough cases in switch statements. */, 97 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 98 | "noImplicitOverride": true /* Ensure overriding members in derived classes are marked with an override modifier. */, 99 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 100 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 101 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 102 | 103 | /* Completeness */ 104 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 105 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 106 | }, 107 | "include": ["src"], 108 | "exclude": ["**/*.stories.ts", "**/*.test.ts"] 109 | } 110 | -------------------------------------------------------------------------------- /web-test-runner.config.js: -------------------------------------------------------------------------------- 1 | import { esbuildPlugin } from '@web/dev-server-esbuild'; 2 | import { playwrightLauncher } from '@web/test-runner-playwright'; 3 | import { fileURLToPath } from 'url'; 4 | 5 | export default { 6 | rootDir: '.', 7 | files: 'src/**/*.test.ts', // "default" group 8 | concurrentBrowsers: 3, 9 | nodeResolve: { 10 | exportConditions: ['production', 'default'], 11 | }, 12 | testFramework: { 13 | config: { 14 | timeout: 3000, 15 | retries: 1, 16 | }, 17 | }, 18 | plugins: [ 19 | esbuildPlugin({ 20 | ts: true, 21 | tsconfig: fileURLToPath(new URL('./tsconfig.json', import.meta.url)), 22 | }), 23 | ], 24 | browsers: [ 25 | playwrightLauncher({ product: 'chromium' }), 26 | playwrightLauncher({ product: 'firefox' }), 27 | playwrightLauncher({ product: 'webkit' }), 28 | ], 29 | testRunnerHtml: testFramework => ` 30 | 31 | 32 | 33 | 36 | 37 | 38 | 39 | `, 40 | }; 41 | --------------------------------------------------------------------------------