diff --git a/.circleci/config.yml b/.circleci/config.yml index ea025cf6e570..a5db1788f966 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -270,7 +270,7 @@ jobs: # executor: # class: large # name: sb_cypress_8_node_14 - # parallelism: 2 + # parallelism: 8 # steps: # - git-shallow-clone/checkout_advanced: # clone_options: '--depth 1 --verbose' @@ -470,7 +470,7 @@ jobs: executor: class: medium+ name: sb_node_14_browsers - parallelism: 2 + parallelism: 6 steps: - git-shallow-clone/checkout_advanced: clone_options: '--depth 1 --verbose' @@ -490,7 +490,7 @@ jobs: executor: class: medium+ name: sb_node_14_browsers - parallelism: 2 + parallelism: 6 steps: - git-shallow-clone/checkout_advanced: clone_options: '--depth 1 --verbose' @@ -506,7 +506,7 @@ jobs: executor: class: medium+ name: sb_node_14_browsers - parallelism: 2 + parallelism: 6 steps: - git-shallow-clone/checkout_advanced: clone_options: '--depth 1 --verbose' @@ -526,7 +526,7 @@ jobs: executor: class: medium+ name: sb_node_14_browsers - parallelism: 2 + parallelism: 6 steps: - git-shallow-clone/checkout_advanced: clone_options: '--depth 1 --verbose' @@ -542,7 +542,7 @@ jobs: executor: class: medium+ name: sb_node_14_browsers - parallelism: 2 + parallelism: 6 steps: - git-shallow-clone/checkout_advanced: clone_options: '--depth 1 --verbose' @@ -558,7 +558,7 @@ jobs: executor: class: medium+ name: sb_playwright - parallelism: 2 + parallelism: 6 steps: - git-shallow-clone/checkout_advanced: clone_options: '--depth 1 --verbose' diff --git a/.github/workflows/generate-repros-next.yml b/.github/workflows/generate-repros-next.yml index a1774867b6c0..43da2e6f8711 100644 --- a/.github/workflows/generate-repros-next.yml +++ b/.github/workflows/generate-repros-next.yml @@ -7,7 +7,7 @@ on: # To remove when the branch will be merged push: branches: - - shilman/add-angular-repro-template + - vite-frameworks-xyz jobs: generate: @@ -15,6 +15,9 @@ jobs: env: YARN_ENABLE_IMMUTABLE_INSTALLS: false steps: + - uses: actions/setup-node@v2 + with: + node-version: 14 - uses: actions/checkout@v2 - name: Setup git user run: | diff --git a/code/.eslintrc.js b/code/.eslintrc.js index 1129908e7eb0..0e590a380807 100644 --- a/code/.eslintrc.js +++ b/code/.eslintrc.js @@ -101,5 +101,11 @@ module.exports = { 'jest/no-test-callback': 'off', // These aren't jest tests }, }, + { + files: ['**/builder-vite/input/iframe.html'], + rules: { + 'no-undef': 'off', // ignore "window" undef errors + }, + }, ], }; diff --git a/code/.vscode/settings.json b/code/.vscode/settings.json new file mode 100644 index 000000000000..25fa6215fdd3 --- /dev/null +++ b/code/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "typescript.tsdk": "node_modules/typescript/lib" +} diff --git a/code/addons/a11y/package.json b/code/addons/a11y/package.json index 51c89561018e..dc1c05351ff4 100644 --- a/code/addons/a11y/package.json +++ b/code/addons/a11y/package.json @@ -31,12 +31,12 @@ "import": "./dist/index.mjs", "types": "./dist/index.d.ts" }, - "./manager": { + "./manager.js": { "require": "./dist/manager.js", "import": "./dist/manager.mjs", "types": "./dist/manager.d.ts" }, - "./preview": { + "./preview.js": { "require": "./dist/preview.js", "import": "./dist/preview.mjs", "types": "./dist/preview.d.ts" diff --git a/code/addons/backgrounds/package.json b/code/addons/backgrounds/package.json index d2b1fec25220..fa6f784cc0c3 100644 --- a/code/addons/backgrounds/package.json +++ b/code/addons/backgrounds/package.json @@ -31,12 +31,12 @@ "import": "./dist/index.mjs", "types": "./dist/index.d.ts" }, - "./manager": { + "./manager.js": { "require": "./dist/manager.js", "import": "./dist/manager.mjs", "types": "./dist/manager.d.ts" }, - "./preview": { + "./preview.js": { "require": "./dist/preview.js", "import": "./dist/preview.mjs", "types": "./dist/preview.d.ts" diff --git a/code/addons/controls/package.json b/code/addons/controls/package.json index 2a3b0e1be05a..e3f070f7968c 100644 --- a/code/addons/controls/package.json +++ b/code/addons/controls/package.json @@ -31,7 +31,7 @@ "import": "./dist/index.mjs", "types": "./dist/index.d.ts" }, - "./manager": { + "./manager.js": { "require": "./dist/manager.js", "import": "./dist/manager.mjs", "types": "./dist/manager.d.ts" diff --git a/code/addons/jest/package.json b/code/addons/jest/package.json index cf01695a391e..74c2b71c64aa 100644 --- a/code/addons/jest/package.json +++ b/code/addons/jest/package.json @@ -33,7 +33,7 @@ "import": "./dist/index.mjs", "types": "./dist/index.d.ts" }, - "./manager": { + "./manager.js": { "require": "./dist/manager.js", "import": "./dist/manager.mjs", "types": "./dist/manager.d.ts" diff --git a/code/addons/links/package.json b/code/addons/links/package.json index 35098abd25c9..f470ac3be33b 100644 --- a/code/addons/links/package.json +++ b/code/addons/links/package.json @@ -27,12 +27,12 @@ "import": "./dist/index.mjs", "types": "./dist/index.d.ts" }, - "./manager": { + "./manager.js": { "require": "./dist/manager.js", "import": "./dist/manager.mjs", "types": "./dist/manager.d.ts" }, - "./preview": { + "./preview.js": { "require": "./dist/preview.js", "import": "./dist/preview.mjs", "types": "./dist/preview.d.ts" diff --git a/code/addons/measure/package.json b/code/addons/measure/package.json index b6f573cd1e2f..397674dcbb6e 100644 --- a/code/addons/measure/package.json +++ b/code/addons/measure/package.json @@ -30,12 +30,12 @@ "import": "./dist/index.mjs", "types": "./dist/index.d.ts" }, - "./manager": { + "./manager.js": { "require": "./dist/manager.js", "import": "./dist/manager.mjs", "types": "./dist/manager.d.ts" }, - "./preview": { + "./preview.js": { "require": "./dist/preview.js", "import": "./dist/preview.mjs", "types": "./dist/preview.d.ts" diff --git a/code/addons/outline/package.json b/code/addons/outline/package.json index 8df7588ea70b..67acdc8ac55e 100644 --- a/code/addons/outline/package.json +++ b/code/addons/outline/package.json @@ -33,12 +33,12 @@ "import": "./dist/index.mjs", "types": "./dist/index.d.ts" }, - "./manager": { + "./manager.js": { "require": "./dist/manager.js", "import": "./dist/manager.mjs", "types": "./dist/manager.d.ts" }, - "./preview": { + "./preview.js": { "require": "./dist/preset/preview.js", "import": "./dist/preset/preview.mjs", "types": "./dist/preview.d.ts" diff --git a/code/addons/toolbars/package.json b/code/addons/toolbars/package.json index 6b040dc08057..a7a286d33f91 100644 --- a/code/addons/toolbars/package.json +++ b/code/addons/toolbars/package.json @@ -31,7 +31,7 @@ "import": "./dist/index.mjs", "types": "./dist/index.d.ts" }, - "./manager": { + "./manager.js": { "require": "./dist/manager.js", "import": "./dist/manager.mjs", "types": "./dist/manager.d.ts" diff --git a/code/e2e-tests/addon-actions.spec.ts b/code/e2e-tests/addon-actions.spec.ts index efdfa76ce324..70b1de7357f6 100644 --- a/code/e2e-tests/addon-actions.spec.ts +++ b/code/e2e-tests/addon-actions.spec.ts @@ -7,6 +7,7 @@ const storybookUrl = process.env.STORYBOOK_URL || 'http://localhost:8001'; test.describe('addon-actions', () => { test.beforeEach(async ({ page }) => { await page.goto(storybookUrl); + await new SbPage(page).waitUntilLoaded(); }); test('should trigger an action', async ({ page }) => { diff --git a/code/e2e-tests/addon-backgrounds.spec.ts b/code/e2e-tests/addon-backgrounds.spec.ts index 3cb70f8031a2..f43c4b7c4021 100644 --- a/code/e2e-tests/addon-backgrounds.spec.ts +++ b/code/e2e-tests/addon-backgrounds.spec.ts @@ -7,6 +7,7 @@ const storybookUrl = process.env.STORYBOOK_URL || 'http://localhost:8001'; test.describe('addon-backgrounds', () => { test.beforeEach(async ({ page }) => { await page.goto(storybookUrl); + await new SbPage(page).waitUntilLoaded(); }); test('should have a dark background', async ({ page }) => { diff --git a/code/e2e-tests/addon-controls.spec.ts b/code/e2e-tests/addon-controls.spec.ts index a49f7cbeb489..df032a81ecaa 100644 --- a/code/e2e-tests/addon-controls.spec.ts +++ b/code/e2e-tests/addon-controls.spec.ts @@ -8,6 +8,7 @@ test.describe('addon-controls', () => { test('should change component when changing controls', async ({ page }) => { await page.goto(storybookUrl); const sbPage = new SbPage(page); + await sbPage.waitUntilLoaded(); await sbPage.navigateToStory('example-button', 'primary'); await sbPage.viewAddonPanel('Controls'); @@ -62,6 +63,7 @@ test.describe('addon-controls', () => { await page.goto(`${storybookUrl}?path=/story/example-button--primary&args=label:Hello+world`); const sbPage = new SbPage(page); + await sbPage.waitUntilLoaded(); await expect(sbPage.previewRoot().locator('button')).toContainText('Hello world'); await sbPage.viewAddonPanel('Controls'); diff --git a/code/e2e-tests/addon-docs.spec.ts b/code/e2e-tests/addon-docs.spec.ts index 2be60673da1b..cf6c444b54a0 100644 --- a/code/e2e-tests/addon-docs.spec.ts +++ b/code/e2e-tests/addon-docs.spec.ts @@ -10,6 +10,7 @@ const templateName = process.env.STORYBOOK_TEMPLATE_NAME || ''; test.describe('addon-docs', () => { test.beforeEach(async ({ page }) => { await page.goto(storybookUrl); + await new SbPage(page).waitUntilLoaded(); }); test('should provide source snippet', async ({ page }) => { diff --git a/code/e2e-tests/addon-interactions.spec.ts b/code/e2e-tests/addon-interactions.spec.ts index 52a517ce6790..f22fd913aeb9 100644 --- a/code/e2e-tests/addon-interactions.spec.ts +++ b/code/e2e-tests/addon-interactions.spec.ts @@ -7,6 +7,7 @@ const storybookUrl = process.env.STORYBOOK_URL || 'http://localhost:8001'; test.describe('addon-interactions', () => { test.beforeEach(async ({ page }) => { await page.goto(storybookUrl); + await new SbPage(page).waitUntilLoaded(); }); // FIXME: skip xxx diff --git a/code/e2e-tests/addon-viewport.spec.ts b/code/e2e-tests/addon-viewport.spec.ts index a5935b4db2cd..1a3624b1ec94 100644 --- a/code/e2e-tests/addon-viewport.spec.ts +++ b/code/e2e-tests/addon-viewport.spec.ts @@ -7,6 +7,7 @@ const storybookUrl = process.env.STORYBOOK_URL || 'http://localhost:8001'; test.describe('addon-viewport', () => { test.beforeEach(async ({ page }) => { await page.goto(storybookUrl); + await new SbPage(page).waitUntilLoaded(); }); test('should have viewport button in the toolbar', async ({ page }) => { diff --git a/code/e2e-tests/example.spec.ts b/code/e2e-tests/example.spec.ts deleted file mode 100644 index 05a034acf419..000000000000 --- a/code/e2e-tests/example.spec.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { test, expect } from '@playwright/test'; -import process from 'process'; - -const storybookUrl = process.env.STORYBOOK_URL || 'http://localhost:8001'; - -test('Basic story test', async ({ page }) => { - await page.goto(storybookUrl); - - const preview = page.frameLocator('#storybook-preview-iframe'); - const root = preview.locator('#storybook-root:visible, #storybook-docs:visible'); - - // Specific check for introduction story - await expect(root).toContainText('Welcome'); -}); diff --git a/code/e2e-tests/util.ts b/code/e2e-tests/util.ts index 3ecc010d7473..cb8ce73949f1 100644 --- a/code/e2e-tests/util.ts +++ b/code/e2e-tests/util.ts @@ -32,6 +32,14 @@ export class SbPage { await expect(selected).toBe('true'); } + async waitUntilLoaded() { + const root = this.previewRoot(); + const docsLoadingPage = root.locator('.sb-preparing-docs'); + const storyLoadingPage = root.locator('.sb-preparing-story'); + await docsLoadingPage.waitFor({ state: 'hidden' }); + await storyLoadingPage.waitFor({ state: 'hidden' }); + } + previewIframe() { return this.page.frameLocator('#storybook-preview-iframe'); } diff --git a/code/frameworks/react-vite/README.md b/code/frameworks/react-vite/README.md new file mode 100644 index 000000000000..e8a35450aec9 --- /dev/null +++ b/code/frameworks/react-vite/README.md @@ -0,0 +1 @@ +# Storybook for React diff --git a/code/frameworks/react-vite/package.json b/code/frameworks/react-vite/package.json new file mode 100644 index 000000000000..f359987cd911 --- /dev/null +++ b/code/frameworks/react-vite/package.json @@ -0,0 +1,98 @@ +{ + "name": "@storybook/react-vite", + "version": "7.0.0-alpha.24", + "description": "Storybook for React: Develop React Component in isolation with Hot Reloading.", + "keywords": [ + "storybook" + ], + "homepage": "https://github.com/storybookjs/storybook/tree/main/frameworks/react-vite", + "bugs": { + "url": "https://github.com/storybookjs/storybook/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/storybookjs/storybook.git", + "directory": "frameworks/react-vite" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "license": "MIT", + "exports": { + ".": { + "require": "./dist/index.js", + "import": "./dist/index.mjs", + "types": "./dist/index.d.ts" + }, + "./preset": { + "require": "./dist/preset.js", + "import": "./dist/preset.mjs", + "types": "./dist/preset.d.ts" + }, + "./package.json": { + "require": "./package.json", + "import": "./package.json", + "types": "./package.json" + } + }, + "main": "dist/index.js", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", + "files": [ + "dist/**/*", + "types/**/*", + "README.md", + "*.js", + "*.d.ts" + ], + "scripts": { + "check": "tsc --noEmit", + "prepare": "../../../scripts/prepare/bundle.ts" + }, + "dependencies": { + "@joshwooding/vite-plugin-react-docgen-typescript": "0.0.4", + "@rollup/pluginutils": "^4.2.0", + "@storybook/builder-vite": "7.0.0-alpha.24", + "@storybook/core-server": "7.0.0-alpha.24", + "@storybook/react": "7.0.0-alpha.24", + "@types/node": "^14.14.20 || ^16.0.0", + "@vitejs/plugin-react": "^1.0.8", + "ast-types": "^0.14.2", + "core-js": "^3.8.2", + "magic-string": "^0.26.1", + "react-docgen": "6.0.0-alpha.1", + "regenerator-runtime": "^0.13.7" + }, + "devDependencies": { + "jest-specific-snapshot": "^4.0.0", + "typescript": "~4.6.3" + }, + "peerDependencies": { + "@babel/core": "^7.11.5", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "typescript": { + "optional": true + } + }, + "engines": { + "node": ">=10.13.0" + }, + "publishConfig": { + "access": "public" + }, + "bundler": { + "entries": [ + "./src/index.ts", + "./src/preset.ts" + ], + "platform": "node" + }, + "gitHead": "55247a8e36da7061bfced80c588a539d3fda3f04" +} diff --git a/code/frameworks/react-vite/preset.js b/code/frameworks/react-vite/preset.js new file mode 100644 index 000000000000..a83f95279e7f --- /dev/null +++ b/code/frameworks/react-vite/preset.js @@ -0,0 +1 @@ +module.exports = require('./dist/preset'); diff --git a/code/frameworks/react-vite/src/index.ts b/code/frameworks/react-vite/src/index.ts new file mode 100644 index 000000000000..73f50674eeac --- /dev/null +++ b/code/frameworks/react-vite/src/index.ts @@ -0,0 +1,2 @@ +export * from '@storybook/react'; +export type { StorybookConfig } from '@storybook/builder-vite'; diff --git a/code/frameworks/react-vite/src/plugins/docgen-handlers/actualNameHandler.ts b/code/frameworks/react-vite/src/plugins/docgen-handlers/actualNameHandler.ts new file mode 100644 index 000000000000..4849df62d087 --- /dev/null +++ b/code/frameworks/react-vite/src/plugins/docgen-handlers/actualNameHandler.ts @@ -0,0 +1,48 @@ +/** + * This is heavily based on the react-docgen `displayNameHandler` + * (https://github.com/reactjs/react-docgen/blob/26c90c0dd105bf83499a83826f2a6ff7a724620d/src/handlers/displayNameHandler.ts) + * but instead defines an `actualName` property on the generated docs that is taken first from the component's actual name. + * This addresses an issue where the name that the generated docs are stored under is incorrectly named with the `displayName` + * and not the component's actual name. + * + * This is inspired by `actualNameHandler` from https://github.com/storybookjs/babel-plugin-react-docgen, but is modified + * directly from displayNameHandler, using the same approach as babel-plugin-react-docgen. + */ + +import { namedTypes as t } from 'ast-types'; +import type { NodePath } from 'ast-types/lib/node-path'; +import { getNameOrValue, isReactForwardRefCall } from 'react-docgen/lib/utils'; +import type { Importer } from 'react-docgen/lib/parse'; +import type Documentation from 'react-docgen/lib/Documentation'; + +export default function actualNameHandler( + documentation: Documentation, + path: NodePath, + importer: Importer +): void { + if (t.ClassDeclaration.check(path.node) || t.FunctionDeclaration.check(path.node)) { + documentation.set('actualName', getNameOrValue(path.get('id'))); + } else if ( + t.ArrowFunctionExpression.check(path.node) || + t.FunctionExpression.check(path.node) || + isReactForwardRefCall(path, importer) + ) { + let currentPath = path; + while (currentPath.parent) { + if (t.VariableDeclarator.check(currentPath.parent.node)) { + documentation.set('actualName', getNameOrValue(currentPath.parent.get('id'))); + return; + } + if (t.AssignmentExpression.check(currentPath.parent.node)) { + const leftPath = currentPath.parent.get('left'); + if (t.Identifier.check(leftPath.node) || t.Literal.check(leftPath.node)) { + documentation.set('actualName', getNameOrValue(leftPath)); + return; + } + } + currentPath = currentPath.parent; + } + // Could not find an actual name + documentation.set('actualName', ''); + } +} diff --git a/code/frameworks/react-vite/src/plugins/react-docgen.ts b/code/frameworks/react-vite/src/plugins/react-docgen.ts new file mode 100644 index 000000000000..699bf30c2745 --- /dev/null +++ b/code/frameworks/react-vite/src/plugins/react-docgen.ts @@ -0,0 +1,69 @@ +import path from 'path'; +import { createFilter } from '@rollup/pluginutils'; +import { + parse, + handlers as docgenHandlers, + resolver as docgenResolver, + importers as docgenImporters, +} from 'react-docgen'; +import type { DocumentationObject } from 'react-docgen/lib/Documentation'; +import MagicString from 'magic-string'; +import type { Plugin } from 'vite'; +import actualNameHandler from './docgen-handlers/actualNameHandler'; + +type DocObj = DocumentationObject & { actualName: string }; + +// TODO: None of these are able to be overridden, so `default` is aspirational here. +const defaultHandlers = Object.values(docgenHandlers).map((handler) => handler); +const defaultResolver = docgenResolver.findAllExportedComponentDefinitions; +const defaultImporter = docgenImporters.makeFsImporter(); +const handlers = [...defaultHandlers, actualNameHandler]; + +type Options = { + include?: string | RegExp | (string | RegExp)[]; + exclude?: string | RegExp | (string | RegExp)[]; +}; + +export function reactDocgen({ + include = /\.(mjs|tsx?|jsx?)$/, + exclude = [/node_modules\/.*/], +}: Options = {}): Plugin { + const cwd = process.cwd(); + const filter = createFilter(include, exclude); + + return { + name: 'react-docgen', + enforce: 'pre', + async transform(src: string, id: string) { + const relPath = path.relative(cwd, id); + if (!filter(relPath)) return; + + try { + // Since we're using `findAllExportedComponentDefinitions`, this will always be an array. + const docgenResults = parse(src, defaultResolver, handlers, { + importer: defaultImporter, + filename: id, + }) as DocObj[]; + const s = new MagicString(src); + + docgenResults.forEach((info) => { + const { actualName, ...docgenInfo } = info; + if (actualName) { + const docNode = JSON.stringify(docgenInfo); + s.append(`;${actualName}.__docgenInfo=${docNode}`); + } + }); + + // eslint-disable-next-line consistent-return + return { + code: s.toString(), + map: s.generateMap(), + }; + } catch (e) { + // Usually this is just an error from react-docgen that it couldn't find a component + // Only uncomment for troubleshooting + // console.error(e); + } + }, + }; +} diff --git a/code/frameworks/react-vite/src/preset.ts b/code/frameworks/react-vite/src/preset.ts new file mode 100644 index 000000000000..10286949c4ad --- /dev/null +++ b/code/frameworks/react-vite/src/preset.ts @@ -0,0 +1,52 @@ +/* eslint-disable global-require */ +import path from 'path'; +import fs from 'fs'; +import type { StorybookConfig, TypescriptOptions } from '@storybook/builder-vite'; + +export const addons: StorybookConfig['addons'] = ['@storybook/react']; + +export const core: StorybookConfig['core'] = { + builder: '@storybook/builder-vite', +}; + +export function readPackageJson(): Record | false { + const packageJsonPath = path.resolve('package.json'); + if (!fs.existsSync(packageJsonPath)) { + return false; + } + + const jsonContent = fs.readFileSync(packageJsonPath, 'utf8'); + return JSON.parse(jsonContent); +} + +export const viteFinal: StorybookConfig['viteFinal'] = async (config, { presets }) => { + const { plugins = [] } = config; + + const { reactDocgen, reactDocgenTypescriptOptions } = await presets.apply( + 'typescript', + {} as TypescriptOptions + ); + let typescriptPresent; + + try { + const pkgJson = readPackageJson(); + typescriptPresent = + pkgJson && (pkgJson.devDependencies?.typescript || pkgJson.dependencies?.typescript); + } catch (e) { + typescriptPresent = false; + } + + if (reactDocgen === 'react-docgen-typescript' && typescriptPresent) { + plugins.push( + require('@joshwooding/vite-plugin-react-docgen-typescript').default( + reactDocgenTypescriptOptions + ) + ); + } else if (reactDocgen) { + const { reactDocgen } = await import('./plugins/react-docgen'); + // Needs to run before the react plugin, so add to the front + plugins.unshift(reactDocgen()); + } + + return config; +}; diff --git a/code/frameworks/react-vite/src/typings.d.ts b/code/frameworks/react-vite/src/typings.d.ts new file mode 100644 index 000000000000..f21e29cca314 --- /dev/null +++ b/code/frameworks/react-vite/src/typings.d.ts @@ -0,0 +1,46 @@ +declare module '@storybook/semver'; +declare module 'global'; + +// TODO: Replace, as soon as @types/react-dom 17.0.14 is used +// Source: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/fb0f14b7a35cde26ffaa82e7536c062e593e9ae6/types/react-dom/client.d.ts +declare module 'react-dom/client' { + import React = require('react'); + export interface HydrationOptions { + onHydrated?(suspenseInstance: Comment): void; + onDeleted?(suspenseInstance: Comment): void; + /** + * Prefix for `useId`. + */ + identifierPrefix?: string; + onRecoverableError?: (error: unknown) => void; + } + + export interface RootOptions { + /** + * Prefix for `useId`. + */ + identifierPrefix?: string; + onRecoverableError?: (error: unknown) => void; + } + + export interface Root { + render(children: React.ReactChild | Iterable): void; + unmount(): void; + } + + /** + * Replaces `ReactDOM.render` when the `.render` method is called and enables Concurrent Mode. + * + * @see https://reactjs.org/docs/concurrent-mode-reference.html#createroot + */ + export function createRoot( + container: Element | Document | DocumentFragment | Comment, + options?: RootOptions + ): Root; + + export function hydrateRoot( + container: Element | Document | DocumentFragment | Comment, + initialChildren: React.ReactChild | Iterable, + options?: HydrationOptions + ): Root; +} diff --git a/code/frameworks/react-vite/tsconfig.json b/code/frameworks/react-vite/tsconfig.json new file mode 100644 index 000000000000..534e4ddd108a --- /dev/null +++ b/code/frameworks/react-vite/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "types": ["node"], + "resolveJsonModule": true + }, + "include": ["src/**/*"], + "exclude": ["src/**/*.test.*", "src/**/__testfixtures__/**"] +} diff --git a/code/frameworks/vue3-vite/README.md b/code/frameworks/vue3-vite/README.md new file mode 100644 index 000000000000..e8a35450aec9 --- /dev/null +++ b/code/frameworks/vue3-vite/README.md @@ -0,0 +1 @@ +# Storybook for React diff --git a/code/frameworks/vue3-vite/package.json b/code/frameworks/vue3-vite/package.json new file mode 100644 index 000000000000..05cb28c886b9 --- /dev/null +++ b/code/frameworks/vue3-vite/package.json @@ -0,0 +1,99 @@ +{ + "name": "@storybook/vue3-vite", + "version": "7.0.0-alpha.24", + "description": "Storybook for Vue3: Develop Vue3 Component in isolation with Hot Reloading.", + "keywords": [ + "storybook" + ], + "homepage": "https://github.com/storybookjs/storybook/tree/main/frameworks/vue3-vite", + "bugs": { + "url": "https://github.com/storybookjs/storybook/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/storybookjs/storybook.git", + "directory": "frameworks/vue3-vite" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "license": "MIT", + "exports": { + ".": { + "require": "./dist/index.js", + "import": "./dist/index.mjs", + "types": "./dist/index.d.ts" + }, + "./preset": { + "require": "./dist/preset.js", + "import": "./dist/preset.mjs", + "types": "./dist/preset.d.ts" + }, + "./package.json": { + "require": "./package.json", + "import": "./package.json", + "types": "./package.json" + } + }, + "main": "dist/index.js", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", + "files": [ + "dist/**/*", + "types/**/*", + "README.md", + "*.js", + "*.d.ts" + ], + "scripts": { + "check": "tsc --noEmit", + "prepare": "../../../scripts/prepare/bundle.ts" + }, + "dependencies": { + "@rollup/pluginutils": "^4.2.0", + "@storybook/builder-vite": "7.0.0-alpha.24", + "@storybook/core-server": "7.0.0-alpha.24", + "@storybook/vue3": "7.0.0-alpha.24", + "@types/node": "^14.14.20 || ^16.0.0", + "@vitejs/plugin-vue": "^3.0.3", + "ast-types": "^0.14.2", + "core-js": "^3.8.2", + "magic-string": "^0.26.1", + "react-docgen": "6.0.0-alpha.1", + "regenerator-runtime": "^0.13.7", + "vite": "3", + "vue-docgen-api": "^4.40.0" + }, + "devDependencies": { + "jest-specific-snapshot": "^4.0.0", + "typescript": "~4.6.3" + }, + "peerDependencies": { + "@babel/core": "^7.11.5", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "typescript": { + "optional": true + } + }, + "engines": { + "node": ">=10.13.0" + }, + "publishConfig": { + "access": "public" + }, + "bundler": { + "entries": [ + "./src/index.ts", + "./src/preset.ts" + ], + "platform": "node" + }, + "gitHead": "55247a8e36da7061bfced80c588a539d3fda3f04" +} diff --git a/code/frameworks/vue3-vite/preset.js b/code/frameworks/vue3-vite/preset.js new file mode 100644 index 000000000000..2e3d77f1dea6 --- /dev/null +++ b/code/frameworks/vue3-vite/preset.js @@ -0,0 +1,2 @@ +console.log('vue3-vite preset!') +module.exports = require('./dist/preset'); diff --git a/code/frameworks/vue3-vite/src/index.ts b/code/frameworks/vue3-vite/src/index.ts new file mode 100644 index 000000000000..5687914b4894 --- /dev/null +++ b/code/frameworks/vue3-vite/src/index.ts @@ -0,0 +1,2 @@ +export * from '@storybook/vue3'; +export type { StorybookConfig } from '@storybook/builder-vite'; diff --git a/code/frameworks/vue3-vite/src/plugins/docgen-handlers/actualNameHandler.ts b/code/frameworks/vue3-vite/src/plugins/docgen-handlers/actualNameHandler.ts new file mode 100644 index 000000000000..4849df62d087 --- /dev/null +++ b/code/frameworks/vue3-vite/src/plugins/docgen-handlers/actualNameHandler.ts @@ -0,0 +1,48 @@ +/** + * This is heavily based on the react-docgen `displayNameHandler` + * (https://github.com/reactjs/react-docgen/blob/26c90c0dd105bf83499a83826f2a6ff7a724620d/src/handlers/displayNameHandler.ts) + * but instead defines an `actualName` property on the generated docs that is taken first from the component's actual name. + * This addresses an issue where the name that the generated docs are stored under is incorrectly named with the `displayName` + * and not the component's actual name. + * + * This is inspired by `actualNameHandler` from https://github.com/storybookjs/babel-plugin-react-docgen, but is modified + * directly from displayNameHandler, using the same approach as babel-plugin-react-docgen. + */ + +import { namedTypes as t } from 'ast-types'; +import type { NodePath } from 'ast-types/lib/node-path'; +import { getNameOrValue, isReactForwardRefCall } from 'react-docgen/lib/utils'; +import type { Importer } from 'react-docgen/lib/parse'; +import type Documentation from 'react-docgen/lib/Documentation'; + +export default function actualNameHandler( + documentation: Documentation, + path: NodePath, + importer: Importer +): void { + if (t.ClassDeclaration.check(path.node) || t.FunctionDeclaration.check(path.node)) { + documentation.set('actualName', getNameOrValue(path.get('id'))); + } else if ( + t.ArrowFunctionExpression.check(path.node) || + t.FunctionExpression.check(path.node) || + isReactForwardRefCall(path, importer) + ) { + let currentPath = path; + while (currentPath.parent) { + if (t.VariableDeclarator.check(currentPath.parent.node)) { + documentation.set('actualName', getNameOrValue(currentPath.parent.get('id'))); + return; + } + if (t.AssignmentExpression.check(currentPath.parent.node)) { + const leftPath = currentPath.parent.get('left'); + if (t.Identifier.check(leftPath.node) || t.Literal.check(leftPath.node)) { + documentation.set('actualName', getNameOrValue(leftPath)); + return; + } + } + currentPath = currentPath.parent; + } + // Could not find an actual name + documentation.set('actualName', ''); + } +} diff --git a/code/frameworks/vue3-vite/src/plugins/vue-docgen.ts b/code/frameworks/vue3-vite/src/plugins/vue-docgen.ts new file mode 100644 index 000000000000..066b44df134c --- /dev/null +++ b/code/frameworks/vue3-vite/src/plugins/vue-docgen.ts @@ -0,0 +1,27 @@ +import { parse } from 'vue-docgen-api'; +import type { Plugin } from 'vite'; +import { createFilter } from 'vite'; +import MagicString from 'magic-string'; + +export function vueDocgen(): Plugin { + const include = /\.(vue)$/; + const filter = createFilter(include); + + return { + name: 'vue-docgen', + + async transform(src: string, id: string) { + if (!filter(id)) return undefined; + + const metaData = await parse(id); + const metaSource = JSON.stringify(metaData); + const s = new MagicString(src); + s.append(`;_sfc_main.__docgenInfo = ${metaSource}`); + + return { + code: s.toString(), + map: s.generateMap({ hires: true, source: id }), + }; + }, + }; +} diff --git a/code/frameworks/vue3-vite/src/preset.ts b/code/frameworks/vue3-vite/src/preset.ts new file mode 100644 index 000000000000..61f7d277e642 --- /dev/null +++ b/code/frameworks/vue3-vite/src/preset.ts @@ -0,0 +1,53 @@ +import path from 'path'; +import fs from 'fs'; +import type { StorybookConfig } from '@storybook/builder-vite'; + +export const addons: StorybookConfig['addons'] = ['@storybook/vue3']; + +export const core: StorybookConfig['core'] = { + builder: '@storybook/builder-vite', +}; + +export function readPackageJson(): Record | false { + const packageJsonPath = path.resolve('package.json'); + if (!fs.existsSync(packageJsonPath)) { + return false; + } + + const jsonContent = fs.readFileSync(packageJsonPath, 'utf8'); + return JSON.parse(jsonContent); +} + +export const viteFinal: StorybookConfig['viteFinal'] = async (config, { presets }) => { + const { plugins = [] } = config; + + try { + // eslint-disable-next-line global-require + const vuePlugin = require('@vitejs/plugin-vue'); + plugins.push(vuePlugin()); + const { vueDocgen } = await import('./plugins/vue-docgen'); + plugins.push(vueDocgen()); + } catch (err) { + if ((err as NodeJS.ErrnoException).code === 'MODULE_NOT_FOUND') { + throw new Error( + '@storybook/builder-vite requires @vitejs/plugin-vue to be installed ' + + 'when using @storybook/vue or @storybook/vue3.' + + ' Please install it and start storybook again.' + ); + } + throw err; + } + + const updated = { + ...config, + plugins, + resolve: { + ...config.resolve, + alias: { + ...config.resolve?.alias, + vue: 'vue/dist/vue.esm-bundler.js', + }, + }, + }; + return updated; +}; diff --git a/code/frameworks/vue3-vite/src/typings.d.ts b/code/frameworks/vue3-vite/src/typings.d.ts new file mode 100644 index 000000000000..f21e29cca314 --- /dev/null +++ b/code/frameworks/vue3-vite/src/typings.d.ts @@ -0,0 +1,46 @@ +declare module '@storybook/semver'; +declare module 'global'; + +// TODO: Replace, as soon as @types/react-dom 17.0.14 is used +// Source: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/fb0f14b7a35cde26ffaa82e7536c062e593e9ae6/types/react-dom/client.d.ts +declare module 'react-dom/client' { + import React = require('react'); + export interface HydrationOptions { + onHydrated?(suspenseInstance: Comment): void; + onDeleted?(suspenseInstance: Comment): void; + /** + * Prefix for `useId`. + */ + identifierPrefix?: string; + onRecoverableError?: (error: unknown) => void; + } + + export interface RootOptions { + /** + * Prefix for `useId`. + */ + identifierPrefix?: string; + onRecoverableError?: (error: unknown) => void; + } + + export interface Root { + render(children: React.ReactChild | Iterable): void; + unmount(): void; + } + + /** + * Replaces `ReactDOM.render` when the `.render` method is called and enables Concurrent Mode. + * + * @see https://reactjs.org/docs/concurrent-mode-reference.html#createroot + */ + export function createRoot( + container: Element | Document | DocumentFragment | Comment, + options?: RootOptions + ): Root; + + export function hydrateRoot( + container: Element | Document | DocumentFragment | Comment, + initialChildren: React.ReactChild | Iterable, + options?: HydrationOptions + ): Root; +} diff --git a/code/frameworks/vue3-vite/tsconfig.json b/code/frameworks/vue3-vite/tsconfig.json new file mode 100644 index 000000000000..534e4ddd108a --- /dev/null +++ b/code/frameworks/vue3-vite/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "types": ["node"], + "resolveJsonModule": true + }, + "include": ["src/**/*"], + "exclude": ["src/**/*.test.*", "src/**/__testfixtures__/**"] +} diff --git a/code/lib/builder-vite/README.md b/code/lib/builder-vite/README.md new file mode 100644 index 000000000000..88d077113f2d --- /dev/null +++ b/code/lib/builder-vite/README.md @@ -0,0 +1,225 @@ +# Storybook builder for Vite + +Build your stories with [vite](https://vitejs.dev/) for fast startup times and near-instant HMR. + +# Table of Contents + +- [Migration from storybook-builder-vite](#migration-from-storybook-builder-vite) +- [Installation](#installation) +- [Usage](#usage) + - [Getting started with Vite and Storybook (on a new project)](#getting-started-with-vite-and-storybook-on-a-new-project) + - [Migration from webpack / CRA](#migration-from-webpack--cra) + - [Customize Vite config](#customize-vite-config) + - [Svelte Customization](#svelte-customization) + - [TypeScript](#typescript) + - [React Docgen](#react-docgen) + - [Note about working directory](#note-about-working-directory) +- [Known issues](#known-issues) +- [Contributing](#contributing) + - [About this codebase](#about-this-codebase) + +## Migration from storybook-builder-vite + +This project has moved from `storybook-builder-vite` to `@storybook/builder-vite` as part of a larger effort to improve Vite support in Storybook. To automatically migrate your existing project, you can run + +```bash +npx sb@next automigrate +``` + +To manually migrate: + +1. Remove `storybook-builder-vite` from your `package.json` dependencies +2. Install `@storybook/builder-vite` +3. Update your `core.builder` setting in `.storybook/main.js` to `@storybook/builder-vite`. + +## Installation + +Requirements: + +- Vite 3.0 or newer (for Vite v2 (2.5+), use `@storybook/builder-vite@0.1.x`) +- Storybook 6.4.0 or newer (for storybook 6.3, use `storybook-builder-vite@0.1.16`) + +```bash +npm install @storybook/builder-vite --save-dev +``` + +or + +```bash +yarn add --dev @storybook/builder-vite +``` + +or + +```bash +pnpm add --save-dev @storybook/builder-vite +``` + +Note: when using `pnpm`, you may need to enable [shamefully-hoist](https://pnpm.io/npmrc#shamefully-hoist), until https://github.com/storybookjs/builder-vite/issues/55 can be fixed. + +## Usage + +In your `main.js` configuration file, +set `core: { builder: "@storybook/builder-vite" }`. + +> For autoreload of react stories to work, they need to have a `.stories.tsx` or `.stories.jsx` file suffix. +> See also [#53](https://github.com/storybookjs/builder-vite/pull/53) + +The builder supports both development mode in Storybook, and building a static production version. + +### Getting started with Vite and Storybook (on a new project) + +See https://vitejs.dev/guide/#scaffolding-your-first-vite-project, + +``` +npm create vite@latest # follow the prompts +npx sb init --builder @storybook/builder-vite && npm run storybook +``` + +### Migration from webpack / CRA + +1. Install `vite` and `@storybook/builder-vite` +2. Remove any explicit project dependencies on `webpack`, `react-scripts`, and any other webpack plugins or loaders. +3. If you were previously using `@storybook/manager-webpack5`, you'll need to remove it, since currently the vite builder only works with `manager-webpack4`, which is the default and does not need to be installed manually. Also remove `@storybook/builder-webpack5` or `@storybook/builder-webpack4` if they are installed. +4. Set `core: { builder: "@storybook/builder-vite" }` in your `.storybook/main.js` file. +5. Remove storybook webpack cache (`rm -rf node_modules/.cache`) +6. Update your `/public/index.html` file for vite (be sure there are no `%PUBLIC_URL%` inside it, which is a CRA variable) +7. Be sure that any files containing JSX syntax use a `.jsx` or `.tsx` file extension, which [vite requires](https://vitejs.dev/guide/features.html#jsx). This includes `.storybook/preview.jsx` if it contains JSX syntax. +8. If you are using `@storybook/addon-interactions`, for now you'll need to add a [workaround](https://github.com/storybookjs/storybook/issues/18399) for jest-mock relying on the node `global` variable by creating a `.storybook/preview-head.html` file containing the following: + +```html + +``` + +9. Start up your storybook using the same `yarn storybook` or `npm run storybook` commands you are used to. + +For other details about the differences between vite and webpack projects, be sure to read through the [vite documentation](https://vitejs.dev/). + +### Customize Vite config + +The builder will _not_ read your `vite.config.js` file by default. + +In `.storybook/main.js` (or whatever your Storybook config file is named) +you can override the Vite config: + +```javascript +// use `mergeConfig` to recursively merge Vite options +const { mergeConfig } = require('vite'); + +module.exports = { + async viteFinal(config, { configType }) { + // return the customized config + return mergeConfig(config, { + // customize the Vite config here + resolve: { + alias: { foo: 'bar' }, + }, + }); + }, + // ... other options here +}; +``` + +The `viteFinal` function will give you `config` which is +the builder's own Vite config. You can tweak this as you want, +for example to set up aliases, add new plugins etc. + +The `configType` variable will be either `"DEVELOPMENT"` or `"PRODUCTION"`. + +The function should return the updated Vite configuration. + +### Svelte Customization + +When using this builder with Svelte, your `.storybook/main.js` (or equivalent) +can contain a `svelteOptions` object to pass custom options to +[`vite-plugin-svelte`](https://github.com/sveltejs/vite-plugin-svelte/tree/main/packages/vite-plugin-svelte): + +```javascript +const preprocess = require('svelte-preprocess'); + +module.exports = { + svelteOptions: { + preprocess: preprocess({ + typescript: true, + postcss: true, + sourceMap: true, + }), + }, +}; +``` + +### TypeScript + +Configure your `.storybook/main.ts` to use TypeScript: + +```typescript +import type { StorybookViteConfig } from '@storybook/builder-vite'; + +const config: StorybookViteConfig = { + // other storybook options..., + async viteFinal(config, options) { + // modify and return config + }, +}; + +export default config; +``` + +Or alternatively, you can use named exports: + +```typescript +import type { ViteFinal } from '@storybook/builder-vite'; + +export const viteFinal: ViteFinal = async (config, options) => { + // modify and return config +}; +``` + +See [Customize Vite config](#customize-vite-config) for details about using `viteFinal`. + +### React Docgen + +Docgen is used in Storybook to populate the props table in docs view, the controls panel, and for several other addons. Docgen is supported in vue and react, and there are two docgen options when using react, `react-docgen` and `react-docgen-typescript`. You can learn more about the pros/cons of each in [this gist](https://gist.github.com/shilman/036313ffa3af52ca986b375d90ea46b0). By default, if we find a `typescript` dependency in your `package.json` file, we will assume you're using typescript and will choose `react-docgen-typescript`. You can change this by setting the `typescript.reactDocgen` option in your `.storybook/main.js` file: + +```javascript +module.exports = { + typescript: { + reactDocgen: 'react-docgen` + } +} +``` + +If you're using TypeScript, we encourage you to experiment and see which option works better for your project. + +### Note about working directory + +The builder will by default enable Vite's [server.fs.strict](https://vitejs.dev/config/#server-fs-strict) +option, for increased security. The default project `root` is set to the parent directory of the +storybook configuration directory. This can be overridden in viteFinal. + +## Known issues + +- HMR: saving a story file does not hot-module-reload, a full reload happens instead. HMR works correctly when saving component files. + +## Contributing + +The Vite builder cannot build itself. +Are you willing to contribute? We are especially looking for vue and svelte experts, as the current maintainers are react users. + +https://github.com/storybookjs/builder-vite/issues/11 + +Have a look at the GitHub issues for known bugs. If you find any new bugs, +feel free to create an issue or send a pull request! + +Please read the [How to contribute](/CONTRIBUTING.md) guide. + +### About this codebase + +The code is a monorepo with the core `@storybook/builder-vite` package, +and examples (like `examples/react`) to test the builder implementation. + +Similar to the main storybook monorepo, you need yarn to develop this builder, because the project is organized as yarn workspaces. +This lets you write new code in the core builder package, and instantly use them from +the example packages. diff --git a/code/lib/builder-vite/input/iframe.html b/code/lib/builder-vite/input/iframe.html new file mode 100644 index 000000000000..86ec23a4bf01 --- /dev/null +++ b/code/lib/builder-vite/input/iframe.html @@ -0,0 +1,29 @@ + + + + + + <!-- [TITLE HERE] --> + + + + + + +
+
+ + + diff --git a/code/lib/builder-vite/input/react-dom-client-placeholder.js b/code/lib/builder-vite/input/react-dom-client-placeholder.js new file mode 100644 index 000000000000..ac58bff7de38 --- /dev/null +++ b/code/lib/builder-vite/input/react-dom-client-placeholder.js @@ -0,0 +1,3 @@ +// This file is to work around https://github.com/vitejs/vite/issues/6007 +// For react < 18 projects, where `react-dom/client` does not exist, yet is +// conditionally imported by @storybook/react. diff --git a/code/lib/builder-vite/package.json b/code/lib/builder-vite/package.json new file mode 100644 index 000000000000..42e63266f1b5 --- /dev/null +++ b/code/lib/builder-vite/package.json @@ -0,0 +1,67 @@ +{ + "name": "@storybook/builder-vite", + "version": "7.0.0-alpha.24", + "description": "A plugin to run and build Storybooks with Vite", + "homepage": "https://github.com/storybookjs/storybook/tree/main/code/lib/builder-vite/#readme", + "repository": { + "type": "git", + "url": "https://github.com/storybookjs/storybook.git", + "directory": "code/lib/builder-vite" + }, + "license": "MIT", + "author": "Eirik Sletteberg", + "main": "dist/cjs/index.js", + "module": "dist/esm/index.js", + "types": "dist/types/index.d.ts", + "scripts": { + "check": "tsc --noEmit", + "prepare": "node ../../../scripts/prepare.js" + }, + "dependencies": { + "@joshwooding/vite-plugin-react-docgen-typescript": "0.0.5", + "@storybook/addons": "7.0.0-alpha.24", + "@storybook/channel-postmessage": "7.0.0-alpha.24", + "@storybook/channel-websocket": "7.0.0-alpha.24", + "@storybook/client-api": "7.0.0-alpha.24", + "@storybook/client-logger": "7.0.0-alpha.24", + "@storybook/core-common": "7.0.0-alpha.24", + "@storybook/mdx1-csf": "0.0.5-canary.13.9ce928a.0", + "@storybook/node-logger": "7.0.0-alpha.24", + "@storybook/preview-web": "7.0.0-alpha.24", + "@storybook/source-loader": "7.0.0-alpha.24", + "@vitejs/plugin-react": "^2.0.0", + "ast-types": "^0.14.2", + "es-module-lexer": "^0.9.3", + "glob": "^7.2.0", + "glob-promise": "^4.2.0", + "magic-string": "^0.26.1", + "react-docgen": "^6.0.0-alpha.0", + "slash": "^3.0.0", + "sveltedoc-parser": "^4.2.1", + "vite": "3" + }, + "devDependencies": { + "@storybook/mdx2-csf": "^0.0.3", + "@sveltejs/vite-plugin-svelte": "^1.0.0", + "@types/express": "^4.17.13", + "@types/node": "^17.0.23", + "svelte": "^3.49.0", + "typescript": "~4.6.3" + }, + "peerDependencies": { + "@storybook/mdx2-csf": "^0.0.3", + "svelte": "^3.0.0" + }, + "peerDependenciesMeta": { + "@storybook/mdx2-csf": { + "optional": true + }, + "svelte": { + "optional": true + } + }, + "publishConfig": { + "access": "public" + }, + "gitHead": "74bdb53f67dd59bae2661c668d2d5d4296113281" +} diff --git a/code/lib/builder-vite/src/build.ts b/code/lib/builder-vite/src/build.ts new file mode 100644 index 000000000000..d0a36401fc7f --- /dev/null +++ b/code/lib/builder-vite/src/build.ts @@ -0,0 +1,32 @@ +import { build as viteBuild } from 'vite'; +import { stringifyProcessEnvs } from './envs'; +import { commonConfig } from './vite-config'; + +import type { EnvsRaw, ExtendedOptions } from './types'; + +export async function build(options: ExtendedOptions) { + const { presets } = options; + + const baseConfig = await commonConfig(options, 'build'); + const config = { + ...baseConfig, + build: { + outDir: options.outputDir, + emptyOutDir: false, // do not clean before running Vite build - Storybook has already added assets in there! + sourcemap: true, + }, + }; + + const finalConfig = await presets.apply('viteFinal', config, options); + + const envsRaw = await presets.apply>('env'); + // Stringify env variables after getting `envPrefix` from the final config + const envs = stringifyProcessEnvs(envsRaw, finalConfig.envPrefix); + // Update `define` + finalConfig.define = { + ...finalConfig.define, + ...envs, + }; + + await viteBuild(finalConfig); +} diff --git a/code/lib/builder-vite/src/code-generator-plugin.ts b/code/lib/builder-vite/src/code-generator-plugin.ts new file mode 100644 index 000000000000..e27240673b5e --- /dev/null +++ b/code/lib/builder-vite/src/code-generator-plugin.ts @@ -0,0 +1,149 @@ +/* eslint-disable no-param-reassign */ + +import * as fs from 'fs'; +import * as path from 'path'; +import { mergeConfig } from 'vite'; +import type { Plugin } from 'vite'; +import { transformIframeHtml } from './transform-iframe-html'; +import { generateIframeScriptCode } from './codegen-iframe-script'; +import { generateModernIframeScriptCode } from './codegen-modern-iframe-script'; +import { generateImportFnScriptCode } from './codegen-importfn-script'; +import { generateVirtualStoryEntryCode, generatePreviewEntryCode } from './codegen-entries'; +import { generateAddonSetupCode } from './codegen-set-addon-channel'; + +import type { ExtendedOptions } from './types'; + +import { + virtualAddonSetupFile, + virtualFileId, + virtualPreviewFile, + virtualStoriesFile, +} from './virtual-file-names'; + +export function codeGeneratorPlugin(options: ExtendedOptions): Plugin { + const iframePath = path.resolve(__dirname, '../..', 'input', 'iframe.html'); + let iframeId: string; + + // noinspection JSUnusedGlobalSymbols + return { + name: 'storybook-vite-code-generator-plugin', + enforce: 'pre', + configureServer(server) { + // invalidate the whole vite-app.js script on every file change. + // (this might be a little too aggressive?) + server.watcher.on('change', () => { + const appModule = server.moduleGraph.getModuleById(virtualFileId); + if (appModule) { + server.moduleGraph.invalidateModule(appModule); + } + const storiesModule = server.moduleGraph.getModuleById(virtualStoriesFile); + if (storiesModule) { + server.moduleGraph.invalidateModule(storiesModule); + } + }); + + // Adding new story files is not covered by the change event above. So we need to detect this and trigger + // HMR to update the importFn. + server.watcher.on('add', (path) => { + // TODO maybe use the stories declaration in main + if (/\.stories\.([tj])sx?$/.test(path) || /\.(story|stories).mdx$/.test(path)) { + // We need to emit a change event to trigger HMR + server.watcher.emit('change', virtualStoriesFile); + } + }); + }, + config(config, { command }) { + // If we are building the static distribution, add iframe.html as an entry. + // In development mode, it's not an entry - instead, we use an express middleware + // to serve iframe.html. The reason is that Vite's dev server (at the time of writing) + // does not support virtual files as entry points. + if (command === 'build') { + if (!config.build) { + config.build = {}; + } + config.build.rollupOptions = { + ...config.build.rollupOptions, + input: iframePath, + }; + } + + // Detect if react 18 is installed. If not, alias it to a virtual placeholder file. + try { + require.resolve('react-dom/client', { paths: [config.root || process.cwd()] }); + } catch (e) { + if (isNodeError(e) && e.code === 'MODULE_NOT_FOUND') { + config.resolve = mergeConfig(config.resolve ?? {}, { + alias: { + 'react-dom/client': path.resolve( + __dirname, + '../..', + 'input', + 'react-dom-client-placeholder.js' + ), + }, + }); + } + } + }, + configResolved(config) { + iframeId = `${config.root}/iframe.html`; + }, + resolveId(source) { + if (source === virtualFileId) { + return virtualFileId; + } + if (source === iframePath) { + return iframeId; + } + if (source === virtualStoriesFile) { + return virtualStoriesFile; + } + if (source === virtualPreviewFile) { + return virtualPreviewFile; + } + if (source === virtualAddonSetupFile) { + return virtualAddonSetupFile; + } + return undefined; + }, + async load(id) { + const storyStoreV7 = options.features?.storyStoreV7; + if (id === virtualStoriesFile) { + if (storyStoreV7) { + return generateImportFnScriptCode(options); + } + return generateVirtualStoryEntryCode(options); + } + + if (id === virtualAddonSetupFile) { + return generateAddonSetupCode(); + } + + if (id === virtualPreviewFile && !storyStoreV7) { + return generatePreviewEntryCode(options); + } + + if (id === virtualFileId) { + if (storyStoreV7) { + return generateModernIframeScriptCode(options); + } + return generateIframeScriptCode(options); + } + + if (id === iframeId) { + return fs.readFileSync(path.resolve(__dirname, '../..', 'input', 'iframe.html'), 'utf-8'); + } + + return undefined; + }, + async transformIndexHtml(html, ctx) { + if (ctx.path !== '/iframe.html') { + return undefined; + } + return transformIframeHtml(html, options); + }, + }; +} + +// Refines an error received from 'catch' to be a NodeJS exception +const isNodeError = (error: unknown): error is NodeJS.ErrnoException => error instanceof Error; diff --git a/code/lib/builder-vite/src/codegen-entries.ts b/code/lib/builder-vite/src/codegen-entries.ts new file mode 100644 index 000000000000..7cdbb0eb4c8d --- /dev/null +++ b/code/lib/builder-vite/src/codegen-entries.ts @@ -0,0 +1,43 @@ +import { loadPreviewOrConfigFile } from '@storybook/core-common'; +import type { Options } from '@storybook/core-common'; +import slash from 'slash'; +import { normalizePath } from 'vite'; +import type { ExtendedOptions } from './types'; +import { listStories } from './list-stories'; + +const absoluteFilesToImport = (files: string[], name: string) => + files.map((el, i) => `import ${name ? `* as ${name}_${i} from ` : ''}'/@fs/${normalizePath(el)}'`).join('\n'); + +export async function generateVirtualStoryEntryCode(options: ExtendedOptions) { + const storyEntries = await listStories(options); + const resolveMap = storyEntries.reduce>( + (prev, entry) => ({ ...prev, [entry]: entry.replace(slash(process.cwd()), '.') }), + {} + ); + const modules = storyEntries.map((entry, i) => `${JSON.stringify(entry)}: story_${i}`).join(','); + + return ` + ${absoluteFilesToImport(storyEntries, 'story')} + + function loadable(key) { + return {${modules}}[key]; + } + + Object.assign(loadable, { + keys: () => (${JSON.stringify(Object.keys(resolveMap))}), + resolve: (key) => (${JSON.stringify(resolveMap)}[key]) + }); + + export function configStories(configure) { + configure(loadable, { hot: import.meta.hot }, false); + } + `.trim(); +} + +export async function generatePreviewEntryCode({ configDir }: Options) { + const previewFile = loadPreviewOrConfigFile({ configDir }); + if (!previewFile) return ''; + + return `import * as preview from '${slash(previewFile)}'; + export default preview;`; +} diff --git a/code/lib/builder-vite/src/codegen-iframe-script.ts b/code/lib/builder-vite/src/codegen-iframe-script.ts new file mode 100644 index 000000000000..975fe1b9cfd1 --- /dev/null +++ b/code/lib/builder-vite/src/codegen-iframe-script.ts @@ -0,0 +1,120 @@ +import { isAbsolute, resolve } from 'path'; +import { virtualPreviewFile, virtualStoriesFile } from './virtual-file-names'; +import { transformAbsPath } from './utils/transform-abs-path'; +import type { ExtendedOptions } from './types'; + +export async function generateIframeScriptCode(options: ExtendedOptions) { + const { presets, frameworkPath, framework } = options; + const frameworkImportPath = frameworkPath || `@storybook/${framework}`; + + const presetEntries = await presets.apply('config', [], options); + const previewEntries = await presets.apply('previewEntries', [], options); + const absolutePreviewEntries = previewEntries.map((entry) => + isAbsolute(entry) ? entry : resolve(entry) + ); + const configEntries = [...presetEntries, ...absolutePreviewEntries].filter(Boolean); + + const absoluteFilesToImport = (files: string[], name: string) => + files + .map((el, i) => `import ${name ? `* as ${name}_${i} from ` : ''}'${transformAbsPath(el)}'`) + .join('\n'); + + const importArray = (name: string, length: number) => + new Array(length).fill(0).map((_, i) => `${name}_${i}`); + + // noinspection UnnecessaryLocalVariableJS + /** @todo Inline variable and remove `noinspection` */ + // language=JavaScript + const code = ` + // Ensure that the client API is initialized by the framework before any other iframe code + // is loaded. That way our client-apis can assume the existence of the API+store + import { configure } from '${frameworkImportPath}'; + + import * as clientApi from "@storybook/client-api"; + import { logger } from '@storybook/client-logger'; + ${absoluteFilesToImport(configEntries, 'config')} + import * as preview from '${virtualPreviewFile}'; + import { configStories } from '${virtualStoriesFile}'; + + const { + addDecorator, + addParameters, + addLoader, + addArgTypesEnhancer, + addArgsEnhancer, + setGlobalRender, + } = clientApi; + + const configs = [${importArray('config', configEntries.length) + .concat('preview.default') + .join(',')}].filter(Boolean) + + configs.forEach(config => { + Object.keys(config).forEach((key) => { + const value = config[key]; + switch (key) { + case 'args': { + if (typeof clientApi.addArgs !== "undefined") { + return clientApi.addArgs(value); + } else { + return logger.warn( + "Could not add global args. Please open an issue in storybookjs/builder-vite." + ); + } + } + case 'argTypes': { + if (typeof clientApi.addArgTypes !== "undefined") { + return clientApi.addArgTypes(value); + } else { + return logger.warn( + "Could not add global argTypes. Please open an issue in storybookjs/builder-vite." + ); + } + } + case 'decorators': { + return value.forEach((decorator) => addDecorator(decorator, false)); + } + case 'loaders': { + return value.forEach((loader) => addLoader(loader, false)); + } + case 'parameters': { + return addParameters({ ...value }, false); + } + case 'argTypesEnhancers': { + return value.forEach((enhancer) => addArgTypesEnhancer(enhancer)); + } + case 'argsEnhancers': { + return value.forEach((enhancer) => addArgsEnhancer(enhancer)) + } + case 'render': { + return setGlobalRender(value) + } + case 'globals': + case 'globalTypes': { + const v = {}; + v[key] = value; + return addParameters(v, false); + } + case 'decorateStory': + case 'applyDecorators': + case 'renderToDOM': { + return null; // This key is not handled directly in v6 mode. + } + default: { + // eslint-disable-next-line prefer-template + return console.log(key + ' was not supported :( !'); + } + } + }); + }) + + /* TODO: not quite sure what to do with this, to fix HMR + if (import.meta.hot) { + import.meta.hot.accept(); + } + */ + + configStories(configure); + `.trim(); + return code; +} diff --git a/code/lib/builder-vite/src/codegen-importfn-script.ts b/code/lib/builder-vite/src/codegen-importfn-script.ts new file mode 100644 index 000000000000..958ca5c59efd --- /dev/null +++ b/code/lib/builder-vite/src/codegen-importfn-script.ts @@ -0,0 +1,56 @@ +import * as path from 'path'; +import { normalizePath } from 'vite'; +import type { Options } from '@storybook/core-common'; +import { logger } from '@storybook/node-logger'; + +import { listStories } from './list-stories'; + +/** + * This file is largely based on https://github.com/storybookjs/storybook/blob/d1195cbd0c61687f1720fefdb772e2f490a46584/lib/core-common/src/utils/to-importFn.ts + */ + +/** + * Paths get passed either with no leading './' - e.g. `src/Foo.stories.js`, + * or with a leading `../` (etc), e.g. `../src/Foo.stories.js`. + * We want to deal in importPaths relative to the working dir, so we normalize + */ +function toImportPath(relativePath: string) { + return relativePath.startsWith('../') ? relativePath : `./${relativePath}`; +} + +/** + * This function takes an array of stories and creates a mapping between the stories' relative paths + * to the working directory and their dynamic imports. The import is done in an asynchronous function + * to delay loading. It then creates a function, `importFn(path)`, which resolves a path to an import + * function and this is called by Storybook to fetch a story dynamically when needed. + * @param stories An array of absolute story paths. + */ +async function toImportFn(stories: string[]) { + const objectEntries = stories.map((file) => { + const ext = path.extname(file); + const relativePath = normalizePath(path.relative(process.cwd(), file)); + if (!['.js', '.jsx', '.ts', '.tsx', '.mdx'].includes(ext)) { + logger.warn(`Cannot process ${ext} file with storyStoreV7: ${relativePath}`); + } + + return ` '${toImportPath(relativePath)}': async () => import('/@fs/${file}')`; + }); + + return ` + const importers = { + ${objectEntries.join(',\n')} + }; + + export async function importFn(path) { + return importers[path](); + } + `; +} + +export async function generateImportFnScriptCode(options: Options) { + // First we need to get an array of stories and their absolute paths. + const stories = await listStories(options); + + // We can then call toImportFn to create a function that can be used to load each story dynamically. + return (await toImportFn(stories)).trim(); +} diff --git a/code/lib/builder-vite/src/codegen-modern-iframe-script.ts b/code/lib/builder-vite/src/codegen-modern-iframe-script.ts new file mode 100644 index 000000000000..dab3c52cebea --- /dev/null +++ b/code/lib/builder-vite/src/codegen-modern-iframe-script.ts @@ -0,0 +1,75 @@ +import { isAbsolute, resolve } from 'path'; +import { loadPreviewOrConfigFile } from '@storybook/core-common'; +import { virtualStoriesFile, virtualAddonSetupFile } from './virtual-file-names'; +import { transformAbsPath } from './utils/transform-abs-path'; +import type { ExtendedOptions } from './types'; + +export async function generateModernIframeScriptCode(options: ExtendedOptions) { + const { presets, configDir, framework } = options; + + const previewOrConfigFile = loadPreviewOrConfigFile({ configDir }); + const presetEntries = await presets.apply('config', [], options); + const previewEntries = await presets.apply('previewEntries', [], options); + const absolutePreviewEntries = previewEntries.map((entry) => + isAbsolute(entry) ? entry : resolve(entry) + ); + const configEntries = [...presetEntries, ...absolutePreviewEntries, previewOrConfigFile] + .filter(Boolean) + .map((configEntry) => transformAbsPath(configEntry as string)); + + const generateHMRHandler = (framework: string): string => { + // Web components are not compatible with HMR, so disable HMR, reload page instead. + if (framework === 'web-components') { + return ` + if (import.meta.hot) { + import.meta.hot.decline(); + }`.trim(); + } + + return ` + if (import.meta.hot) { + import.meta.hot.accept('${virtualStoriesFile}', (newModule) => { + // importFn has changed so we need to patch the new one in + preview.onStoriesChanged({ importFn: newModule.importFn }); + }); + + import.meta.hot.accept(${JSON.stringify(configEntries)}, ([...newConfigEntries]) => { + const newGetProjectAnnotations = () => composeConfigs(newConfigEntries); + + // getProjectAnnotations has changed so we need to patch the new one in + preview.onGetProjectAnnotationsChanged({ getProjectAnnotations: newGetProjectAnnotations }); + }); + }`.trim(); + }; + + /** + * This code is largely taken from https://github.com/storybookjs/storybook/blob/d1195cbd0c61687f1720fefdb772e2f490a46584/lib/builder-webpack4/src/preview/virtualModuleModernEntry.js.handlebars + * Some small tweaks were made to `getProjectAnnotations` (since `import()` needs to be resolved asynchronously) + * and the HMR implementation has been tweaked to work with Vite. + * @todo Inline variable and remove `noinspection` + */ + const code = ` + import { composeConfigs, PreviewWeb } from '@storybook/preview-web'; + import { ClientApi } from '@storybook/client-api'; + import '${virtualAddonSetupFile}'; + import { importFn } from '${virtualStoriesFile}'; + + const getProjectAnnotations = async () => { + const configs = await Promise.all([${configEntries + .map((configEntry) => `import('${configEntry}')`) + .join(',\n')}]) + return composeConfigs(configs); + } + + const preview = new PreviewWeb(); + + window.__STORYBOOK_PREVIEW__ = preview; + window.__STORYBOOK_STORY_STORE__ = preview.storyStore; + window.__STORYBOOK_CLIENT_API__ = new ClientApi({ storyStore: preview.storyStore }); + + preview.initialize({ importFn, getProjectAnnotations }); + + ${generateHMRHandler(framework)}; + `.trim(); + return code; +} diff --git a/code/lib/builder-vite/src/codegen-set-addon-channel.ts b/code/lib/builder-vite/src/codegen-set-addon-channel.ts new file mode 100644 index 000000000000..f00e3d83e1e3 --- /dev/null +++ b/code/lib/builder-vite/src/codegen-set-addon-channel.ts @@ -0,0 +1,18 @@ +export function generateAddonSetupCode() { + return ` + import { createChannel as createPostMessageChannel } from '@storybook/channel-postmessage'; + import { createChannel as createWebSocketChannel } from '@storybook/channel-websocket'; + import { addons } from '@storybook/addons'; + + const channel = createPostMessageChannel({ page: 'preview' }); + addons.setChannel(channel); + window.__STORYBOOK_ADDONS_CHANNEL__ = channel; + + const { SERVER_CHANNEL_URL } = globalThis; + if (SERVER_CHANNEL_URL) { + const serverChannel = createWebSocketChannel({ url: SERVER_CHANNEL_URL }); + addons.setServerChannel(serverChannel); + window.__STORYBOOK_SERVER_CHANNEL__ = serverChannel; + } + `.trim(); +} diff --git a/code/lib/builder-vite/src/declarations/extract-stories.d.ts b/code/lib/builder-vite/src/declarations/extract-stories.d.ts new file mode 100644 index 000000000000..2731f657cc5e --- /dev/null +++ b/code/lib/builder-vite/src/declarations/extract-stories.d.ts @@ -0,0 +1,18 @@ +/** + * @see https://github.com/storybookjs/addon-svelte-csf/blob/f72b8f28dabbb99c92e12d0170d3c1db4397ee7c/src/parser/extract-stories.ts + */ +declare module '@storybook/addon-svelte-csf/dist/cjs/parser/extract-stories' { + interface StoryDef { + name: string; + template: boolean; + source: string; + hasArgs: boolean; + } + + interface StoriesDef { + stories: Record; + allocatedIds: string[]; + } + + function extractStories(component: string): { stories: StoriesDef; allocatedIds: string[] }; +} diff --git a/code/lib/builder-vite/src/declarations/svetle-stories-loader.d.ts b/code/lib/builder-vite/src/declarations/svetle-stories-loader.d.ts new file mode 100644 index 000000000000..1ae04708af52 --- /dev/null +++ b/code/lib/builder-vite/src/declarations/svetle-stories-loader.d.ts @@ -0,0 +1,7 @@ +/** + * @see https://github.com/storybookjs/addon-svelte-csf/blob/f72b8f28dabbb99c92e12d0170d3c1db4397ee7c/src/parser/svelte-stories-loader.ts + * @see https://github.com/sveltejs/svelte/blob/deed340cf5d9c278f9a0605297ad6e4a3a1579d9/src/compiler/compile/utils/get_name_from_filename.ts + */ +declare module '@storybook/addon-svelte-csf/dist/cjs/parser/svelte-stories-loader' { + function getNameFromFilename(filename: string): string; +} diff --git a/code/lib/builder-vite/src/envs.ts b/code/lib/builder-vite/src/envs.ts new file mode 100644 index 000000000000..107391642627 --- /dev/null +++ b/code/lib/builder-vite/src/envs.ts @@ -0,0 +1,50 @@ +import { stringifyEnvs } from '@storybook/core-common'; + +import type { UserConfig } from 'vite'; +import type { EnvsRaw } from './types'; + +// Allowed env variables on the client +const allowedEnvVariables = [ + 'STORYBOOK', + // Vite `import.meta.env` default variables + // @see https://github.com/vitejs/vite/blob/6b8d94dca2a1a8b4952e3e3fcd0aed1aedb94215/packages/vite/types/importMeta.d.ts#L68-L75 + 'BASE_URL', + 'MODE', + 'DEV', + 'PROD', + 'SSR', +]; + +// Env variables starts with env prefix will be exposed to your client source code via `import.meta.env` +export const allowedEnvPrefix = ['VITE_', 'STORYBOOK_']; + +/** + * Customized version of stringifyProcessEnvs from @storybook/core-common which + * uses import.meta.env instead of process.env and checks for allowed variables. + */ +export function stringifyProcessEnvs(raw: EnvsRaw, envPrefix: UserConfig['envPrefix']) { + const updatedRaw: EnvsRaw = {}; + const envs = Object.entries(raw).reduce( + (acc: EnvsRaw, [key, value]) => { + // Only add allowed values OR values from array OR string started with allowed prefixes + if ( + allowedEnvVariables.includes(key) || + (Array.isArray(envPrefix) && !!envPrefix.find((prefix) => key.startsWith(prefix))) || + (typeof envPrefix === 'string' && key.startsWith(envPrefix)) + ) { + acc[`import.meta.env.${key}`] = JSON.stringify(value); + updatedRaw[key] = value; + } + return acc; + }, + { + // Default fallback + 'process.env.XSTORYBOOK_EXAMPLE_APP': '""', + } + ); + // support destructuring like + // const { foo } = import.meta.env; + envs['import.meta.env'] = JSON.stringify(stringifyEnvs(updatedRaw)); + + return envs; +} diff --git a/code/lib/builder-vite/src/index.ts b/code/lib/builder-vite/src/index.ts new file mode 100644 index 000000000000..bb3b5dc1f98b --- /dev/null +++ b/code/lib/builder-vite/src/index.ts @@ -0,0 +1,97 @@ +// noinspection JSUnusedGlobalSymbols + +import * as fs from 'fs'; +import * as path from 'path'; +import type { + Builder, + StorybookConfig as StorybookBaseConfig, + Options, +} from '@storybook/core-common'; +import type { RequestHandler, Request, Response } from 'express'; +import type { InlineConfig, UserConfig, ViteDevServer } from 'vite'; +import { transformIframeHtml } from './transform-iframe-html'; +import { createViteServer } from './vite-server'; +import { build as viteBuild } from './build'; +import type { ExtendedOptions } from './types'; + +export type { TypescriptOptions } from '@storybook/core-common'; + +// Storybook's Stats are optional Webpack related property +export type ViteStats = { + toJson: () => any; +}; + +export type ViteBuilder = Builder; + +export type ViteFinal = ( + config: InlineConfig, + options: Options +) => InlineConfig | Promise; + +export type StorybookConfig = StorybookBaseConfig & { + viteFinal?: ViteFinal; +}; + +function iframeMiddleware(options: ExtendedOptions, server: ViteDevServer): RequestHandler { + return async (req, res, next) => { + if (!req.url.match(/^\/iframe\.html($|\?)/)) { + next(); + return; + } + + // We need to handle `html-proxy` params for style tag HMR https://github.com/storybookjs/builder-vite/issues/266#issuecomment-1055677865 + // e.g. /iframe.html?html-proxy&index=0.css + if (req.query['html-proxy'] !== undefined) { + next(); + return; + } + + const indexHtml = fs.readFileSync( + path.resolve(__dirname, '../..', 'input', 'iframe.html'), + 'utf-8' + ); + const generated = await transformIframeHtml(indexHtml, options); + const transformed = await server.transformIndexHtml('/iframe.html', generated); + res.setHeader('Content-Type', 'text/html'); + res.status(200).send(transformed); + }; +} + +export const start: ViteBuilder['start'] = async ({ + startTime, + options, + router, + server: devServer, +}) => { + const server = await createViteServer(options as ExtendedOptions, devServer); + + // Just mock this endpoint (which is really Webpack-specific) so we don't get spammed with 404 in browser devtools + // TODO: we should either show some sort of progress from Vite, or just try to disable the whole Loader in the Manager UI. + router.get('/progress', (req: Request, res: Response) => { + res.header('Cache-Control', 'no-cache'); + res.header('Content-Type', 'text/event-stream'); + }); + + router.use(iframeMiddleware(options as ExtendedOptions, server)); + router.use(server.middlewares); + + async function bail(e?: Error): Promise { + try { + return await server.close(); + } catch (err) { + console.warn('unable to close vite server'); + } + + throw e; + } + + return { + bail, + stats: { toJson: () => null }, + totalTime: process.hrtime(startTime), + }; +}; + +export const build: ViteBuilder['build'] = async ({ options }) => { + return viteBuild(options as ExtendedOptions); +}; diff --git a/code/lib/builder-vite/src/inject-export-order-plugin.ts b/code/lib/builder-vite/src/inject-export-order-plugin.ts new file mode 100644 index 000000000000..4afd67cd5111 --- /dev/null +++ b/code/lib/builder-vite/src/inject-export-order-plugin.ts @@ -0,0 +1,32 @@ +import { parse } from 'es-module-lexer'; +import MagicString from 'magic-string'; +import { createFilter } from 'vite'; + +const include = [/\.stories\.([tj])sx?$/, /(stories|story).mdx$/]; +const filter = createFilter(include); + +export const injectExportOrderPlugin = { + name: 'storybook-vite-inject-export-order-plugin', + // This should only run after the typescript has been transpiled + enforce: 'post', + async transform(code: string, id: string) { + if (!filter(id)) return undefined; + + // TODO: Maybe convert `injectExportOrderPlugin` to function that returns object, + // and run `await init;` once and then call `parse()` without `await`, + // instead of calling `await parse()` every time. + const [, exports] = await parse(code); + + if (exports.includes('__namedExportsOrder')) { + // user has defined named exports already + return undefined; + } + const s = new MagicString(code); + const orderedExports = exports.filter((e) => e !== 'default'); + s.append(`;export const __namedExportsOrder = ${JSON.stringify(orderedExports)};`); + return { + code: s.toString(), + map: s.generateMap({ hires: true, source: id }), + }; + }, +}; diff --git a/code/lib/builder-vite/src/list-stories.ts b/code/lib/builder-vite/src/list-stories.ts new file mode 100644 index 000000000000..08be02004df1 --- /dev/null +++ b/code/lib/builder-vite/src/list-stories.ts @@ -0,0 +1,20 @@ +import * as path from 'path'; +import { promise as glob } from 'glob-promise'; +import { normalizeStories } from '@storybook/core-common'; + +import type { Options } from '@storybook/core-common'; + +export async function listStories(options: Options) { + return ( + await Promise.all( + normalizeStories(await options.presets.apply('stories', [], options), { + configDir: options.configDir, + workingDir: options.configDir, + }).map(({ directory, files }) => { + const pattern = path.join(directory, files); + + return glob(path.isAbsolute(pattern) ? pattern : path.join(options.configDir, pattern)); + }) + ) + ).reduce((carry, stories) => carry.concat(stories), []); +} diff --git a/code/lib/builder-vite/src/optimizeDeps.ts b/code/lib/builder-vite/src/optimizeDeps.ts new file mode 100644 index 000000000000..fbe560720ed4 --- /dev/null +++ b/code/lib/builder-vite/src/optimizeDeps.ts @@ -0,0 +1,125 @@ +import * as path from 'path'; +import { normalizePath, resolveConfig, UserConfig } from 'vite'; +import { listStories } from './list-stories'; + +import type { ExtendedOptions } from './types'; + +const INCLUDE_CANDIDATES = [ + '@base2/pretty-print-object', + '@emotion/core', + '@emotion/is-prop-valid', + '@emotion/styled', + '@mdx-js/react', + '@storybook/addon-docs > acorn-jsx', + '@storybook/addon-docs', + '@storybook/addons', + '@storybook/channel-postmessage', + '@storybook/channel-websocket', + '@storybook/client-api', + '@storybook/client-logger', + '@storybook/core/client', + '@storybook/csf', + '@storybook/preview-web', + '@storybook/react > acorn-jsx', + '@storybook/react', + '@storybook/svelte', + '@storybook/vue3', + 'acorn-jsx', + 'acorn-walk', + 'acorn', + 'airbnb-js-shims', + 'ansi-to-html', + 'axe-core', + 'color-convert', + 'deep-object-diff', + 'doctrine', + 'emotion-theming', + 'escodegen', + 'estraverse', + 'fast-deep-equal', + 'global', + 'html-tags', + 'isobject', + 'jest-mock', + 'loader-utils', + 'lodash/cloneDeep', + 'lodash/isFunction', + 'lodash/isPlainObject', + 'lodash/isString', + 'lodash/mapKeys', + 'lodash/mapValues', + 'lodash/pick', + 'lodash/pickBy', + 'lodash/startCase', + 'lodash/throttle', + 'lodash/uniq', + 'markdown-to-jsx', + 'memoizerific', + 'overlayscrollbars', + 'polished', + 'prettier/parser-babel', + 'prettier/parser-flow', + 'prettier/parser-typescript', + 'prop-types', + 'qs', + 'react-dom', + 'react-dom/client', + 'react-fast-compare', + 'react-is', + 'react-textarea-autosize', + 'react', + 'react/jsx-runtime', + 'refractor/core', + 'refractor/lang/bash.js', + 'refractor/lang/css.js', + 'refractor/lang/graphql.js', + 'refractor/lang/js-extras.js', + 'refractor/lang/json.js', + 'refractor/lang/jsx.js', + 'refractor/lang/markdown.js', + 'refractor/lang/markup.js', + 'refractor/lang/tsx.js', + 'refractor/lang/typescript.js', + 'refractor/lang/yaml.js', + 'regenerator-runtime/runtime.js', + 'slash', + 'stable', + 'store2', + 'synchronous-promise', + 'telejson', + 'ts-dedent', + 'unfetch', + 'util-deprecate', + 'uuid-browser/v4', + 'vue', + 'warning', +]; + +/** + * Helper function which allows us to `filter` with an async predicate. Uses Promise.all for performance. + */ +const asyncFilter = async (arr: string[], predicate: (val: string) => Promise) => + Promise.all(arr.map(predicate)).then((results) => arr.filter((_v, index) => results[index])); + +export async function getOptimizeDeps( + config: UserConfig & { configFile: false; root: string }, + options: ExtendedOptions +) { + const { root } = config; + const absoluteStories = await listStories(options); + const stories = absoluteStories.map((storyPath) => normalizePath(path.relative(root, storyPath))); + const resolvedConfig = await resolveConfig(config, 'serve', 'development'); + + // This function converts ids which might include ` > ` to a real path, if it exists on disk. + // See https://github.com/vitejs/vite/blob/67d164392e8e9081dc3f0338c4b4b8eea6c5f7da/packages/vite/src/node/optimizer/index.ts#L182-L199 + const resolve = resolvedConfig.createResolver({ asSrc: false }); + const include = await asyncFilter(INCLUDE_CANDIDATES, async (id) => Boolean(await resolve(id))); + + return { + // We don't need to resolve the glob since vite supports globs for entries. + entries: stories, + // We need Vite to precompile these dependencies, because they contain non-ESM code that would break + // if we served it directly to the browser. + include, + }; +} diff --git a/code/lib/builder-vite/src/plugins/docgen-handlers/actualNameHandler.ts b/code/lib/builder-vite/src/plugins/docgen-handlers/actualNameHandler.ts new file mode 100644 index 000000000000..ddf860b37ee8 --- /dev/null +++ b/code/lib/builder-vite/src/plugins/docgen-handlers/actualNameHandler.ts @@ -0,0 +1,51 @@ +/** + * This is heavily based on the react-docgen `displayNameHandler` + * (https://github.com/reactjs/react-docgen/blob/26c90c0dd105bf83499a83826f2a6ff7a724620d/src/handlers/displayNameHandler.ts) + * but instead defines an `actualName` property on the generated docs that is taken first from the component's actual name. + * This addresses an issue where the name that the generated docs are stored under is incorrectly named with the `displayName` + * and not the component's actual name. + * + * This is inspired by `actualNameHandler` from https://github.com/storybookjs/babel-plugin-react-docgen, but is modified + * directly from displayNameHandler, using the same approach as babel-plugin-react-docgen. + */ + +import { namedTypes as t } from 'ast-types'; +import type { NodePath } from 'ast-types/lib/node-path'; +import { getNameOrValue, isReactForwardRefCall } from 'react-docgen/dist/utils'; +// import { getNameOrValue, isReactForwardRefCall } from 'react-docgen/lib/utils'; +import type { Importer } from 'react-docgen/dist/parse'; +// import type { Importer } from 'react-docgen/lib/parse'; +import type Documentation from 'react-docgen/lib/Documentation'; + +export default function actualNameHandler( + documentation: Documentation, + path: NodePath, + importer: Importer +): void { + if (t.ClassDeclaration.check(path.node) || t.FunctionDeclaration.check(path.node)) { + documentation.set('actualName', getNameOrValue(path.get('id'))); + } else if ( + t.ArrowFunctionExpression.check(path.node) || + t.FunctionExpression.check(path.node) || + isReactForwardRefCall(path, importer) + ) { + let currentPath = path; + while (currentPath.parent) { + if (t.VariableDeclarator.check(currentPath.parent.node)) { + documentation.set('actualName', getNameOrValue(currentPath.parent.get('id'))); + return; + } + + if (t.AssignmentExpression.check(currentPath.parent.node)) { + const leftPath = currentPath.parent.get('left'); + if (t.Identifier.check(leftPath.node) || t.Literal.check(leftPath.node)) { + documentation.set('actualName', getNameOrValue(leftPath)); + return; + } + } + currentPath = currentPath.parent; + } + // Could not find an actual name + documentation.set('actualName', ''); + } +} diff --git a/code/lib/builder-vite/src/plugins/mdx-plugin.ts b/code/lib/builder-vite/src/plugins/mdx-plugin.ts new file mode 100644 index 000000000000..eee7043481ab --- /dev/null +++ b/code/lib/builder-vite/src/plugins/mdx-plugin.ts @@ -0,0 +1,82 @@ +import type { Options } from '@storybook/core-common'; +import type { Plugin } from 'vite'; +import { createFilter } from 'vite'; + +const isStorybookMdx = (id: string) => id.endsWith('stories.mdx') || id.endsWith('story.mdx'); + +function injectRenderer(code: string, mdx2: boolean) { + if (mdx2) { + return ` + import React from 'react'; + ${code} + `; + } + + return ` + /* @jsx mdx */ + import React from 'react'; + import { mdx } from '@mdx-js/react'; + ${code} + `; +} + +/** + * Storybook uses two different loaders when dealing with MDX: + * + * - *stories.mdx and *story.mdx are compiled with the CSF compiler + * - *.mdx are compiled with the MDX compiler directly + * + * @see https://github.com/storybookjs/storybook/blob/next/addons/docs/docs/recipes.md#csf-stories-with-arbitrary-mdx + */ +export function mdxPlugin(options: Options): Plugin { + const { features } = options; + + let reactRefresh: Plugin | undefined; + const include = /\.mdx?$/; + const filter = createFilter(include); + + return { + name: 'storybook-vite-mdx-plugin', + enforce: 'pre', + configResolved({ plugins }) { + // @vitejs/plugin-react-refresh has been upgraded to @vitejs/plugin-react, + // and the name of the plugin performing `transform` has been changed from 'react-refresh' to 'vite:react-babel', + // to be compatible, we need to look for both plugin name. + // We should also look for the other plugins names exported from @vitejs/plugin-react in case there are some internal refactors. + const reactRefreshPlugins = plugins.filter( + (p) => + p.name === 'react-refresh' || + p.name === 'vite:react-babel' || + p.name === 'vite:react-refresh' || + p.name === 'vite:react-jsx' + ); + reactRefresh = reactRefreshPlugins.find((p) => p.transform); + }, + async transform(src, id, options) { + if (!filter(id)) return undefined; + + // @ts-expect-error typescript doesn't think compile exists, but it does. + const { compile } = features?.previewMdx2 + ? await import('@storybook/mdx2-csf') + : await import('@storybook/mdx1-csf'); + + const mdxCode = String(await compile(src, { skipCsf: !isStorybookMdx(id) })); + + const modifiedCode = injectRenderer(mdxCode, Boolean(features?.previewMdx2)); + + const result = await reactRefresh?.transform!.call(this, modifiedCode, `${id}.jsx`, options); + + if (!result) return modifiedCode; + + if (typeof result === 'string') return result; + + const { code, map: resultMap } = result; + + return { + code, + map: + !resultMap || typeof resultMap === 'string' ? resultMap : { ...resultMap, sources: [id] }, + }; + }, + }; +} diff --git a/code/lib/builder-vite/src/plugins/no-fouc.ts b/code/lib/builder-vite/src/plugins/no-fouc.ts new file mode 100644 index 000000000000..8e741db2464f --- /dev/null +++ b/code/lib/builder-vite/src/plugins/no-fouc.ts @@ -0,0 +1,52 @@ +import type { Plugin } from 'vite'; + +/** + * This plugin is a workaround to inject some styles into the `` of the iframe to + * prevent the "no story" text from appearing breifly while the page loads in. + * + * It can be removed, and these styles placed back into the head, + * when https://github.com/vitejs/vite/issues/6737 is closed. + */ +export function noFouc(): Plugin { + return { + name: 'no-fouc', + enforce: 'post', + async transformIndexHtml(html, ctx) { + if (ctx.path !== '/iframe.html') return undefined; + + return insertHeadStyles(html); + }, + }; +} + +/** + * Insert default styles to hide storybook elements as the page loads until JS can + * add the official storybook default head styles and scripts. These lines are mostly + * taken from https://github.com/storybookjs/storybook/blob/next/lib/core-common/templates/base-preview-head.html#L6-L20 + */ +function insertHeadStyles(html: string) { + return html.replace( + '', + ` + + + `.trim() + ); +} diff --git a/code/lib/builder-vite/src/plugins/react-docgen.ts b/code/lib/builder-vite/src/plugins/react-docgen.ts new file mode 100644 index 000000000000..d3e0ae64766c --- /dev/null +++ b/code/lib/builder-vite/src/plugins/react-docgen.ts @@ -0,0 +1,69 @@ +import path from 'path'; +import { + parse, + handlers as docgenHandlers, + resolver as docgenResolver, + importers as docgenImporters, +} from 'react-docgen'; +import type { DocumentationObject } from 'react-docgen/lib/Documentation'; +import MagicString from 'magic-string'; +import type { Plugin } from 'vite'; +import { createFilter } from 'vite'; +import actualNameHandler from './docgen-handlers/actualNameHandler'; + +type DocObj = DocumentationObject & { actualName: string }; + +// TODO: None of these are able to be overridden, so `default` is aspirational here. +const defaultHandlers = Object.values(docgenHandlers).map((handler) => handler); +const defaultResolver = docgenResolver.findAllExportedComponentDefinitions; +const defaultImporter = docgenImporters.makeFsImporter(); +const handlers = [...defaultHandlers, actualNameHandler]; + +type Options = { + include?: string | RegExp | (string | RegExp)[]; + exclude?: string | RegExp | (string | RegExp)[]; +}; + +export function reactDocgen({ + include = /\.(mjs|tsx?|jsx?)$/, + exclude = [/node_modules\/.*/, '**/**.stories.tsx'], +}: Options = {}): Plugin { + const cwd = process.cwd(); + const filter = createFilter(include, exclude); + + return { + name: 'react-docgen', + enforce: 'pre', + async transform(src: string, id: string) { + const relPath = path.relative(cwd, id); + if (!filter(relPath)) return undefined; + + try { + // Since we're using `findAllExportedComponentDefinitions`, this will always be an array. + const docgenResults = parse(src, defaultResolver, handlers, { + importer: defaultImporter, + filename: id, + }) as DocObj[]; + const s = new MagicString(src); + + docgenResults.forEach((info) => { + const { actualName, ...docgenInfo } = info; + if (actualName) { + const docNode = JSON.stringify(docgenInfo); + s.append(`;${actualName}.__docgenInfo=${docNode}`); + } + }); + + return { + code: s.toString(), + map: s.generateMap({ hires: true, source: id }), + }; + } catch (e) { + // Usually this is just an error from react-docgen that it couldn't find a component + // Only uncomment for troubleshooting + // console.error(e); + return undefined; + } + }, + }; +} diff --git a/code/lib/builder-vite/src/plugins/svelte-docgen.ts b/code/lib/builder-vite/src/plugins/svelte-docgen.ts new file mode 100644 index 000000000000..22378e1a6658 --- /dev/null +++ b/code/lib/builder-vite/src/plugins/svelte-docgen.ts @@ -0,0 +1,102 @@ +import type { Plugin } from 'vite'; +import MagicString from 'magic-string'; +import path from 'path'; +import fs from 'fs'; +import svelteDoc from 'sveltedoc-parser'; +import type { SvelteParserOptions } from 'sveltedoc-parser'; +import { logger } from '@storybook/node-logger'; +import { preprocess } from 'svelte/compiler'; +import { createFilter } from 'vite'; + +// Most of the code here should probably be exported by @storybook/svelte and reused here. +// See: https://github.com/storybookjs/storybook/blob/next/app/svelte/src/server/svelte-docgen-loader.ts + +// From https://github.com/sveltejs/svelte/blob/8db3e8d0297e052556f0b6dde310ef6e197b8d18/src/compiler/compile/utils/get_name_from_filename.ts +// Copied because it is not exported from the compiler +function getNameFromFilename(filename: string) { + if (!filename) return null; + + const parts = filename.split(/[/\\]/).map(encodeURI); + + if (parts.length > 1) { + const indexMatch = parts[parts.length - 1].match(/^index(\.\w+)/); + if (indexMatch) { + parts.pop(); + parts[parts.length - 1] += indexMatch[1]; + } + } + + const base = parts + .pop() + ?.replace(/%/g, 'u') + .replace(/\.[^.]+$/, '') + .replace(/[^a-zA-Z_$0-9]+/g, '_') + .replace(/^_/, '') + .replace(/_$/, '') + .replace(/^(\d)/, '_$1'); + + if (!base) { + throw new Error(`Could not derive component name from file ${filename}`); + } + + return base[0].toUpperCase() + base.slice(1); +} + +export function svelteDocgen(svelteOptions: Record): Plugin { + const cwd = process.cwd(); + const { preprocess: preprocessOptions, logDocgen = false } = svelteOptions; + const include = /\.(svelte)$/; + const filter = createFilter(include); + + return { + name: 'svelte-docgen', + async transform(src: string, id: string) { + if (!filter(id)) return undefined; + + const resource = path.relative(cwd, id); + + let docOptions; + if (preprocessOptions) { + const src = fs.readFileSync(resource).toString(); + + const { code: fileContent } = await preprocess(src, preprocessOptions, { + filename: resource, + }); + + docOptions = { + fileContent, + }; + } else { + docOptions = { filename: resource }; + } + + // set SvelteDoc options + const options: SvelteParserOptions = { + ...docOptions, + version: 3, + }; + + const s = new MagicString(src); + + try { + const componentDoc = await svelteDoc.parse(options); + // get filename for source content + const file = path.basename(resource); + + componentDoc.name = path.basename(file); + + const componentName = getNameFromFilename(resource); + s.append(`;${componentName}.__docgen = ${JSON.stringify(componentDoc)}`); + } catch (error: any) { + if (logDocgen) { + logger.error(error); + } + } + + return { + code: s.toString(), + map: s.generateMap({ hires: true, source: id }), + }; + }, + }; +} diff --git a/code/lib/builder-vite/src/source-loader-plugin.ts b/code/lib/builder-vite/src/source-loader-plugin.ts new file mode 100644 index 000000000000..2192653c9097 --- /dev/null +++ b/code/lib/builder-vite/src/source-loader-plugin.ts @@ -0,0 +1,103 @@ +import type { Plugin } from 'vite'; +import sourceLoaderTransform from '@storybook/source-loader'; +import MagicString from 'magic-string'; +import type { ExtendedOptions } from './types'; + +const storyPattern = /\.stories\.[jt]sx?$/; +const storySourcePattern = /var __STORY__ = "(.*)"/; +const storySourceReplacement = '--STORY_SOURCE_REPLACEMENT--'; + +const mockClassLoader = (id: string) => ({ + // eslint-disable-next-line no-console + emitWarning: (message: string) => console.warn(message), + resourcePath: id, +}); + +// HACK: Until we can support only node 15+ and use string.prototype.replaceAll +const replaceAll = (str: string, search: string, replacement: string) => { + return str.split(search).join(replacement); +}; + +export function sourceLoaderPlugin(config: ExtendedOptions): Plugin | Plugin[] { + if (config.configType === 'DEVELOPMENT') { + return { + name: 'storybook-vite-source-loader-plugin', + enforce: 'pre', + async transform(src: string, id: string) { + if (id.match(storyPattern)) { + const code: string = await sourceLoaderTransform.call(mockClassLoader(id), src); + const s = new MagicString(src); + // Entirely replace with new code + s.overwrite(0, src.length, code); + + return { + code: s.toString(), + map: s.generateMap({ hires: true, source: id }), + }; + } + return undefined; + }, + }; + } + + // In production, we need to be fancier, to avoid vite:define plugin from replacing values inside the `__STORY__` string + const storySources = new WeakMap>(); + + return [ + { + name: 'storybook-vite-source-loader-plugin', + enforce: 'pre', + buildStart() { + storySources.set(config, new Map()); + }, + async transform(src: string, id: string) { + if (id.match(storyPattern)) { + let code: string = await sourceLoaderTransform.call(mockClassLoader(id), src); + const [_, sourceString] = code.match(storySourcePattern) ?? [null, null]; + if (sourceString) { + const map = storySources.get(config); + map?.set(id, sourceString); + + // Remove story source so that it is not processed by vite:define plugin + code = replaceAll(code, sourceString, storySourceReplacement); + } + + const s = new MagicString(src); + // Entirely replace with new code + s.overwrite(0, src.length, code); + + return { + code: s.toString(), + map: s.generateMap(), + }; + } + return undefined; + }, + }, + { + name: 'storybook-vite-source-loader-plugin-post', + enforce: 'post', + buildStart() { + storySources.set(config, new Map()); + }, + async transform(src: string, id: string) { + if (id.match(storyPattern)) { + const s = new MagicString(src); + const map = storySources.get(config); + const storySourceStatement = map?.get(id); + // Put the previously-extracted source back in + if (storySourceStatement) { + const newCode = replaceAll(src, storySourceReplacement, storySourceStatement); + s.overwrite(0, src.length, newCode); + } + + return { + code: s.toString(), + map: s.generateMap(), + }; + } + return undefined; + }, + }, + ]; +} diff --git a/code/lib/builder-vite/src/svelte/csf-plugin.ts b/code/lib/builder-vite/src/svelte/csf-plugin.ts new file mode 100644 index 000000000000..ce6cfe9c1561 --- /dev/null +++ b/code/lib/builder-vite/src/svelte/csf-plugin.ts @@ -0,0 +1,59 @@ +import { getNameFromFilename } from '@storybook/addon-svelte-csf/dist/cjs/parser/svelte-stories-loader'; +import { readFileSync } from 'fs'; +import { extractStories } from '@storybook/addon-svelte-csf/dist/cjs/parser/extract-stories'; +import type { Options } from '@sveltejs/vite-plugin-svelte'; +import * as svelte from 'svelte/compiler'; +import MagicString from 'magic-string'; +import { createFilter } from 'vite'; + +const parser = require + .resolve('@storybook/addon-svelte-csf/dist/esm/parser/collect-stories') + .replace(/[/\\]/g, '/'); + +export default function csfPlugin(svelteOptions?: Options) { + const include = /\.stories\.svelte$/; + const filter = createFilter(include); + + return { + name: 'storybook-addon-svelte-csf', + enforce: 'post', + async transform(code: string, id: string) { + if (!filter(id)) return undefined; + + const s = new MagicString(code); + const component = getNameFromFilename(id); + let source = readFileSync(id).toString(); + if (svelteOptions && svelteOptions.preprocess) { + source = (await svelte.preprocess(source, svelteOptions.preprocess, { filename: id })).code; + } + const all = extractStories(source); + const { stories } = all; + const storyDef = Object.entries(stories) + .filter(([, def]) => !def.template) + .map(([id]) => `export const ${id} = __storiesMetaData.stories[${JSON.stringify(id)}];`) + .join('\n'); + + s.replace('export default', '// export default'); + + const namedExportsOrder = Object.entries(stories) + .filter(([, def]) => !def.template) + .map(([id]) => id); + + const output = [ + '', + `import parser from '${parser}';`, + `const __storiesMetaData = parser(${component}, ${JSON.stringify(all)});`, + 'export default __storiesMetaData.meta;', + `export const __namedExportsOrder = ${JSON.stringify(namedExportsOrder)};`, + storyDef, + ].join('\n'); + + s.append(output); + + return { + code: s.toString(), + map: s.generateMap({ hires: true, source: id }), + }; + }, + }; +} diff --git a/code/lib/builder-vite/src/transform-iframe-html.ts b/code/lib/builder-vite/src/transform-iframe-html.ts new file mode 100644 index 000000000000..4af39393f77b --- /dev/null +++ b/code/lib/builder-vite/src/transform-iframe-html.ts @@ -0,0 +1,36 @@ +import { normalizeStories } from '@storybook/core-common'; +import type { CoreConfig } from '@storybook/core-common'; +import type { ExtendedOptions } from './types'; + +export type PreviewHtml = string | undefined; + +export async function transformIframeHtml(html: string, options: ExtendedOptions) { + const { configType, features, framework, presets, serverChannelUrl, title } = options; + const headHtmlSnippet = await presets.apply('previewHead'); + const bodyHtmlSnippet = await presets.apply('previewBody'); + const logLevel = await presets.apply('logLevel', undefined); + const frameworkOptions = await presets.apply(`${framework}Options`, {}); + const coreOptions = await presets.apply('core'); + const stories = normalizeStories(await options.presets.apply('stories', [], options), { + configDir: options.configDir, + workingDir: process.cwd(), + }).map((specifier) => ({ + ...specifier, + importPathMatcher: specifier.importPathMatcher.source, + })); + + return html + .replace('', title || 'Storybook') + .replace('[CONFIG_TYPE HERE]', configType || '') + .replace('[LOGLEVEL HERE]', logLevel || '') + .replace(`'[FRAMEWORK_OPTIONS HERE]'`, JSON.stringify(frameworkOptions || {})) + .replace( + `'[CHANNEL_OPTIONS HERE]'`, + JSON.stringify(coreOptions && coreOptions.channelOptions ? coreOptions.channelOptions : {}) + ) + .replace(`'[FEATURES HERE]'`, JSON.stringify(features || {})) + .replace(`'[STORIES HERE]'`, JSON.stringify(stories || {})) + .replace(`'[SERVER_CHANNEL_URL HERE]'`, JSON.stringify(serverChannelUrl)) + .replace('', headHtmlSnippet || '') + .replace('', bodyHtmlSnippet || ''); +} diff --git a/code/lib/builder-vite/src/types/envs-raw.type.ts b/code/lib/builder-vite/src/types/envs-raw.type.ts new file mode 100644 index 000000000000..cb62e513aacd --- /dev/null +++ b/code/lib/builder-vite/src/types/envs-raw.type.ts @@ -0,0 +1 @@ +export type EnvsRaw = Record; diff --git a/code/lib/builder-vite/src/types/extended-options.type.ts b/code/lib/builder-vite/src/types/extended-options.type.ts new file mode 100644 index 000000000000..7b582f115c20 --- /dev/null +++ b/code/lib/builder-vite/src/types/extended-options.type.ts @@ -0,0 +1,11 @@ +import type { Options } from '@storybook/core-common'; + +// Using instead of `Record` to provide better aware of used options +type IframeOptions = { + frameworkPath: string; + title: string; + // FIXME: Use @ndelangen's improved types + framework: string; +}; + +export type ExtendedOptions = Options & IframeOptions; diff --git a/code/lib/builder-vite/src/types/index.ts b/code/lib/builder-vite/src/types/index.ts new file mode 100644 index 000000000000..e50c278bbd1e --- /dev/null +++ b/code/lib/builder-vite/src/types/index.ts @@ -0,0 +1,2 @@ +export * from './envs-raw.type'; +export * from './extended-options.type'; diff --git a/code/lib/builder-vite/src/types/react-docgen.d.ts b/code/lib/builder-vite/src/types/react-docgen.d.ts new file mode 100644 index 000000000000..cc2a8d6322fb --- /dev/null +++ b/code/lib/builder-vite/src/types/react-docgen.d.ts @@ -0,0 +1,22 @@ +// TODO: delete this stub file once a new alpha of react-docgen is released (will include ts types). + +declare module 'react-docgen' { + declare const parse; + declare const handlers; + declare const resolver; + declare const importers; +} + +declare module 'react-docgen/lib/Documentation' { + export type DocumentationObject = Record; + export default Documentation; +} + +declare module 'react-docgen/dist/utils' { + declare const getNameOrValue; + declare const isReactForwardRefCall; +} + +declare module 'react-docgen/dist/parse' { + declare type Importer = any; +} diff --git a/code/lib/builder-vite/src/utils/transform-abs-path.ts b/code/lib/builder-vite/src/utils/transform-abs-path.ts new file mode 100644 index 000000000000..bd8c89f8fbc3 --- /dev/null +++ b/code/lib/builder-vite/src/utils/transform-abs-path.ts @@ -0,0 +1,11 @@ +import path from 'path'; +import { normalizePath } from 'vite'; + +// We need to convert from an absolute path, to a traditional node module import path, +// so that vite can correctly pre-bundle/optimize +export function transformAbsPath(absPath: string) { + const splits = absPath.split(`node_modules${path.sep}`); + // Return everything after the final "node_modules/" + const module = normalizePath(splits[splits.length - 1]); + return module; +} diff --git a/code/lib/builder-vite/src/virtual-file-names.ts b/code/lib/builder-vite/src/virtual-file-names.ts new file mode 100644 index 000000000000..0da0c5517dec --- /dev/null +++ b/code/lib/builder-vite/src/virtual-file-names.ts @@ -0,0 +1,4 @@ +export const virtualFileId = '/virtual:/@storybook/builder-vite/vite-app.js'; +export const virtualStoriesFile = '/virtual:/@storybook/builder-vite/storybook-stories.js'; +export const virtualPreviewFile = '/virtual:/@storybook/builder-vite/preview-entry.js'; +export const virtualAddonSetupFile = '/virtual:/@storybook/builder-vite/setup-addons.js'; diff --git a/code/lib/builder-vite/src/vite-config.ts b/code/lib/builder-vite/src/vite-config.ts new file mode 100644 index 000000000000..19ca4d59db55 --- /dev/null +++ b/code/lib/builder-vite/src/vite-config.ts @@ -0,0 +1,150 @@ +import * as path from 'path'; +import fs from 'fs'; +import { Plugin } from 'vite'; +import viteReact from '@vitejs/plugin-react'; +import type { UserConfig } from 'vite'; +import { allowedEnvPrefix as envPrefix } from './envs'; +import { codeGeneratorPlugin } from './code-generator-plugin'; +import { injectExportOrderPlugin } from './inject-export-order-plugin'; +import { mdxPlugin } from './plugins/mdx-plugin'; +import { noFouc } from './plugins/no-fouc'; +import type { ExtendedOptions } from './types'; + +export type PluginConfigType = 'build' | 'development'; + +export function readPackageJson(): Record | false { + const packageJsonPath = path.resolve('package.json'); + if (!fs.existsSync(packageJsonPath)) { + return false; + } + + const jsonContent = fs.readFileSync(packageJsonPath, 'utf8'); + return JSON.parse(jsonContent); +} + +// Vite config that is common to development and production mode +export async function commonConfig( + options: ExtendedOptions, + _type: PluginConfigType +): Promise { + return { + configFile: false, + root: path.resolve(options.configDir, '..'), + cacheDir: 'node_modules/.vite-storybook', + envPrefix, + define: {}, + plugins: await pluginConfig(options, _type), + }; +} + +export async function pluginConfig(options: ExtendedOptions, _type: PluginConfigType) { + const { framework, presets } = options; + const svelteOptions: Record = await presets.apply('svelteOptions', {}, options); + + const plugins = [ + codeGeneratorPlugin(options), + // sourceLoaderPlugin(options), + mdxPlugin(options), + noFouc(), + injectExportOrderPlugin, + // We need the react plugin here to support MDX. + viteReact({ + // Do not treat story files as HMR boundaries, storybook itself needs to handle them. + exclude: [/\.stories\.([tj])sx?$/, /node_modules/].concat( + framework === 'react' ? [] : [/\.([tj])sx?$/] + ), + }), + { + name: 'vite-plugin-storybook-allow', + enforce: 'post', + config(config) { + // if there is no allow list then Vite allows anything in the root directory + // if there is an allow list then Vite allows anything in the listed directories + // add the .storybook directory only if there's an allow list so that we don't end up + // disallowing the root directory unless it's already disallowed + if (config?.server?.fs?.allow) { + config.server.fs.allow.push('.storybook'); + } + }, + }, + ] as Plugin[]; + if (framework === 'svelte') { + try { + // eslint-disable-next-line import/no-extraneous-dependencies, global-require + const sveltePlugin = require('@sveltejs/vite-plugin-svelte').svelte; + + // We need to create two separate svelte plugins, one for stories, and one for other svelte files + // because stories.svelte files cannot be hot-module-reloaded. + // Suggested in: https://github.com/sveltejs/vite-plugin-svelte/issues/321#issuecomment-1113205509 + + // First, create an array containing user exclude patterns, to combine with ours. + + let userExclude = []; + if (Array.isArray(svelteOptions?.exclude)) { + userExclude = svelteOptions?.exclude; + } else if (svelteOptions?.exclude) { + userExclude = [svelteOptions?.exclude]; + } + + // These are the svelte stories we need to exclude from HMR + const storyPatterns = ['**/*.story.svelte', '**/*.stories.svelte']; + // Non-story svelte files + // Starting in 1.0.0-next.42, svelte.config.js is included by default. + // We disable that, but allow it to be overridden in svelteOptions + plugins.push(sveltePlugin({ ...svelteOptions, exclude: [...userExclude, ...storyPatterns] })); + // Svelte stories without HMR + const storySveltePlugin = sveltePlugin({ + ...svelteOptions, + exclude: userExclude, + include: storyPatterns, + hot: false, + }); + plugins.push({ + // Starting in 1.0.0-next.43, the plugin function returns an array of plugins. We only want the first one here. + ...(Array.isArray(storySveltePlugin) ? storySveltePlugin[0] : storySveltePlugin), + name: 'vite-plugin-svelte-stories', + }); + } catch (err) { + if ((err as NodeJS.ErrnoException).code === 'MODULE_NOT_FOUND') { + throw new Error( + '@storybook/builder-vite requires @sveltejs/vite-plugin-svelte to be installed' + + ' when using @storybook/svelte.' + + ' Please install it and start storybook again.' + ); + } + throw err; + } + + // eslint-disable-next-line import/no-extraneous-dependencies, global-require + const { loadSvelteConfig } = require('@sveltejs/vite-plugin-svelte'); + const config = { ...loadSvelteConfig(), ...svelteOptions }; + + try { + // eslint-disable-next-line global-require + const csfPlugin = require('./svelte/csf-plugin').default; + plugins.push(csfPlugin(config)); + } catch (err) { + // Not all projects use `.stories.svelte` for stories, and by default 6.5+ does not auto-install @storybook/addon-svelte-csf. + // If it's any other kind of error, re-throw. + if ((err as NodeJS.ErrnoException).code !== 'MODULE_NOT_FOUND') { + throw err; + } + } + + const { svelteDocgen } = await import('./plugins/svelte-docgen'); + plugins.push(svelteDocgen(config)); + } + + if (framework === 'preact') { + // eslint-disable-next-line global-require + plugins.push(require('@preact/preset-vite').default()); + } + + if (framework === 'glimmerx') { + // eslint-disable-next-line global-require, import/extensions + const plugin = require('vite-plugin-glimmerx/index.cjs'); + plugins.push(plugin.default()); + } + + return plugins; +} diff --git a/code/lib/builder-vite/src/vite-server.ts b/code/lib/builder-vite/src/vite-server.ts new file mode 100644 index 000000000000..26ecbc4d51ad --- /dev/null +++ b/code/lib/builder-vite/src/vite-server.ts @@ -0,0 +1,40 @@ +import type { Server } from 'http'; +import { createServer } from 'vite'; +import { stringifyProcessEnvs } from './envs'; +import { getOptimizeDeps } from './optimizeDeps'; +import { commonConfig } from './vite-config'; +import type { EnvsRaw, ExtendedOptions } from './types'; + +export async function createViteServer(options: ExtendedOptions, devServer: Server) { + const { port, presets } = options; + + const baseConfig = await commonConfig(options, 'development'); + const defaultConfig = { + ...baseConfig, + server: { + middlewareMode: true, + hmr: { + port, + server: devServer, + }, + fs: { + strict: true, + }, + }, + appType: 'custom' as const, + optimizeDeps: await getOptimizeDeps(baseConfig, options), + }; + + const finalConfig = await presets.apply('viteFinal', defaultConfig, options); + + const envsRaw = await presets.apply>('env'); + // Stringify env variables after getting `envPrefix` from the final config + const envs = stringifyProcessEnvs(envsRaw, finalConfig.envPrefix); + // Update `define` + finalConfig.define = { + ...finalConfig.define, + ...envs, + }; + + return createServer(finalConfig); +} diff --git a/code/lib/builder-vite/tsconfig.json b/code/lib/builder-vite/tsconfig.json new file mode 100644 index 000000000000..76d943e5d483 --- /dev/null +++ b/code/lib/builder-vite/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "strict": true, + "resolveJsonModule": true, + "skipLibCheck": true + }, + "include": ["src/**/*"], + "exclude": ["src/**/*.test.*", "src/**/__testfixtures__/**"] +} diff --git a/code/lib/channel-websocket/src/index.ts b/code/lib/channel-websocket/src/index.ts index 87b5cce0b903..fb5628be5032 100644 --- a/code/lib/channel-websocket/src/index.ts +++ b/code/lib/channel-websocket/src/index.ts @@ -85,3 +85,6 @@ export function createChannel({ const transport = new WebsocketTransport({ url, onError }); return new Channel({ transport, async }); } + +// backwards compat with builder-vite +export default createChannel; diff --git a/code/lib/cli/package.json b/code/lib/cli/package.json index c6f4d3143eff..c5fc257c5fc0 100644 --- a/code/lib/cli/package.json +++ b/code/lib/cli/package.json @@ -22,13 +22,6 @@ }, "license": "MIT", "author": "Storybook Team", - "typesVersions": { - "<3.8": { - "*": [ - "ts3.4/*" - ] - } - }, "bin": { "getstorybook": "./bin/index.js", "sb": "./bin/index.js" diff --git a/code/lib/cli/src/generators/baseGenerator.ts b/code/lib/cli/src/generators/baseGenerator.ts index 2227fbb5040c..18b6a7202410 100644 --- a/code/lib/cli/src/generators/baseGenerator.ts +++ b/code/lib/cli/src/generators/baseGenerator.ts @@ -212,7 +212,7 @@ export async function baseGenerator( } // FIXME: temporary workaround for https://github.com/storybookjs/storybook/issues/17516 - if (frameworkPackages.includes('@storybook/builder-vite')) { + if (frameworkPackages.find((pkg) => pkg.match(/^@storybook\/.*-vite$/))) { const previewHead = dedent` diff --git a/code/renderers/vue3/template/components/button.css b/code/renderers/vue3/template/components/button.css new file mode 100644 index 000000000000..dc91dc76370b --- /dev/null +++ b/code/renderers/vue3/template/components/button.css @@ -0,0 +1,30 @@ +.storybook-button { + font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-weight: 700; + border: 0; + border-radius: 3em; + cursor: pointer; + display: inline-block; + line-height: 1; +} +.storybook-button--primary { + color: white; + background-color: #1ea7fd; +} +.storybook-button--secondary { + color: #333; + background-color: transparent; + box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset; +} +.storybook-button--small { + font-size: 12px; + padding: 10px 16px; +} +.storybook-button--medium { + font-size: 14px; + padding: 11px 20px; +} +.storybook-button--large { + font-size: 16px; + padding: 12px 24px; +} diff --git a/code/renderers/vue3/template/components/index.js b/code/renderers/vue3/template/components/index.js new file mode 100644 index 000000000000..bcf11c4fef18 --- /dev/null +++ b/code/renderers/vue3/template/components/index.js @@ -0,0 +1,5 @@ +import globalThis from 'global'; + +import Button from './Button.vue'; + +globalThis.Components = { Button }; diff --git a/code/renderers/web-components/package.json b/code/renderers/web-components/package.json index 2e3d13e9f308..f49874245b83 100644 --- a/code/renderers/web-components/package.json +++ b/code/renderers/web-components/package.json @@ -27,7 +27,7 @@ "import": "./dist/index.mjs", "types": "./dist/index.d.ts" }, - "./preview": { + "./preview.js": { "require": "./dist/config.js", "import": "./dist/config.mjs", "types": "./dist/config.d.ts" diff --git a/code/workspace.json b/code/workspace.json index f5f67f24ee49..a0aaff3f7aca 100644 --- a/code/workspace.json +++ b/code/workspace.json @@ -40,7 +40,12 @@ "root": "frameworks/web-components-webpack5", "type": "library" }, + + "@storybook/react-vite": { "root": "frameworks/react-vite", "type": "library" }, + "@storybook/vue3-vite": { "root": "frameworks/vue3-vite", "type": "library" }, + "@storybook/core-webpack": { "root": "lib/core-webpack", "type": "library" }, + "@storybook/core-vite": { "root": "lib/core-vite", "type": "library" }, "angular-cli": { "root": "examples/angular-cli", "type": "library" }, "cra-kitchen-sink": { "root": "examples/cra-kitchen-sink", "type": "library" }, @@ -66,6 +71,7 @@ "@storybook/addons": { "root": "lib/addons", "type": "library" }, "@storybook/api": { "root": "lib/api", "type": "library" }, + "@storybook/builder-vite": { "root": "lib/builder-vite", "type": "library" }, "@storybook/builder-webpack5": { "root": "lib/builder-webpack5", "type": "library" }, "@storybook/channel-postmessage": { "root": "lib/channel-postmessage", "type": "library" }, "@storybook/channel-websocket": { "root": "lib/channel-websocket", "type": "library" }, diff --git a/code/yarn.lock b/code/yarn.lock index 30c7d24494b5..e19dd8bea3e8 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -516,6 +516,29 @@ __metadata: languageName: node linkType: hard +"@babel/core@npm:^7.17.10, @babel/core@npm:^7.18.10": + version: 7.18.10 + resolution: "@babel/core@npm:7.18.10" + dependencies: + "@ampproject/remapping": ^2.1.0 + "@babel/code-frame": ^7.18.6 + "@babel/generator": ^7.18.10 + "@babel/helper-compilation-targets": ^7.18.9 + "@babel/helper-module-transforms": ^7.18.9 + "@babel/helpers": ^7.18.9 + "@babel/parser": ^7.18.10 + "@babel/template": ^7.18.10 + "@babel/traverse": ^7.18.10 + "@babel/types": ^7.18.10 + convert-source-map: ^1.7.0 + debug: ^4.1.0 + gensync: ^1.0.0-beta.2 + json5: ^2.2.1 + semver: ^6.3.0 + checksum: 94f749fb8bb844f5c5324513cac23edf781e24c15645a513eb54afc0e2f71d8c15c02e61f216a79f0f997deafc062b7c54bf2ebf31a2f62d0dd12bcbbc15dc97 + languageName: node + linkType: hard + "@babel/eslint-parser@npm:^7.16.3": version: 7.18.9 resolution: "@babel/eslint-parser@npm:7.18.9" @@ -552,6 +575,17 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.18.10": + version: 7.18.12 + resolution: "@babel/generator@npm:7.18.12" + dependencies: + "@babel/types": ^7.18.10 + "@jridgewell/gen-mapping": ^0.3.2 + jsesc: ^2.5.1 + checksum: 0a81453f3d6f458b6eeac046cb47b897674ea12ac7c72068faed1762aedade2290fbd139fcb605c4ea8386014aa87a0c96e609d8c469b434ab5923189e075ad1 + languageName: node + linkType: hard + "@babel/helper-annotate-as-pure@npm:7.16.7": version: 7.16.7 resolution: "@babel/helper-annotate-as-pure@npm:7.16.7" @@ -805,6 +839,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-string-parser@npm:^7.18.10": + version: 7.18.10 + resolution: "@babel/helper-string-parser@npm:7.18.10" + checksum: d32cba5466806e79a8e6242773619fd72dd504e5f432752811c143e7d721c18c5143322a583f18ea0e8ba579de8a5ea32552dc79ef14f76fb2990d5b71fa06ef + languageName: node + linkType: hard + "@babel/helper-validator-identifier@npm:^7.18.6": version: 7.18.6 resolution: "@babel/helper-validator-identifier@npm:7.18.6" @@ -862,6 +903,15 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.18.10, @babel/parser@npm:^7.18.11": + version: 7.18.11 + resolution: "@babel/parser@npm:7.18.11" + bin: + parser: ./bin/babel-parser.js + checksum: babaa1a445681102f9d5e6dfae5155720eefe60e0b4f2623aa1a9454252e6ea840b5bce0e1f07fb880bf0a3f604d4b6220cf368a09dd6b77b462f9e2cb618e15 + languageName: node + linkType: hard + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.16.7, @babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.18.6" @@ -1717,7 +1767,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-react-jsx-development@npm:^7.18.6": +"@babel/plugin-transform-react-jsx-development@npm:^7.16.7, @babel/plugin-transform-react-jsx-development@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-transform-react-jsx-development@npm:7.18.6" dependencies: @@ -1728,6 +1778,28 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-react-jsx-self@npm:^7.16.7, @babel/plugin-transform-react-jsx-self@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-transform-react-jsx-self@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 3fb17ae87eb264f77c5d1b30f4687f863f849bf4e8892159aee8e6bd069ff66d909f378dffdb7e6e157f9424cfbfe7c48e884aceac39e33f6a8abbdb04f83303 + languageName: node + linkType: hard + +"@babel/plugin-transform-react-jsx-source@npm:^7.16.7, @babel/plugin-transform-react-jsx-source@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-transform-react-jsx-source@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: af95477936a29f6c8c33277d6f1c8484309f7c8ddd2cb19a8e90d0ea944eb6a988d2ba21f4b9948918aa801cd0aaddbae6cea57a076f8cae2126fd5a43dbf57a + languageName: node + linkType: hard + "@babel/plugin-transform-react-jsx@npm:^7.12.12, @babel/plugin-transform-react-jsx@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-transform-react-jsx@npm:7.18.6" @@ -1743,6 +1815,21 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-react-jsx@npm:^7.17.3, @babel/plugin-transform-react-jsx@npm:^7.18.10": + version: 7.18.10 + resolution: "@babel/plugin-transform-react-jsx@npm:7.18.10" + dependencies: + "@babel/helper-annotate-as-pure": ^7.18.6 + "@babel/helper-module-imports": ^7.18.6 + "@babel/helper-plugin-utils": ^7.18.9 + "@babel/plugin-syntax-jsx": ^7.18.6 + "@babel/types": ^7.18.10 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: c993bd897df448928ce02f27a69cb5a9b9eafd8ab1dc70aaeee42ba501f5cb358efcf71b54f3b2c575852bbad622d7a0d0366268bc255b243e21290f7c18df1d + languageName: node + linkType: hard + "@babel/plugin-transform-react-pure-annotations@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-transform-react-pure-annotations@npm:7.18.6" @@ -2253,6 +2340,17 @@ __metadata: languageName: node linkType: hard +"@babel/template@npm:^7.18.10": + version: 7.18.10 + resolution: "@babel/template@npm:7.18.10" + dependencies: + "@babel/code-frame": ^7.18.6 + "@babel/parser": ^7.18.10 + "@babel/types": ^7.18.10 + checksum: d807944427b8899125e71687d2f631731e44a64a155d39e479ff9d1eaf5341de78c5c19cf64d3341bd676e16f779f13b588aac0ec75bf65f822d8936ee227490 + languageName: node + linkType: hard + "@babel/traverse@npm:^7.0.0, @babel/traverse@npm:^7.1.0, @babel/traverse@npm:^7.1.6, @babel/traverse@npm:^7.12.11, @babel/traverse@npm:^7.12.9, @babel/traverse@npm:^7.13.0, @babel/traverse@npm:^7.16.10, @babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.4.3, @babel/traverse@npm:^7.4.5, @babel/traverse@npm:^7.7.0, @babel/traverse@npm:^7.7.2, @babel/traverse@npm:^7.8.6": version: 7.18.9 resolution: "@babel/traverse@npm:7.18.9" @@ -2271,6 +2369,24 @@ __metadata: languageName: node linkType: hard +"@babel/traverse@npm:^7.18.10": + version: 7.18.11 + resolution: "@babel/traverse@npm:7.18.11" + dependencies: + "@babel/code-frame": ^7.18.6 + "@babel/generator": ^7.18.10 + "@babel/helper-environment-visitor": ^7.18.9 + "@babel/helper-function-name": ^7.18.9 + "@babel/helper-hoist-variables": ^7.18.6 + "@babel/helper-split-export-declaration": ^7.18.6 + "@babel/parser": ^7.18.11 + "@babel/types": ^7.18.10 + debug: ^4.1.0 + globals: ^11.1.0 + checksum: 04d5342190a2699ac314767a2af2e67e1a3f77e15c02c1801834e77eb50d2fa633dbc30dc64dccf0eabd40b1e7a4b1c04b67d0664030e54902e90e5c1b773f75 + languageName: node + linkType: hard + "@babel/types@npm:^7.0.0, @babel/types@npm:^7.11.5, @babel/types@npm:^7.12.11, @babel/types@npm:^7.12.6, @babel/types@npm:^7.12.7, @babel/types@npm:^7.13.12, @babel/types@npm:^7.16.7, @babel/types@npm:^7.16.8, @babel/types@npm:^7.18.6, @babel/types@npm:^7.18.9, @babel/types@npm:^7.2.0, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.0, @babel/types@npm:^7.4.4, @babel/types@npm:^7.6.1, @babel/types@npm:^7.7.0, @babel/types@npm:^7.7.2, @babel/types@npm:^7.8.3, @babel/types@npm:^7.8.6, @babel/types@npm:^7.8.7, @babel/types@npm:^7.9.6": version: 7.18.9 resolution: "@babel/types@npm:7.18.9" @@ -2281,6 +2397,17 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.18.10, @babel/types@npm:^7.18.8": + version: 7.18.10 + resolution: "@babel/types@npm:7.18.10" + dependencies: + "@babel/helper-string-parser": ^7.18.10 + "@babel/helper-validator-identifier": ^7.18.6 + to-fast-properties: ^2.0.0 + checksum: 62f50bc10967cff9cb9f1b46ba38946a6d084bdfba4587ad80898fc2a3812e51895f65fe21dcaa7d78e494d7e319acf0e4445492a3a9ae1331a806d660754f47 + languageName: node + linkType: hard + "@base2/pretty-print-object@npm:1.0.1": version: 1.0.1 resolution: "@base2/pretty-print-object@npm:1.0.1" @@ -3756,6 +3883,37 @@ __metadata: languageName: node linkType: hard +"@joshwooding/vite-plugin-react-docgen-typescript@npm:0.0.4": + version: 0.0.4 + resolution: "@joshwooding/vite-plugin-react-docgen-typescript@npm:0.0.4" + dependencies: + glob: ^7.2.0 + glob-promise: ^4.2.0 + magic-string: ^0.26.1 + react-docgen-typescript: ^2.1.1 + peerDependencies: + typescript: ">= 4.3.x" + vite: ">2.0.0-0" + checksum: 32edc485a121e1b5dcc0ca1344378f3e4a8a8ef726103dec59fb5b1d062858b0373050631592c336b468c599eedaf5a0591428ddfecd21b08be4c9899eac32dc + languageName: node + linkType: hard + +"@joshwooding/vite-plugin-react-docgen-typescript@npm:0.0.5": + version: 0.0.5 + resolution: "@joshwooding/vite-plugin-react-docgen-typescript@npm:0.0.5" + dependencies: + "@rollup/pluginutils": ^4.2.1 + glob: ^7.2.0 + glob-promise: ^4.2.0 + magic-string: ^0.26.1 + react-docgen-typescript: ^2.1.1 + peerDependencies: + typescript: ">= 4.3.x" + vite: ">2.0.0-0" + checksum: 075dc5a98122e66ef4203550fb4050a283a0c889d55cce729219b0707ddc703162c07dafd188363e398bdbd5c3604aa9b310d845c37402dffb284ff1669394e1 + languageName: node + linkType: hard + "@jridgewell/gen-mapping@npm:^0.1.0": version: 0.1.1 resolution: "@jridgewell/gen-mapping@npm:0.1.1" @@ -6617,6 +6775,16 @@ __metadata: languageName: node linkType: hard +"@rollup/pluginutils@npm:^4.2.0, @rollup/pluginutils@npm:^4.2.1": + version: 4.2.1 + resolution: "@rollup/pluginutils@npm:4.2.1" + dependencies: + estree-walker: ^2.0.1 + picomatch: ^2.2.2 + checksum: 3ee56b2c8f1ed8dfd0a92631da1af3a2dfdd0321948f089b3752b4de1b54dc5076701eadd0e5fc18bd191b77af594ac1db6279e83951238ba16bf8a414c64c48 + languageName: node + linkType: hard + "@rushstack/eslint-patch@npm:^1.0.8, @rushstack/eslint-patch@npm:^1.1.0": version: 1.1.4 resolution: "@rushstack/eslint-patch@npm:1.1.4" @@ -7578,6 +7746,48 @@ __metadata: languageName: unknown linkType: soft +"@storybook/builder-vite@7.0.0-alpha.24, @storybook/builder-vite@workspace:lib/builder-vite": + version: 0.0.0-use.local + resolution: "@storybook/builder-vite@workspace:lib/builder-vite" + dependencies: + "@joshwooding/vite-plugin-react-docgen-typescript": 0.0.5 + "@storybook/addons": 7.0.0-alpha.24 + "@storybook/channel-postmessage": 7.0.0-alpha.24 + "@storybook/channel-websocket": 7.0.0-alpha.24 + "@storybook/client-api": 7.0.0-alpha.24 + "@storybook/client-logger": 7.0.0-alpha.24 + "@storybook/core-common": 7.0.0-alpha.24 + "@storybook/mdx1-csf": 0.0.5-canary.13.9ce928a.0 + "@storybook/mdx2-csf": ^0.0.3 + "@storybook/node-logger": 7.0.0-alpha.24 + "@storybook/preview-web": 7.0.0-alpha.24 + "@storybook/source-loader": 7.0.0-alpha.24 + "@sveltejs/vite-plugin-svelte": ^1.0.0 + "@types/express": ^4.17.13 + "@types/node": ^17.0.23 + "@vitejs/plugin-react": ^2.0.0 + ast-types: ^0.14.2 + es-module-lexer: ^0.9.3 + glob: ^7.2.0 + glob-promise: ^4.2.0 + magic-string: ^0.26.1 + react-docgen: ^6.0.0-alpha.0 + slash: ^3.0.0 + svelte: ^3.49.0 + sveltedoc-parser: ^4.2.1 + typescript: ~4.6.3 + vite: 3 + peerDependencies: + "@storybook/mdx2-csf": ^0.0.3 + svelte: ^3.0.0 + peerDependenciesMeta: + "@storybook/mdx2-csf": + optional: true + svelte: + optional: true + languageName: unknown + linkType: soft + "@storybook/builder-webpack5@7.0.0-alpha.24, @storybook/builder-webpack5@workspace:lib/builder-webpack5": version: 0.0.0-use.local resolution: "@storybook/builder-webpack5@workspace:lib/builder-webpack5" @@ -8800,6 +9010,36 @@ __metadata: languageName: node linkType: hard +"@storybook/react-vite@workspace:*, @storybook/react-vite@workspace:frameworks/react-vite": + version: 0.0.0-use.local + resolution: "@storybook/react-vite@workspace:frameworks/react-vite" + dependencies: + "@joshwooding/vite-plugin-react-docgen-typescript": 0.0.4 + "@rollup/pluginutils": ^4.2.0 + "@storybook/builder-vite": 7.0.0-alpha.24 + "@storybook/core-server": 7.0.0-alpha.24 + "@storybook/react": 7.0.0-alpha.24 + "@types/node": ^14.14.20 || ^16.0.0 + "@vitejs/plugin-react": ^1.0.8 + ast-types: ^0.14.2 + core-js: ^3.8.2 + jest-specific-snapshot: ^4.0.0 + magic-string: ^0.26.1 + react-docgen: 6.0.0-alpha.1 + regenerator-runtime: ^0.13.7 + typescript: ~4.6.3 + peerDependencies: + "@babel/core": ^7.11.5 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@babel/core": + optional: true + typescript: + optional: true + languageName: unknown + linkType: soft + "@storybook/react-webpack5@7.0.0-alpha.24, @storybook/react-webpack5@workspace:*, @storybook/react-webpack5@workspace:frameworks/react-webpack5": version: 0.0.0-use.local resolution: "@storybook/react-webpack5@workspace:frameworks/react-webpack5" @@ -8954,6 +9194,7 @@ __metadata: "@storybook/preset-web-components-webpack": "workspace:*" "@storybook/preview-web": "workspace:*" "@storybook/react": "workspace:*" + "@storybook/react-vite": "workspace:*" "@storybook/react-webpack5": "workspace:*" "@storybook/router": "workspace:*" "@storybook/semver": ^7.3.2 @@ -8970,6 +9211,7 @@ __metadata: "@storybook/vue": "workspace:*" "@storybook/vue-webpack5": "workspace:*" "@storybook/vue3": "workspace:*" + "@storybook/vue3-vite": "workspace:*" "@storybook/vue3-webpack5": "workspace:*" "@storybook/web-components": "workspace:*" "@storybook/web-components-webpack5": "workspace:*" @@ -9084,7 +9326,7 @@ __metadata: ts-jest: ^26.4.4 ts-loader: ^9.2.8 ts-node: ^10.4.0 - tsup: ^6.1.2 + tsup: ^6.2.2 typescript: ~4.6.3 util: ^0.12.4 verdaccio: ^4.10.0 @@ -9468,6 +9710,37 @@ __metadata: languageName: unknown linkType: soft +"@storybook/vue3-vite@workspace:*, @storybook/vue3-vite@workspace:frameworks/vue3-vite": + version: 0.0.0-use.local + resolution: "@storybook/vue3-vite@workspace:frameworks/vue3-vite" + dependencies: + "@rollup/pluginutils": ^4.2.0 + "@storybook/builder-vite": 7.0.0-alpha.24 + "@storybook/core-server": 7.0.0-alpha.24 + "@storybook/vue3": 7.0.0-alpha.24 + "@types/node": ^14.14.20 || ^16.0.0 + "@vitejs/plugin-vue": ^3.0.3 + ast-types: ^0.14.2 + core-js: ^3.8.2 + jest-specific-snapshot: ^4.0.0 + magic-string: ^0.26.1 + react-docgen: 6.0.0-alpha.1 + regenerator-runtime: ^0.13.7 + typescript: ~4.6.3 + vite: 3 + vue-docgen-api: ^4.40.0 + peerDependencies: + "@babel/core": ^7.11.5 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@babel/core": + optional: true + typescript: + optional: true + languageName: unknown + linkType: soft + "@storybook/vue3-webpack5@7.0.0-alpha.24, @storybook/vue3-webpack5@workspace:*, @storybook/vue3-webpack5@workspace:frameworks/vue3-webpack5": version: 0.0.0-use.local resolution: "@storybook/vue3-webpack5@workspace:frameworks/vue3-webpack5" @@ -9507,7 +9780,7 @@ __metadata: react-dom: 16.14.0 ts-dedent: ^2.0.0 typescript: ~4.6.3 - vue: 3.0.0 + vue: ^3.0.0 peerDependencies: "@babel/core": "*" babel-loader: ^7.0.0 || ^8.0.0 @@ -9603,6 +9876,27 @@ __metadata: languageName: node linkType: hard +"@sveltejs/vite-plugin-svelte@npm:^1.0.0": + version: 1.0.1 + resolution: "@sveltejs/vite-plugin-svelte@npm:1.0.1" + dependencies: + "@rollup/pluginutils": ^4.2.1 + debug: ^4.3.4 + deepmerge: ^4.2.2 + kleur: ^4.1.5 + magic-string: ^0.26.2 + svelte-hmr: ^0.14.12 + peerDependencies: + diff-match-patch: ^1.0.5 + svelte: ^3.44.0 + vite: ^3.0.0 + peerDependenciesMeta: + diff-match-patch: + optional: true + checksum: 08f7f1e29a76d62c5f00a258a35e134c4d928fb81ce882a4582feb71560661dfe9aae12785b43f6b6dfc6ec19eb41f1aa8a794c15b313735233563445aaa1bb2 + languageName: node + linkType: hard + "@svgr/babel-plugin-add-jsx-attribute@npm:^5.4.0": version: 5.4.0 resolution: "@svgr/babel-plugin-add-jsx-attribute@npm:5.4.0" @@ -10573,7 +10867,7 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^17.0": +"@types/node@npm:^17.0, @types/node@npm:^17.0.23": version: 17.0.45 resolution: "@types/node@npm:17.0.45" checksum: 0db377133d709b33a47892581a21a41cd7958f22723a3cc6c71d55ac018121382de42fbfc7970d5ae3e7819dbe5f40e1c6a5174aedf7e7964e9cb8fa72b580b0 @@ -11436,6 +11730,49 @@ __metadata: languageName: node linkType: hard +"@vitejs/plugin-react@npm:^1.0.8": + version: 1.3.2 + resolution: "@vitejs/plugin-react@npm:1.3.2" + dependencies: + "@babel/core": ^7.17.10 + "@babel/plugin-transform-react-jsx": ^7.17.3 + "@babel/plugin-transform-react-jsx-development": ^7.16.7 + "@babel/plugin-transform-react-jsx-self": ^7.16.7 + "@babel/plugin-transform-react-jsx-source": ^7.16.7 + "@rollup/pluginutils": ^4.2.1 + react-refresh: ^0.13.0 + resolve: ^1.22.0 + checksum: 0d386828bdc2a03251959638ccf326e9533e820ffb3108f7bba6112dd07021ec839498395d9345391ae9233b17dbb81a732ea9f8e021fc8904fccae844d008f2 + languageName: node + linkType: hard + +"@vitejs/plugin-react@npm:^2.0.0": + version: 2.0.1 + resolution: "@vitejs/plugin-react@npm:2.0.1" + dependencies: + "@babel/core": ^7.18.10 + "@babel/plugin-transform-react-jsx": ^7.18.10 + "@babel/plugin-transform-react-jsx-development": ^7.18.6 + "@babel/plugin-transform-react-jsx-self": ^7.18.6 + "@babel/plugin-transform-react-jsx-source": ^7.18.6 + magic-string: ^0.26.2 + react-refresh: ^0.14.0 + peerDependencies: + vite: ^3.0.0 + checksum: 67e37622b0aa82506fdce4e6ad786f09362fb2eec1789e3d10ff139860b460bb3cea7adccc3100f6a7e4f6b84c9f20abf8d8ace29e52ccc17c3e1ad0bc23f3a6 + languageName: node + linkType: hard + +"@vitejs/plugin-vue@npm:^3.0.3": + version: 3.0.3 + resolution: "@vitejs/plugin-vue@npm:3.0.3" + peerDependencies: + vite: ^3.0.0 + vue: ^3.2.25 + checksum: 42ddb22e9b80df16f48826c5911d6aa5b2f81ba4210866c1519b1df181a34d2c231ba7c2d45c1b5aa32e1e88ef03d075b11c8026f2f4b96c4f5c987be0fecb0e + languageName: node + linkType: hard + "@vue/babel-helper-vue-jsx-merge-props@npm:^1.2.1": version: 1.2.1 resolution: "@vue/babel-helper-vue-jsx-merge-props@npm:1.2.1" @@ -12489,6 +12826,15 @@ __metadata: languageName: node linkType: hard +"acorn@npm:^8.6.0": + version: 8.8.0 + resolution: "acorn@npm:8.8.0" + bin: + acorn: bin/acorn + checksum: 5efce4f59554e0ab766f32932cba34b86cc2ecdf24fcd27463beff41d8a1b1b9575c21f92c1b9f7f82b93374a9d5aed33c91f93e2d0cb1bdf3f1e06ec131e816 + languageName: node + linkType: hard + "address@npm:^1.0.1, address@npm:^1.1.2": version: 1.2.0 resolution: "address@npm:1.2.0" @@ -13367,6 +13713,15 @@ __metadata: languageName: node linkType: hard +"ast-types@npm:0.15.2": + version: 0.15.2 + resolution: "ast-types@npm:0.15.2" + dependencies: + tslib: ^2.0.1 + checksum: 5b26e3656e9e8d1db8c8d14971d0cb88ca0138aacce72171cb4cd4555fc8dc53c07e821c568e57fe147366931708fefd25cb9d7e880d42ce9cb569947844c962 + languageName: node + linkType: hard + "ast-types@npm:0.9.6": version: 0.9.6 resolution: "ast-types@npm:0.9.6" @@ -20243,7 +20598,7 @@ __metadata: languageName: node linkType: hard -"es-module-lexer@npm:^0.9.0": +"es-module-lexer@npm:^0.9.0, es-module-lexer@npm:^0.9.3": version: 0.9.3 resolution: "es-module-lexer@npm:0.9.3" checksum: be77d73aee709fdc68d22b9938da81dfee3bc45e8d601629258643fe5bfdab253d6e2540035e035cfa8cf52a96366c1c19b46bcc23b4507b1d44e5907d2e7f6c @@ -21096,7 +21451,7 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^3.0.0, eslint-visitor-keys@npm:^3.3.0": +"eslint-visitor-keys@npm:^3.0.0, eslint-visitor-keys@npm:^3.1.0, eslint-visitor-keys@npm:^3.3.0": version: 3.3.0 resolution: "eslint-visitor-keys@npm:3.3.0" checksum: fc6a9b5bdee8d90e35e7564fd9db10fdf507a2c089a4f0d4d3dd091f7f4ac6790547c8b1b7a760642ef819f875ef86dd5bcb8cdf01b0775f57a699f4e6a20a18 @@ -21187,6 +21542,17 @@ __metadata: languageName: node linkType: hard +"espree@npm:9.2.0": + version: 9.2.0 + resolution: "espree@npm:9.2.0" + dependencies: + acorn: ^8.6.0 + acorn-jsx: ^5.3.1 + eslint-visitor-keys: ^3.1.0 + checksum: fa0acceb6bf151193b873379ba8ee3771c93ce3b656aeb9fe4a36adcf170e315925096e8addddd2d347ae6026ab18febc5892e314e6b109b61a9c3be75f88dc0 + languageName: node + linkType: hard + "espree@npm:^7.3.0, espree@npm:^7.3.1": version: 7.3.1 resolution: "espree@npm:7.3.1" @@ -23505,6 +23871,17 @@ __metadata: languageName: node linkType: hard +"glob-promise@npm:^4.2.0": + version: 4.2.2 + resolution: "glob-promise@npm:4.2.2" + dependencies: + "@types/glob": ^7.1.3 + peerDependencies: + glob: ^7.1.6 + checksum: 3eb01bed2901539365df6a4d27800afb8788840647d01f9bf3500b3de756597f2ff4b8c823971ace34db228c83159beca459dc42a70968d4e9c8200ed2cc96bd + languageName: node + linkType: hard + "glob-to-regexp@npm:^0.3.0": version: 0.3.0 resolution: "glob-to-regexp@npm:0.3.0" @@ -28801,7 +29178,7 @@ __metadata: languageName: node linkType: hard -"kleur@npm:^4.0.3": +"kleur@npm:^4.0.3, kleur@npm:^4.1.5": version: 4.1.5 resolution: "kleur@npm:4.1.5" checksum: e9de6cb49657b6fa70ba2d1448fd3d691a5c4370d8f7bbf1c2f64c24d461270f2117e1b0afe8cb3114f13bbd8e51de158c2a224953960331904e636a5e4c0f2a @@ -30099,7 +30476,7 @@ __metadata: languageName: node linkType: hard -"magic-string@npm:^0.26.0": +"magic-string@npm:^0.26.0, magic-string@npm:^0.26.1, magic-string@npm:^0.26.2": version: 0.26.2 resolution: "magic-string@npm:0.26.2" dependencies: @@ -35531,6 +35908,17 @@ __metadata: languageName: node linkType: hard +"postcss@npm:^8.4.16": + version: 8.4.16 + resolution: "postcss@npm:8.4.16" + dependencies: + nanoid: ^3.3.4 + picocolors: ^1.0.0 + source-map-js: ^1.0.2 + checksum: 971e10b5492ffce3d30e39ffba1b4f135d0c751bf774493babe4cb89ddb4995aabb2ce290f48bbf1d9ae60016a6ad492535e614b22b15322f977c7744e800fa2 + languageName: node + linkType: hard + "posthtml-parser@npm:^0.10.1": version: 0.10.2 resolution: "posthtml-parser@npm:0.10.2" @@ -36761,6 +37149,26 @@ __metadata: languageName: node linkType: hard +"react-docgen@npm:6.0.0-alpha.1": + version: 6.0.0-alpha.1 + resolution: "react-docgen@npm:6.0.0-alpha.1" + dependencies: + "@babel/core": ^7.7.5 + "@babel/generator": ^7.12.11 + ast-types: ^0.14.2 + commander: ^2.19.0 + doctrine: ^3.0.0 + estree-to-babel: ^3.1.0 + neo-async: ^2.6.1 + node-dir: ^0.1.10 + resolve: ^1.17.0 + strip-indent: ^3.0.0 + bin: + react-docgen: bin/react-docgen.js + checksum: 4033713ee81c27e5659096284dfb350f9c046ac8b3d9e8da89a83ef710f6a140b0347c5371cd39d45568d3c53cee9b214f457687b24241da794d5a9ac73a71e4 + languageName: node + linkType: hard + "react-docgen@npm:^5.0.0": version: 5.4.3 resolution: "react-docgen@npm:5.4.3" @@ -36781,6 +37189,26 @@ __metadata: languageName: node linkType: hard +"react-docgen@npm:^6.0.0-alpha.0": + version: 6.0.0-alpha.3 + resolution: "react-docgen@npm:6.0.0-alpha.3" + dependencies: + "@babel/core": ^7.7.5 + "@babel/generator": ^7.12.11 + ast-types: ^0.14.2 + commander: ^2.19.0 + doctrine: ^3.0.0 + estree-to-babel: ^3.1.0 + neo-async: ^2.6.1 + node-dir: ^0.1.10 + resolve: ^1.17.0 + strip-indent: ^3.0.0 + bin: + react-docgen: bin/react-docgen.js + checksum: 284bba5528d5e9084c3ed36b2d2fec8fc5d55f3fb8ca544ec3a0d1ab98c39001ecb7db6e03a1088b82eb3d750c1343cde2fc9b7729540277eda40e10f38912d8 + languageName: node + linkType: hard + "react-dom@npm:16.14.0, react-dom@npm:^16.14.0, react-dom@npm:^16.8.0": version: 16.14.0 resolution: "react-dom@npm:16.14.0" @@ -37037,6 +37465,20 @@ __metadata: languageName: node linkType: hard +"react-refresh@npm:^0.13.0": + version: 0.13.0 + resolution: "react-refresh@npm:0.13.0" + checksum: cb9f324d471485e569628854dc08d1550c0798cde57f1bfb8d954e006659de1da0bdccaf7d5d2ac0d3d53df1aae7b740b2a36128789afb8aff0f7ec01b128587 + languageName: node + linkType: hard + +"react-refresh@npm:^0.14.0": + version: 0.14.0 + resolution: "react-refresh@npm:0.14.0" + checksum: b8ae07ad153357d77830928a7f1fc2df837aabefee907fa273ba04c7643f3b860e986f1d4b7ada9b721c8d79b8c24b5b911a314a1a2398b105f1b13d19ea2b8d + languageName: node + linkType: hard + "react-refresh@npm:^0.9.0": version: 0.9.0 resolution: "react-refresh@npm:0.9.0" @@ -37471,6 +37913,18 @@ __metadata: languageName: node linkType: hard +"recast@npm:0.21.1": + version: 0.21.1 + resolution: "recast@npm:0.21.1" + dependencies: + ast-types: 0.15.2 + esprima: ~4.0.0 + source-map: ~0.6.1 + tslib: ^2.0.1 + checksum: f3e103a21594dc3ef1d481f81be327d349798e7d1b9a57cecfac5d4cadc59e3865b12a28f364f7191e158b16c9deaf6cb3341953d591fe63d22e4ad5b6a995dd + languageName: node + linkType: hard + "recast@npm:^0.11.17": version: 0.11.23 resolution: "recast@npm:0.11.23" @@ -38896,6 +39350,20 @@ __metadata: languageName: node linkType: hard +"rollup@npm:>=2.75.6 <2.77.0 || ~2.77.0": + version: 2.77.3 + resolution: "rollup@npm:2.77.3" + dependencies: + fsevents: ~2.3.2 + dependenciesMeta: + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: 7e04ba4e8fdbc3a4a368013e4b788044c16fe94e7301aacbf38e37210983e159b97887ddd3333be9f78fedb30264f094c111ff56a0207c21d4e1745248a7aa42 + languageName: node + linkType: hard + "rollup@npm:^2.43.1, rollup@npm:^2.60.2, rollup@npm:^2.74.1": version: 2.77.0 resolution: "rollup@npm:2.77.0" @@ -41388,7 +41856,7 @@ __metadata: languageName: unknown linkType: soft -"svelte-hmr@npm:^0.14.2": +"svelte-hmr@npm:^0.14.12, svelte-hmr@npm:^0.14.2": version: 0.14.12 resolution: "svelte-hmr@npm:0.14.12" peerDependencies: @@ -41469,7 +41937,7 @@ __metadata: languageName: node linkType: hard -"svelte@npm:^3.31.2, svelte@npm:^3.48.0": +"svelte@npm:^3.31.2, svelte@npm:^3.48.0, svelte@npm:^3.49.0": version: 3.49.0 resolution: "svelte@npm:3.49.0" checksum: 22bf1a252911c03fcbb840c89e02493af29304c8920e67cb2d63bb9477892dd7babd2fdeed9147328e72ba38211427e0658831b89ef63563415091b643fb116d @@ -41487,6 +41955,17 @@ __metadata: languageName: node linkType: hard +"sveltedoc-parser@npm:^4.2.1": + version: 4.3.1 + resolution: "sveltedoc-parser@npm:4.3.1" + dependencies: + eslint: 8.4.1 + espree: 9.2.0 + htmlparser2-svelte: 4.1.0 + checksum: 38e0258956fcf1bbe2ecc61286b73e96eedbec62e236c6b3adfc7055a4b2b50034b99f887a9e5eaccac233a87ccbc9434abe156f6a3ef25a640dcb54a4b99f46 + languageName: node + linkType: hard + "svg-parser@npm:^2.0.2": version: 2.0.4 resolution: "svg-parser@npm:2.0.4" @@ -42809,15 +43288,15 @@ __metadata: languageName: node linkType: hard -"tsup@npm:^6.1.2": - version: 6.1.3 - resolution: "tsup@npm:6.1.3" +"tsup@npm:^6.2.2": + version: 6.2.2 + resolution: "tsup@npm:6.2.2" dependencies: bundle-require: ^3.0.2 cac: ^6.7.12 chokidar: ^3.5.1 debug: ^4.3.1 - esbuild: ^0.14.25 + esbuild: ^0.15.1 execa: ^5.0.0 globby: ^11.0.3 joycon: ^3.0.1 @@ -42841,7 +43320,7 @@ __metadata: bin: tsup: dist/cli-default.js tsup-node: dist/cli-node.js - checksum: d5f04c5610a39c34b125a12aeae4af259f1b5f219acfcd1787e735b60cf737ca52906fae538d8e41280558b5093f594d31654f0f2ea9a7d90b223404bc97d83b + checksum: f06c6b42b09f14271ea9a2a1e12a9d242822de4bfd56cafe5c6147b9027a4c967f26bafe59fa0be93c8e84990f42c9b936c8938d756546c71ed91843b971e040 languageName: node linkType: hard @@ -44147,6 +44626,38 @@ __metadata: languageName: node linkType: hard +"vite@npm:3": + version: 3.0.7 + resolution: "vite@npm:3.0.7" + dependencies: + esbuild: ^0.14.47 + fsevents: ~2.3.2 + postcss: ^8.4.16 + resolve: ^1.22.1 + rollup: ">=2.75.6 <2.77.0 || ~2.77.0" + peerDependencies: + less: "*" + sass: "*" + stylus: "*" + terser: ^5.4.0 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + less: + optional: true + sass: + optional: true + stylus: + optional: true + terser: + optional: true + bin: + vite: bin/vite.js + checksum: 6d34332bbd3eab2928cdb720b2d8cfe22153908e4061fe0d509c9a9870f3db33652c9370da86c4705732f4da55dd54c4705f9ca5ca3e3b8f4bdd9177130988ef + languageName: node + linkType: hard + "vlq@npm:^0.2.1": version: 0.2.3 resolution: "vlq@npm:0.2.3" @@ -44269,6 +44780,25 @@ __metadata: languageName: unknown linkType: soft +"vue-docgen-api@npm:^4.40.0": + version: 4.52.0 + resolution: "vue-docgen-api@npm:4.52.0" + dependencies: + "@babel/parser": ^7.13.12 + "@babel/types": ^7.18.8 + "@vue/compiler-dom": ^3.2.0 + "@vue/compiler-sfc": ^3.2.0 + ast-types: 0.14.2 + hash-sum: ^1.0.2 + lru-cache: ^4.1.5 + pug: ^3.0.2 + recast: 0.21.1 + ts-map: ^1.0.3 + vue-inbrowser-compiler-independent-utils: ^4.52.0 + checksum: 38e7d44205a6fc14fc28c98e77f89fee5167ef0ad59e9083fcf17f8b9fb9e7aa20941a107b6b67fa7f9bfbf5bf0159925ac750b4d99ee06154e01a2695bf58ad + languageName: node + linkType: hard + "vue-docgen-api@npm:^4.44.23, vue-docgen-api@npm:^4.46.0": version: 4.47.0 resolution: "vue-docgen-api@npm:4.47.0" @@ -44358,6 +44888,15 @@ __metadata: languageName: node linkType: hard +"vue-inbrowser-compiler-independent-utils@npm:^4.52.0": + version: 4.52.0 + resolution: "vue-inbrowser-compiler-independent-utils@npm:4.52.0" + peerDependencies: + vue: ">=2" + checksum: 20d8577a94cd7e0a591ba1f0bea7ccbe87d51389b33523ad7b86bc1f6e13c78f3438b837ed78631b6e7cb4ef7a56b1d6d2a70a5f51b6f6fc100d644438ae28aa + languageName: node + linkType: hard + "vue-inbrowser-compiler-utils@npm:^4.44.23": version: 4.46.0 resolution: "vue-inbrowser-compiler-utils@npm:4.46.0" diff --git a/scripts/package.json b/scripts/package.json index 672506c42863..d311c05a97c0 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -179,7 +179,7 @@ "ts-dedent": "^2.0.0", "ts-jest": "^26.4.4", "ts-node": "^10.4.0", - "tsup": "^6.1.2", + "tsup": "^6.2.2", "typescript": "~4.6.3", "util": "^0.12.4", "wait-on": "^5.2.1", diff --git a/scripts/prepare/bundle.ts b/scripts/prepare/bundle.ts index f33b5c2e2ea4..321f0ffb2f64 100755 --- a/scripts/prepare/bundle.ts +++ b/scripts/prepare/bundle.ts @@ -130,7 +130,12 @@ const run = async ({ cwd, flags }: { cwd: string; flags: string[] }) => { const flags = process.argv.slice(2); const cwd = process.cwd(); -run({ cwd, flags }).catch((err) => { - console.error(err.stack); +run({ cwd, flags }).catch((err: unknown) => { + // We can't let the stack try to print, it crashes in a way that sets the exit code to 0. + // Seems to have something to do with running JSON.parse() on binary / base64 encoded sourcemaps + // in @cspotcode/source-map-support + if (err instanceof Error) { + console.error(err.message); + } process.exit(1); }); diff --git a/scripts/tasks/smoke-test.ts b/scripts/tasks/smoke-test.ts index d87ea323c3f0..907f3e156350 100644 --- a/scripts/tasks/smoke-test.ts +++ b/scripts/tasks/smoke-test.ts @@ -7,6 +7,9 @@ export const smokeTest: Task = { return false; }, async run(_, { sandboxDir }) { - return exec(`yarn storybook --smoke-test --quiet`, { cwd: sandboxDir }); + // eslint-disable-next-line no-console + console.log(`smoke testing in ${sandboxDir}`); + + return exec(`yarn storybook --smoke-test`, { cwd: sandboxDir }); }, }; diff --git a/scripts/yarn.lock b/scripts/yarn.lock index 1b5009bad1e0..b3e71a922130 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -3368,7 +3368,7 @@ __metadata: ts-jest: ^26.4.4 ts-loader: ^9.2.8 ts-node: ^10.4.0 - tsup: ^6.1.2 + tsup: ^6.2.2 typescript: ~4.6.3 util: ^0.12.4 verdaccio: ^4.10.0 @@ -19069,15 +19069,15 @@ __metadata: languageName: node linkType: hard -"tsup@npm:^6.1.2": - version: 6.1.3 - resolution: "tsup@npm:6.1.3" +"tsup@npm:^6.2.2": + version: 6.2.2 + resolution: "tsup@npm:6.2.2" dependencies: bundle-require: ^3.0.2 cac: ^6.7.12 chokidar: ^3.5.1 debug: ^4.3.1 - esbuild: ^0.14.25 + esbuild: ^0.15.1 execa: ^5.0.0 globby: ^11.0.3 joycon: ^3.0.1 @@ -19101,7 +19101,7 @@ __metadata: bin: tsup: dist/cli-default.js tsup-node: dist/cli-node.js - checksum: d5f04c5610a39c34b125a12aeae4af259f1b5f219acfcd1787e735b60cf737ca52906fae538d8e41280558b5093f594d31654f0f2ea9a7d90b223404bc97d83b + checksum: f06c6b42b09f14271ea9a2a1e12a9d242822de4bfd56cafe5c6147b9027a4c967f26bafe59fa0be93c8e84990f42c9b936c8938d756546c71ed91843b971e040 languageName: node linkType: hard