From 57baf1168e965692d6c989adf9868feb023d8ed9 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Fri, 20 Sep 2024 22:49:40 +0800 Subject: [PATCH 001/208] Chore: remove need to run everything under WSL --- CONTRIBUTING.md | 3 ++- docs/contribute/code.mdx | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4f65401dd4a8..879a675fcc3c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,6 +5,7 @@ Storybook is developed against a specific node version which is defined in an `. ## Ensure you have the required system utilities You will need to have the following installed: + - git - node - yarn @@ -20,7 +21,7 @@ You will need to have the following installed: ## Running the local development environment -- Ensure if you are using Windows to use the Windows Subsystem for Linux (WSL). +- Ensure if you are using Windows to use a terminal with admin privileges. - Run `yarn start` in the root directory to run a basic test Storybook "sandbox". The `yarn start` script will generate a React Vite TypeScript sandbox with a set of test stories inside it, as well as taking all steps required to get it running (building the various packages we need etc). There is no need to run `yarn` or `yarn install` as `yarn start` will do this for you. diff --git a/docs/contribute/code.mdx b/docs/contribute/code.mdx index e3b2e3bd0ceb..f0f9d88f1a45 100644 --- a/docs/contribute/code.mdx +++ b/docs/contribute/code.mdx @@ -10,7 +10,7 @@ Contribute a new feature or bug fix to [Storybook's monorepo](https://github.com ## Prerequisites * Ensure you have Node version 18 installed (suggestion: v18.16.0). -* Ensure if you are using Windows to use the Windows Subsystem for Linux (WSL). +* Ensure if you are using Windows to run the commands below in a terminal with administrator privileges. ## Initial setup From ee215cbe1a8ea82cc6ba4b3ae63600cfbc035d7e Mon Sep 17 00:00:00 2001 From: Grant Forsythe Date: Thu, 26 Sep 2024 21:37:06 -0400 Subject: [PATCH 002/208] docs: add yarn to upgrade block --- .../core/src/manager/components/upgrade/UpgradeBlock.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/code/core/src/manager/components/upgrade/UpgradeBlock.tsx b/code/core/src/manager/components/upgrade/UpgradeBlock.tsx index 207b9fcef6b1..f7371abc2e63 100644 --- a/code/core/src/manager/components/upgrade/UpgradeBlock.tsx +++ b/code/core/src/manager/components/upgrade/UpgradeBlock.tsx @@ -14,7 +14,7 @@ interface UpgradeBlockProps { export const UpgradeBlock: FC = ({ onNavigateToWhatsNew }) => { const api = useStorybookApi(); - const [activeTab, setActiveTab] = useState<'npm' | 'pnpm'>('npm'); + const [activeTab, setActiveTab] = useState<'npm' | 'yarn' | 'pnpm'>('npm'); return ( @@ -24,12 +24,17 @@ export const UpgradeBlock: FC = ({ onNavigateToWhatsNew }) => setActiveTab('npm')}> npm + setActiveTab('yarn')}> + yarn + setActiveTab('pnpm')}> pnpm - {activeTab === 'npm' ? 'npx storybook@latest upgrade' : 'pnpm dlx storybook@latest upgrade'} + {activeTab === 'npm' + ? 'npx storybook@latest upgrade' + : `${activeTab} dlx storybook@latest upgrade`} {onNavigateToWhatsNew && ( // eslint-disable-next-line jsx-a11y/anchor-is-valid From 1636ea32a333cb1336cd39bee1f4f40d344c7121 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Fri, 4 Oct 2024 16:43:25 +0800 Subject: [PATCH 003/208] Update docs/contribute/code.mdx Co-authored-by: jonniebigodes --- docs/contribute/code.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contribute/code.mdx b/docs/contribute/code.mdx index f0f9d88f1a45..822b3c625ef3 100644 --- a/docs/contribute/code.mdx +++ b/docs/contribute/code.mdx @@ -10,7 +10,7 @@ Contribute a new feature or bug fix to [Storybook's monorepo](https://github.com ## Prerequisites * Ensure you have Node version 18 installed (suggestion: v18.16.0). -* Ensure if you are using Windows to run the commands below in a terminal with administrator privileges. +* If you're working with Windows, all commands should be run in a terminal with administrator privileges. ## Initial setup From 033dc372fa0af69cdfca2e7395e3a5e15f5c212d Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Fri, 4 Oct 2024 16:43:32 +0800 Subject: [PATCH 004/208] Update CONTRIBUTING.md Co-authored-by: jonniebigodes --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 879a675fcc3c..6d2f04d9e253 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,7 +21,7 @@ You will need to have the following installed: ## Running the local development environment -- Ensure if you are using Windows to use a terminal with admin privileges. +- All commands should be run in a terminal with administrator privileges in Windows environments. - Run `yarn start` in the root directory to run a basic test Storybook "sandbox". The `yarn start` script will generate a React Vite TypeScript sandbox with a set of test stories inside it, as well as taking all steps required to get it running (building the various packages we need etc). There is no need to run `yarn` or `yarn install` as `yarn start` will do this for you. From 03dea27c8077b0a9a6baf575bb7f6fa913d0ace9 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Fri, 11 Oct 2024 22:53:30 +0200 Subject: [PATCH 005/208] initial script to bench individual packages --- scripts/bench/bench-package.ts | 157 +++++++++++++++++++++++++++++++++ scripts/package.json | 1 + scripts/yarn.lock | 8 ++ 3 files changed, 166 insertions(+) create mode 100644 scripts/bench/bench-package.ts diff --git a/scripts/bench/bench-package.ts b/scripts/bench/bench-package.ts new file mode 100644 index 000000000000..c7e74c8abbfe --- /dev/null +++ b/scripts/bench/bench-package.ts @@ -0,0 +1,157 @@ +import { program } from 'commander'; +import detectFreePort from 'detect-port'; +import { mkdir, readdir, rm, stat, writeFile } from 'fs/promises'; +import pLimit from 'p-limit'; +import { join } from 'path'; +import { x } from 'tinyexec'; + +import versions from '../../code/core/src/common/versions'; +import { runRegistry } from '../tasks/run-registry'; +import { maxConcurrentTasks } from '../utils/concurrency'; +import { esMain } from '../utils/esmain'; + +const PACKAGES_PATH = join(__dirname, '..', '..', 'bench', 'packages'); +const REGISTRY_PORT = 6001; + +type PackageName = keyof typeof versions; + +/** + * This script is used to bench the size of Storybook packages and their dependencies. For each + * package, the steps are: + * + * 1. Create a temporary directory in /bench/packages and create a package.json file that only depends + * on that one package + * 2. Install the package and its dependencies, without peer dependencies + * 3. Measure the size of the package and its dependencies, and count the number of dependencies + * (including transitive) + * 4. Print the results + */ + +export const benchPackage = async (packageName: PackageName) => { + console.log(`Benching ${packageName}...`); + const packagePath = join(PACKAGES_PATH, packageName.replace('@storybook', '')); + + await rm(packagePath, { recursive: true }).catch(() => {}); + await mkdir(packagePath, { recursive: true }); + + await writeFile( + join(packagePath, 'package.json'), + JSON.stringify( + { + name: `${packageName}-bench`, + version: '1.0.0', + overrides: versions, + }, + null, + 2 + ) + ); + + await x( + 'npm', + `install --save-exact --registry http://localhost:6001 --omit peer ${packageName}@${versions[packageName]}`.split( + ' ' + ), + { + nodeOptions: { cwd: packagePath }, + } + ); + + const npmLsResult = await x('npm', `ls --all --parseable`.split(' '), { + nodeOptions: { cwd: packagePath }, + }); + + const amountOfDependencies = npmLsResult.stdout.trim().split('\n').length - 2; + // the first line is the temporary benching package itself, don't count it + // the second line is the package we're benching, don't count it + + const nodeModulesSize = await getDirSize(join(packagePath, 'node_modules')); + const selfSize = await getDirSize(join(packagePath, 'node_modules', packageName)); + const dependencySize = nodeModulesSize - selfSize; + + console.log({ + package: packageName, + dependencies: amountOfDependencies, + selfSize: formatBytes(selfSize), + dependencySize: formatBytes(dependencySize), + totalSize: formatBytes(nodeModulesSize), + }); +}; + +const getDirSize = async (path: string) => { + const entities = await readdir(path, { + recursive: true, + withFileTypes: true, + }); + const stats = await Promise.all( + entities + .filter((entity) => entity.isFile()) + .map((entity) => stat(join(entity.parentPath, entity.name))) + ); + return stats.reduce((acc, { size }) => acc + size, 0); +}; + +const formatBytes = (bytes: number) => { + const units = ['B', 'KB', 'MB', 'GB', 'TB']; + let size = bytes; + let unitIndex = 0; + + while (size >= 1000 && unitIndex < units.length - 1) { + size /= 1000; + unitIndex++; + } + + // show 2 decimal places for MB, GB and TB and 0 for KB, B + const decimals = unitIndex < 2 ? 0 : 2; + return `${size.toFixed(decimals)} ${units[unitIndex]}`; +}; + +const run = async () => { + program.argument( + '[packages...]', + 'which packages to bench. If omitted, all packages are benched' + ); + program.parse(process.argv); + + const packages = ( + program.args.length > 0 ? program.args : Object.keys(versions) + ) as PackageName[]; + + packages.forEach((packageName) => { + if (!Object.keys(versions).includes(packageName)) { + throw new Error(`Package '${packageName}' not found`); + } + }); + + let registryController: AbortController | undefined; + if ((await detectFreePort(REGISTRY_PORT)) === REGISTRY_PORT) { + registryController = await runRegistry({ dryRun: false, debug: false }); + } + + // The amount of VCPUs for this task in CI is 8 (xlarge resource) + const amountOfVCPUs = 8; + const concurrentLimt = process.env.CI ? amountOfVCPUs - 1 : maxConcurrentTasks; + const limit = pLimit(concurrentLimt); + + const progressIntervalId = setInterval(() => { + const doneCount = packages.length - limit.activeCount - limit.pendingCount; + if (doneCount === packages.length) { + clearInterval(progressIntervalId); + return; + } + console.log( + `Currently benching ${limit.activeCount} packages, ${limit.pendingCount} pending, ${doneCount} done...` + ); + }, 2_000); + await Promise.all(packages.map((packageName) => limit(() => benchPackage(packageName)))); + + console.log('Done benching all packages'); + registryController?.abort(); +}; + +if (esMain(import.meta.url)) { + run().catch((err) => { + console.error(err); + process.exit(1); + }); +} diff --git a/scripts/package.json b/scripts/package.json index d7c89c37cb20..472203aebdbe 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -174,6 +174,7 @@ "slash": "^3.0.0", "sort-package-json": "^2.10.0", "tiny-invariant": "^1.3.3", + "tinyexec": "^0.3.0", "trash": "^7.2.0", "ts-dedent": "^2.2.0", "tsup": "^6.7.0", diff --git a/scripts/yarn.lock b/scripts/yarn.lock index 5f52272a0427..d2d3ac97bafd 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -1653,6 +1653,7 @@ __metadata: slash: "npm:^3.0.0" sort-package-json: "npm:^2.10.0" tiny-invariant: "npm:^1.3.3" + tinyexec: "npm:^0.3.0" trash: "npm:^7.2.0" ts-dedent: "npm:^2.2.0" tsup: "npm:^6.7.0" @@ -13393,6 +13394,13 @@ __metadata: languageName: node linkType: hard +"tinyexec@npm:^0.3.0": + version: 0.3.0 + resolution: "tinyexec@npm:0.3.0" + checksum: 10c0/138a4f4241aea6b6312559508468ab275a31955e66e2f57ed206e0aaabecee622624f208c5740345f0a66e33478fd065e359ed1eb1269eb6fd4fa25d44d0ba3b + languageName: node + linkType: hard + "tinypool@npm:^1.0.0": version: 1.0.0 resolution: "tinypool@npm:1.0.0" From ca5efc0ae576c066341bd51636bd9bf599fa4f7f Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Fri, 11 Oct 2024 22:53:56 +0200 Subject: [PATCH 006/208] use node apis instead of CLIs to rm files in run-registry script --- scripts/run-registry.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/scripts/run-registry.ts b/scripts/run-registry.ts index 5f179501583d..82d6513f40a2 100755 --- a/scripts/run-registry.ts +++ b/scripts/run-registry.ts @@ -1,12 +1,12 @@ import { exec } from 'node:child_process'; -import { mkdir } from 'node:fs/promises'; +import { mkdir, rm } from 'node:fs/promises'; import http from 'node:http'; import type { Server } from 'node:http'; import { join, resolve as resolvePath } from 'node:path'; import { program } from 'commander'; // eslint-disable-next-line depend/ban-dependencies -import { execa, execaSync } from 'execa'; +import { execa } from 'execa'; // eslint-disable-next-line depend/ban-dependencies import { pathExists, readJSON, remove } from 'fs-extra'; import pLimit from 'p-limit'; @@ -207,7 +207,7 @@ const run = async () => { await publish(packages, 'http://localhost:6002'); } - await execa('npx', ['rimraf', '.npmrc'], { cwd: root }); + await rm(join(root, '.npmrc'), { force: true }); if (!opts.open) { verdaccioServer.close(); @@ -217,6 +217,7 @@ const run = async () => { run().catch((e) => { logger.error(e); - execaSync('npx', ['rimraf', '.npmrc'], { cwd: root }); - process.exit(1); + rm(join(root, '.npmrc'), { force: true }).then(() => { + process.exit(1); + }); }); From 4224e79ba17f29f4cde37a7936b81dc7c1a42d91 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Tue, 15 Oct 2024 11:15:54 +0200 Subject: [PATCH 007/208] improve readability --- scripts/bench/bench-package.ts | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/scripts/bench/bench-package.ts b/scripts/bench/bench-package.ts index c7e74c8abbfe..4e7cb205c7f0 100644 --- a/scripts/bench/bench-package.ts +++ b/scripts/bench/bench-package.ts @@ -10,7 +10,7 @@ import { runRegistry } from '../tasks/run-registry'; import { maxConcurrentTasks } from '../utils/concurrency'; import { esMain } from '../utils/esmain'; -const PACKAGES_PATH = join(__dirname, '..', '..', 'bench', 'packages'); +const BENCH_PACKAGES_PATH = join(__dirname, '..', '..', 'bench', 'packages'); const REGISTRY_PORT = 6001; type PackageName = keyof typeof versions; @@ -29,17 +29,18 @@ type PackageName = keyof typeof versions; export const benchPackage = async (packageName: PackageName) => { console.log(`Benching ${packageName}...`); - const packagePath = join(PACKAGES_PATH, packageName.replace('@storybook', '')); + const tmpBenchPackagePath = join(BENCH_PACKAGES_PATH, packageName.replace('@storybook', '')); - await rm(packagePath, { recursive: true }).catch(() => {}); - await mkdir(packagePath, { recursive: true }); + await rm(tmpBenchPackagePath, { recursive: true }).catch(() => {}); + await mkdir(tmpBenchPackagePath, { recursive: true }); await writeFile( - join(packagePath, 'package.json'), + join(tmpBenchPackagePath, 'package.json'), JSON.stringify( { name: `${packageName}-bench`, version: '1.0.0', + // Overrides ensures that Storybook packages outside the monorepo are using the versions we have in the monorepo overrides: versions, }, null, @@ -53,20 +54,20 @@ export const benchPackage = async (packageName: PackageName) => { ' ' ), { - nodeOptions: { cwd: packagePath }, + nodeOptions: { cwd: tmpBenchPackagePath }, } ); const npmLsResult = await x('npm', `ls --all --parseable`.split(' '), { - nodeOptions: { cwd: packagePath }, + nodeOptions: { cwd: tmpBenchPackagePath }, }); - const amountOfDependencies = npmLsResult.stdout.trim().split('\n').length - 2; // the first line is the temporary benching package itself, don't count it // the second line is the package we're benching, don't count it + const amountOfDependencies = npmLsResult.stdout.trim().split('\n').length - 2; - const nodeModulesSize = await getDirSize(join(packagePath, 'node_modules')); - const selfSize = await getDirSize(join(packagePath, 'node_modules', packageName)); + const nodeModulesSize = await getDirSize(join(tmpBenchPackagePath, 'node_modules')); + const selfSize = await getDirSize(join(tmpBenchPackagePath, 'node_modules', packageName)); const dependencySize = nodeModulesSize - selfSize; console.log({ @@ -101,7 +102,8 @@ const formatBytes = (bytes: number) => { unitIndex++; } - // show 2 decimal places for MB, GB and TB and 0 for KB, B + // B, KB = 0 decimal places + // MB, GB, TB = 2 decimal places const decimals = unitIndex < 2 ? 0 : 2; return `${size.toFixed(decimals)} ${units[unitIndex]}`; }; @@ -125,11 +127,12 @@ const run = async () => { let registryController: AbortController | undefined; if ((await detectFreePort(REGISTRY_PORT)) === REGISTRY_PORT) { + console.log('Starting local registry...'); registryController = await runRegistry({ dryRun: false, debug: false }); } - // The amount of VCPUs for this task in CI is 8 (xlarge resource) - const amountOfVCPUs = 8; + // The amount of VCPUs for this task in CI is 2 (medium resource) + const amountOfVCPUs = 2; const concurrentLimt = process.env.CI ? amountOfVCPUs - 1 : maxConcurrentTasks; const limit = pLimit(concurrentLimt); From c21288098a27ef344bfec720e3d3ed3a675b1490 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Tue, 15 Oct 2024 11:25:36 +0200 Subject: [PATCH 008/208] add circleci job for package benching --- .circleci/config.yml | 27 +++++++++++++++++++++++++++ scripts/package.json | 1 + 2 files changed, 28 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1546a7db8430..c63a54226d63 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -211,6 +211,24 @@ jobs: yarn knip --no-exit-code - report-workflow-on-failure - cancel-workflow-on-failure + bench-packages: + executor: + class: medium + name: sb_node_22_classic + steps: + - git-shallow-clone/checkout_advanced: + clone_options: "--depth 1 --verbose" + - attach_workspace: + at: . + - run: + name: Benchmarking Packages + working_directory: scripts + command: yarn bench-package + # - run: + # name: Uploading results + # command: yarn upload-bench $(yarn get-template --cadence << pipeline.parameters.workflow >> --task bench) << pipeline.parameters.ghPrNumber >> << pipeline.parameters.ghBaseBranch >> + - report-workflow-on-failure + - cancel-workflow-on-failure check: executor: class: xlarge @@ -707,6 +725,9 @@ workflows: - knip: requires: - build + - bench-packages: + requires: + - build - check: requires: - build @@ -776,6 +797,9 @@ workflows: - knip: requires: - build + - bench-packages: + requires: + - build - check: requires: - build @@ -846,6 +870,9 @@ workflows: - knip: requires: - build + - bench-packages: + requires: + - build - check: requires: - build diff --git a/scripts/package.json b/scripts/package.json index 472203aebdbe..fc25e1bed562 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -4,6 +4,7 @@ "private": true, "type": "module", "scripts": { + "bench-package": "jiti ./bench/bench-package.ts", "build-package": "jiti ./build-package.ts", "check": "jiti ./prepare/check-scripts.ts", "check-package": "jiti ./check-package.ts", From a21f3213716079ae6f7124da100a3d0dbbd1b0b2 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Tue, 15 Oct 2024 11:33:36 +0200 Subject: [PATCH 009/208] save results to file --- .circleci/config.yml | 10 ++++++---- scripts/bench/bench-package.ts | 32 ++++++++++++++++++++++++++++---- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c63a54226d63..95c8976a4b1a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -224,6 +224,8 @@ jobs: name: Benchmarking Packages working_directory: scripts command: yarn bench-package + - store_artifacts: + path: code/bench/packages/package-bench-results.json # - run: # name: Uploading results # command: yarn upload-bench $(yarn get-template --cadence << pipeline.parameters.workflow >> --task bench) << pipeline.parameters.ghPrNumber >> << pipeline.parameters.ghBaseBranch >> @@ -540,7 +542,7 @@ jobs: - store_artifacts: # this is where playwright puts more complex stuff path: code/playwright-results/ destination: playwright - bench: + bench-sandboxes: parameters: parallelism: type: integer @@ -771,7 +773,7 @@ workflows: parallelism: 5 requires: - create-sandboxes - - bench: + - bench-sandboxes: parallelism: 5 requires: - build-sandboxes @@ -849,7 +851,7 @@ workflows: matrix: parameters: directory: ["react", "vue3", "nextjs", "svelte"] - - bench: + - bench-sandboxes: parallelism: 5 requires: - build-sandboxes @@ -940,7 +942,7 @@ workflows: # --smoke-test is not supported for the angular builder right now # - "angular-cli" - "lit-vite-ts" - - bench: + - bench-sandboxes: parallelism: 5 requires: - build-sandboxes diff --git a/scripts/bench/bench-package.ts b/scripts/bench/bench-package.ts index 4e7cb205c7f0..86422927f273 100644 --- a/scripts/bench/bench-package.ts +++ b/scripts/bench/bench-package.ts @@ -14,7 +14,13 @@ const BENCH_PACKAGES_PATH = join(__dirname, '..', '..', 'bench', 'packages'); const REGISTRY_PORT = 6001; type PackageName = keyof typeof versions; - +type Result = { + package: PackageName; + dependencies: number; + selfSize: string; + dependencySize: string; + totalSize: string; +}; /** * This script is used to bench the size of Storybook packages and their dependencies. For each * package, the steps are: @@ -70,13 +76,15 @@ export const benchPackage = async (packageName: PackageName) => { const selfSize = await getDirSize(join(tmpBenchPackagePath, 'node_modules', packageName)); const dependencySize = nodeModulesSize - selfSize; - console.log({ + const result: Result = { package: packageName, dependencies: amountOfDependencies, selfSize: formatBytes(selfSize), dependencySize: formatBytes(dependencySize), totalSize: formatBytes(nodeModulesSize), - }); + }; + console.log(result); + return result; }; const getDirSize = async (path: string) => { @@ -108,6 +116,19 @@ const formatBytes = (bytes: number) => { return `${size.toFixed(decimals)} ${units[unitIndex]}`; }; +const saveResults = async (results: Result[]) => { + console.log('Saving results...'); + const allResults: Record> = {}; + for (const result of results) { + const { package: packageName, ...withoutPackage } = result; + allResults[result.package] = withoutPackage; + } + await writeFile( + join(BENCH_PACKAGES_PATH, 'package-bench-results.json'), + JSON.stringify(allResults, null, 2) + ); +}; + const run = async () => { program.argument( '[packages...]', @@ -146,7 +167,10 @@ const run = async () => { `Currently benching ${limit.activeCount} packages, ${limit.pendingCount} pending, ${doneCount} done...` ); }, 2_000); - await Promise.all(packages.map((packageName) => limit(() => benchPackage(packageName)))); + const results = await Promise.all( + packages.map((packageName) => limit(() => benchPackage(packageName))) + ); + await saveResults(results); console.log('Done benching all packages'); registryController?.abort(); From 10ec1ede0d1a9f3149b244a9e6a1c0636dce52fb Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Tue, 15 Oct 2024 12:38:46 +0200 Subject: [PATCH 010/208] fix result path --- .circleci/config.yml | 2 +- scripts/bench/bench-package.ts | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 95c8976a4b1a..21067d420d5b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -225,7 +225,7 @@ jobs: working_directory: scripts command: yarn bench-package - store_artifacts: - path: code/bench/packages/package-bench-results.json + path: bench/packages/results.json # - run: # name: Uploading results # command: yarn upload-bench $(yarn get-template --cadence << pipeline.parameters.workflow >> --task bench) << pipeline.parameters.ghPrNumber >> << pipeline.parameters.ghBaseBranch >> diff --git a/scripts/bench/bench-package.ts b/scripts/bench/bench-package.ts index 86422927f273..40709eba2998 100644 --- a/scripts/bench/bench-package.ts +++ b/scripts/bench/bench-package.ts @@ -117,16 +117,14 @@ const formatBytes = (bytes: number) => { }; const saveResults = async (results: Result[]) => { - console.log('Saving results...'); + const resultPath = join(BENCH_PACKAGES_PATH, 'results.json'); + console.log(`Saving results to ${resultPath}...`); const allResults: Record> = {}; for (const result of results) { const { package: packageName, ...withoutPackage } = result; allResults[result.package] = withoutPackage; } - await writeFile( - join(BENCH_PACKAGES_PATH, 'package-bench-results.json'), - JSON.stringify(allResults, null, 2) - ); + await writeFile(resultPath, JSON.stringify(allResults, null, 2)); }; const run = async () => { From 7af86e2cf834de1f0782b7fb66fc8634f539e02b Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Tue, 15 Oct 2024 22:53:53 +0200 Subject: [PATCH 011/208] add support for comparing bench results with a base --- .circleci/config.yml | 7 +- scripts/bench/bench-package.ts | 139 ++++++++++++++++++++++++++++----- 2 files changed, 123 insertions(+), 23 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 21067d420d5b..30d46b4f098c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -223,12 +223,11 @@ jobs: - run: name: Benchmarking Packages working_directory: scripts - command: yarn bench-package + command: yarn bench-package --base << pipeline.parameters.ghBaseBranch >> --pull-request << pipeline.parameters.ghPrNumber >> - store_artifacts: path: bench/packages/results.json - # - run: - # name: Uploading results - # command: yarn upload-bench $(yarn get-template --cadence << pipeline.parameters.workflow >> --task bench) << pipeline.parameters.ghPrNumber >> << pipeline.parameters.ghBaseBranch >> + - store_artifacts: + path: bench/packages/compare-with-<< pipeline.parameters.ghBaseBranch >>.json - report-workflow-on-failure - cancel-workflow-on-failure check: diff --git a/scripts/bench/bench-package.ts b/scripts/bench/bench-package.ts index 40709eba2998..b00b22daa512 100644 --- a/scripts/bench/bench-package.ts +++ b/scripts/bench/bench-package.ts @@ -1,3 +1,5 @@ +import { BigQuery } from '@google-cloud/bigquery'; +import { request } from '@octokit/request'; import { program } from 'commander'; import detectFreePort from 'detect-port'; import { mkdir, readdir, rm, stat, writeFile } from 'fs/promises'; @@ -15,12 +17,20 @@ const REGISTRY_PORT = 6001; type PackageName = keyof typeof versions; type Result = { + package: PackageName; + dependencies: number; + selfSize: number; + dependencySize: number; +}; +type HumanReadableResult = { package: PackageName; dependencies: number; selfSize: string; dependencySize: string; totalSize: string; }; +type ResultMap = Record; +type HumanReadableResultMap = Record; /** * This script is used to bench the size of Storybook packages and their dependencies. For each * package, the steps are: @@ -79,11 +89,10 @@ export const benchPackage = async (packageName: PackageName) => { const result: Result = { package: packageName, dependencies: amountOfDependencies, - selfSize: formatBytes(selfSize), - dependencySize: formatBytes(dependencySize), - totalSize: formatBytes(nodeModulesSize), + selfSize, + dependencySize, }; - console.log(result); + console.log(toHumanReadable(result)); return result; }; @@ -100,9 +109,19 @@ const getDirSize = async (path: string) => { return stats.reduce((acc, { size }) => acc + size, 0); }; +const toHumanReadable = (result: Result): HumanReadableResult => { + return { + package: result.package, + dependencies: result.dependencies, + selfSize: formatBytes(result.selfSize), + dependencySize: formatBytes(result.dependencySize), + totalSize: formatBytes(result.selfSize + result.dependencySize), + }; +}; + const formatBytes = (bytes: number) => { const units = ['B', 'KB', 'MB', 'GB', 'TB']; - let size = bytes; + let size = Math.abs(bytes); let unitIndex = 0; while (size >= 1000 && unitIndex < units.length - 1) { @@ -113,30 +132,93 @@ const formatBytes = (bytes: number) => { // B, KB = 0 decimal places // MB, GB, TB = 2 decimal places const decimals = unitIndex < 2 ? 0 : 2; - return `${size.toFixed(decimals)} ${units[unitIndex]}`; + const formattedSize = `${size.toFixed(decimals)} ${units[unitIndex]}`; + + return bytes < 0 ? `-${formattedSize}` : formattedSize; }; -const saveResults = async (results: Result[]) => { - const resultPath = join(BENCH_PACKAGES_PATH, 'results.json'); +const saveResultsLocally = async ({ + results, + filename, +}: { + results: ResultMap; + filename: string; +}) => { + const resultPath = join(BENCH_PACKAGES_PATH, filename); console.log(`Saving results to ${resultPath}...`); - const allResults: Record> = {}; - for (const result of results) { - const { package: packageName, ...withoutPackage } = result; - allResults[result.package] = withoutPackage; + + const humanReadableResults = Object.entries(results).reduce((acc, [packageName, result]) => { + acc[packageName] = toHumanReadable(result); + return acc; + }, {} as HumanReadableResultMap); + await writeFile(resultPath, JSON.stringify(humanReadableResults, null, 2)); +}; + +const compareResults = async ({ results, base }: { results: ResultMap; base: string }) => { + // const GCP_CREDENTIALS = JSON.parse(process.env.GCP_CREDENTIALS || '{}'); + + // const store = new BigQuery({ + // projectId: GCP_CREDENTIALS.project_id, + // credentials: GCP_CREDENTIALS, + // }); + // const dataset = store.dataset('benchmark_results'); + // const appTable = dataset.table('bench2'); + + // const [baseResults] = await appTable.query({ + // query: ` + // WITH latest_packages AS ( + // SELECT branch, package, timestamp, + // ROW_NUMBER() OVER (PARTITION BY branch, package ORDER BY timestamp DESC) as rownumber + // FROM \`storybook-benchmark.benchmark_results.bench2\` + // WHERE branch = @base + // AND package IN UNNEST(@packages) + // AND timestamp > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 7 DAY) + // ) + // SELECT branch, package, timestamp + // FROM latest_packages + // WHERE rownumber = 1 + // ORDER BY package;`, + // params: { base, packages: Object.keys(results) }, + // }); + // console.log(baseResults); + + const baseResults = [ + { + package: '@storybook/react', + dependencies: 4, + selfSize: 900_000, + dependencySize: 50_000, + }, + ]; + + const comparisonResults = {} as ResultMap; + for (const [packageName, result] of Object.entries(results)) { + const baseResult = baseResults.find((row) => row.package === packageName); + if (!baseResult) { + console.warn(`No base result found for ${packageName}, skipping comparison.`); + continue; + } + comparisonResults[packageName] = { + package: packageName, + dependencies: result.dependencies - baseResult.dependencies, + selfSize: result.selfSize - baseResult.selfSize, + dependencySize: result.dependencySize - baseResult.dependencySize, + }; } - await writeFile(resultPath, JSON.stringify(allResults, null, 2)); + return comparisonResults; }; const run = async () => { - program.argument( - '[packages...]', - 'which packages to bench. If omitted, all packages are benched' - ); + program + .option('-b, --base ', 'The base branch to compare the results with') + .option('-p, --pull-request ', 'The PR number to comment comparions on') + .argument('[packages...]', 'which packages to bench. If omitted, all packages are benched'); program.parse(process.argv); const packages = ( program.args.length > 0 ? program.args : Object.keys(versions) ) as PackageName[]; + const options = program.opts<{ pullRequest?: string; base?: string }>(); packages.forEach((packageName) => { if (!Object.keys(versions).includes(packageName)) { @@ -165,10 +247,29 @@ const run = async () => { `Currently benching ${limit.activeCount} packages, ${limit.pendingCount} pending, ${doneCount} done...` ); }, 2_000); - const results = await Promise.all( + const resultsArray = await Promise.all( packages.map((packageName) => limit(() => benchPackage(packageName))) ); - await saveResults(results); + const results = resultsArray.reduce((acc, result) => { + acc[result.package] = result; + return acc; + }, {} as ResultMap); + await saveResultsLocally({ + filename: `results.json`, + results, + }); + + if (options.base) { + const comparisonResults = await compareResults({ results, base: options.base }); + await saveResultsLocally({ + filename: `compare-with-${options.base}.json`, + results: comparisonResults, + }); + + if (options.pullRequest) { + // send to github bot + } + } console.log('Done benching all packages'); registryController?.abort(); From fcd2d968cb0fea47cbd669bd4eca36432f662667 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Wed, 16 Oct 2024 12:35:32 +0200 Subject: [PATCH 012/208] cleanup --- .circleci/config.yml | 2 +- scripts/bench/bench-package.ts | 40 +++++++++++++++++++--------------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 30d46b4f098c..e7e39471e3d6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -223,7 +223,7 @@ jobs: - run: name: Benchmarking Packages working_directory: scripts - command: yarn bench-package --base << pipeline.parameters.ghBaseBranch >> --pull-request << pipeline.parameters.ghPrNumber >> + command: yarn bench-package --baseBranch << pipeline.parameters.ghBaseBranch >> --pull-request << pipeline.parameters.ghPrNumber >> - store_artifacts: path: bench/packages/results.json - store_artifacts: diff --git a/scripts/bench/bench-package.ts b/scripts/bench/bench-package.ts index b00b22daa512..84517e370a30 100644 --- a/scripts/bench/bench-package.ts +++ b/scripts/bench/bench-package.ts @@ -31,18 +31,18 @@ type HumanReadableResult = { }; type ResultMap = Record; type HumanReadableResultMap = Record; + /** - * This script is used to bench the size of Storybook packages and their dependencies. For each - * package, the steps are: + * This function benchmarks the size of Storybook packages and their dependencies. For each package, + * the steps are: * * 1. Create a temporary directory in /bench/packages and create a package.json file that only depends * on that one package * 2. Install the package and its dependencies, without peer dependencies * 3. Measure the size of the package and its dependencies, and count the number of dependencies * (including transitive) - * 4. Print the results + * 4. Print and return the results */ - export const benchPackage = async (packageName: PackageName) => { console.log(`Benching ${packageName}...`); const tmpBenchPackagePath = join(BENCH_PACKAGES_PATH, packageName.replace('@storybook', '')); @@ -154,7 +154,13 @@ const saveResultsLocally = async ({ await writeFile(resultPath, JSON.stringify(humanReadableResults, null, 2)); }; -const compareResults = async ({ results, base }: { results: ResultMap; base: string }) => { +const compareResults = async ({ + results, + baseBranch, +}: { + results: ResultMap; + baseBranch: string; +}) => { // const GCP_CREDENTIALS = JSON.parse(process.env.GCP_CREDENTIALS || '{}'); // const store = new BigQuery({ @@ -170,7 +176,7 @@ const compareResults = async ({ results, base }: { results: ResultMap; base: str // SELECT branch, package, timestamp, // ROW_NUMBER() OVER (PARTITION BY branch, package ORDER BY timestamp DESC) as rownumber // FROM \`storybook-benchmark.benchmark_results.bench2\` - // WHERE branch = @base + // WHERE branch = @baseBranch // AND package IN UNNEST(@packages) // AND timestamp > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 7 DAY) // ) @@ -178,7 +184,7 @@ const compareResults = async ({ results, base }: { results: ResultMap; base: str // FROM latest_packages // WHERE rownumber = 1 // ORDER BY package;`, - // params: { base, packages: Object.keys(results) }, + // params: { baseBranch, packages: Object.keys(results) }, // }); // console.log(baseResults); @@ -192,14 +198,14 @@ const compareResults = async ({ results, base }: { results: ResultMap; base: str ]; const comparisonResults = {} as ResultMap; - for (const [packageName, result] of Object.entries(results)) { - const baseResult = baseResults.find((row) => row.package === packageName); + for (const result of Object.values(results)) { + const baseResult = baseResults.find((row) => row.package === result.package); if (!baseResult) { - console.warn(`No base result found for ${packageName}, skipping comparison.`); + console.warn(`No base result found for ${result.package}, skipping comparison.`); continue; } - comparisonResults[packageName] = { - package: packageName, + comparisonResults[result.package] = { + package: result.package, dependencies: result.dependencies - baseResult.dependencies, selfSize: result.selfSize - baseResult.selfSize, dependencySize: result.dependencySize - baseResult.dependencySize, @@ -210,7 +216,7 @@ const compareResults = async ({ results, base }: { results: ResultMap; base: str const run = async () => { program - .option('-b, --base ', 'The base branch to compare the results with') + .option('-b, --baseBranch ', 'The base branch to compare the results with') .option('-p, --pull-request ', 'The PR number to comment comparions on') .argument('[packages...]', 'which packages to bench. If omitted, all packages are benched'); program.parse(process.argv); @@ -218,7 +224,7 @@ const run = async () => { const packages = ( program.args.length > 0 ? program.args : Object.keys(versions) ) as PackageName[]; - const options = program.opts<{ pullRequest?: string; base?: string }>(); + const options = program.opts<{ pullRequest?: string; baseBranch?: string }>(); packages.forEach((packageName) => { if (!Object.keys(versions).includes(packageName)) { @@ -259,10 +265,10 @@ const run = async () => { results, }); - if (options.base) { - const comparisonResults = await compareResults({ results, base: options.base }); + if (options.baseBranch) { + const comparisonResults = await compareResults({ results, baseBranch: options.baseBranch }); await saveResultsLocally({ - filename: `compare-with-${options.base}.json`, + filename: `compare-with-${options.baseBranch}.json`, results: comparisonResults, }); From 8a40fd3b6dc4f7d760ea9286f81d34736823c9b5 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Wed, 16 Oct 2024 14:00:27 +0200 Subject: [PATCH 013/208] start upload benches --- scripts/bench/bench-package.ts | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/scripts/bench/bench-package.ts b/scripts/bench/bench-package.ts index 84517e370a30..5a672c5adfd5 100644 --- a/scripts/bench/bench-package.ts +++ b/scripts/bench/bench-package.ts @@ -14,6 +14,13 @@ import { esMain } from '../utils/esmain'; const BENCH_PACKAGES_PATH = join(__dirname, '..', '..', 'bench', 'packages'); const REGISTRY_PORT = 6001; +const GCP_CREDENTIALS = JSON.parse(process.env.GCP_CREDENTIALS || '{}'); +const bigQueryBenchTable = new BigQuery({ + projectId: GCP_CREDENTIALS.project_id, + credentials: GCP_CREDENTIALS, +}) + .dataset('benchmark_results') + .table('bench2'); type PackageName = keyof typeof versions; type Result = { @@ -161,16 +168,7 @@ const compareResults = async ({ results: ResultMap; baseBranch: string; }) => { - // const GCP_CREDENTIALS = JSON.parse(process.env.GCP_CREDENTIALS || '{}'); - - // const store = new BigQuery({ - // projectId: GCP_CREDENTIALS.project_id, - // credentials: GCP_CREDENTIALS, - // }); - // const dataset = store.dataset('benchmark_results'); - // const appTable = dataset.table('bench2'); - - // const [baseResults] = await appTable.query({ + // const [baseResults] = await bigQueryBenchTable.query({ // query: ` // WITH latest_packages AS ( // SELECT branch, package, timestamp, @@ -214,6 +212,18 @@ const compareResults = async ({ return comparisonResults; }; +const uploadResultsToBigQuery = async (results: ResultMap) => { + const row = { + branch: + process.env.CIRCLE_BRANCH || + (await x('git', 'rev-parse --abbrev-ref HEAD'.split(' '))).stdout.trim(), + commit: process.env.CIRCLE_SHA1 || (await x('git', 'rev-parse HEAD'.split(' '))).stdout.trim(), + timestamp: new Date().toISOString(), + results, + }; + +}; + const run = async () => { program .option('-b, --baseBranch ', 'The base branch to compare the results with') From 738c6bbbfb62d933aa1cc59a541486d20b319adb Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Thu, 17 Oct 2024 15:00:05 +0200 Subject: [PATCH 014/208] upload to BigQuery --- scripts/bench/bench-package.ts | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/scripts/bench/bench-package.ts b/scripts/bench/bench-package.ts index 5a672c5adfd5..d87e0da598c8 100644 --- a/scripts/bench/bench-package.ts +++ b/scripts/bench/bench-package.ts @@ -20,7 +20,7 @@ const bigQueryBenchTable = new BigQuery({ credentials: GCP_CREDENTIALS, }) .dataset('benchmark_results') - .table('bench2'); + .table('package_bench'); type PackageName = keyof typeof versions; type Result = { @@ -155,7 +155,7 @@ const saveResultsLocally = async ({ console.log(`Saving results to ${resultPath}...`); const humanReadableResults = Object.entries(results).reduce((acc, [packageName, result]) => { - acc[packageName] = toHumanReadable(result); + acc[packageName as PackageName] = toHumanReadable(result); return acc; }, {} as HumanReadableResultMap); await writeFile(resultPath, JSON.stringify(humanReadableResults, null, 2)); @@ -213,15 +213,26 @@ const compareResults = async ({ }; const uploadResultsToBigQuery = async (results: ResultMap) => { - const row = { + if (!GCP_CREDENTIALS.project_id) { + console.warn('No GCP credentials found, skipping upload to BigQuery'); + return; + } + const commonFields = { branch: process.env.CIRCLE_BRANCH || (await x('git', 'rev-parse --abbrev-ref HEAD'.split(' '))).stdout.trim(), commit: process.env.CIRCLE_SHA1 || (await x('git', 'rev-parse HEAD'.split(' '))).stdout.trim(), - timestamp: new Date().toISOString(), - results, + benchmarkedAt: new Date(), }; + const rows = Object.values(results).map((result) => ({ + ...commonFields, + package: result.package, + selfSize: result.selfSize, + dependencySize: result.dependencySize, + dependencyCount: result.dependencies, + })); + await bigQueryBenchTable.insert(rows); }; const run = async () => { @@ -277,10 +288,13 @@ const run = async () => { if (options.baseBranch) { const comparisonResults = await compareResults({ results, baseBranch: options.baseBranch }); - await saveResultsLocally({ - filename: `compare-with-${options.baseBranch}.json`, - results: comparisonResults, - }); + await Promise.all([ + saveResultsLocally({ + filename: `compare-with-${options.baseBranch}.json`, + results: comparisonResults, + }), + uploadResultsToBigQuery(results), + ]); if (options.pullRequest) { // send to github bot From caf693acd2e1f4db350ab8f7b04beb5b2f4f4402 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Fri, 18 Oct 2024 10:43:09 +0200 Subject: [PATCH 015/208] compare with base branch from bigquery --- .circleci/config.yml | 2 +- scripts/bench/bench-package.ts | 110 +++++++++++++++++++-------------- 2 files changed, 63 insertions(+), 49 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e7e39471e3d6..2acd362190f2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -223,7 +223,7 @@ jobs: - run: name: Benchmarking Packages working_directory: scripts - command: yarn bench-package --baseBranch << pipeline.parameters.ghBaseBranch >> --pull-request << pipeline.parameters.ghPrNumber >> + command: yarn bench-package --baseBranch << pipeline.parameters.ghBaseBranch >> --pull-request << pipeline.parameters.ghPrNumber >> --upload - store_artifacts: path: bench/packages/results.json - store_artifacts: diff --git a/scripts/bench/bench-package.ts b/scripts/bench/bench-package.ts index d87e0da598c8..64c511265717 100644 --- a/scripts/bench/bench-package.ts +++ b/scripts/bench/bench-package.ts @@ -25,13 +25,13 @@ const bigQueryBenchTable = new BigQuery({ type PackageName = keyof typeof versions; type Result = { package: PackageName; - dependencies: number; + dependencyCount: number; selfSize: number; dependencySize: number; }; type HumanReadableResult = { package: PackageName; - dependencies: number; + dependencyCount: number; selfSize: string; dependencySize: string; totalSize: string; @@ -39,6 +39,8 @@ type HumanReadableResult = { type ResultMap = Record; type HumanReadableResultMap = Record; +let registryController: AbortController | undefined; + /** * This function benchmarks the size of Storybook packages and their dependencies. For each package, * the steps are: @@ -87,7 +89,7 @@ export const benchPackage = async (packageName: PackageName) => { // the first line is the temporary benching package itself, don't count it // the second line is the package we're benching, don't count it - const amountOfDependencies = npmLsResult.stdout.trim().split('\n').length - 2; + const dependencyCount = npmLsResult.stdout.trim().split('\n').length - 2; const nodeModulesSize = await getDirSize(join(tmpBenchPackagePath, 'node_modules')); const selfSize = await getDirSize(join(tmpBenchPackagePath, 'node_modules', packageName)); @@ -95,7 +97,7 @@ export const benchPackage = async (packageName: PackageName) => { const result: Result = { package: packageName, - dependencies: amountOfDependencies, + dependencyCount, selfSize, dependencySize, }; @@ -119,7 +121,7 @@ const getDirSize = async (path: string) => { const toHumanReadable = (result: Result): HumanReadableResult => { return { package: result.package, - dependencies: result.dependencies, + dependencyCount: result.dependencyCount, selfSize: formatBytes(result.selfSize), dependencySize: formatBytes(result.dependencySize), totalSize: formatBytes(result.selfSize + result.dependencySize), @@ -168,32 +170,24 @@ const compareResults = async ({ results: ResultMap; baseBranch: string; }) => { - // const [baseResults] = await bigQueryBenchTable.query({ - // query: ` - // WITH latest_packages AS ( - // SELECT branch, package, timestamp, - // ROW_NUMBER() OVER (PARTITION BY branch, package ORDER BY timestamp DESC) as rownumber - // FROM \`storybook-benchmark.benchmark_results.bench2\` - // WHERE branch = @baseBranch - // AND package IN UNNEST(@packages) - // AND timestamp > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 7 DAY) - // ) - // SELECT branch, package, timestamp - // FROM latest_packages - // WHERE rownumber = 1 - // ORDER BY package;`, - // params: { baseBranch, packages: Object.keys(results) }, - // }); - // console.log(baseResults); - - const baseResults = [ - { - package: '@storybook/react', - dependencies: 4, - selfSize: 900_000, - dependencySize: 50_000, - }, - ]; + console.log(`Comparing results with base branch ${baseBranch}...`); + const [baseResults] = await bigQueryBenchTable.query({ + query: ` + WITH + latest_packages AS ( + SELECT *, + ROW_NUMBER() OVER (PARTITION BY package ORDER BY benchmarkedAt DESC) AS row_number + FROM + \`storybook-benchmark.benchmark_results.package_bench\` + WHERE + branch = @baseBranch + AND package IN UNNEST(@packages) + AND benchmarkedAt > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 7 DAY) ) + SELECT * + FROM latest_packages + WHERE row_number = 1;`, + params: { baseBranch, packages: Object.keys(results) }, + }); const comparisonResults = {} as ResultMap; for (const result of Object.values(results)) { @@ -204,19 +198,17 @@ const compareResults = async ({ } comparisonResults[result.package] = { package: result.package, - dependencies: result.dependencies - baseResult.dependencies, + dependencyCount: result.dependencyCount - baseResult.dependencyCount, selfSize: result.selfSize - baseResult.selfSize, dependencySize: result.dependencySize - baseResult.dependencySize, }; } + console.log('LOG: comparisonResults', comparisonResults); return comparisonResults; }; const uploadResultsToBigQuery = async (results: ResultMap) => { - if (!GCP_CREDENTIALS.project_id) { - console.warn('No GCP credentials found, skipping upload to BigQuery'); - return; - } + console.log('Uploading results to BigQuery...'); const commonFields = { branch: process.env.CIRCLE_BRANCH || @@ -229,7 +221,7 @@ const uploadResultsToBigQuery = async (results: ResultMap) => { package: result.package, selfSize: result.selfSize, dependencySize: result.dependencySize, - dependencyCount: result.dependencies, + dependencyCount: result.dependencyCount, })); await bigQueryBenchTable.insert(rows); @@ -237,15 +229,30 @@ const uploadResultsToBigQuery = async (results: ResultMap) => { const run = async () => { program - .option('-b, --baseBranch ', 'The base branch to compare the results with') - .option('-p, --pull-request ', 'The PR number to comment comparions on') + .option( + '-b, --baseBranch ', + 'The base branch to compare the results with. Requires GCP_CREDENTIALS env var' + ) + .option( + '-p, --pull-request ', + 'The PR number to add compare results to. Only used together with --baseBranch' + ) + .option('-u, --upload', 'Upload results to BigQuery. Requires GCP_CREDENTIALS env var') .argument('[packages...]', 'which packages to bench. If omitted, all packages are benched'); program.parse(process.argv); const packages = ( program.args.length > 0 ? program.args : Object.keys(versions) ) as PackageName[]; - const options = program.opts<{ pullRequest?: string; baseBranch?: string }>(); + const options = program.opts<{ pullRequest?: string; baseBranch?: string; upload?: boolean }>(); + + if (options.upload || options.baseBranch) { + if (!GCP_CREDENTIALS.project_id) { + throw new Error( + 'GCP_CREDENTIALS env var is required to upload to BigQuery or compare against a base branch' + ); + } + } packages.forEach((packageName) => { if (!Object.keys(versions).includes(packageName)) { @@ -253,7 +260,6 @@ const run = async () => { } }); - let registryController: AbortController | undefined; if ((await detectFreePort(REGISTRY_PORT)) === REGISTRY_PORT) { console.log('Starting local registry...'); registryController = await runRegistry({ dryRun: false, debug: false }); @@ -285,16 +291,16 @@ const run = async () => { filename: `results.json`, results, }); + if (options.upload) { + await uploadResultsToBigQuery(results); + } if (options.baseBranch) { const comparisonResults = await compareResults({ results, baseBranch: options.baseBranch }); - await Promise.all([ - saveResultsLocally({ - filename: `compare-with-${options.baseBranch}.json`, - results: comparisonResults, - }), - uploadResultsToBigQuery(results), - ]); + await saveResultsLocally({ + filename: `compare-with-${options.baseBranch}.json`, + results: comparisonResults, + }); if (options.pullRequest) { // send to github bot @@ -307,7 +313,15 @@ const run = async () => { if (esMain(import.meta.url)) { run().catch((err) => { + registryController?.abort(); console.error(err); process.exit(1); }); } + +// TODO: +// compile no-link +// upload results as "next" branch +// set up threshold for comparisons +// send to github bot +// make github bot comment PRs From 22164c1ad49780ac73dac62d30da211d27c326dd Mon Sep 17 00:00:00 2001 From: Ryan DeBeasi Date: Fri, 18 Oct 2024 10:25:04 -0400 Subject: [PATCH 016/208] Update docs to cover changes discussed in #22674 --- MIGRATION.md | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index d63cd568816f..1aa43e260542 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -105,17 +105,17 @@ - [Tab addons cannot manually route, Tool addons can filter their visibility via tabId](#tab-addons-cannot-manually-route-tool-addons-can-filter-their-visibility-via-tabid) - [Removed `config` preset](#removed-config-preset-1) - [From version 7.5.0 to 7.6.0](#from-version-750-to-760) - - [CommonJS with Vite is deprecated](#commonjs-with-vite-is-deprecated) - - [Using implicit actions during rendering is deprecated](#using-implicit-actions-during-rendering-is-deprecated) - - [typescript.skipBabel deprecated](#typescriptskipbabel-deprecated) - - [Primary doc block accepts of prop](#primary-doc-block-accepts-of-prop) - - [Addons no longer need a peer dependency on React](#addons-no-longer-need-a-peer-dependency-on-react) + - [CommonJS with Vite is deprecated](#commonjs-with-vite-is-deprecated) + - [Using implicit actions during rendering is deprecated](#using-implicit-actions-during-rendering-is-deprecated) + - [typescript.skipBabel deprecated](#typescriptskipbabel-deprecated) + - [Primary doc block accepts of prop](#primary-doc-block-accepts-of-prop) + - [Addons no longer need a peer dependency on React](#addons-no-longer-need-a-peer-dependency-on-react) - [From version 7.4.0 to 7.5.0](#from-version-740-to-750) - - [`storyStoreV6` and `storiesOf` is deprecated](#storystorev6-and-storiesof-is-deprecated) - - [`storyIndexers` is replaced with `experimental_indexers`](#storyindexers-is-replaced-with-experimental_indexers) + - [`storyStoreV6` and `storiesOf` is deprecated](#storystorev6-and-storiesof-is-deprecated) + - [`storyIndexers` is replaced with `experimental_indexers`](#storyindexers-is-replaced-with-experimental_indexers) - [From version 7.0.0 to 7.2.0](#from-version-700-to-720) - - [Addon API is more type-strict](#addon-api-is-more-type-strict) - - [Addon-controls hideNoControlsWarning parameter is deprecated](#addon-controls-hidenocontrolswarning-parameter-is-deprecated) + - [Addon API is more type-strict](#addon-api-is-more-type-strict) + - [Addon-controls hideNoControlsWarning parameter is deprecated](#addon-controls-hidenocontrolswarning-parameter-is-deprecated) - [From version 6.5.x to 7.0.0](#from-version-65x-to-700) - [7.0 breaking changes](#70-breaking-changes) - [Dropped support for Node 15 and below](#dropped-support-for-node-15-and-below) @@ -141,7 +141,7 @@ - [Deploying build artifacts](#deploying-build-artifacts) - [Dropped support for file URLs](#dropped-support-for-file-urls) - [Serving with nginx](#serving-with-nginx) - - [Ignore story files from node\_modules](#ignore-story-files-from-node_modules) + - [Ignore story files from node_modules](#ignore-story-files-from-node_modules) - [7.0 Core changes](#70-core-changes) - [7.0 feature flags removed](#70-feature-flags-removed) - [Story context is prepared before for supporting fine grained updates](#story-context-is-prepared-before-for-supporting-fine-grained-updates) @@ -155,7 +155,7 @@ - [Addon-interactions: Interactions debugger is now default](#addon-interactions-interactions-debugger-is-now-default) - [7.0 Vite changes](#70-vite-changes) - [Vite builder uses Vite config automatically](#vite-builder-uses-vite-config-automatically) - - [Vite cache moved to node\_modules/.cache/.vite-storybook](#vite-cache-moved-to-node_modulescachevite-storybook) + - [Vite cache moved to node_modules/.cache/.vite-storybook](#vite-cache-moved-to-node_modulescachevite-storybook) - [7.0 Webpack changes](#70-webpack-changes) - [Webpack4 support discontinued](#webpack4-support-discontinued) - [Babel mode v7 exclusively](#babel-mode-v7-exclusively) @@ -167,6 +167,7 @@ - [Angular: Drop support for calling Storybook directly](#angular-drop-support-for-calling-storybook-directly) - [Angular: Application providers and ModuleWithProviders](#angular-application-providers-and-modulewithproviders) - [Angular: Removed legacy renderer](#angular-removed-legacy-renderer) + - [Angular: initializer functions](#angular-initializer-functions) - [Next.js: use the `@storybook/nextjs` framework](#nextjs-use-the-storybooknextjs-framework) - [SvelteKit: needs the `@storybook/sveltekit` framework](#sveltekit-needs-the-storybooksveltekit-framework) - [Vue3: replaced app export with setup](#vue3-replaced-app-export-with-setup) @@ -205,7 +206,7 @@ - [Dropped addon-docs manual babel configuration](#dropped-addon-docs-manual-babel-configuration) - [Dropped addon-docs manual configuration](#dropped-addon-docs-manual-configuration) - [Autoplay in docs](#autoplay-in-docs) - - [Removed STORYBOOK\_REACT\_CLASSES global](#removed-storybook_react_classes-global) + - [Removed STORYBOOK_REACT_CLASSES global](#removed-storybook_react_classes-global) - [7.0 Deprecations and default changes](#70-deprecations-and-default-changes) - [storyStoreV7 enabled by default](#storystorev7-enabled-by-default) - [`Story` type deprecated](#story-type-deprecated) @@ -430,7 +431,7 @@ These APIs allowed addons to render arbitrary content in the Storybook sidebar. > [!NOTE] > You need to set the feature flag `backgroundsStoryGlobals` to `true` in your `.storybook/main.ts` to use the new format and set the value with `globals`. -> +> > See here how to set feature flags: https://storybook.js.org/docs/api/main-config/main-config-features The `addon-backgrounds` addon now uses a new format for configuring its list of selectable backgrounds. @@ -476,7 +477,7 @@ This locks that story to the `twitter` background, it cannot be changed by the a > [!NOTE] > You need to set the feature flag `viewportStoryGlobals` to `true` in your `.storybook/main.ts` to use the new format and set the value with `globals`. -> +> > See here how to set feature flags: https://storybook.js.org/docs/api/main-config/main-config-features The `addon-viewport` addon now uses a new format for configuring its list of selectable viewports. @@ -2579,6 +2580,15 @@ Please visit https://angular.io/guide/standalone-components#configuring-dependen The `parameters.angularLegacyRendering` option is removed. You cannot use the old legacy renderer anymore. +#### Angular: initializer functions + +Initializer functions that use the `APP_INITIALIZER` token only run when Angular components are rendered. To ensure that an initailizer runs whether or not Angular components are rendered, call the function inside `preview.js`: + +```js +myCustomInitializer(); +export default preview; +``` + #### Next.js: use the `@storybook/nextjs` framework In Storybook 7.0 we introduced a convenient package that provides an out of the box experience for Next.js projects: `@storybook/nextjs`. Please see the [following resource](./code/frameworks/nextjs/README.md#getting-started) to get started with it. From c99cb88daee7317fdab35a6acb6149c94bd9147c Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Mon, 21 Oct 2024 09:27:28 +0200 Subject: [PATCH 017/208] upload comparisons to github --- scripts/bench/bench-package.ts | 133 +++++++++++++++++++++++++-------- 1 file changed, 103 insertions(+), 30 deletions(-) diff --git a/scripts/bench/bench-package.ts b/scripts/bench/bench-package.ts index 64c511265717..a79b963f4cad 100644 --- a/scripts/bench/bench-package.ts +++ b/scripts/bench/bench-package.ts @@ -1,6 +1,5 @@ import { BigQuery } from '@google-cloud/bigquery'; -import { request } from '@octokit/request'; -import { program } from 'commander'; +import { InvalidArgumentError, program } from 'commander'; import detectFreePort from 'detect-port'; import { mkdir, readdir, rm, stat, writeFile } from 'fs/promises'; import pLimit from 'p-limit'; @@ -12,6 +11,14 @@ import { runRegistry } from '../tasks/run-registry'; import { maxConcurrentTasks } from '../utils/concurrency'; import { esMain } from '../utils/esmain'; +const Thresholds = { + SELF_SIZE_RATIO: 0.1, + SELF_SIZE_ABSOLUTE: 10_000, + DEPS_SIZE_RATIO: 0.1, + DEPS_SIZE_ABSOLUTE: 10_000, + DEPS_COUNT_ABSOLUTE: 1, +} as const; + const BENCH_PACKAGES_PATH = join(__dirname, '..', '..', 'bench', 'packages'); const REGISTRY_PORT = 6001; const GCP_CREDENTIALS = JSON.parse(process.env.GCP_CREDENTIALS || '{}'); @@ -73,7 +80,7 @@ export const benchPackage = async (packageName: PackageName) => { ) ); - await x( + const npmInstallResult = await x( 'npm', `install --save-exact --registry http://localhost:6001 --omit peer ${packageName}@${versions[packageName]}`.split( ' ' @@ -83,13 +90,9 @@ export const benchPackage = async (packageName: PackageName) => { } ); - const npmLsResult = await x('npm', `ls --all --parseable`.split(' '), { - nodeOptions: { cwd: tmpBenchPackagePath }, - }); - - // the first line is the temporary benching package itself, don't count it - // the second line is the package we're benching, don't count it - const dependencyCount = npmLsResult.stdout.trim().split('\n').length - 2; + // -1 of reported packages added because we shouldn't count the actual package as a dependency + const dependencyCount = + Number.parseInt(npmInstallResult.stdout.match(/added (\d+) packages?/)?.[1] ?? '') - 1; const nodeModulesSize = await getDirSize(join(tmpBenchPackagePath, 'node_modules')); const selfSize = await getDirSize(join(tmpBenchPackagePath, 'node_modules', packageName)); @@ -101,7 +104,7 @@ export const benchPackage = async (packageName: PackageName) => { selfSize, dependencySize, }; - console.log(toHumanReadable(result)); + console.log(`Done benching ${packageName}`); return result; }; @@ -146,11 +149,11 @@ const formatBytes = (bytes: number) => { return bytes < 0 ? `-${formattedSize}` : formattedSize; }; -const saveResultsLocally = async ({ +const saveLocally = async ({ results, filename, }: { - results: ResultMap; + results: Partial; filename: string; }) => { const resultPath = join(BENCH_PACKAGES_PATH, filename); @@ -203,11 +206,38 @@ const compareResults = async ({ dependencySize: result.dependencySize - baseResult.dependencySize, }; } - console.log('LOG: comparisonResults', comparisonResults); + console.log('Done comparing results'); return comparisonResults; }; -const uploadResultsToBigQuery = async (results: ResultMap) => { +const filterResultsByThresholds = ({ + currentResults, + comparisonResults, +}: { + currentResults: ResultMap; + comparisonResults: ResultMap; +}) => { + const filteredResults: Partial = {}; + for (const comparisonResult of Object.values(comparisonResults)) { + const currentResult = currentResults[comparisonResult.package]; + + const exceedsThresholds = + Math.abs(comparisonResult.selfSize) > Thresholds.SELF_SIZE_ABSOLUTE || + Math.abs(comparisonResult.selfSize) / currentResult.selfSize > Thresholds.SELF_SIZE_RATIO || + Math.abs(comparisonResult.dependencySize) > Thresholds.DEPS_SIZE_ABSOLUTE || + Math.abs(comparisonResult.dependencySize) / currentResult.dependencySize > + Thresholds.DEPS_SIZE_RATIO || + Math.abs(comparisonResult.dependencyCount) > Thresholds.DEPS_COUNT_ABSOLUTE; + + if (exceedsThresholds) { + filteredResults[comparisonResult.package] = comparisonResult; + } + } + console.log(`${Object.keys(filteredResults).length} packages exceeded the thresholds`); + return filteredResults; +}; + +const uploadToBigQuery = async (results: ResultMap) => { console.log('Uploading results to BigQuery...'); const commonFields = { branch: @@ -227,6 +257,30 @@ const uploadResultsToBigQuery = async (results: ResultMap) => { await bigQueryBenchTable.insert(rows); }; +const uploadToGithub = async ({ + results, + pullRequest, +}: { + results: Partial; + pullRequest: number; +}) => { + if (Object.keys(results).length === 0) { + console.log('No results to upload to GitHub, skipping.'); + return; + } + + console.log('Uploading results to GitHub...'); + await fetch('https://storybook-benchmark-bot.vercel.app/package-bench', { + method: 'POST', + body: JSON.stringify({ + owner: 'storybookjs', + repo: 'storybook', + issueNumber: pullRequest, + results, + }), + }); +}; + const run = async () => { program .option( @@ -235,16 +289,35 @@ const run = async () => { ) .option( '-p, --pull-request ', - 'The PR number to add compare results to. Only used together with --baseBranch' + 'The PR number to add compare results to. Only used together with --baseBranch', + function parseInt(value) { + const parsedValue = Number.parseInt(value); + if (Number.isNaN(parsedValue)) { + throw new InvalidArgumentError('Must be a number'); + } + return parsedValue; + } ) .option('-u, --upload', 'Upload results to BigQuery. Requires GCP_CREDENTIALS env var') - .argument('[packages...]', 'which packages to bench. If omitted, all packages are benched'); + .argument( + '[packages...]', + 'which packages to bench. If omitted, all packages are benched', + function parsePackages(value) { + const parsedValue = value.split(' '); + parsedValue.forEach((packageName) => { + if (!Object.keys(versions).includes(packageName)) { + throw new InvalidArgumentError(`Package '${packageName}' not found in the monorepo`); + } + }); + return parsedValue; + } + ); program.parse(process.argv); const packages = ( program.args.length > 0 ? program.args : Object.keys(versions) ) as PackageName[]; - const options = program.opts<{ pullRequest?: string; baseBranch?: string; upload?: boolean }>(); + const options = program.opts<{ pullRequest?: number; baseBranch?: string; upload?: boolean }>(); if (options.upload || options.baseBranch) { if (!GCP_CREDENTIALS.project_id) { @@ -287,23 +360,30 @@ const run = async () => { acc[result.package] = result; return acc; }, {} as ResultMap); - await saveResultsLocally({ + await saveLocally({ filename: `results.json`, results, }); if (options.upload) { - await uploadResultsToBigQuery(results); + await uploadToBigQuery(results); } if (options.baseBranch) { const comparisonResults = await compareResults({ results, baseBranch: options.baseBranch }); - await saveResultsLocally({ + await saveLocally({ filename: `compare-with-${options.baseBranch}.json`, results: comparisonResults, }); - + const resultsAboveThreshold = filterResultsByThresholds({ + currentResults: results, + comparisonResults, + }); + await saveLocally({ + filename: `comparisons-above-threshold-with-${options.baseBranch}.json`, + results: resultsAboveThreshold, + }); if (options.pullRequest) { - // send to github bot + await uploadToGithub({ results: resultsAboveThreshold, pullRequest: options.pullRequest }); } } @@ -318,10 +398,3 @@ if (esMain(import.meta.url)) { process.exit(1); }); } - -// TODO: -// compile no-link -// upload results as "next" branch -// set up threshold for comparisons -// send to github bot -// make github bot comment PRs From 00be90c8aa344a1659ba487eaece47b2dbc9f07a Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Mon, 21 Oct 2024 10:06:42 +0200 Subject: [PATCH 018/208] better logging --- scripts/bench/bench-package.ts | 80 +++++++++++++++++++++++----------- 1 file changed, 54 insertions(+), 26 deletions(-) diff --git a/scripts/bench/bench-package.ts b/scripts/bench/bench-package.ts index a79b963f4cad..94e81d987428 100644 --- a/scripts/bench/bench-package.ts +++ b/scripts/bench/bench-package.ts @@ -4,6 +4,7 @@ import detectFreePort from 'detect-port'; import { mkdir, readdir, rm, stat, writeFile } from 'fs/promises'; import pLimit from 'p-limit'; import { join } from 'path'; +import picocolors from 'picocolors'; import { x } from 'tinyexec'; import versions from '../../code/core/src/common/versions'; @@ -38,7 +39,7 @@ type Result = { }; type HumanReadableResult = { package: PackageName; - dependencyCount: number; + dependencyCount: string; selfSize: string; dependencySize: string; totalSize: string; @@ -60,7 +61,7 @@ let registryController: AbortController | undefined; * 4. Print and return the results */ export const benchPackage = async (packageName: PackageName) => { - console.log(`Benching ${packageName}...`); + console.log(`Benching ${picocolors.blue(packageName)}...`); const tmpBenchPackagePath = join(BENCH_PACKAGES_PATH, packageName.replace('@storybook', '')); await rm(tmpBenchPackagePath, { recursive: true }).catch(() => {}); @@ -104,7 +105,7 @@ export const benchPackage = async (packageName: PackageName) => { selfSize, dependencySize, }; - console.log(`Done benching ${packageName}`); + console.log(`Done benching ${picocolors.blue(packageName)}`); return result; }; @@ -121,17 +122,17 @@ const getDirSize = async (path: string) => { return stats.reduce((acc, { size }) => acc + size, 0); }; -const toHumanReadable = (result: Result): HumanReadableResult => { +const toHumanReadable = (result: Result, diff = false): HumanReadableResult => { return { package: result.package, - dependencyCount: result.dependencyCount, - selfSize: formatBytes(result.selfSize), - dependencySize: formatBytes(result.dependencySize), - totalSize: formatBytes(result.selfSize + result.dependencySize), + dependencyCount: `${diff && result.dependencyCount > 0 ? '+' : ''}${result.dependencyCount}`, + selfSize: formatBytes(result.selfSize, diff), + dependencySize: formatBytes(result.dependencySize, diff), + totalSize: formatBytes(result.selfSize + result.dependencySize, diff), }; }; -const formatBytes = (bytes: number) => { +const formatBytes = (bytes: number, diff = false) => { const units = ['B', 'KB', 'MB', 'GB', 'TB']; let size = Math.abs(bytes); let unitIndex = 0; @@ -146,21 +147,29 @@ const formatBytes = (bytes: number) => { const decimals = unitIndex < 2 ? 0 : 2; const formattedSize = `${size.toFixed(decimals)} ${units[unitIndex]}`; - return bytes < 0 ? `-${formattedSize}` : formattedSize; + if (bytes < 0) { + return `-${formattedSize}`; + } + if (diff) { + return `+${formattedSize}`; + } + return formattedSize; }; const saveLocally = async ({ results, filename, + diff = false, }: { results: Partial; filename: string; + diff?: boolean; }) => { const resultPath = join(BENCH_PACKAGES_PATH, filename); - console.log(`Saving results to ${resultPath}...`); + console.log(`Saving results to ${picocolors.magenta(resultPath)}...`); const humanReadableResults = Object.entries(results).reduce((acc, [packageName, result]) => { - acc[packageName as PackageName] = toHumanReadable(result); + acc[packageName as PackageName] = toHumanReadable(result, diff); return acc; }, {} as HumanReadableResultMap); await writeFile(resultPath, JSON.stringify(humanReadableResults, null, 2)); @@ -173,7 +182,7 @@ const compareResults = async ({ results: ResultMap; baseBranch: string; }) => { - console.log(`Comparing results with base branch ${baseBranch}...`); + console.log(`Comparing results with base branch ${picocolors.magenta(baseBranch)}...`); const [baseResults] = await bigQueryBenchTable.query({ query: ` WITH @@ -194,11 +203,23 @@ const compareResults = async ({ const comparisonResults = {} as ResultMap; for (const result of Object.values(results)) { - const baseResult = baseResults.find((row) => row.package === result.package); + let baseResult = baseResults.find((row) => row.package === result.package); if (!baseResult) { - console.warn(`No base result found for ${result.package}, skipping comparison.`); + console.warn( + `No base result found for ${picocolors.blue(result.package)}, skipping comparison.` + ); continue; } + if (Math.random() > 0.5) { + console.log('LOG: faking base results', baseResult.package); + baseResult = { + package: baseResult.package, + dependencyCount: Math.floor(baseResult.dependencyCount * Math.random()), + selfSize: Math.floor(baseResult.selfSize * Math.random()), + dependencySize: Math.floor(baseResult.dependencySize * Math.random()), + }; + } + comparisonResults[result.package] = { package: result.package, dependencyCount: result.dependencyCount - baseResult.dependencyCount, @@ -206,7 +227,7 @@ const compareResults = async ({ dependencySize: result.dependencySize - baseResult.dependencySize, }; } - console.log('Done comparing results'); + console.log(picocolors.green('Done comparing results')); return comparisonResults; }; @@ -233,7 +254,11 @@ const filterResultsByThresholds = ({ filteredResults[comparisonResult.package] = comparisonResult; } } - console.log(`${Object.keys(filteredResults).length} packages exceeded the thresholds`); + + const amountAboveThreshold = Object.keys(filteredResults).length; + const color = amountAboveThreshold === 0 ? picocolors.green : picocolors.red; + console.log(color(`${amountAboveThreshold} packages exceeded the thresholds`)); + return filteredResults; }; @@ -270,7 +295,7 @@ const uploadToGithub = async ({ } console.log('Uploading results to GitHub...'); - await fetch('https://storybook-benchmark-bot.vercel.app/package-bench', { + const response = await fetch('https://storybook-benchmark-bot.vercel.app/package-bench', { method: 'POST', body: JSON.stringify({ owner: 'storybookjs', @@ -279,6 +304,13 @@ const uploadToGithub = async ({ results, }), }); + if (response.status < 200 || response.status >= 400) { + const body = await response.text(); + throw new Error(`Failed to upload results to GitHub. + STATUS: ${response.status} - ${response.statusText} + BODY: + ${body}`); + } }; const run = async () => { @@ -327,12 +359,6 @@ const run = async () => { } } - packages.forEach((packageName) => { - if (!Object.keys(versions).includes(packageName)) { - throw new Error(`Package '${packageName}' not found`); - } - }); - if ((await detectFreePort(REGISTRY_PORT)) === REGISTRY_PORT) { console.log('Starting local registry...'); registryController = await runRegistry({ dryRun: false, debug: false }); @@ -350,7 +376,7 @@ const run = async () => { return; } console.log( - `Currently benching ${limit.activeCount} packages, ${limit.pendingCount} pending, ${doneCount} done...` + `Benching status: ${picocolors.red(limit.pendingCount)} pending, ${picocolors.yellow(limit.activeCount)} running, ${picocolors.green(doneCount)} done...` ); }, 2_000); const resultsArray = await Promise.all( @@ -373,6 +399,7 @@ const run = async () => { await saveLocally({ filename: `compare-with-${options.baseBranch}.json`, results: comparisonResults, + diff: true, }); const resultsAboveThreshold = filterResultsByThresholds({ currentResults: results, @@ -381,13 +408,14 @@ const run = async () => { await saveLocally({ filename: `comparisons-above-threshold-with-${options.baseBranch}.json`, results: resultsAboveThreshold, + diff: true, }); if (options.pullRequest) { await uploadToGithub({ results: resultsAboveThreshold, pullRequest: options.pullRequest }); } } - console.log('Done benching all packages'); + console.log(picocolors.green('Done benching all packages')); registryController?.abort(); }; From 8b3d1cf486a77d0a54461f7c9e4665ac6be1331c Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Tue, 22 Oct 2024 10:27:34 +0200 Subject: [PATCH 019/208] refactor comparison result structure --- scripts/bench/bench-package.ts | 263 ++++++++++++++++++++++----------- 1 file changed, 174 insertions(+), 89 deletions(-) diff --git a/scripts/bench/bench-package.ts b/scripts/bench/bench-package.ts index 94e81d987428..2f7d026a0b6c 100644 --- a/scripts/bench/bench-package.ts +++ b/scripts/bench/bench-package.ts @@ -37,15 +37,26 @@ type Result = { selfSize: number; dependencySize: number; }; -type HumanReadableResult = { +type ComparisonResult = { package: PackageName; - dependencyCount: string; - selfSize: string; - dependencySize: string; - totalSize: string; + dependencyCount: { + base: number; + new: number; + diff: number; + }; + selfSize: { + base: number; + new: number; + diff: number; + }; + dependencySize: { + base: number; + new: number; + diff: number; + }; }; type ResultMap = Record; -type HumanReadableResultMap = Record; +type ComparisonResultMap = Record; let registryController: AbortController | undefined; @@ -95,6 +106,19 @@ export const benchPackage = async (packageName: PackageName) => { const dependencyCount = Number.parseInt(npmInstallResult.stdout.match(/added (\d+) packages?/)?.[1] ?? '') - 1; + const getDirSize = async (path: string) => { + const entities = await readdir(path, { + recursive: true, + withFileTypes: true, + }); + const stats = await Promise.all( + entities + .filter((entity) => entity.isFile()) + .map((entity) => stat(join(entity.parentPath, entity.name))) + ); + return stats.reduce((acc, { size }) => acc + size, 0); + }; + const nodeModulesSize = await getDirSize(join(tmpBenchPackagePath, 'node_modules')); const selfSize = await getDirSize(join(tmpBenchPackagePath, 'node_modules', packageName)); const dependencySize = nodeModulesSize - selfSize; @@ -109,69 +133,86 @@ export const benchPackage = async (packageName: PackageName) => { return result; }; -const getDirSize = async (path: string) => { - const entities = await readdir(path, { - recursive: true, - withFileTypes: true, - }); - const stats = await Promise.all( - entities - .filter((entity) => entity.isFile()) - .map((entity) => stat(join(entity.parentPath, entity.name))) - ); - return stats.reduce((acc, { size }) => acc + size, 0); -}; - -const toHumanReadable = (result: Result, diff = false): HumanReadableResult => { - return { - package: result.package, - dependencyCount: `${diff && result.dependencyCount > 0 ? '+' : ''}${result.dependencyCount}`, - selfSize: formatBytes(result.selfSize, diff), - dependencySize: formatBytes(result.dependencySize, diff), - totalSize: formatBytes(result.selfSize + result.dependencySize, diff), - }; -}; +const toHumanReadable = (result: Partial | Partial) => { + const formatBytes = (bytes: number, diff = false) => { + const units = ['B', 'KB', 'MB', 'GB', 'TB']; + let size = Math.abs(bytes); + let unitIndex = 0; -const formatBytes = (bytes: number, diff = false) => { - const units = ['B', 'KB', 'MB', 'GB', 'TB']; - let size = Math.abs(bytes); - let unitIndex = 0; + while (size >= 1000 && unitIndex < units.length - 1) { + size /= 1000; + unitIndex++; + } - while (size >= 1000 && unitIndex < units.length - 1) { - size /= 1000; - unitIndex++; - } + // B, KB = 0 decimal places + // MB, GB, TB = 2 decimal places + const decimals = unitIndex < 2 ? 0 : 2; + const formattedSize = `${size.toFixed(decimals)} ${units[unitIndex]}`; - // B, KB = 0 decimal places - // MB, GB, TB = 2 decimal places - const decimals = unitIndex < 2 ? 0 : 2; - const formattedSize = `${size.toFixed(decimals)} ${units[unitIndex]}`; + if (bytes < 0) { + return `-${formattedSize}`; + } + if (diff && bytes > 0) { + return `+${formattedSize}`; + } + return formattedSize; + }; - if (bytes < 0) { - return `-${formattedSize}`; - } - if (diff) { - return `+${formattedSize}`; + if (typeof result.dependencyCount === 'number') { + const { dependencyCount, selfSize, dependencySize } = result as Result; + return { + package: result.package, + dependencyCount: dependencyCount.toString(), + selfSize: formatBytes(selfSize), + dependencySize: formatBytes(dependencySize), + totalSize: formatBytes(selfSize + dependencySize), + }; } - return formattedSize; + const { dependencyCount, selfSize, dependencySize } = result as ComparisonResult; + + return { + package: result.package, + dependencyCount: { + base: dependencyCount.base.toString(), + current: dependencyCount.new.toString(), + diff: `${dependencyCount.diff > 0 ? '+' : dependencyCount.diff < 0 ? '-' : ''}${dependencyCount.diff}`, + }, + selfSize: { + base: formatBytes(selfSize.base), + current: formatBytes(selfSize.new), + diff: formatBytes(selfSize.diff, true), + }, + dependencySize: { + base: formatBytes(dependencySize.base), + current: formatBytes(dependencySize.new), + diff: formatBytes(dependencySize.diff, true), + }, + totalSize: { + base: formatBytes(selfSize.base + dependencySize.base), + current: formatBytes(selfSize.new + dependencySize.new), + diff: formatBytes(selfSize.diff + dependencySize.diff, true), + }, + }; }; const saveLocally = async ({ results, filename, - diff = false, }: { - results: Partial; + results: Partial; filename: string; diff?: boolean; }) => { const resultPath = join(BENCH_PACKAGES_PATH, filename); console.log(`Saving results to ${picocolors.magenta(resultPath)}...`); - const humanReadableResults = Object.entries(results).reduce((acc, [packageName, result]) => { - acc[packageName as PackageName] = toHumanReadable(result, diff); - return acc; - }, {} as HumanReadableResultMap); + const humanReadableResults = Object.entries(results).reduce( + (acc, [packageName, result]) => { + acc[packageName as PackageName] = toHumanReadable(result); + return acc; + }, + {} as Record> + ); await writeFile(resultPath, JSON.stringify(humanReadableResults, null, 2)); }; @@ -201,7 +242,7 @@ const compareResults = async ({ params: { baseBranch, packages: Object.keys(results) }, }); - const comparisonResults = {} as ResultMap; + const comparisonResults = {} as ComparisonResultMap; for (const result of Object.values(results)) { let baseResult = baseResults.find((row) => row.package === result.package); if (!baseResult) { @@ -222,33 +263,38 @@ const compareResults = async ({ comparisonResults[result.package] = { package: result.package, - dependencyCount: result.dependencyCount - baseResult.dependencyCount, - selfSize: result.selfSize - baseResult.selfSize, - dependencySize: result.dependencySize - baseResult.dependencySize, + dependencyCount: { + base: baseResult.dependencyCount, + new: result.dependencyCount, + diff: result.dependencyCount - baseResult.dependencyCount, + }, + selfSize: { + base: baseResult.selfSize, + new: result.selfSize, + diff: result.selfSize - baseResult.selfSize, + }, + dependencySize: { + base: baseResult.dependencySize, + new: result.dependencySize, + diff: result.dependencySize - baseResult.dependencySize, + }, }; } console.log(picocolors.green('Done comparing results')); return comparisonResults; }; -const filterResultsByThresholds = ({ - currentResults, - comparisonResults, -}: { - currentResults: ResultMap; - comparisonResults: ResultMap; -}) => { - const filteredResults: Partial = {}; +const filterResultsByThresholds = (comparisonResults: ComparisonResultMap) => { + const filteredResults: Partial = {}; for (const comparisonResult of Object.values(comparisonResults)) { - const currentResult = currentResults[comparisonResult.package]; - const exceedsThresholds = - Math.abs(comparisonResult.selfSize) > Thresholds.SELF_SIZE_ABSOLUTE || - Math.abs(comparisonResult.selfSize) / currentResult.selfSize > Thresholds.SELF_SIZE_RATIO || - Math.abs(comparisonResult.dependencySize) > Thresholds.DEPS_SIZE_ABSOLUTE || - Math.abs(comparisonResult.dependencySize) / currentResult.dependencySize > + Math.abs(comparisonResult.selfSize.diff) > Thresholds.SELF_SIZE_ABSOLUTE || + Math.abs(comparisonResult.selfSize.diff) / comparisonResult.selfSize.new > + Thresholds.SELF_SIZE_RATIO || + Math.abs(comparisonResult.dependencySize.diff) > Thresholds.DEPS_SIZE_ABSOLUTE || + Math.abs(comparisonResult.dependencySize.diff) / comparisonResult.dependencySize.new > Thresholds.DEPS_SIZE_RATIO || - Math.abs(comparisonResult.dependencyCount) > Thresholds.DEPS_COUNT_ABSOLUTE; + Math.abs(comparisonResult.dependencyCount.diff) > Thresholds.DEPS_COUNT_ABSOLUTE; if (exceedsThresholds) { filteredResults[comparisonResult.package] = comparisonResult; @@ -262,17 +308,22 @@ const filterResultsByThresholds = ({ return filteredResults; }; -const uploadToBigQuery = async (results: ResultMap) => { +const uploadToBigQuery = async ({ + results, + branch, + commit, + benchmarkedAt, +}: { + results: ResultMap; + branch: string; + commit: string; + benchmarkedAt: Date; +}) => { console.log('Uploading results to BigQuery...'); - const commonFields = { - branch: - process.env.CIRCLE_BRANCH || - (await x('git', 'rev-parse --abbrev-ref HEAD'.split(' '))).stdout.trim(), - commit: process.env.CIRCLE_SHA1 || (await x('git', 'rev-parse HEAD'.split(' '))).stdout.trim(), - benchmarkedAt: new Date(), - }; const rows = Object.values(results).map((result) => ({ - ...commonFields, + branch, + commit, + benchmarkedAt, package: result.package, selfSize: result.selfSize, dependencySize: result.dependencySize, @@ -284,24 +335,46 @@ const uploadToBigQuery = async (results: ResultMap) => { const uploadToGithub = async ({ results, + headBranch, + baseBranch, + commit, + benchmarkedAt, pullRequest, }: { - results: Partial; + results: Partial; + headBranch: string; + baseBranch: string; + commit: string; + benchmarkedAt: Date; pullRequest: number; }) => { if (Object.keys(results).length === 0) { + // TODO: no, we need to update the table when results are good again console.log('No results to upload to GitHub, skipping.'); return; } + const humanReadableResults = Object.values(results).reduce( + (acc, result) => { + acc[result.package] = toHumanReadable(result); + return acc; + }, + {} as Record> + ); + console.log('Uploading results to GitHub...'); - const response = await fetch('https://storybook-benchmark-bot.vercel.app/package-bench', { + const response = await fetch('http://localhost:3000/package-bench', { + // const response = await fetch('https://storybook-benchmark-bot.vercel.app/package-bench', { method: 'POST', body: JSON.stringify({ owner: 'storybookjs', repo: 'storybook', issueNumber: pullRequest, - results, + headBranch, + baseBranch, + commit, + benchmarkedAt: benchmarkedAt.toISOString(), + results: humanReadableResults, }), }); if (response.status < 200 || response.status >= 400) { @@ -390,28 +463,40 @@ const run = async () => { filename: `results.json`, results, }); + + const headBranch = + process.env.CIRCLE_BRANCH || + (await x('git', 'rev-parse --abbrev-ref HEAD'.split(' '))).stdout.trim(); + const commit = + process.env.CIRCLE_SHA1 || (await x('git', 'rev-parse HEAD'.split(' '))).stdout.trim(); + const benchmarkedAt = new Date(); + if (options.upload) { - await uploadToBigQuery(results); + await uploadToBigQuery({ results, branch: headBranch, commit, benchmarkedAt }); } if (options.baseBranch) { const comparisonResults = await compareResults({ results, baseBranch: options.baseBranch }); + const resultsAboveThreshold = filterResultsByThresholds(comparisonResults); await saveLocally({ filename: `compare-with-${options.baseBranch}.json`, results: comparisonResults, diff: true, }); - const resultsAboveThreshold = filterResultsByThresholds({ - currentResults: results, - comparisonResults, - }); await saveLocally({ filename: `comparisons-above-threshold-with-${options.baseBranch}.json`, results: resultsAboveThreshold, diff: true, }); if (options.pullRequest) { - await uploadToGithub({ results: resultsAboveThreshold, pullRequest: options.pullRequest }); + await uploadToGithub({ + results: resultsAboveThreshold, + pullRequest: options.pullRequest, + baseBranch: options.baseBranch, + headBranch, + commit, + benchmarkedAt, + }); } } From 6af79dda7de9427220c992de38f72a0643d0492e Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Tue, 22 Oct 2024 12:36:12 +0200 Subject: [PATCH 020/208] fix results sent to github --- scripts/bench/bench-package.ts | 40 ++++++++++++++-------------------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/scripts/bench/bench-package.ts b/scripts/bench/bench-package.ts index 2f7d026a0b6c..054033ee13f2 100644 --- a/scripts/bench/bench-package.ts +++ b/scripts/bench/bench-package.ts @@ -174,22 +174,22 @@ const toHumanReadable = (result: Partial | Partial) => package: result.package, dependencyCount: { base: dependencyCount.base.toString(), - current: dependencyCount.new.toString(), + new: dependencyCount.new.toString(), diff: `${dependencyCount.diff > 0 ? '+' : dependencyCount.diff < 0 ? '-' : ''}${dependencyCount.diff}`, }, selfSize: { base: formatBytes(selfSize.base), - current: formatBytes(selfSize.new), + new: formatBytes(selfSize.new), diff: formatBytes(selfSize.diff, true), }, dependencySize: { base: formatBytes(dependencySize.base), - current: formatBytes(dependencySize.new), + new: formatBytes(dependencySize.new), diff: formatBytes(dependencySize.diff, true), }, totalSize: { base: formatBytes(selfSize.base + dependencySize.base), - current: formatBytes(selfSize.new + dependencySize.new), + new: formatBytes(selfSize.new + dependencySize.new), diff: formatBytes(selfSize.diff + dependencySize.diff, true), }, }; @@ -244,22 +244,20 @@ const compareResults = async ({ const comparisonResults = {} as ComparisonResultMap; for (const result of Object.values(results)) { - let baseResult = baseResults.find((row) => row.package === result.package); + const baseResult = baseResults.find((row) => row.package === result.package); if (!baseResult) { console.warn( `No base result found for ${picocolors.blue(result.package)}, skipping comparison.` ); continue; } - if (Math.random() > 0.5) { - console.log('LOG: faking base results', baseResult.package); - baseResult = { - package: baseResult.package, - dependencyCount: Math.floor(baseResult.dependencyCount * Math.random()), - selfSize: Math.floor(baseResult.selfSize * Math.random()), - dependencySize: Math.floor(baseResult.dependencySize * Math.random()), - }; - } + // console.log('LOG: faking base results', baseResult.package); + // baseResult = { + // package: baseResult.package, + // dependencyCount: Math.floor(baseResult.dependencyCount * Math.random()), + // selfSize: Math.floor(baseResult.selfSize * Math.random()), + // dependencySize: Math.floor(baseResult.dependencySize * Math.random()), + // }; comparisonResults[result.package] = { package: result.package, @@ -289,10 +287,10 @@ const filterResultsByThresholds = (comparisonResults: ComparisonResultMap) => { for (const comparisonResult of Object.values(comparisonResults)) { const exceedsThresholds = Math.abs(comparisonResult.selfSize.diff) > Thresholds.SELF_SIZE_ABSOLUTE || - Math.abs(comparisonResult.selfSize.diff) / comparisonResult.selfSize.new > + Math.abs(comparisonResult.selfSize.diff) / comparisonResult.selfSize.base > Thresholds.SELF_SIZE_RATIO || Math.abs(comparisonResult.dependencySize.diff) > Thresholds.DEPS_SIZE_ABSOLUTE || - Math.abs(comparisonResult.dependencySize.diff) / comparisonResult.dependencySize.new > + Math.abs(comparisonResult.dependencySize.diff) / comparisonResult.dependencySize.base > Thresholds.DEPS_SIZE_RATIO || Math.abs(comparisonResult.dependencyCount.diff) > Thresholds.DEPS_COUNT_ABSOLUTE; @@ -348,12 +346,6 @@ const uploadToGithub = async ({ benchmarkedAt: Date; pullRequest: number; }) => { - if (Object.keys(results).length === 0) { - // TODO: no, we need to update the table when results are good again - console.log('No results to upload to GitHub, skipping.'); - return; - } - const humanReadableResults = Object.values(results).reduce( (acc, result) => { acc[result.package] = toHumanReadable(result); @@ -363,8 +355,8 @@ const uploadToGithub = async ({ ); console.log('Uploading results to GitHub...'); - const response = await fetch('http://localhost:3000/package-bench', { - // const response = await fetch('https://storybook-benchmark-bot.vercel.app/package-bench', { + // const response = await fetch('http://localhost:3000/package-bench', { + const response = await fetch('https://storybook-benchmark-bot.vercel.app/package-bench', { method: 'POST', body: JSON.stringify({ owner: 'storybookjs', From 4d6f029c37fa258bb0d1f5985bfc5b42b5ab747a Mon Sep 17 00:00:00 2001 From: jsingh0026 Date: Sat, 26 Oct 2024 03:00:16 +0530 Subject: [PATCH 021/208] fix: add external Storybook entries to index --- .../mobile/navigation/MobileNavigation.tsx | 36 ++++++++++++++----- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/code/core/src/manager/components/mobile/navigation/MobileNavigation.tsx b/code/core/src/manager/components/mobile/navigation/MobileNavigation.tsx index fdc7de9651db..ddef2c0f2de4 100644 --- a/code/core/src/manager/components/mobile/navigation/MobileNavigation.tsx +++ b/code/core/src/manager/components/mobile/navigation/MobileNavigation.tsx @@ -4,6 +4,7 @@ import React from 'react'; import { IconButton } from '@storybook/core/components'; import { styled } from '@storybook/core/theming'; import { BottomBarToggleIcon, MenuIcon } from '@storybook/icons'; +import type { API_IndexHash, API_Refs } from '@storybook/types'; import { useStorybookApi, useStorybookState } from '@storybook/core/manager-api'; @@ -17,27 +18,46 @@ interface MobileNavigationProps { showPanel: boolean; } +// Function to combine all indexes +function combineIndexes(rootIndex: API_IndexHash | undefined, refs: API_Refs) { + // Create a copy of the root index to avoid mutation + const combinedIndex = { ...(rootIndex || {}) }; // Use an empty object as fallback + + // Traverse refs and merge each nested index with the root index + Object.values(refs).forEach((ref) => { + if (ref.index) { + Object.assign(combinedIndex, ref.index); + } + }); + + return combinedIndex; +} + /** * Walks the tree from the current story to combine story+component+folder names into a single * string */ const useFullStoryName = () => { - const { index } = useStorybookState(); + const { index, refs } = useStorybookState(); const api = useStorybookApi(); const currentStory = api.getCurrentStoryData(); if (!currentStory) { return ''; } - + const combinedIndex = combineIndexes(index, refs || {}); let fullStoryName = currentStory.renderLabel?.(currentStory, api) || currentStory.name; - // @ts-expect-error (non strict) - let node = index[currentStory.id]; - // @ts-expect-error (non strict) - while ('parent' in node && node.parent && index[node.parent] && fullStoryName.length < 24) { - // @ts-expect-error (non strict) - node = index[node.parent]; + let node = combinedIndex[currentStory.id]; + + while ( + node && + 'parent' in node && + node.parent && + combinedIndex[node.parent] && + fullStoryName.length < 24 + ) { + node = combinedIndex[node.parent]; const parentName = node.renderLabel?.(node, api) || node.name; fullStoryName = `${parentName}/${fullStoryName}`; } From 4871e902fa22cd388df7ee48f142c12e03342263 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Tue, 29 Oct 2024 12:38:34 +0100 Subject: [PATCH 022/208] use --json for npm install --- scripts/bench/bench-package.ts | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/scripts/bench/bench-package.ts b/scripts/bench/bench-package.ts index 054033ee13f2..ccb841179954 100644 --- a/scripts/bench/bench-package.ts +++ b/scripts/bench/bench-package.ts @@ -84,6 +84,9 @@ export const benchPackage = async (packageName: PackageName) => { { name: `${packageName}-bench`, version: '1.0.0', + dependencies: { + [packageName]: versions[packageName], + }, // Overrides ensures that Storybook packages outside the monorepo are using the versions we have in the monorepo overrides: versions, }, @@ -94,17 +97,15 @@ export const benchPackage = async (packageName: PackageName) => { const npmInstallResult = await x( 'npm', - `install --save-exact --registry http://localhost:6001 --omit peer ${packageName}@${versions[packageName]}`.split( - ' ' - ), + `install --registry http://localhost:6001 --omit peer --json`.split(' '), { nodeOptions: { cwd: tmpBenchPackagePath }, } ); + const { added } = JSON.parse(npmInstallResult.stdout) as { added: number }; // -1 of reported packages added because we shouldn't count the actual package as a dependency - const dependencyCount = - Number.parseInt(npmInstallResult.stdout.match(/added (\d+) packages?/)?.[1] ?? '') - 1; + const dependencyCount = added - 1; const getDirSize = async (path: string) => { const entities = await readdir(path, { @@ -249,6 +250,7 @@ const compareResults = async ({ console.warn( `No base result found for ${picocolors.blue(result.package)}, skipping comparison.` ); + // TODO: keep this in the report, comparing it to 0 continue; } // console.log('LOG: faking base results', baseResult.package); @@ -346,6 +348,7 @@ const uploadToGithub = async ({ benchmarkedAt: Date; pullRequest: number; }) => { + // TODO: send raw data instead const humanReadableResults = Object.values(results).reduce( (acc, result) => { acc[result.package] = toHumanReadable(result); @@ -411,7 +414,7 @@ const run = async () => { ); program.parse(process.argv); - const packages = ( + const packageNames = ( program.args.length > 0 ? program.args : Object.keys(versions) ) as PackageName[]; const options = program.opts<{ pullRequest?: number; baseBranch?: string; upload?: boolean }>(); @@ -435,8 +438,8 @@ const run = async () => { const limit = pLimit(concurrentLimt); const progressIntervalId = setInterval(() => { - const doneCount = packages.length - limit.activeCount - limit.pendingCount; - if (doneCount === packages.length) { + const doneCount = packageNames.length - limit.activeCount - limit.pendingCount; + if (doneCount === packageNames.length) { clearInterval(progressIntervalId); return; } @@ -445,7 +448,7 @@ const run = async () => { ); }, 2_000); const resultsArray = await Promise.all( - packages.map((packageName) => limit(() => benchPackage(packageName))) + packageNames.map((packageName) => limit(() => benchPackage(packageName))) ); const results = resultsArray.reduce((acc, result) => { acc[result.package] = result; From be33759ec69f9f402d3f00015c056b2f67f44e9c Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Tue, 29 Oct 2024 12:40:16 +0100 Subject: [PATCH 023/208] compare with zero when package not found in base --- scripts/bench/bench-package.ts | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/scripts/bench/bench-package.ts b/scripts/bench/bench-package.ts index ccb841179954..e2c8a555ad6b 100644 --- a/scripts/bench/bench-package.ts +++ b/scripts/bench/bench-package.ts @@ -245,21 +245,18 @@ const compareResults = async ({ const comparisonResults = {} as ComparisonResultMap; for (const result of Object.values(results)) { - const baseResult = baseResults.find((row) => row.package === result.package); + let baseResult = baseResults.find((row) => row.package === result.package); if (!baseResult) { console.warn( - `No base result found for ${picocolors.blue(result.package)}, skipping comparison.` + `No base result found for ${picocolors.blue(result.package)}, comparing with zero values.` ); - // TODO: keep this in the report, comparing it to 0 - continue; + baseResult = { + package: result.package, + dependencyCount: 0, + selfSize: 0, + dependencySize: 0, + }; } - // console.log('LOG: faking base results', baseResult.package); - // baseResult = { - // package: baseResult.package, - // dependencyCount: Math.floor(baseResult.dependencyCount * Math.random()), - // selfSize: Math.floor(baseResult.selfSize * Math.random()), - // dependencySize: Math.floor(baseResult.dependencySize * Math.random()), - // }; comparisonResults[result.package] = { package: result.package, From de8f3d248e6f21ebef00951779defa96b5a75dba Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Tue, 29 Oct 2024 13:41:19 +0100 Subject: [PATCH 024/208] send raw results to github bot --- scripts/bench/bench-package.ts | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/scripts/bench/bench-package.ts b/scripts/bench/bench-package.ts index e2c8a555ad6b..210812b5ba61 100644 --- a/scripts/bench/bench-package.ts +++ b/scripts/bench/bench-package.ts @@ -345,15 +345,6 @@ const uploadToGithub = async ({ benchmarkedAt: Date; pullRequest: number; }) => { - // TODO: send raw data instead - const humanReadableResults = Object.values(results).reduce( - (acc, result) => { - acc[result.package] = toHumanReadable(result); - return acc; - }, - {} as Record> - ); - console.log('Uploading results to GitHub...'); // const response = await fetch('http://localhost:3000/package-bench', { const response = await fetch('https://storybook-benchmark-bot.vercel.app/package-bench', { @@ -366,7 +357,7 @@ const uploadToGithub = async ({ baseBranch, commit, benchmarkedAt: benchmarkedAt.toISOString(), - results: humanReadableResults, + results, }), }); if (response.status < 200 || response.status >= 400) { From 76079d0646bcf839a6b70fd2c862c2180aadf6f9 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Tue, 29 Oct 2024 13:54:51 +0100 Subject: [PATCH 025/208] fix lock-file --- scripts/yarn.lock | 7 ------- 1 file changed, 7 deletions(-) diff --git a/scripts/yarn.lock b/scripts/yarn.lock index 02ec8d3d3410..aadc9d8c181b 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -13452,13 +13452,6 @@ __metadata: languageName: node linkType: hard -"tinyexec@npm:^0.3.0": - version: 0.3.1 - resolution: "tinyexec@npm:0.3.1" - checksum: 10c0/11e7a7c5d8b3bddf8b5cbe82a9290d70a6fad84d528421d5d18297f165723cb53d2e737d8f58dcce5ca56f2e4aa2d060f02510b1f8971784f97eb3e9aec28f09 - languageName: node - linkType: hard - "tinyexec@npm:^0.3.0": version: 0.3.0 resolution: "tinyexec@npm:0.3.0" From 62cc4dd361d27f4bbb2aed60cb689d3eca7bb6aa Mon Sep 17 00:00:00 2001 From: toothlessdev Date: Wed, 30 Oct 2024 02:42:20 +0900 Subject: [PATCH 026/208] Add test code for 'help' common commands --- .../cli-storybook/test/default/cli.test.cjs | 91 ++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/code/lib/cli-storybook/test/default/cli.test.cjs b/code/lib/cli-storybook/test/default/cli.test.cjs index b569fa4dc5c6..2be8af9caca5 100755 --- a/code/lib/cli-storybook/test/default/cli.test.cjs +++ b/code/lib/cli-storybook/test/default/cli.test.cjs @@ -1,4 +1,4 @@ -import { describe, it, expect } from 'vitest'; +import { describe, expect, it } from 'vitest'; const run = require('../helpers.cjs'); @@ -12,3 +12,92 @@ describe('Default behavior', () => { expect(stdout.toString()).toContain('Did you mean upgrade?'); }); }); + +describe('Help command', () => { + it('should prints out "init" command', () => { + const { status, stdout, stderr } = run(['help']); + + expect(status).toBe(0); + expect(stderr.toString()).toBe(''); + expect(stdout.toString()).toContain('init'); + expect(stdout.toString()).toContain('Initialize Storybook into your project.'); + }); + + it('should prints out "add" command', () => { + const { status, stdout, stderr } = run(['help']); + + expect(status).toBe(0); + expect(stderr.toString()).toBe(''); + expect(stdout.toString()).toContain('add'); + expect(stdout.toString()).toContain('Add an addon to your Storybook'); + }); + + it('should prints out "remove" command', () => { + const { status, stdout, stderr } = run(['help']); + + expect(status).toBe(0); + expect(stderr.toString()).toBe(''); + expect(stdout.toString()).toContain('remove'); + expect(stdout.toString()).toContain('Remove an addon from your Storybook'); + }); + + it('should prints out "upgrade" command', () => { + const { status, stdout, stderr } = run(['help']); + + expect(status).toBe(0); + expect(stderr.toString()).toBe(''); + expect(stdout.toString()).toContain('upgrade'); + expect(stdout.toString()).toContain('Upgrade your Storybook packages to'); + }); + + it('should prints out "migrate" command', () => { + const { status, stdout, stderr } = run(['help']); + + expect(status).toBe(0); + expect(stderr.toString()).toBe(''); + expect(stdout.toString()).toContain('migrate'); + expect(stdout.toString()).toContain('Run a Storybook codemod migration on your source files'); + }); + + it('should prints out "sandbox" command', () => { + const { status, stdout, stderr } = run(['help']); + + expect(status).toBe(0); + expect(stderr.toString()).toBe(''); + expect(stdout.toString()).toContain('sandbox'); + expect(stdout.toString()).toContain('Create a sandbox from a set of possible templates'); + }); + + it('should prints out "link" command', () => { + const { status, stdout, stderr } = run(['help']); + + expect(status).toBe(0); + expect(stderr.toString()).toBe(''); + expect(stdout.toString()).toContain('link'); + expect(stdout.toString()).toContain( + 'Pull down a repro from a URL (or a local directory), link it, and run storybook' + ); + }); + + it('sholud prints out "automigrate" command', () => { + const { status, stdout, stderr } = run(['help']); + + expect(status).toBe(0); + expect(stderr.toString()).toBe(''); + expect(stdout.toString()).toContain('automigrate'); + expect(stdout.toString()).toContain( + 'Check storybook for incompatibilities or migrations and apply fixes' + ); + }); + + it('sholud prints out "doctor" command', () => { + const { status, stdout, stderr } = run(['help']); + + expect(status).toBe(0); + expect(stderr.toString()).toBe(''); + expect(stdout.toString()).toContain('doctor'); + expect(stdout.toString()).toContain( + 'Check Storybook for known problems and provide suggestions or fixes' + ); + }); +}); From 378ab7756a849bfa40303b249bc216d326f54024 Mon Sep 17 00:00:00 2001 From: toothlessdev Date: Wed, 30 Oct 2024 02:42:43 +0900 Subject: [PATCH 027/208] Add init command to common CLI commands --- code/lib/cli-storybook/src/bin/index.ts | 30 +++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/code/lib/cli-storybook/src/bin/index.ts b/code/lib/cli-storybook/src/bin/index.ts index c2bd666b02e6..fb6028eec08f 100644 --- a/code/lib/cli-storybook/src/bin/index.ts +++ b/code/lib/cli-storybook/src/bin/index.ts @@ -8,6 +8,7 @@ import { logger } from 'storybook/internal/node-logger'; import { addToGlobalContext, telemetry } from 'storybook/internal/telemetry'; import { program } from 'commander'; +import { initiate } from 'create-storybook'; import envinfo from 'envinfo'; import { findPackageSync } from 'fd-package-json'; import leven from 'leven'; @@ -40,6 +41,35 @@ const command = (name: string) => .option('--debug', 'Get more logs in debug mode', false) .option('--enable-crash-reports', 'Enable sending crash reports to telemetry data'); +command('init') + .description('Initialize Storybook into your project.') + .option('-f --force', 'Force add Storybook') + .option('-s --skip-install', 'Skip installing deps') + .option('--package-manager ', 'Force package manager for installing deps') + .option('--use-pnp', 'Enable PnP mode for Yarn 2+') + .option('-p --parser ', 'jscodeshift parser') + .option('-t --type ', 'Add Storybook for a specific project type') + .option('-y --yes', 'Answer yes to all prompts') + .option('-b --builder ', 'Builder library') + .option('-l --linkable', 'Prepare installation for link (contributor helper)') + .option( + '--dev', + 'Launch the development server after completing initialization. Enabled by default (default: true)', + process.env.CI !== 'true' && process.env.IN_STORYBOOK_SANDBOX !== 'true' + ) + .option( + '--no-dev', + 'Complete the initialization of Storybook without launching the Storybook development server' + ) + .option('-V --version', 'output the version number') + .option('-h --help', 'display help for command') + .action((options) => { + initiate(options).catch((e) => { + logger.error(e); + process.exit(1); + }); + }); + command('add ') .description('Add an addon to your Storybook') .option( From 8d4e96a8fe5e63557d83b2449b68bf2299752947 Mon Sep 17 00:00:00 2001 From: toothlessdev Date: Wed, 30 Oct 2024 02:54:47 +0900 Subject: [PATCH 028/208] Fix misspelled test code --- code/lib/cli-storybook/test/default/cli.test.cjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/lib/cli-storybook/test/default/cli.test.cjs b/code/lib/cli-storybook/test/default/cli.test.cjs index 2be8af9caca5..be8421c74a2b 100755 --- a/code/lib/cli-storybook/test/default/cli.test.cjs +++ b/code/lib/cli-storybook/test/default/cli.test.cjs @@ -79,7 +79,7 @@ describe('Help command', () => { ); }); - it('sholud prints out "automigrate" command', () => { + it('should prints out "automigrate" command', () => { const { status, stdout, stderr } = run(['help']); expect(status).toBe(0); @@ -90,7 +90,7 @@ describe('Help command', () => { ); }); - it('sholud prints out "doctor" command', () => { + it('should prints out "doctor" command', () => { const { status, stdout, stderr } = run(['help']); expect(status).toBe(0); From 2bd300b0dc691f73e0071591de1a4c170962b5bb Mon Sep 17 00:00:00 2001 From: toothlessdev Date: Wed, 30 Oct 2024 02:57:01 +0900 Subject: [PATCH 029/208] Refactor init command with withTelementry wrapper --- code/lib/cli-storybook/src/bin/index.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/code/lib/cli-storybook/src/bin/index.ts b/code/lib/cli-storybook/src/bin/index.ts index fb6028eec08f..52e11828edf9 100644 --- a/code/lib/cli-storybook/src/bin/index.ts +++ b/code/lib/cli-storybook/src/bin/index.ts @@ -61,14 +61,17 @@ command('init') '--no-dev', 'Complete the initialization of Storybook without launching the Storybook development server' ) - .option('-V --version', 'output the version number') - .option('-h --help', 'display help for command') - .action((options) => { - initiate(options).catch((e) => { + .action((options) => + withTelemetry('init', { cliOptions: options }, async () => { + await initiate(options); + if (!options.disableTelemetry) { + await telemetry('init', { source: 'cli' }); + } + }).catch((e) => { logger.error(e); process.exit(1); - }); - }); + }) + ); command('add ') .description('Add an addon to your Storybook') From 7df59751319d017b2a561bf7db6e433797a20f35 Mon Sep 17 00:00:00 2001 From: toothlessdev Date: Wed, 30 Oct 2024 03:05:08 +0900 Subject: [PATCH 030/208] Change to the output format of the cli command --- code/lib/cli-storybook/src/bin/index.ts | 2 +- code/lib/cli-storybook/test/default/cli.test.cjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/code/lib/cli-storybook/src/bin/index.ts b/code/lib/cli-storybook/src/bin/index.ts index 52e11828edf9..52ff737c75a1 100644 --- a/code/lib/cli-storybook/src/bin/index.ts +++ b/code/lib/cli-storybook/src/bin/index.ts @@ -42,7 +42,7 @@ const command = (name: string) => .option('--enable-crash-reports', 'Enable sending crash reports to telemetry data'); command('init') - .description('Initialize Storybook into your project.') + .description('Initialize Storybook into your project') .option('-f --force', 'Force add Storybook') .option('-s --skip-install', 'Skip installing deps') .option('--package-manager ', 'Force package manager for installing deps') diff --git a/code/lib/cli-storybook/test/default/cli.test.cjs b/code/lib/cli-storybook/test/default/cli.test.cjs index be8421c74a2b..f55fa0ef9a61 100755 --- a/code/lib/cli-storybook/test/default/cli.test.cjs +++ b/code/lib/cli-storybook/test/default/cli.test.cjs @@ -20,7 +20,7 @@ describe('Help command', () => { expect(status).toBe(0); expect(stderr.toString()).toBe(''); expect(stdout.toString()).toContain('init'); - expect(stdout.toString()).toContain('Initialize Storybook into your project.'); + expect(stdout.toString()).toContain('Initialize Storybook into your project'); }); it('should prints out "add" command', () => { From a2a479441e26409ad3712fb6af2ca2b58650a9be Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Tue, 29 Oct 2024 21:47:00 +0100 Subject: [PATCH 031/208] remove auto handling of the registry --- .circleci/config.yml | 2 +- scripts/bench/{bench-package.ts => bench-packages.ts} | 11 ++++------- scripts/package.json | 2 +- 3 files changed, 6 insertions(+), 9 deletions(-) rename scripts/bench/{bench-package.ts => bench-packages.ts} (97%) diff --git a/.circleci/config.yml b/.circleci/config.yml index b64dbeea2571..52f6c5ebd734 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -223,7 +223,7 @@ jobs: - run: name: Benchmarking Packages working_directory: scripts - command: yarn bench-package --baseBranch << pipeline.parameters.ghBaseBranch >> --pull-request << pipeline.parameters.ghPrNumber >> --upload + command: yarn bench-packages --baseBranch << pipeline.parameters.ghBaseBranch >> --pull-request << pipeline.parameters.ghPrNumber >> --upload - store_artifacts: path: bench/packages/results.json - store_artifacts: diff --git a/scripts/bench/bench-package.ts b/scripts/bench/bench-packages.ts similarity index 97% rename from scripts/bench/bench-package.ts rename to scripts/bench/bench-packages.ts index 210812b5ba61..309e30095fb3 100644 --- a/scripts/bench/bench-package.ts +++ b/scripts/bench/bench-packages.ts @@ -6,9 +6,9 @@ import pLimit from 'p-limit'; import { join } from 'path'; import picocolors from 'picocolors'; import { x } from 'tinyexec'; +import dedent from 'ts-dedent'; import versions from '../../code/core/src/common/versions'; -import { runRegistry } from '../tasks/run-registry'; import { maxConcurrentTasks } from '../utils/concurrency'; import { esMain } from '../utils/esmain'; @@ -58,8 +58,6 @@ type ComparisonResult = { type ResultMap = Record; type ComparisonResultMap = Record; -let registryController: AbortController | undefined; - /** * This function benchmarks the size of Storybook packages and their dependencies. For each package, * the steps are: @@ -416,8 +414,9 @@ const run = async () => { } if ((await detectFreePort(REGISTRY_PORT)) === REGISTRY_PORT) { - console.log('Starting local registry...'); - registryController = await runRegistry({ dryRun: false, debug: false }); + throw new Error(dedent`The local verdaccio registry must be running in the background for package benching to work, + and packages must be published to it in --no-link mode with 'yarn --task publish --no-link' + Then runn the registry with 'yarn --task run-registry --no-link'`); } // The amount of VCPUs for this task in CI is 2 (medium resource) @@ -484,12 +483,10 @@ const run = async () => { } console.log(picocolors.green('Done benching all packages')); - registryController?.abort(); }; if (esMain(import.meta.url)) { run().catch((err) => { - registryController?.abort(); console.error(err); process.exit(1); }); diff --git a/scripts/package.json b/scripts/package.json index b3e0e4d46e57..e8903525a4f3 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "scripts": { - "bench-package": "jiti ./bench/bench-package.ts", + "bench-packages": "jiti ./bench/bench-packages.ts", "build-package": "jiti ./build-package.ts", "check": "jiti ./prepare/check-scripts.ts", "check-package": "jiti ./check-package.ts", From fc54fd3247849a1560f82885a4b7a3d7d3304d5b Mon Sep 17 00:00:00 2001 From: toothlessdev Date: Wed, 30 Oct 2024 22:09:17 +0900 Subject: [PATCH 032/208] Remove action call --- code/lib/cli-storybook/src/bin/index.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/code/lib/cli-storybook/src/bin/index.ts b/code/lib/cli-storybook/src/bin/index.ts index 52ff737c75a1..2e083713020a 100644 --- a/code/lib/cli-storybook/src/bin/index.ts +++ b/code/lib/cli-storybook/src/bin/index.ts @@ -8,7 +8,6 @@ import { logger } from 'storybook/internal/node-logger'; import { addToGlobalContext, telemetry } from 'storybook/internal/telemetry'; import { program } from 'commander'; -import { initiate } from 'create-storybook'; import envinfo from 'envinfo'; import { findPackageSync } from 'fd-package-json'; import leven from 'leven'; @@ -60,17 +59,6 @@ command('init') .option( '--no-dev', 'Complete the initialization of Storybook without launching the Storybook development server' - ) - .action((options) => - withTelemetry('init', { cliOptions: options }, async () => { - await initiate(options); - if (!options.disableTelemetry) { - await telemetry('init', { source: 'cli' }); - } - }).catch((e) => { - logger.error(e); - process.exit(1); - }) ); command('add ') From 949c687d8691314d5985c7ce94a263b3a2e870d0 Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Sun, 3 Nov 2024 23:40:14 +0800 Subject: [PATCH 033/208] React Native Web Vite: WIP framework, sandboxes, and CLI integration --- code/core/src/cli/detect.ts | 2 + code/core/src/cli/project_types.ts | 1 + code/core/src/common/versions.ts | 1 + code/core/src/types/modules/frameworks.ts | 1 + .../react-native-web-vite/README.md | 3 + .../react-native-web-vite/package.json | 88 +++ .../react-native-web-vite/preset.js | 1 + .../react-native-web-vite/project.json | 8 + .../react-native-web-vite/src/index.ts | 1 + .../react-native-web-vite/src/preset.ts | 90 +++ .../react-native-web-vite/src/types.ts | 68 +++ .../react-native-web-vite/tsconfig.json | 11 + .../react-native-web-vite/vitest.config.ts | 10 + .../cli-storybook/src/sandbox-templates.ts | 23 + .../src/generators/REACT_NATIVE_WEB/index.ts | 28 + code/lib/create-storybook/src/initiate.ts | 7 + code/yarn.lock | 568 +++++++++++++++++- scripts/sandbox/generate.ts | 2 + scripts/tasks/sandbox-parts.ts | 2 + 19 files changed, 914 insertions(+), 1 deletion(-) create mode 100644 code/frameworks/react-native-web-vite/README.md create mode 100644 code/frameworks/react-native-web-vite/package.json create mode 100644 code/frameworks/react-native-web-vite/preset.js create mode 100644 code/frameworks/react-native-web-vite/project.json create mode 100644 code/frameworks/react-native-web-vite/src/index.ts create mode 100644 code/frameworks/react-native-web-vite/src/preset.ts create mode 100644 code/frameworks/react-native-web-vite/src/types.ts create mode 100644 code/frameworks/react-native-web-vite/tsconfig.json create mode 100644 code/frameworks/react-native-web-vite/vitest.config.ts create mode 100644 code/lib/create-storybook/src/generators/REACT_NATIVE_WEB/index.ts diff --git a/code/core/src/cli/detect.ts b/code/core/src/cli/detect.ts index 09ef8cae02cf..5b50abee4301 100644 --- a/code/core/src/cli/detect.ts +++ b/code/core/src/cli/detect.ts @@ -130,6 +130,8 @@ export async function detectBuilder(packageManager: JsPackageManager, projectTyp // Fallback to Vite or Webpack based on project type switch (projectType) { + case ProjectType.REACT_NATIVE_WEB: + return CoreBuilder.Vite; case ProjectType.REACT_SCRIPTS: case ProjectType.ANGULAR: case ProjectType.REACT_NATIVE: // technically react native doesn't use webpack, we just want to set something diff --git a/code/core/src/cli/project_types.ts b/code/core/src/cli/project_types.ts index 5d7d4a4d3ead..25148d2bc089 100644 --- a/code/core/src/cli/project_types.ts +++ b/code/core/src/cli/project_types.ts @@ -47,6 +47,7 @@ export enum ProjectType { REACT = 'REACT', REACT_SCRIPTS = 'REACT_SCRIPTS', REACT_NATIVE = 'REACT_NATIVE', + REACT_NATIVE_WEB = 'REACT_NATIVE_WEB', REACT_PROJECT = 'REACT_PROJECT', WEBPACK_REACT = 'WEBPACK_REACT', NEXTJS = 'NEXTJS', diff --git a/code/core/src/common/versions.ts b/code/core/src/common/versions.ts index 68001af0ed44..a3fcf098d3a1 100644 --- a/code/core/src/common/versions.ts +++ b/code/core/src/common/versions.ts @@ -48,6 +48,7 @@ export default { '@storybook/nextjs': '8.5.0-alpha.2', '@storybook/preact-vite': '8.5.0-alpha.2', '@storybook/preact-webpack5': '8.5.0-alpha.2', + '@storybook/react-native-web-vite': '8.5.0-alpha.2', '@storybook/react-vite': '8.5.0-alpha.2', '@storybook/react-webpack5': '8.5.0-alpha.2', '@storybook/server-webpack5': '8.5.0-alpha.2', diff --git a/code/core/src/types/modules/frameworks.ts b/code/core/src/types/modules/frameworks.ts index 2f2028db810b..e3e1b6383a7f 100644 --- a/code/core/src/types/modules/frameworks.ts +++ b/code/core/src/types/modules/frameworks.ts @@ -8,6 +8,7 @@ export type SupportedFrameworks = | 'nextjs' | 'preact-vite' | 'preact-webpack5' + | 'react-native-web-vite' | 'react-vite' | 'react-webpack5' | 'server-webpack5' diff --git a/code/frameworks/react-native-web-vite/README.md b/code/frameworks/react-native-web-vite/README.md new file mode 100644 index 000000000000..c6b6e6abf0a4 --- /dev/null +++ b/code/frameworks/react-native-web-vite/README.md @@ -0,0 +1,3 @@ +# Storybook for React & Vite + +See [documentation](https://storybook.js.org/docs/get-started/frameworks/react-vite?renderer=react) for installation instructions, usage examples, APIs, and more. diff --git a/code/frameworks/react-native-web-vite/package.json b/code/frameworks/react-native-web-vite/package.json new file mode 100644 index 000000000000..0c9d3ee7a369 --- /dev/null +++ b/code/frameworks/react-native-web-vite/package.json @@ -0,0 +1,88 @@ +{ + "name": "@storybook/react-native-web-vite", + "version": "8.5.0-alpha.2", + "description": "Develop react-native components an isolated web environment with hot reloading.", + "keywords": [ + "storybook" + ], + "homepage": "https://github.com/storybookjs/storybook/tree/next/code/frameworks/react-native-web-vite", + "bugs": { + "url": "https://github.com/storybookjs/storybook/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/storybookjs/storybook.git", + "directory": "code/frameworks/react-native-web-vite" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "license": "MIT", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "node": "./dist/index.js", + "import": "./dist/index.mjs", + "require": "./dist/index.js" + }, + "./preset": { + "types": "./dist/preset.d.ts", + "require": "./dist/preset.js" + }, + "./package.json": "./package.json" + }, + "main": "dist/index.js", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", + "files": [ + "dist/**/*", + "template/cli/**/*", + "README.md", + "*.js", + "*.d.ts", + "!src/**/*" + ], + "scripts": { + "check": "jiti ../../../scripts/prepare/check.ts", + "prep": "jiti ../../../scripts/prepare/bundle.ts" + }, + "dependencies": { + "@joshwooding/vite-plugin-react-docgen-typescript": "0.3.0", + "@rollup/pluginutils": "^5.0.2", + "@storybook/builder-vite": "workspace:*", + "@storybook/react": "workspace:*", + "@vitejs/plugin-react": "^4.3.2", + "find-up": "^5.0.0", + "magic-string": "^0.30.0", + "react-docgen": "^7.0.0", + "resolve": "^1.22.8", + "tsconfig-paths": "^4.2.0", + "vite": "^4.0.0 || ^5.0.0" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "typescript": "^5.3.2" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "react-native": ">=0.74.5", + "react-native-web": "^0.19.12", + "storybook": "workspace:^" + }, + "engines": { + "node": ">=18.0.0" + }, + "publishConfig": { + "access": "public" + }, + "bundler": { + "entries": [ + "./src/index.ts", + "./src/preset.ts" + ], + "platform": "node" + }, + "gitHead": "e6a7fd8a655c69780bc20b9749c2699e44beae16" +} diff --git a/code/frameworks/react-native-web-vite/preset.js b/code/frameworks/react-native-web-vite/preset.js new file mode 100644 index 000000000000..a83f95279e7f --- /dev/null +++ b/code/frameworks/react-native-web-vite/preset.js @@ -0,0 +1 @@ +module.exports = require('./dist/preset'); diff --git a/code/frameworks/react-native-web-vite/project.json b/code/frameworks/react-native-web-vite/project.json new file mode 100644 index 000000000000..219e9c00077d --- /dev/null +++ b/code/frameworks/react-native-web-vite/project.json @@ -0,0 +1,8 @@ +{ + "name": "react-native-web-vite", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "projectType": "library", + "targets": { + "build": {} + } +} diff --git a/code/frameworks/react-native-web-vite/src/index.ts b/code/frameworks/react-native-web-vite/src/index.ts new file mode 100644 index 000000000000..1855ad61a70b --- /dev/null +++ b/code/frameworks/react-native-web-vite/src/index.ts @@ -0,0 +1 @@ +export type { FrameworkOptions, StorybookConfig } from './types'; diff --git a/code/frameworks/react-native-web-vite/src/preset.ts b/code/frameworks/react-native-web-vite/src/preset.ts new file mode 100644 index 000000000000..d1eba924044d --- /dev/null +++ b/code/frameworks/react-native-web-vite/src/preset.ts @@ -0,0 +1,90 @@ +import { hasVitePlugins } from '@storybook/builder-vite'; + +import type { BabelOptions, Options as ReactOptions } from '@vitejs/plugin-react'; +import react from '@vitejs/plugin-react'; +import type { Plugin } from 'vite'; + +import type { FrameworkOptions, StorybookConfig } from './types'; + +function reactNativeWeb( + reactOptions: Omit & { babel?: BabelOptions } +): Plugin { + const plugin: Plugin = { + name: 'vite:react-native-web', + enforce: 'pre', + config(_userConfig, env) { + return { + plugins: [ + react({ + jsxRuntime: 'automatic', + ...reactOptions, + }), + ], + define: { + // reanimated support + 'global.__x': {}, + _frameTimestamp: undefined, + _WORKLET: false, + __DEV__: `${env.mode === 'development'}`, + 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || env.mode), + }, + optimizeDeps: { + include: [], + esbuildOptions: { + jsx: 'transform', + resolveExtensions: [ + '.web.js', + '.web.ts', + '.web.tsx', + '.js', + '.jsx', + '.json', + '.ts', + '.tsx', + '.mjs', + ], + loader: { + '.js': 'jsx', + }, + }, + }, + resolve: { + extensions: [ + '.web.js', + '.web.ts', + '.web.tsx', + '.js', + '.jsx', + '.json', + '.ts', + '.tsx', + '.mjs', + ], + alias: { + 'react-native': 'react-native-web', + }, + }, + }; + }, + }; + + return plugin; +} + +export const viteFinal: StorybookConfig['viteFinal'] = async (config, options) => { + const { pluginReactOptions = {} } = + await options.presets.apply('frameworkOptions'); + + const { plugins = [] } = config; + + if (!(await hasVitePlugins(plugins, ['vite:react-native-web']))) { + plugins.push(reactNativeWeb(pluginReactOptions)); + } + + return config; +}; + +export const core = { + builder: '@storybook/builder-vite', + renderer: '@storybook/react', +}; diff --git a/code/frameworks/react-native-web-vite/src/types.ts b/code/frameworks/react-native-web-vite/src/types.ts new file mode 100644 index 000000000000..c0831eb06486 --- /dev/null +++ b/code/frameworks/react-native-web-vite/src/types.ts @@ -0,0 +1,68 @@ +import type { + CompatibleString, + StorybookConfig as StorybookConfigBase, + TypescriptOptions as TypescriptOptionsBase, +} from 'storybook/internal/types'; + +import type { BuilderOptions, StorybookConfigVite } from '@storybook/builder-vite'; + +import type docgenTypescript from '@joshwooding/vite-plugin-react-docgen-typescript'; +import type { BabelOptions, Options as ReactOptions } from '@vitejs/plugin-react'; + +type FrameworkName = CompatibleString<'@storybook/react-native-web-vite'>; +type BuilderName = CompatibleString<'@storybook/builder-vite'>; + +export type FrameworkOptions = { + builder?: BuilderOptions; + strictMode?: boolean; + /** + * Use React's legacy root API to mount components + * + * React has introduced a new root API with React 18.x to enable a whole set of new features (e.g. + * concurrent features) If this flag is true, the legacy Root API is used to mount components to + * make it easier to migrate step by step to React 18. + * + * @default false + */ + legacyRootApi?: boolean; + + pluginReactOptions?: Omit & { babel?: BabelOptions }; +}; + +type StorybookConfigFramework = { + framework: + | FrameworkName + | { + name: FrameworkName; + options: FrameworkOptions; + }; + core?: StorybookConfigBase['core'] & { + builder?: + | BuilderName + | { + name: BuilderName; + options: BuilderOptions; + }; + }; +}; + +type TypescriptOptions = TypescriptOptionsBase & { + /** + * Sets the type of Docgen when working with React and TypeScript + * + * @default `'react-docgen'` + */ + reactDocgen: 'react-docgen-typescript' | 'react-docgen' | false; + /** Configures `@joshwooding/vite-plugin-react-docgen-typescript` */ + reactDocgenTypescriptOptions: Parameters[0]; +}; + +/** The interface for Storybook configuration in `main.ts` files. */ +export type StorybookConfig = Omit< + StorybookConfigBase, + keyof StorybookConfigVite | keyof StorybookConfigFramework | 'typescript' +> & + StorybookConfigVite & + StorybookConfigFramework & { + typescript?: Partial; + }; diff --git a/code/frameworks/react-native-web-vite/tsconfig.json b/code/frameworks/react-native-web-vite/tsconfig.json new file mode 100644 index 000000000000..c749496d9a6e --- /dev/null +++ b/code/frameworks/react-native-web-vite/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "storybook/internal/*": ["../../lib/cli/core/*"] + }, + "rootDir": "./src" + }, + "extends": "../../tsconfig.json", + "include": ["src/**/*"] +} diff --git a/code/frameworks/react-native-web-vite/vitest.config.ts b/code/frameworks/react-native-web-vite/vitest.config.ts new file mode 100644 index 000000000000..7420176b2e46 --- /dev/null +++ b/code/frameworks/react-native-web-vite/vitest.config.ts @@ -0,0 +1,10 @@ +import { defineConfig, mergeConfig } from 'vitest/config'; + +import { vitestCommonConfig } from '../../vitest.workspace'; + +export default mergeConfig( + vitestCommonConfig, + defineConfig({ + // Add custom config here + }) +); diff --git a/code/lib/cli-storybook/src/sandbox-templates.ts b/code/lib/cli-storybook/src/sandbox-templates.ts index b474937769ff..f4684cef8e53 100644 --- a/code/lib/cli-storybook/src/sandbox-templates.ts +++ b/code/lib/cli-storybook/src/sandbox-templates.ts @@ -592,6 +592,29 @@ const baseTemplates = { builder: '@storybook/builder-webpack5', }, }, + 'react-native-web-vite/expo-ts': { + name: 'React Native Expo Latest (Vite | TypeScript)', + script: 'npx create-expo-app -y {{beforeDir}}', + inDevelopment: true, + expected: { + framework: '@storybook/react-native-web-vite', + renderer: '@storybook/react', + builder: '@storybook/builder-vite', + }, + skipTasks: ['bench'], + }, + 'react-native-web-vite/rn-cli-ts': { + name: 'React Native CLI Latest (Vite | TypeScript)', + script: + 'npx @react-native-community/cli@latest init --install-pods=false --directory={{beforeDir}} rnapp', + inDevelopment: true, + expected: { + framework: '@storybook/react-native-web-vite', + renderer: '@storybook/react', + builder: '@storybook/builder-vite', + }, + skipTasks: ['bench'], + }, } satisfies Record; /** diff --git a/code/lib/create-storybook/src/generators/REACT_NATIVE_WEB/index.ts b/code/lib/create-storybook/src/generators/REACT_NATIVE_WEB/index.ts new file mode 100644 index 000000000000..721987d46661 --- /dev/null +++ b/code/lib/create-storybook/src/generators/REACT_NATIVE_WEB/index.ts @@ -0,0 +1,28 @@ +import { detectLanguage } from 'storybook/internal/cli'; +import { SupportedLanguage } from 'storybook/internal/cli'; + +import { baseGenerator } from '../baseGenerator'; +import type { Generator } from '../types'; + +const generator: Generator = async (packageManager, npmOptions, options) => { + // Add prop-types dependency if not using TypeScript + const language = await detectLanguage(packageManager); + const extraPackages = ['vite']; + if (language === SupportedLanguage.JAVASCRIPT) { + extraPackages.push('prop-types'); + } + + await baseGenerator( + packageManager, + npmOptions, + options, + 'react', + { + extraPackages, + extraAddons: [`@storybook/addon-onboarding`], + }, + 'react-native-web-vite' + ); +}; + +export default generator; diff --git a/code/lib/create-storybook/src/initiate.ts b/code/lib/create-storybook/src/initiate.ts index 868834a1e144..6c1c6559d262 100644 --- a/code/lib/create-storybook/src/initiate.ts +++ b/code/lib/create-storybook/src/initiate.ts @@ -31,6 +31,7 @@ import preactGenerator from './generators/PREACT'; import qwikGenerator from './generators/QWIK'; import reactGenerator from './generators/REACT'; import reactNativeGenerator from './generators/REACT_NATIVE'; +import reactNativeWebGenerator from './generators/REACT_NATIVE_WEB'; import reactScriptsGenerator from './generators/REACT_SCRIPTS'; import serverGenerator from './generators/SERVER'; import solidGenerator from './generators/SOLID'; @@ -84,6 +85,12 @@ const installStorybook = async ( ); } + case ProjectType.REACT_NATIVE_WEB: { + return reactNativeWebGenerator(packageManager, npmOptions, generatorOptions).then( + commandLog('Adding Storybook support to your "React Native" app') + ); + } + case ProjectType.QWIK: { return qwikGenerator(packageManager, npmOptions, generatorOptions).then( commandLog('Adding Storybook support to your "Qwik" app') diff --git a/code/yarn.lock b/code/yarn.lock index 272933533d60..c6134557544d 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -377,6 +377,17 @@ __metadata: languageName: node linkType: hard +"@babel/code-frame@npm:^7.25.9, @babel/code-frame@npm:^7.26.0": + version: 7.26.2 + resolution: "@babel/code-frame@npm:7.26.2" + dependencies: + "@babel/helper-validator-identifier": "npm:^7.25.9" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.0.0" + checksum: 10c0/7d79621a6849183c415486af99b1a20b84737e8c11cd55b6544f688c51ce1fd710e6d869c3dd21232023da272a79b91efb3e83b5bc2dc65c1187c5fcd1b72ea8 + languageName: node + linkType: hard + "@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.23.5, @babel/compat-data@npm:^7.24.4, @babel/compat-data@npm:^7.25.2": version: 7.25.2 resolution: "@babel/compat-data@npm:7.25.2" @@ -384,6 +395,13 @@ __metadata: languageName: node linkType: hard +"@babel/compat-data@npm:^7.25.9": + version: 7.26.2 + resolution: "@babel/compat-data@npm:7.26.2" + checksum: 10c0/c9b5f3724828d17f728a778f9d66c19b55c018d0d76de6d731178cca64f182c22b71400a73bf2b65dcc4fcfe52b630088a94d5902911b54206aa90e3ffe07d12 + languageName: node + linkType: hard + "@babel/core@npm:7.23.9": version: 7.23.9 resolution: "@babel/core@npm:7.23.9" @@ -453,6 +471,29 @@ __metadata: languageName: node linkType: hard +"@babel/core@npm:^7.25.2": + version: 7.26.0 + resolution: "@babel/core@npm:7.26.0" + dependencies: + "@ampproject/remapping": "npm:^2.2.0" + "@babel/code-frame": "npm:^7.26.0" + "@babel/generator": "npm:^7.26.0" + "@babel/helper-compilation-targets": "npm:^7.25.9" + "@babel/helper-module-transforms": "npm:^7.26.0" + "@babel/helpers": "npm:^7.26.0" + "@babel/parser": "npm:^7.26.0" + "@babel/template": "npm:^7.25.9" + "@babel/traverse": "npm:^7.25.9" + "@babel/types": "npm:^7.26.0" + convert-source-map: "npm:^2.0.0" + debug: "npm:^4.1.0" + gensync: "npm:^1.0.0-beta.2" + json5: "npm:^2.2.3" + semver: "npm:^6.3.1" + checksum: 10c0/91de73a7ff5c4049fbc747930aa039300e4d2670c2a91f5aa622f1b4868600fc89b01b6278385fbcd46f9574186fa3d9b376a9e7538e50f8d118ec13cfbcb63e + languageName: node + linkType: hard + "@babel/generator@npm:7.17.7": version: 7.17.7 resolution: "@babel/generator@npm:7.17.7" @@ -488,6 +529,19 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.25.9, @babel/generator@npm:^7.26.0": + version: 7.26.2 + resolution: "@babel/generator@npm:7.26.2" + dependencies: + "@babel/parser": "npm:^7.26.2" + "@babel/types": "npm:^7.26.0" + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.25" + jsesc: "npm:^3.0.2" + checksum: 10c0/167ebce8977142f5012fad6bd91da51ac52bcd752f2261a54b7ab605d928aebe57e21636cdd2a9c7757e552652c68d9fcb5d40b06fcb66e02d9ee7526e118a5c + languageName: node + linkType: hard + "@babel/helper-annotate-as-pure@npm:7.22.5": version: 7.22.5 resolution: "@babel/helper-annotate-as-pure@npm:7.22.5" @@ -529,6 +583,19 @@ __metadata: languageName: node linkType: hard +"@babel/helper-compilation-targets@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-compilation-targets@npm:7.25.9" + dependencies: + "@babel/compat-data": "npm:^7.25.9" + "@babel/helper-validator-option": "npm:^7.25.9" + browserslist: "npm:^4.24.0" + lru-cache: "npm:^5.1.1" + semver: "npm:^6.3.1" + checksum: 10c0/a6b26a1e4222e69ef8e62ee19374308f060b007828bc11c65025ecc9e814aba21ff2175d6d3f8bf53c863edd728ee8f94ba7870f8f90a37d39552ad9933a8aaa + languageName: node + linkType: hard + "@babel/helper-create-class-features-plugin@npm:^7.18.6, @babel/helper-create-class-features-plugin@npm:^7.21.0, @babel/helper-create-class-features-plugin@npm:^7.24.0, @babel/helper-create-class-features-plugin@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-create-class-features-plugin@npm:7.24.7" @@ -639,6 +706,16 @@ __metadata: languageName: node linkType: hard +"@babel/helper-module-imports@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-module-imports@npm:7.25.9" + dependencies: + "@babel/traverse": "npm:^7.25.9" + "@babel/types": "npm:^7.25.9" + checksum: 10c0/078d3c2b45d1f97ffe6bb47f61961be4785d2342a4156d8b42c92ee4e1b7b9e365655dd6cb25329e8fe1a675c91eeac7e3d04f0c518b67e417e29d6e27b6aa70 + languageName: node + linkType: hard + "@babel/helper-module-transforms@npm:^7.23.3, @babel/helper-module-transforms@npm:^7.24.7, @babel/helper-module-transforms@npm:^7.25.2": version: 7.25.2 resolution: "@babel/helper-module-transforms@npm:7.25.2" @@ -653,6 +730,19 @@ __metadata: languageName: node linkType: hard +"@babel/helper-module-transforms@npm:^7.26.0": + version: 7.26.0 + resolution: "@babel/helper-module-transforms@npm:7.26.0" + dependencies: + "@babel/helper-module-imports": "npm:^7.25.9" + "@babel/helper-validator-identifier": "npm:^7.25.9" + "@babel/traverse": "npm:^7.25.9" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/ee111b68a5933481d76633dad9cdab30c41df4479f0e5e1cc4756dc9447c1afd2c9473b5ba006362e35b17f4ebddd5fca090233bef8dfc84dca9d9127e56ec3a + languageName: node + linkType: hard + "@babel/helper-optimise-call-expression@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-optimise-call-expression@npm:7.24.7" @@ -669,6 +759,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-plugin-utils@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-plugin-utils@npm:7.25.9" + checksum: 10c0/483066a1ba36ff16c0116cd24f93de05de746a603a777cd695ac7a1b034928a65a4ecb35f255761ca56626435d7abdb73219eba196f9aa83b6c3c3169325599d + languageName: node + linkType: hard + "@babel/helper-remap-async-to-generator@npm:^7.22.20, @babel/helper-remap-async-to-generator@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-remap-async-to-generator@npm:7.24.7" @@ -740,6 +837,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-string-parser@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-string-parser@npm:7.25.9" + checksum: 10c0/7244b45d8e65f6b4338a6a68a8556f2cb161b782343e97281a5f2b9b93e420cad0d9f5773a59d79f61d0c448913d06f6a2358a87f2e203cf112e3c5b53522ee6 + languageName: node + linkType: hard + "@babel/helper-validator-identifier@npm:^7.16.7, @babel/helper-validator-identifier@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-validator-identifier@npm:7.24.7" @@ -747,6 +851,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-identifier@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-validator-identifier@npm:7.25.9" + checksum: 10c0/4fc6f830177b7b7e887ad3277ddb3b91d81e6c4a24151540d9d1023e8dc6b1c0505f0f0628ae653601eb4388a8db45c1c14b2c07a9173837aef7e4116456259d + languageName: node + linkType: hard + "@babel/helper-validator-option@npm:^7.22.15, @babel/helper-validator-option@npm:^7.23.5, @babel/helper-validator-option@npm:^7.24.8": version: 7.24.8 resolution: "@babel/helper-validator-option@npm:7.24.8" @@ -754,6 +865,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-option@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-validator-option@npm:7.25.9" + checksum: 10c0/27fb195d14c7dcb07f14e58fe77c44eea19a6a40a74472ec05c441478fa0bb49fa1c32b2d64be7a38870ee48ef6601bdebe98d512f0253aea0b39756c4014f3e + languageName: node + linkType: hard + "@babel/helper-wrap-function@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-wrap-function@npm:7.24.7" @@ -776,6 +894,16 @@ __metadata: languageName: node linkType: hard +"@babel/helpers@npm:^7.26.0": + version: 7.26.0 + resolution: "@babel/helpers@npm:7.26.0" + dependencies: + "@babel/template": "npm:^7.25.9" + "@babel/types": "npm:^7.26.0" + checksum: 10c0/343333cced6946fe46617690a1d0789346960910225ce359021a88a60a65bc0d791f0c5d240c0ed46cf8cc63b5fd7df52734ff14e43b9c32feae2b61b1647097 + languageName: node + linkType: hard + "@babel/highlight@npm:^7.24.7": version: 7.24.7 resolution: "@babel/highlight@npm:7.24.7" @@ -799,6 +927,17 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.25.9, @babel/parser@npm:^7.26.0, @babel/parser@npm:^7.26.2": + version: 7.26.2 + resolution: "@babel/parser@npm:7.26.2" + dependencies: + "@babel/types": "npm:^7.26.0" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/751a743087b3a9172a7599f1421830d44c38f065ef781588d2bfb1c98f9b461719a226feb13c868d7a284783eee120c88ea522593118f2668f46ebfb1105c4d7 + languageName: node + linkType: hard + "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.24.4": version: 7.24.4 resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.24.4" @@ -1722,6 +1861,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-react-jsx-self@npm:^7.24.7": + version: 7.25.9 + resolution: "@babel/plugin-transform-react-jsx-self@npm:7.25.9" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.25.9" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/ce0e289f6af93d7c4dc6b385512199c5bb138ae61507b4d5117ba88b6a6b5092f704f1bdf80080b7d69b1b8c36649f2a0b250e8198667d4d30c08bbb1546bd99 + languageName: node + linkType: hard + "@babel/plugin-transform-react-jsx-source@npm:^7.19.6": version: 7.22.5 resolution: "@babel/plugin-transform-react-jsx-source@npm:7.22.5" @@ -1733,6 +1883,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-react-jsx-source@npm:^7.24.7": + version: 7.25.9 + resolution: "@babel/plugin-transform-react-jsx-source@npm:7.25.9" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.25.9" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/fc9ee08efc9be7cbd2cc6788bbf92579adf3cab37912481f1b915221be3d22b0613b5b36a721df5f4c0ab65efe8582fcf8673caab83e6e1ce4cc04ceebf57dfa + languageName: node + linkType: hard + "@babel/plugin-transform-react-jsx@npm:^7.22.5, @babel/plugin-transform-react-jsx@npm:^7.23.4": version: 7.23.4 resolution: "@babel/plugin-transform-react-jsx@npm:7.23.4" @@ -2258,6 +2419,17 @@ __metadata: languageName: node linkType: hard +"@babel/template@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/template@npm:7.25.9" + dependencies: + "@babel/code-frame": "npm:^7.25.9" + "@babel/parser": "npm:^7.25.9" + "@babel/types": "npm:^7.25.9" + checksum: 10c0/ebe677273f96a36c92cc15b7aa7b11cc8bc8a3bb7a01d55b2125baca8f19cae94ff3ce15f1b1880fb8437f3a690d9f89d4e91f16fc1dc4d3eb66226d128983ab + languageName: node + linkType: hard + "@babel/traverse@npm:7.23.2": version: 7.23.2 resolution: "@babel/traverse@npm:7.23.2" @@ -2291,6 +2463,21 @@ __metadata: languageName: node linkType: hard +"@babel/traverse@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/traverse@npm:7.25.9" + dependencies: + "@babel/code-frame": "npm:^7.25.9" + "@babel/generator": "npm:^7.25.9" + "@babel/parser": "npm:^7.25.9" + "@babel/template": "npm:^7.25.9" + "@babel/types": "npm:^7.25.9" + debug: "npm:^4.3.1" + globals: "npm:^11.1.0" + checksum: 10c0/e90be586a714da4adb80e6cb6a3c5cfcaa9b28148abdafb065e34cc109676fc3db22cf98cd2b2fff66ffb9b50c0ef882cab0f466b6844be0f6c637b82719bba1 + languageName: node + linkType: hard + "@babel/types@npm:7.17.0": version: 7.17.0 resolution: "@babel/types@npm:7.17.0" @@ -2312,6 +2499,16 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.25.9, @babel/types@npm:^7.26.0": + version: 7.26.0 + resolution: "@babel/types@npm:7.26.0" + dependencies: + "@babel/helper-string-parser": "npm:^7.25.9" + "@babel/helper-validator-identifier": "npm:^7.25.9" + checksum: 10c0/b694f41ad1597127e16024d766c33a641508aad037abd08d0d1f73af753e1119fa03b4a107d04b5f92cc19c095a594660547ae9bead1db2299212d644b0a5cb8 + 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" @@ -5153,6 +5350,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-android-arm-eabi@npm:4.24.3": + version: 4.24.3 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.24.3" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + "@rollup/rollup-android-arm-eabi@npm:4.9.1": version: 4.9.1 resolution: "@rollup/rollup-android-arm-eabi@npm:4.9.1" @@ -5160,6 +5364,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-android-arm64@npm:4.24.3": + version: 4.24.3 + resolution: "@rollup/rollup-android-arm64@npm:4.24.3" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@rollup/rollup-android-arm64@npm:4.9.1": version: 4.9.1 resolution: "@rollup/rollup-android-arm64@npm:4.9.1" @@ -5167,6 +5378,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-darwin-arm64@npm:4.24.3": + version: 4.24.3 + resolution: "@rollup/rollup-darwin-arm64@npm:4.24.3" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@rollup/rollup-darwin-arm64@npm:4.9.1": version: 4.9.1 resolution: "@rollup/rollup-darwin-arm64@npm:4.9.1" @@ -5174,6 +5392,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-darwin-x64@npm:4.24.3": + version: 4.24.3 + resolution: "@rollup/rollup-darwin-x64@npm:4.24.3" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@rollup/rollup-darwin-x64@npm:4.9.1": version: 4.9.1 resolution: "@rollup/rollup-darwin-x64@npm:4.9.1" @@ -5181,6 +5406,27 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-freebsd-arm64@npm:4.24.3": + version: 4.24.3 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.24.3" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-freebsd-x64@npm:4.24.3": + version: 4.24.3 + resolution: "@rollup/rollup-freebsd-x64@npm:4.24.3" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-gnueabihf@npm:4.24.3": + version: 4.24.3 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.24.3" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-arm-gnueabihf@npm:4.9.1": version: 4.9.1 resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.9.1" @@ -5188,6 +5434,20 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-arm-musleabihf@npm:4.24.3": + version: 4.24.3 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.24.3" + conditions: os=linux & cpu=arm & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-gnu@npm:4.24.3": + version: 4.24.3 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.24.3" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-arm64-gnu@npm:4.9.1": version: 4.9.1 resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.9.1" @@ -5195,6 +5455,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-arm64-musl@npm:4.24.3": + version: 4.24.3 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.24.3" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + "@rollup/rollup-linux-arm64-musl@npm:4.9.1": version: 4.9.1 resolution: "@rollup/rollup-linux-arm64-musl@npm:4.9.1" @@ -5202,6 +5469,20 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-powerpc64le-gnu@npm:4.24.3": + version: 4.24.3 + resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.24.3" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-gnu@npm:4.24.3": + version: 4.24.3 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.24.3" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-riscv64-gnu@npm:4.9.1": version: 4.9.1 resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.9.1" @@ -5209,6 +5490,20 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-s390x-gnu@npm:4.24.3": + version: 4.24.3 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.24.3" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-gnu@npm:4.24.3": + version: 4.24.3 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.24.3" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-x64-gnu@npm:4.9.1": version: 4.9.1 resolution: "@rollup/rollup-linux-x64-gnu@npm:4.9.1" @@ -5216,6 +5511,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-x64-musl@npm:4.24.3": + version: 4.24.3 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.24.3" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + "@rollup/rollup-linux-x64-musl@npm:4.9.1": version: 4.9.1 resolution: "@rollup/rollup-linux-x64-musl@npm:4.9.1" @@ -5223,6 +5525,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-win32-arm64-msvc@npm:4.24.3": + version: 4.24.3 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.24.3" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@rollup/rollup-win32-arm64-msvc@npm:4.9.1": version: 4.9.1 resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.9.1" @@ -5230,6 +5539,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-win32-ia32-msvc@npm:4.24.3": + version: 4.24.3 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.24.3" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@rollup/rollup-win32-ia32-msvc@npm:4.9.1": version: 4.9.1 resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.9.1" @@ -5237,6 +5553,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-win32-x64-msvc@npm:4.24.3": + version: 4.24.3 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.24.3" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@rollup/rollup-win32-x64-msvc@npm:4.9.1": version: 4.9.1 resolution: "@rollup/rollup-win32-x64-msvc@npm:4.9.1" @@ -6742,6 +7065,32 @@ __metadata: languageName: unknown linkType: soft +"@storybook/react-native-web-vite@workspace:frameworks/react-native-web-vite": + version: 0.0.0-use.local + resolution: "@storybook/react-native-web-vite@workspace:frameworks/react-native-web-vite" + dependencies: + "@joshwooding/vite-plugin-react-docgen-typescript": "npm:0.3.0" + "@rollup/pluginutils": "npm:^5.0.2" + "@storybook/builder-vite": "workspace:*" + "@storybook/react": "workspace:*" + "@types/node": "npm:^22.0.0" + "@vitejs/plugin-react": "npm:^4.3.2" + find-up: "npm:^5.0.0" + magic-string: "npm:^0.30.0" + react-docgen: "npm:^7.0.0" + resolve: "npm:^1.22.8" + tsconfig-paths: "npm:^4.2.0" + typescript: "npm:^5.3.2" + vite: "npm:^4.0.0 || ^5.0.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta + react-native: ">=0.74.5" + react-native-web: ^0.19.12 + storybook: "workspace:^" + languageName: unknown + linkType: soft + "@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" @@ -7605,7 +7954,7 @@ __metadata: languageName: node linkType: hard -"@types/babel__core@npm:*, @types/babel__core@npm:^7, @types/babel__core@npm:^7.18.0": +"@types/babel__core@npm:*, @types/babel__core@npm:^7, @types/babel__core@npm:^7.18.0, @types/babel__core@npm:^7.20.5": version: 7.20.5 resolution: "@types/babel__core@npm:7.20.5" dependencies: @@ -7835,6 +8184,13 @@ __metadata: languageName: node linkType: hard +"@types/estree@npm:1.0.6": + version: 1.0.6 + resolution: "@types/estree@npm:1.0.6" + checksum: 10c0/cdfd751f6f9065442cd40957c07fd80361c962869aa853c1c2fd03e101af8b9389d8ff4955a43a6fcfa223dd387a089937f95be0f3eec21ca527039fd2d9859a + languageName: node + linkType: hard + "@types/estree@npm:^0.0.51": version: 0.0.51 resolution: "@types/estree@npm:0.0.51" @@ -8766,6 +9122,21 @@ __metadata: languageName: node linkType: hard +"@vitejs/plugin-react@npm:^4.3.2": + version: 4.3.3 + resolution: "@vitejs/plugin-react@npm:4.3.3" + dependencies: + "@babel/core": "npm:^7.25.2" + "@babel/plugin-transform-react-jsx-self": "npm:^7.24.7" + "@babel/plugin-transform-react-jsx-source": "npm:^7.24.7" + "@types/babel__core": "npm:^7.20.5" + react-refresh: "npm:^0.14.2" + peerDependencies: + vite: ^4.2.0 || ^5.0.0 + checksum: 10c0/b370c25fb47bb96f0cc51f3aadbbcfce54e40f95a4de67cf932e5ec526f139648da703725c6ea2c71a1b525eb3dd6e3e8ff877da143627cd2575de5ec4f00aa3 + languageName: node + linkType: hard + "@vitejs/plugin-vue@npm:^4.4.0": version: 4.5.2 resolution: "@vitejs/plugin-vue@npm:4.5.2" @@ -11274,6 +11645,20 @@ __metadata: languageName: node linkType: hard +"browserslist@npm:^4.24.0": + version: 4.24.2 + resolution: "browserslist@npm:4.24.2" + dependencies: + caniuse-lite: "npm:^1.0.30001669" + electron-to-chromium: "npm:^1.5.41" + node-releases: "npm:^2.0.18" + update-browserslist-db: "npm:^1.1.1" + bin: + browserslist: cli.js + checksum: 10c0/d747c9fb65ed7b4f1abcae4959405707ed9a7b835639f8a9ba0da2911995a6ab9b0648fd05baf2a4d4e3cf7f9fdbad56d3753f91881e365992c1d49c8d88ff7a + languageName: node + linkType: hard + "buffer-crc32@npm:^0.2.5": version: 0.2.13 resolution: "buffer-crc32@npm:0.2.13" @@ -11534,6 +11919,13 @@ __metadata: languageName: node linkType: hard +"caniuse-lite@npm:^1.0.30001669": + version: 1.0.30001677 + resolution: "caniuse-lite@npm:1.0.30001677" + checksum: 10c0/22b4aa738b213b5d0bc820c26ba23fa265ca90a5c59776e1a686b9ab6fff9120d0825fd920c0a601a4b65056ef40d01548405feb95c8dd6083255f50c71a0864 + languageName: node + linkType: hard + "case-sensitive-paths-webpack-plugin@npm:^2.4.0": version: 2.4.0 resolution: "case-sensitive-paths-webpack-plugin@npm:2.4.0" @@ -13598,6 +13990,13 @@ __metadata: languageName: node linkType: hard +"electron-to-chromium@npm:^1.5.41": + version: 1.5.50 + resolution: "electron-to-chromium@npm:1.5.50" + checksum: 10c0/8b77b18ae833bfe2173e346ac33b8d66b5b5acf0cf5de65df9799f4d482334c938aa0950e4d01391d5fab8994f46c0e9059f4517843e7b8d861f9b0c49eb4c5d + languageName: node + linkType: hard + "elliptic@npm:^6.5.3, elliptic@npm:^6.5.4": version: 6.5.4 resolution: "elliptic@npm:6.5.4" @@ -14265,6 +14664,13 @@ __metadata: languageName: node linkType: hard +"escalade@npm:^3.2.0": + version: 3.2.0 + resolution: "escalade@npm:3.2.0" + checksum: 10c0/ced4dd3a78e15897ed3be74e635110bbf3b08877b0a41be50dcb325ee0e0b5f65fc2d50e9845194d7c4633f327e2e1c6cce00a71b617c5673df0374201d67f65 + languageName: node + linkType: hard + "escape-html@npm:~1.0.3": version: 1.0.3 resolution: "escape-html@npm:1.0.3" @@ -18540,6 +18946,15 @@ __metadata: languageName: node linkType: hard +"jsesc@npm:^3.0.2": + version: 3.0.2 + resolution: "jsesc@npm:3.0.2" + bin: + jsesc: bin/jsesc + checksum: 10c0/ef22148f9e793180b14d8a145ee6f9f60f301abf443288117b4b6c53d0ecd58354898dc506ccbb553a5f7827965cd38bc5fb726575aae93c5e8915e2de8290e1 + languageName: node + linkType: hard + "jsesc@npm:~0.5.0": version: 0.5.0 resolution: "jsesc@npm:0.5.0" @@ -23102,6 +23517,17 @@ __metadata: languageName: node linkType: hard +"postcss@npm:^8.4.43": + version: 8.4.47 + resolution: "postcss@npm:8.4.47" + dependencies: + nanoid: "npm:^3.3.7" + picocolors: "npm:^1.1.0" + source-map-js: "npm:^1.2.1" + checksum: 10c0/929f68b5081b7202709456532cee2a145c1843d391508c5a09de2517e8c4791638f71dd63b1898dba6712f8839d7a6da046c72a5e44c162e908f5911f57b5f44 + languageName: node + linkType: hard + "preact@npm:^10.5.13": version: 10.19.3 resolution: "preact@npm:10.19.3" @@ -23975,6 +24401,13 @@ __metadata: languageName: node linkType: hard +"react-refresh@npm:^0.14.2": + version: 0.14.2 + resolution: "react-refresh@npm:0.14.2" + checksum: 10c0/875b72ef56b147a131e33f2abd6ec059d1989854b3ff438898e4f9310bfcc73acff709445b7ba843318a953cb9424bcc2c05af2b3d80011cee28f25aef3e2ebb + languageName: node + linkType: hard + "react-remove-scroll-bar@npm:^2.3.3": version: 2.3.5 resolution: "react-remove-scroll-bar@npm:2.3.5" @@ -25167,6 +25600,75 @@ __metadata: languageName: node linkType: hard +"rollup@npm:^4.20.0": + version: 4.24.3 + resolution: "rollup@npm:4.24.3" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.24.3" + "@rollup/rollup-android-arm64": "npm:4.24.3" + "@rollup/rollup-darwin-arm64": "npm:4.24.3" + "@rollup/rollup-darwin-x64": "npm:4.24.3" + "@rollup/rollup-freebsd-arm64": "npm:4.24.3" + "@rollup/rollup-freebsd-x64": "npm:4.24.3" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.24.3" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.24.3" + "@rollup/rollup-linux-arm64-gnu": "npm:4.24.3" + "@rollup/rollup-linux-arm64-musl": "npm:4.24.3" + "@rollup/rollup-linux-powerpc64le-gnu": "npm:4.24.3" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.24.3" + "@rollup/rollup-linux-s390x-gnu": "npm:4.24.3" + "@rollup/rollup-linux-x64-gnu": "npm:4.24.3" + "@rollup/rollup-linux-x64-musl": "npm:4.24.3" + "@rollup/rollup-win32-arm64-msvc": "npm:4.24.3" + "@rollup/rollup-win32-ia32-msvc": "npm:4.24.3" + "@rollup/rollup-win32-x64-msvc": "npm:4.24.3" + "@types/estree": "npm:1.0.6" + fsevents: "npm:~2.3.2" + dependenciesMeta: + "@rollup/rollup-android-arm-eabi": + optional: true + "@rollup/rollup-android-arm64": + optional: true + "@rollup/rollup-darwin-arm64": + optional: true + "@rollup/rollup-darwin-x64": + optional: true + "@rollup/rollup-freebsd-arm64": + optional: true + "@rollup/rollup-freebsd-x64": + optional: true + "@rollup/rollup-linux-arm-gnueabihf": + optional: true + "@rollup/rollup-linux-arm-musleabihf": + optional: true + "@rollup/rollup-linux-arm64-gnu": + optional: true + "@rollup/rollup-linux-arm64-musl": + optional: true + "@rollup/rollup-linux-powerpc64le-gnu": + optional: true + "@rollup/rollup-linux-riscv64-gnu": + optional: true + "@rollup/rollup-linux-s390x-gnu": + optional: true + "@rollup/rollup-linux-x64-gnu": + optional: true + "@rollup/rollup-linux-x64-musl": + optional: true + "@rollup/rollup-win32-arm64-msvc": + optional: true + "@rollup/rollup-win32-ia32-msvc": + optional: true + "@rollup/rollup-win32-x64-msvc": + optional: true + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: 10c0/32425475db7a0bcb8937f92488ee8e48f7adaff711b5b5c52d86d37114c9f21fe756e21a91312d12d30da146d33d8478a11dfeb6249dbecc54fbfcc78da46005 + languageName: node + linkType: hard + "rsvp@npm:^3.0.14, rsvp@npm:^3.0.18": version: 3.6.2 resolution: "rsvp@npm:3.6.2" @@ -26014,6 +26516,13 @@ __metadata: languageName: node linkType: hard +"source-map-js@npm:^1.2.1": + version: 1.2.1 + resolution: "source-map-js@npm:1.2.1" + checksum: 10c0/7bda1fc4c197e3c6ff17de1b8b2c20e60af81b63a52cb32ec5a5d67a20a7d42651e2cb34ebe93833c5a2a084377e17455854fee3e21e7925c64a51b6a52b0faf + languageName: node + linkType: hard + "source-map-loader@npm:5.0.0": version: 5.0.0 resolution: "source-map-loader@npm:5.0.0" @@ -28182,6 +28691,20 @@ __metadata: languageName: node linkType: hard +"update-browserslist-db@npm:^1.1.1": + version: 1.1.1 + resolution: "update-browserslist-db@npm:1.1.1" + dependencies: + escalade: "npm:^3.2.0" + picocolors: "npm:^1.1.0" + peerDependencies: + browserslist: ">= 4.21.0" + bin: + update-browserslist-db: cli.js + checksum: 10c0/536a2979adda2b4be81b07e311bd2f3ad5e978690987956bc5f514130ad50cac87cd22c710b686d79731e00fbee8ef43efe5fcd72baa241045209195d43dcc80 + languageName: node + linkType: hard + "uri-js@npm:^4.2.2": version: 4.4.1 resolution: "uri-js@npm:4.4.1" @@ -28621,6 +29144,49 @@ __metadata: languageName: node linkType: hard +"vite@npm:^4.0.0 || ^5.0.0": + version: 5.4.10 + resolution: "vite@npm:5.4.10" + dependencies: + esbuild: "npm:^0.21.3" + fsevents: "npm:~2.3.3" + postcss: "npm:^8.4.43" + rollup: "npm:^4.20.0" + peerDependencies: + "@types/node": ^18.0.0 || >=20.0.0 + less: "*" + lightningcss: ^1.21.0 + sass: "*" + sass-embedded: "*" + stylus: "*" + sugarss: "*" + terser: ^5.4.0 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + bin: + vite: bin/vite.js + checksum: 10c0/4ef4807d2fd166a920de244dbcec791ba8a903b017a7d8e9f9b4ac40d23f8152c1100610583d08f542b47ca617a0505cfc5f8407377d610599d58296996691ed + languageName: node + linkType: hard + "vite@npm:^4.0.0, vite@npm:^4.0.4": version: 4.5.1 resolution: "vite@npm:4.5.1" diff --git a/scripts/sandbox/generate.ts b/scripts/sandbox/generate.ts index db3d3ce02f23..1c5618453696 100755 --- a/scripts/sandbox/generate.ts +++ b/scripts/sandbox/generate.ts @@ -189,6 +189,8 @@ const runGenerators = async ( flags = ['--type html']; } else if (expected.renderer === '@storybook/server') { flags = ['--type server']; + } else if (expected.framework === '@storybook/react-native-web-vite') { + flags = ['--type react_native_web']; } const time = process.hrtime(); diff --git a/scripts/tasks/sandbox-parts.ts b/scripts/tasks/sandbox-parts.ts index 7123357ae93f..4f8dd85ecc46 100644 --- a/scripts/tasks/sandbox-parts.ts +++ b/scripts/tasks/sandbox-parts.ts @@ -138,6 +138,8 @@ export const init: Task['run'] = async ( extra = { type: 'html' }; } else if (template.expected.renderer === '@storybook/server') { extra = { type: 'server' }; + } else if (template.expected.framework === '@storybook/react-native-web-vite') { + extra = { type: 'react_native_web' }; } await executeCLIStep(steps.init, { From 59c10e8c8d3f5051d3385e6e5a5c3d6e8669a0d1 Mon Sep 17 00:00:00 2001 From: Daniel Williams Date: Sun, 3 Nov 2024 17:29:43 +0000 Subject: [PATCH 034/208] feat: add template files --- .../react-native-web-vite/package.json | 6 +- .../template/cli/.eslintrc.json | 6 + .../template/cli/ts-3-8/Button.stories.tsx | 45 +++++ .../template/cli/ts-3-8/Button.tsx | 123 +++++++++++++ .../template/cli/ts-3-8/Header.stories.tsx | 27 +++ .../template/cli/ts-3-8/Header.tsx | 82 +++++++++ .../template/cli/ts-3-8/Page.stories.tsx | 19 ++ .../template/cli/ts-3-8/Page.tsx | 167 ++++++++++++++++++ .../template/cli/ts-4-9/Button.stories.tsx | 45 +++++ .../template/cli/ts-4-9/Button.tsx | 123 +++++++++++++ .../template/cli/ts-4-9/Header.stories.tsx | 27 +++ .../template/cli/ts-4-9/Header.tsx | 80 +++++++++ .../template/cli/ts-4-9/Page.stories.tsx | 15 ++ .../template/cli/ts-4-9/Page.tsx | 159 +++++++++++++++++ .../src/generators/baseGenerator.ts | 2 +- 15 files changed, 922 insertions(+), 4 deletions(-) create mode 100644 code/frameworks/react-native-web-vite/template/cli/.eslintrc.json create mode 100644 code/frameworks/react-native-web-vite/template/cli/ts-3-8/Button.stories.tsx create mode 100644 code/frameworks/react-native-web-vite/template/cli/ts-3-8/Button.tsx create mode 100644 code/frameworks/react-native-web-vite/template/cli/ts-3-8/Header.stories.tsx create mode 100644 code/frameworks/react-native-web-vite/template/cli/ts-3-8/Header.tsx create mode 100644 code/frameworks/react-native-web-vite/template/cli/ts-3-8/Page.stories.tsx create mode 100644 code/frameworks/react-native-web-vite/template/cli/ts-3-8/Page.tsx create mode 100644 code/frameworks/react-native-web-vite/template/cli/ts-4-9/Button.stories.tsx create mode 100644 code/frameworks/react-native-web-vite/template/cli/ts-4-9/Button.tsx create mode 100644 code/frameworks/react-native-web-vite/template/cli/ts-4-9/Header.stories.tsx create mode 100644 code/frameworks/react-native-web-vite/template/cli/ts-4-9/Header.tsx create mode 100644 code/frameworks/react-native-web-vite/template/cli/ts-4-9/Page.stories.tsx create mode 100644 code/frameworks/react-native-web-vite/template/cli/ts-4-9/Page.tsx diff --git a/code/frameworks/react-native-web-vite/package.json b/code/frameworks/react-native-web-vite/package.json index 0c9d3ee7a369..ea71b798d292 100644 --- a/code/frameworks/react-native-web-vite/package.json +++ b/code/frameworks/react-native-web-vite/package.json @@ -57,8 +57,7 @@ "magic-string": "^0.30.0", "react-docgen": "^7.0.0", "resolve": "^1.22.8", - "tsconfig-paths": "^4.2.0", - "vite": "^4.0.0 || ^5.0.0" + "tsconfig-paths": "^4.2.0" }, "devDependencies": { "@types/node": "^22.0.0", @@ -69,7 +68,8 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-native": ">=0.74.5", "react-native-web": "^0.19.12", - "storybook": "workspace:^" + "storybook": "workspace:^", + "vite": "^4.0.0 || ^5.0.0" }, "engines": { "node": ">=18.0.0" diff --git a/code/frameworks/react-native-web-vite/template/cli/.eslintrc.json b/code/frameworks/react-native-web-vite/template/cli/.eslintrc.json new file mode 100644 index 000000000000..012842ba7b28 --- /dev/null +++ b/code/frameworks/react-native-web-vite/template/cli/.eslintrc.json @@ -0,0 +1,6 @@ +{ + "rules": { + "import/extensions": "off", + "react/no-unknown-property": "off" + } +} diff --git a/code/frameworks/react-native-web-vite/template/cli/ts-3-8/Button.stories.tsx b/code/frameworks/react-native-web-vite/template/cli/ts-3-8/Button.stories.tsx new file mode 100644 index 000000000000..b9d1f5943170 --- /dev/null +++ b/code/frameworks/react-native-web-vite/template/cli/ts-3-8/Button.stories.tsx @@ -0,0 +1,45 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { View } from 'react-native'; +import { Button } from './Button'; + +const meta: Meta = { + component: Button, + decorators: [ + (Story) => ( + + + + ), + ], +}; + +export default meta; + +type Story = StoryObj; + +export const Primary: Story = { + args: { + primary: true, + label: 'Button', + }, +}; + +export const Secondary: Story = { + args: { + label: 'Button', + }, +}; + +export const Large: Story = { + args: { + size: 'large', + label: 'Button', + }, +}; + +export const Small: Story = { + args: { + size: 'small', + label: 'Button', + }, +}; diff --git a/code/frameworks/react-native-web-vite/template/cli/ts-3-8/Button.tsx b/code/frameworks/react-native-web-vite/template/cli/ts-3-8/Button.tsx new file mode 100644 index 000000000000..5ca6dffb1b06 --- /dev/null +++ b/code/frameworks/react-native-web-vite/template/cli/ts-3-8/Button.tsx @@ -0,0 +1,123 @@ +import { + StyleSheet, + StyleProp, + ViewStyle, + TouchableOpacity, + Text, + View, +} from 'react-native'; + +export interface ButtonProps { + /** + * Is this the principal call to action on the page? + */ + primary?: boolean; + /** + * What background color to use + */ + backgroundColor?: string; + /** + * How large should the button be? + */ + size?: 'small' | 'medium' | 'large'; + /** + * Button contents + */ + label: string; + /** + * Optional click handler + */ + onPress?: () => void; + style?: StyleProp; +} + +/** + * Primary UI component for user interaction + */ +export const Button = ({ + primary = false, + size = 'medium', + backgroundColor, + label, + style, + onPress, +}: ButtonProps) => { + const modeStyle = primary ? styles.primary : styles.secondary; + const textModeStyle = primary ? styles.primaryText : styles.secondaryText; + + const sizeStyle = styles[size]; + const textSizeStyle = textSizeStyles[size]; + + return ( + + + {label} + + + ); +}; + +const styles = StyleSheet.create({ + button: { + borderWidth: 0, + borderRadius: 48, + }, + buttonText: { + fontWeight: '700', + lineHeight: 1, + }, + primary: { + backgroundColor: '#1ea7fd', + }, + primaryText: { + color: 'white', + }, + secondary: { + backgroundColor: 'transparent', + borderColor: 'rgba(0, 0, 0, 0.15)', + borderWidth: 1, + }, + secondaryText: { + color: '#333', + }, + small: { + paddingVertical: 10, + paddingHorizontal: 16, + }, + smallText: { + fontSize: 12, + }, + medium: { + paddingVertical: 11, + paddingHorizontal: 20, + }, + mediumText: { + fontSize: 14, + }, + large: { + paddingVertical: 12, + paddingHorizontal: 24, + }, + largeText: { + fontSize: 16, + }, +}); + +const textSizeStyles = { + small: styles.smallText, + medium: styles.mediumText, + large: styles.largeText, +}; diff --git a/code/frameworks/react-native-web-vite/template/cli/ts-3-8/Header.stories.tsx b/code/frameworks/react-native-web-vite/template/cli/ts-3-8/Header.stories.tsx new file mode 100644 index 000000000000..0781c62ea63a --- /dev/null +++ b/code/frameworks/react-native-web-vite/template/cli/ts-3-8/Header.stories.tsx @@ -0,0 +1,27 @@ +import { Meta, StoryObj } from '@storybook/react'; +import { Header } from './Header'; + +const meta: Meta = { + component: Header, +}; + +export default meta; + +type Story = StoryObj; + +export const LoggedIn: Story = { + args: { + user: {}, + onLogin: () => {}, + onLogout: () => {}, + onCreateAccount: () => {}, + }, +}; + +export const LoggedOut: Story = { + args: { + onLogin: () => {}, + onLogout: () => {}, + onCreateAccount: () => {}, + }, +}; diff --git a/code/frameworks/react-native-web-vite/template/cli/ts-3-8/Header.tsx b/code/frameworks/react-native-web-vite/template/cli/ts-3-8/Header.tsx new file mode 100644 index 000000000000..34ab15d11e63 --- /dev/null +++ b/code/frameworks/react-native-web-vite/template/cli/ts-3-8/Header.tsx @@ -0,0 +1,82 @@ +import { Button } from './Button'; +import { View, Text, StyleSheet } from 'react-native'; + +export type HeaderProps = { + user?: {}; + onLogin: () => void; + onLogout: () => void; + onCreateAccount: () => void; +}; + +export const Header = ({ + user, + onLogin, + onLogout, + onCreateAccount, +}: HeaderProps) => ( + + + + Acme + + + + {user ? ( + ; ``` @@ -81,9 +76,7 @@ export const Button = () => ; {/* prettier-ignore-start */} -```md -{/* ButtonDocs.mdx */} - +```md title="ButtonDocs.mdx" import { Meta, Description } from '@storybook/blocks'; import * as ButtonStories from './Button.stories'; diff --git a/docs/api/doc-blocks/doc-block-icongallery.mdx b/docs/api/doc-blocks/doc-block-icongallery.mdx index 7da3d7960cc2..be79b79e9213 100644 --- a/docs/api/doc-blocks/doc-block-icongallery.mdx +++ b/docs/api/doc-blocks/doc-block-icongallery.mdx @@ -11,12 +11,14 @@ The `IconGallery` block enables you to easily document React icon components ass ![Screenshot of IconGallery and IconItem blocks](../../_assets/api/doc-block-icongallery.png) -{/* prettier-ignore-start */} +## Documenting icons + +To document a set of icons, use the `IconGallery` block to display them in a grid. Each icon is wrapped in an `IconItem` block, enabling you to specify its properties, such as the name and the icon itself. -```md -{/* Iconography.mdx */} +{/* prettier-ignore-start */} -import { Meta, Title, IconGallery, IconItem } from '@storybook/blocks'; +```md title="Iconography.mdx" +import { Meta, IconGallery, IconItem } from '@storybook/blocks'; import { Icon as IconExample } from './Icon'; @@ -60,6 +62,31 @@ import { Icon as IconExample } from './Icon'; {/* prettier-ignore-end */} +### Automate icon documentation + +If you're working on a project that contains a large number of icons that you want to document, you can extend the `IconGallery` block, wrap `IconItem` in a loop, and iterate over the icons you want to document, including their properties. For example: + +{/* prettier-ignore-start */} + +```md title="Iconography.mdx" +import { Meta, IconGallery, IconItem } from '@storybook/blocks'; + +import { Icon as IconExample } from './Icon'; +import * as icons from './icons'; + +# Iconography + + + {Object.keys(icons).map((icon) => ( + + + + ))} + +``` + +{/* prettier-ignore-end */} + ## IconGallery ```js diff --git a/docs/api/doc-blocks/doc-block-markdown.mdx b/docs/api/doc-blocks/doc-block-markdown.mdx index ea807584bbe2..2732cd9b2cb5 100644 --- a/docs/api/doc-blocks/doc-block-markdown.mdx +++ b/docs/api/doc-blocks/doc-block-markdown.mdx @@ -13,9 +13,7 @@ When importing markdown files, itโ€™s important to use the `?raw` suffix on the {/* prettier-ignore-start */} -````md -{/* README.md */} - +````md title="README.md" # Button Primary UI component for user interaction @@ -29,9 +27,7 @@ import { Button } from "@storybook/design-system"; {/* prettier-ignore-start */} -```md -{/* Header.mdx */} - +```md title="Header.mdx" // DON'T do this, will error import ReadMe from './README.md'; // DO this, will work diff --git a/docs/api/doc-blocks/doc-block-meta.mdx b/docs/api/doc-blocks/doc-block-meta.mdx index f0121df062e1..7d7cc0f724fa 100644 --- a/docs/api/doc-blocks/doc-block-meta.mdx +++ b/docs/api/doc-blocks/doc-block-meta.mdx @@ -12,9 +12,7 @@ The `Meta` block is used to [attach](#attached-vs-unattached) a custom MDX docs {/* prettier-ignore-start */} -```md -{/* ButtonDocs.mdx */} - +```md title="ButtonDocs.mdx" import { Meta } from '@storybook/blocks'; import * as ButtonStories from './Button.stories'; @@ -49,9 +47,7 @@ Sets the name of the [attached](#attached-vs-unattached) doc entry. You can atta {/* prettier-ignore-start */} -```md -{/* Component.mdx */} - +```md title="Component.mdx" import { Meta } from '@storybook/blocks'; import * as ComponentStories from './component.stories'; @@ -69,9 +65,7 @@ Specifies which CSF file is [attached](#attached-vs-unattached) to this MDX file {/* prettier-ignore-start */} -```md -{/* ButtonDocs.mdx */} - +```md title="ButtonDocs.mdx" import { Meta, Story } from '@storybook/blocks'; import * as ButtonStories from './Button.stories'; diff --git a/docs/api/doc-blocks/doc-block-primary.mdx b/docs/api/doc-blocks/doc-block-primary.mdx index 62882f3a1d88..28dd3d678fd2 100644 --- a/docs/api/doc-blocks/doc-block-primary.mdx +++ b/docs/api/doc-blocks/doc-block-primary.mdx @@ -13,9 +13,7 @@ The `Primary` block displays the primary (first defined in the stories file) sto {/* prettier-ignore-start */} -```md -{/* ButtonDocs.mdx */} - +```md title="ButtonDocs.mdx" import { Meta, Primary } from '@storybook/blocks'; import * as ButtonStories from './Button.stories'; diff --git a/docs/api/doc-blocks/doc-block-source.mdx b/docs/api/doc-blocks/doc-block-source.mdx index e2d5f94d5985..f3edb6d6df9d 100644 --- a/docs/api/doc-blocks/doc-block-source.mdx +++ b/docs/api/doc-blocks/doc-block-source.mdx @@ -13,9 +13,7 @@ The `Source` block is used to render a snippet of source code directly. {/* prettier-ignore-start */} -```md -{/* ButtonDocs.mdx */} - +```md title="ButtonDocs.mdx" import { Meta, Source } from '@storybook/blocks'; import * as ButtonStories from './Button.stories'; @@ -47,9 +45,7 @@ import { Source } from '@storybook/blocks'; {/* prettier-ignore-start */} - ```md - {/* ButtonDocs.mdx */} - + ```md title="ButtonDocs.mdx" ``` @@ -68,9 +64,7 @@ Provides the source code to be rendered. {/* prettier-ignore-start */} -```md -{/* ButtonDocs.mdx */} - +```md title="ButtonDocs.mdx" import { Meta, Source } from '@storybook/blocks'; import * as ButtonStories from './Button.stories'; diff --git a/docs/api/doc-blocks/doc-block-stories.mdx b/docs/api/doc-blocks/doc-block-stories.mdx index 42cd9fcc2c95..6dcd5e0bff59 100644 --- a/docs/api/doc-blocks/doc-block-stories.mdx +++ b/docs/api/doc-blocks/doc-block-stories.mdx @@ -13,9 +13,7 @@ The `Stories` block renders the full collection of stories in a stories file. {/* prettier-ignore-start */} -```md -{/* ButtonDocs.mdx */} - +```md title="ButtonDocs.mdx" import { Meta, Stories } from '@storybook/blocks'; import * as ButtonStories from './Button.stories'; diff --git a/docs/api/doc-blocks/doc-block-story.mdx b/docs/api/doc-blocks/doc-block-story.mdx index c5a8828f421f..df1446ecb550 100644 --- a/docs/api/doc-blocks/doc-block-story.mdx +++ b/docs/api/doc-blocks/doc-block-story.mdx @@ -21,9 +21,7 @@ In Storybook Docs, you can render any of your stories from your CSF files in the {/* prettier-ignore-start */} -```md -{/* ButtonDocs.mdx */} - +```md title="ButtonDocs.mdx"+ import { Meta, Story } from '@storybook/blocks'; import * as ButtonStories from './Button.stories'; @@ -55,9 +53,7 @@ import { Story } from '@storybook/blocks'; {/* prettier-ignore-start */} - ```md - {/* ButtonDocs.mdx */} - + ```md title="ButtonDocs.mdx" ``` @@ -114,9 +110,7 @@ You can render a story from a CSF file that you havenโ€™t attached to the MDX fi {/* prettier-ignore-start */} -```md -{/* ButtonDocs.mdx */} - +```md title="ButtonDocs.mdx" import { Meta, Story } from '@storybook/blocks'; import * as ButtonStories from './Button.stories'; import * as HeaderStories from './Header.stories'; diff --git a/docs/api/doc-blocks/doc-block-subtitle.mdx b/docs/api/doc-blocks/doc-block-subtitle.mdx index a04da42c328d..dee2b243f534 100644 --- a/docs/api/doc-blocks/doc-block-subtitle.mdx +++ b/docs/api/doc-blocks/doc-block-subtitle.mdx @@ -13,9 +13,7 @@ The `Subtitle` block can serve as a secondary heading for your docs entry. {/* prettier-ignore-start */} -```md -{/* ButtonDocs.mdx */} - +```md title="ButtonDocs.mdx" import { Subtitle } from '@storybook/blocks'; This is the subtitle diff --git a/docs/api/doc-blocks/doc-block-title.mdx b/docs/api/doc-blocks/doc-block-title.mdx index 3d8c74c25949..54a474b738f7 100644 --- a/docs/api/doc-blocks/doc-block-title.mdx +++ b/docs/api/doc-blocks/doc-block-title.mdx @@ -13,9 +13,7 @@ The `Title` block serves as the primary heading for your docs entry. It is typic {/* prettier-ignore-start */} -```md -{/* ButtonDocs.mdx */} - +```md title="ButtonDocs.mdx" import { Title } from '@storybook/blocks'; This is the title diff --git a/docs/api/doc-blocks/doc-block-typeset.mdx b/docs/api/doc-blocks/doc-block-typeset.mdx index 8ceb2ab7b67d..5cedbf8ab772 100644 --- a/docs/api/doc-blocks/doc-block-typeset.mdx +++ b/docs/api/doc-blocks/doc-block-typeset.mdx @@ -13,9 +13,7 @@ The `Typeset` block helps document the fonts used throughout your project. {/* prettier-ignore-start */} -```md -{/* Typography.mdx */} - +```md title="Typography.mdx" import { Meta, Typeset } from '@storybook/blocks'; diff --git a/docs/api/doc-blocks/doc-block-unstyled.mdx b/docs/api/doc-blocks/doc-block-unstyled.mdx index 4e9e56a8ade3..dce273c69d0b 100644 --- a/docs/api/doc-blocks/doc-block-unstyled.mdx +++ b/docs/api/doc-blocks/doc-block-unstyled.mdx @@ -11,7 +11,7 @@ By default, most elements (like `h1`, `p`, etc.) in docs have a few default styl {/* prettier-ignore-start */} -```md +```md title="ButtonDocs.mdx" import { Meta, Unstyled } from "@storybook/blocks"; import { Header } from "./Header.tsx"; diff --git a/docs/api/doc-blocks/doc-block-useof.mdx b/docs/api/doc-blocks/doc-block-useof.mdx index 7e5944c07869..89a7fb7c7d05 100644 --- a/docs/api/doc-blocks/doc-block-useof.mdx +++ b/docs/api/doc-blocks/doc-block-useof.mdx @@ -11,9 +11,7 @@ If your own doc blocks need to interface with annotations from Storybookโ€”that Hereโ€™s an example of how the`useOf` hook could be used to create a custom block that displays the name of the story: -```jsx -// .storybook/blocks/StoryName.jsx - +```jsx title=".storybook/blocks/StoryName.jsx" import { useOf } from '@storybook/blocks'; /** @@ -38,22 +36,20 @@ export const StoryName = ({ of }) => { {/* prettier-ignore-start */} -```md -{/* ButtonDocs.mdx */} - +```md title="ButtonDocs.mdx" import { Meta } from '@storybook/blocks'; import { StoryName } from '../.storybook/blocks/StoryName'; import * as ButtonStories from './Button.stories'; -{/* renders "Secondary" */} +{/* Renders "Secondary" */} -{/* renders "Primary" */} +{/* Renders "Primary" */} -{/* renders "Button" */} +{/* Renders "Button" */} ``` diff --git a/docs/api/new-frameworks.mdx b/docs/api/new-frameworks.mdx index edd780db1191..11d8b8407f13 100644 --- a/docs/api/new-frameworks.mdx +++ b/docs/api/new-frameworks.mdx @@ -33,7 +33,7 @@ Storybook has the concept of [presets](../addons/writing-presets.mdx), which are It's helpful to understand Storybook's package structure before adding a framework preset. Each framework typically exposes two executables in its `package.json`: -```json +```json title="package.json" { "bin": { "storybook": "./bin/index.js", @@ -94,7 +94,7 @@ Consider the following React story: {/* prettier-ignore-start */} - + {/* prettier-ignore-end */} @@ -106,7 +106,7 @@ Consider the following hypothetical example: {/* prettier-ignore-start */} - + {/* prettier-ignore-end */} diff --git a/docs/api/parameters.mdx b/docs/api/parameters.mdx index 8f1313203871..28362afe6a5a 100644 --- a/docs/api/parameters.mdx +++ b/docs/api/parameters.mdx @@ -9,10 +9,6 @@ Parameters are static metadata used to configure your [stories](../get-started/w ## Story parameters -
- โ„น๏ธ Parameters specified at the story level will [override](#parameter-inheritance) those specified at the project level and meta (component) level. -
- Parameters specified at the story level apply to that story only. They are defined in the `parameters` property of the story (named export): {/* prettier-ignore-start */} @@ -21,11 +17,11 @@ Parameters specified at the story level apply to that story only. They are defin {/* prettier-ignore-end */} -## Meta parameters + + Parameters specified at the story level will [override](#parameter-inheritance) those specified at the project level and meta (component) level. + -
- โ„น๏ธ Parameters specified at the meta (component) level will [override](#parameter-inheritance) those specified at the project level. -
+## Meta parameters Parameter's specified in a [CSF](../writing-stories/index.mdx#component-story-format-csf) file's meta configuration apply to all stories in that file. They are defined in the `parameters` property of the `meta` (default export): @@ -35,6 +31,10 @@ Parameter's specified in a [CSF](../writing-stories/index.mdx#component-story-fo {/* prettier-ignore-end */} + + Parameters specified at the meta (component) level will [override](#parameter-inheritance) those specified at the project level. + + ## Project parameters Parameters specified at the project (global) level apply to **all stories** in your Storybook. They are defined in the `parameters` property of the default export in your `.storybook/preview.js|ts` file: @@ -172,7 +172,6 @@ All other parameters are contributed by addons. The [essential addon's](../essen * [Backgrounds](../essentials/backgrounds.mdx#parameters) * [Controls](../essentials/controls.mdx#parameters) * [Highlight](../essentials/highlight.mdx#parameters) -* [Interactions](../essentials/interactions.mdx#parameters) * [Measure & Outline](../essentials/measure-and-outline.mdx#parameters) * [Viewport](../essentials/viewport.mdx#parameters) @@ -186,15 +185,13 @@ When specifying parameters, they are merged together in order of increasing spec 2. Meta (component) parameters 3. Story parameters -
- โ„น๏ธ Parameters are **merged**, so objects are deep-merged, but arrays and other properties are overwritten. -
+ + Parameters are **merged**, so objects are deep-merged, but arrays and other properties are overwritten. + In other words, the following specifications of parameters: -```js -// .storybook/preview.js|ts - +```js title=".storybook/preview.js|ts" const preview = { // ๐Ÿ‘‡ Project-level parameters parameters: { @@ -209,9 +206,7 @@ const preview = { export default preview; ``` -```js -// Dialog.stories.js|ts - +```js title="Dialog.stories.js|ts" const meta = { component: Dialog, // ๐Ÿ‘‡ Meta-level parameters diff --git a/docs/configure/environment-variables.mdx b/docs/configure/environment-variables.mdx index 1403cc371c69..f1b14c9a4c5c 100644 --- a/docs/configure/environment-variables.mdx +++ b/docs/configure/environment-variables.mdx @@ -90,7 +90,7 @@ When Storybook loads, it will enable you to access them in your stories similar {/* prettier-ignore-start */} - + {/* prettier-ignore-end */} diff --git a/docs/configure/integration/frameworks-feature-support.mdx b/docs/configure/integration/frameworks-feature-support.mdx index aead9b265217..006673db19f1 100644 --- a/docs/configure/integration/frameworks-feature-support.mdx +++ b/docs/configure/integration/frameworks-feature-support.mdx @@ -1,5 +1,6 @@ --- title: 'Feature support for frameworks' +hideRendererSelector: true sidebar: order: 2 title: Feature support for frameworks @@ -117,4 +118,4 @@ To align the Storybook ecosystem with the current state of frontend development, | ---------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Knobs](https://github.com/storybookjs/addon-knobs) | The Knobs addon was officially deprecated with the release of Storybook 6.3 and is no longer actively maintained. We recommend using the [controls](../../essentials/controls.mdx) instead. | | [Storyshots](../../writing-tests/snapshot-testing/storyshots-migration-guide.mdx) | The Storyshots addon was officially deprecated with the release of Storybook 7.6, is no longer actively maintained and was removed in Storybook 8. See the [migration guide](../../writing-tests/snapshot-testing/storyshots-migration-guide.mdx) for the available alternatives. | -| StoriesOf | The `storiesOf` API was officially removed with the release of Storybook 8 and is no longer maintained. We recommend using the [CSF API](../../api/csf.mdx) instead for writing stories.
See the [migration guide](../../migration-guide/index.mdx#storiesof-to-csf) for more information. | +| StoriesOf | The `storiesOf` API was officially removed with the release of Storybook 8 and is no longer maintained. We recommend using the [CSF API](../../api/csf.mdx) instead for writing stories.
See the [migration guide](../../migration-guide/index.mdx#major-breaking-changes) for more information. | diff --git a/docs/configure/integration/images-and-assets.mdx b/docs/configure/integration/images-and-assets.mdx index c500de8d210c..5d125c16d41a 100644 --- a/docs/configure/integration/images-and-assets.mdx +++ b/docs/configure/integration/images-and-assets.mdx @@ -15,7 +15,7 @@ Afterward, you can use any asset in your stories: {/* prettier-ignore-start */} - + {/* prettier-ignore-end */} @@ -35,7 +35,7 @@ Here `../public` is your static directory. Now use it in a component or story li {/* prettier-ignore-start */} - + {/* prettier-ignore-end */} @@ -61,7 +61,7 @@ Upload your files to an online CDN and reference them. In this example, weโ€™re {/* prettier-ignore-start */} - + {/* prettier-ignore-end */} diff --git a/docs/configure/story-layout.mdx b/docs/configure/story-layout.mdx index c17f520d3579..deac6de3b8e7 100644 --- a/docs/configure/story-layout.mdx +++ b/docs/configure/story-layout.mdx @@ -41,6 +41,6 @@ Or even apply it to specific stories like so: {/* prettier-ignore-start */} - + {/* prettier-ignore-end */} diff --git a/docs/configure/story-rendering.mdx b/docs/configure/story-rendering.mdx index dccc3e518d1e..3749a925e9c8 100644 --- a/docs/configure/story-rendering.mdx +++ b/docs/configure/story-rendering.mdx @@ -14,8 +14,7 @@ Code executed in the preview file (`.storybook/preview.js|ts`) runs for every st Here's an example of how you might use the preview file to initialize a library that must run before your components render: - ```ts - // .storybook/preview.ts + ```ts title=".storybook/preview.ts" // Replace your-renderer with the renderer you are using (e.g., react, vue3) import { Preview } from '@storybook/your-renderer'; diff --git a/docs/configure/styling-and-css.mdx b/docs/configure/styling-and-css.mdx index ceebd2bb6957..70c0b4303c3f 100644 --- a/docs/configure/styling-and-css.mdx +++ b/docs/configure/styling-and-css.mdx @@ -111,7 +111,7 @@ sidebar: Don't forget to also add your global styles to your `build-storybook` target in your `angular.json` file. This will ensure that your global styles are included in the static build of your Storybook as well. - ```json + ```json title="angular.json" { "storybook": { "builder": "@storybook/angular:start-storybook", @@ -162,8 +162,9 @@ sidebar: For earlier Nx versions (before 14.1.8), your configuration would look like this: - ```json - "build-storybook": { + ```json title="project.json" + { + "build-storybook": { "executor": "@nrwl/storybook:build", "outputs": ["{options.outputPath}"], "options": { @@ -175,34 +176,37 @@ sidebar: "projectBuildConfig": "example-lib:build-storybook", "styles": ["apps/example-app/src/styles.scss"] } - } + }, + } ``` Starting with version 14.1.8, Nx uses the Storybook builder directly, which means any configuration supplied to the builder also applies to the NX setup. If you're working with a library, you'll need to configure the styling options ( e.g., preprocessors) inside the `build-storybook` options configuration object. For example: - ```json - "storybook": { - "executor": "@storybook/angular:start-storybook", - "options": { - "configDir": "apps/example-lib/.storybook", - "browserTarget": "example-lib:build-storybook", - }, + ```json title="workspace.json" + { + "storybook": { + "executor": "@storybook/angular:start-storybook", + "options": { + "configDir": "apps/example-lib/.storybook", + "browserTarget": "example-lib:build-storybook", }, - "build-storybook": { - "executor": "@storybook/angular:build-storybook", - "outputs": ["{options.outputPath}"], - "options": { - "outputDir": "dist/storybook/example-lib", - "configDir": "apps/example-lib/.storybook", - "browserTarget": "example-lib:build-storybook", - "styles": [".storybook/custom-styles.scss"], - "stylePreprocessorOptions": { - "includePaths": [ - "libs/design-system/src/lib" - ] - } + }, + "build-storybook": { + "executor": "@storybook/angular:build-storybook", + "outputs": ["{options.outputPath}"], + "options": { + "outputDir": "dist/storybook/example-lib", + "configDir": "apps/example-lib/.storybook", + "browserTarget": "example-lib:build-storybook", + "styles": [".storybook/custom-styles.scss"], + "stylePreprocessorOptions": { + "includePaths": [ + "libs/design-system/src/lib" + ] } } + }, + } ``` When Nx runs, it will load Storybook's configuration and styling based on [`storybook.browserTarget`](https://nx.dev/storybook/extra-topics-for-angular-projects#setting-up-browsertarget). diff --git a/docs/configure/telemetry.mdx b/docs/configure/telemetry.mdx index d4c3af5804da..56a6ef8cc707 100644 --- a/docs/configure/telemetry.mdx +++ b/docs/configure/telemetry.mdx @@ -1,5 +1,6 @@ --- title: 'Telemetry' +hideRendererSelector: true sidebar: order: 3 title: Telemetry @@ -163,7 +164,7 @@ In the future, we plan to share relevant data with the community through public ## How to opt-out -You may opt-out of the telemetry by setting Storybook's configuration element `disableTelemetry` to `true`, using the `--disable-telemetry` flag, or setting the environment variable`STORYBOOK_DISABLE_TELEMETRY` to `1`. For example: +You may opt out of the telemetry within your Storybook configuration by setting the `disableTelemetry` configuration element to `true`. {/* prettier-ignore-start */} @@ -171,13 +172,29 @@ You may opt-out of the telemetry by setting Storybook's configuration element `d {/* prettier-ignore-end */} +If necessary, you can also turn off telemetry via the command line with the `--disable-telemetry` flag. + +{/* prettier-ignore-start */} + + + +{/* prettier-ignore-end */} + +Or via the `STORYBOOK_DISABLE_TELEMETRY` environment variable. + +{/* prettier-ignore-start */} + + + +{/* prettier-ignore-end */} + There is a `boot` event containing no metadata (used to ensure the telemetry is working). It is sent prior to evaluating your [Storybook configuration file](../api/main-config/main-config.mdx) (i.e., `main.js|ts`), so it is unaffected by the `disableTelemetry` option. If you want to ensure that the event is not sent, use the `STORYBOOK_DISABLE_TELEMETRY` environment variable. ## Crash reports (disabled by default) -In addition to general usage telemetry, you may also choose to share crash reports. Storybook will then sanitize the error object (removing all user paths) and append it to the telemetry event. To enable crash reporting, you can set the `enableCrashReports` configuration element to `true`, using the `--enable-crash-reports` flag, or set the `STORYBOOK_ENABLE_CRASH_REPORTS` environment variable to `1`. For example: +In addition to general usage telemetry, you may also choose to share crash reports. Storybook will then sanitize the error object (removing all user paths) and append it to the telemetry event. To enable crash reporting, you can set the `enableCrashReports` configuration element to `true`. {/* prettier-ignore-start */} @@ -185,7 +202,23 @@ In addition to general usage telemetry, you may also choose to share crash repor {/* prettier-ignore-end */} -Generates the following item in the telemetry event: +You can also enable crash reporting via the command line with the `--enable-crash-reports` flag. + +{/* prettier-ignore-start */} + + + +{/* prettier-ignore-end */} + +Or by setting the `STORYBOOK_ENABLE_CRASH_REPORTS` environment variable to `1`. + +{/* prettier-ignore-start */} + + + +{/* prettier-ignore-end */} + +Enabling any of the options will generate the following item in the telemetry event: {/* prettier-ignore-start */} diff --git a/docs/configure/user-interface/sidebar-and-urls.mdx b/docs/configure/user-interface/sidebar-and-urls.mdx index 55537246e214..8ee6ecd62f3d 100644 --- a/docs/configure/user-interface/sidebar-and-urls.mdx +++ b/docs/configure/user-interface/sidebar-and-urls.mdx @@ -35,7 +35,7 @@ Consider the following story: {/* prettier-ignore-start */} - + {/* prettier-ignore-end */} @@ -45,7 +45,7 @@ It is possible to manually set the story's id, which is helpful if you want to r {/* prettier-ignore-start */} - + {/* prettier-ignore-end */} diff --git a/docs/contribute/RFC.mdx b/docs/contribute/RFC.mdx index 32f1aae9b636..d67f71e9a179 100644 --- a/docs/contribute/RFC.mdx +++ b/docs/contribute/RFC.mdx @@ -1,5 +1,6 @@ --- title: 'RFC process' +hideRendererSelector: true sidebar: order: 1 title: RFC process @@ -55,4 +56,4 @@ This RFC process took heavy inspiration from the RFC processes from [Rust](https * [Code](./code.mdx) for features and bug fixes * [Frameworks](./framework.mdx) to get started with a new framework * [Documentation](./documentation/documentation-updates.mdx) for documentation improvements, typos, and clarifications -* [Examples](./documentation/new-snippets.mdx) for new snippets and examples +* [Examples](./documentation/new-snippets.mdx) for new snippets diff --git a/docs/contribute/code.mdx b/docs/contribute/code.mdx index 6621bfcea257..51a74533afde 100644 --- a/docs/contribute/code.mdx +++ b/docs/contribute/code.mdx @@ -1,5 +1,6 @@ --- title: 'Code contributions' +hideRendererSelector: true sidebar: order: 2 title: Code @@ -264,4 +265,4 @@ Once the PR is merged, the template will be generated on a nightly cadence and y * Code for features and bug fixes * [Frameworks](./framework.mdx) to get started with a new framework * [Documentation](./documentation/documentation-updates.mdx) for documentation improvements, typos, and clarifications -* [Examples](./documentation/new-snippets.mdx) for new snippets and examples +* [Examples](./documentation/new-snippets.mdx) for new snippets diff --git a/docs/contribute/documentation/documentation-updates.mdx b/docs/contribute/documentation/documentation-updates.mdx index 3e62a5a4d225..51e38f8a523a 100644 --- a/docs/contribute/documentation/documentation-updates.mdx +++ b/docs/contribute/documentation/documentation-updates.mdx @@ -1,5 +1,6 @@ --- title: 'Documentation updates' +hideRendererSelector: true sidebar: order: 1 title: Content @@ -31,4 +32,4 @@ In the Storybook repository, create a pull request that describes changes and in * [Code](../code.mdx) for features and bug fixes * [Frameworks](../framework.mdx) to get started with a new framework * Documentation for documentation improvements, typos, and clarifications -* [Examples](./new-snippets.mdx) for new snippets and examples +* [Examples](./new-snippets.mdx) for new snippets diff --git a/docs/contribute/documentation/new-snippets.mdx b/docs/contribute/documentation/new-snippets.mdx index 1a7cf0c0b601..f70d15f633eb 100644 --- a/docs/contribute/documentation/new-snippets.mdx +++ b/docs/contribute/documentation/new-snippets.mdx @@ -1,5 +1,6 @@ --- title: 'Code snippets contributions' +hideRendererSelector: true sidebar: order: 2 title: Code snippets @@ -9,129 +10,288 @@ Add or update the code snippets in the documentation. This page outlines how the ## Documented frameworks -Storybook maintains code snippets for a [variety of frameworks](../../configure/integration/frameworks-feature-support.mdx). We try to keep them up to date as framework APIs evolve. But it's tricky to keep track of every API change in every framework. +Storybook maintains code snippets for a [variety of frameworks](../../configure/integration/frameworks-feature-support.mdx). We try to keep them up to date as framework APIs evolve. But keeping track of every API change in every framework is tricky. -We welcome community contributions to the code snippets. Here's a matrix of the frameworks we have snippets for. Help us add snippets for your favorite framework. +We welcome community contributions to the code snippets. Here's a matrix of the frameworks for which we have snippets. Help us add snippets for your favorite framework. -| React | Vue 3 | Angular | Web Components | Svelte | Solid | Ember | HTML | Preact | -| ---------------------------------------------------------------------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | ----- | ---- | ------ | -| [โœ…](https://github.com/storybookjs/storybook/tree/next/docs/snippets/react) | [โœ…](https://github.com/storybookjs/storybook/tree/next/docs/snippets/vue) | [โœ…](https://github.com/storybookjs/storybook/tree/next/docs/snippets/angular) | [โœ…](https://github.com/storybookjs/storybook/tree/next/docs/snippets/web-components) | [โœ…](https://github.com/storybookjs/storybook/tree/next/docs/snippets/svelte) | [โœ…](https://github.com/storybookjs/storybook/tree/next/docs/snippets/solid) | โŒ | โŒ | โŒ | -## Setup +| React | Vue 3 | Angular | Web Components | Svelte | Solid | Ember | HTML | Preact | Qwik | +| ------ | ----- | --------- | ---------------|--------|--------|-------|------| -------|------| +| โœ… | โœ… | โœ… | โœ… | โœ… | โœ… | โŒ | โŒ | โŒ | โŒ | -Assuming you've already followed the [local environment guide](../code.mdx#initial-setup), the first thing you need to do is create a branch on your local Storybook monorepo by running the following command: -```shell -git checkout -b code-snippets-for-framework +## Snippet syntax + +The code snippets referenced throughout the Storybook documentation are located in the [`docs/_snippets`](https://github.com/storybookjs/storybook/tree/next/docs/_snippets) directory inside individual Markdown files, containing the [supported frameworks](../../configure/integration/frameworks-feature-support.mdx), features and languages (i.e., JavaScript, MDX, TypeScript). + +### Example + +The following code block demonstrates how to structure a code snippet in the Storybook documentation and the attributes you can use to provide additional context to the code snippet. + +{/* prettier-ignore-start */} + +````md title="docs/_snippets/button-group-story.md" +```ts filename="ButtonGroup.stories.ts" renderer="vue" language="ts" tabTitle="3" +import type { Meta, StoryObj } from '@storybook/vue3'; + +import ButtonGroup from './ButtonGroup.vue'; + +//๐Ÿ‘‡ Imports the Button stories +import * as ButtonStories from './Button.stories'; + +const meta: Meta = { + component: ButtonGroup, +}; + +export default meta; +type Story = StoryObj; + +export const Pair: Story = { + render: (args) => ({ + components: { ButtonGroup }, + setup() { + return { args }; + }, + template: '', + }), + args: { + buttons: [{ ...ButtonStories.Primary.args }, { ...ButtonStories.Secondary.args }], + orientation: 'horizontal', + }, +}; +``` +```` + +{/* prettier-ignore-end */} + + +## Common attributes for code snippets + +Following are the attributes you'll use most often in the Storybook documentation code snippets, as well as a brief explanation of each to help you understand the context in which they are used. + +### File name as title + +Most code examples should include a file name so readers can understand which file they relate to and where to paste it into their project. For code examples, include the `filename` attribute wrapped with quotation marks to indicate the file name. This is not required if the example relates to a terminal command. + +{/* prettier-ignore-start */} + +````md title="docs/_snippets/button-stories.md" +```ts filename="Button.stories.ts" ``` +```` -Before adding your snippets, open the `docs` folder with your editor of choice. Get familiarized with the documentation, including how the snippets are organized and their contents. +{/* prettier-ignore-end */} -Then inside the root folder of the Storybook monorepo, run the following command: +### Language configuration -```shell -yarn task +Use the `language` attribute to define the language to which the code snippet applies. The documentation uses this attribute to determine which variant to display (e.g., JavaScript, TypeScript, TypeScript 4.9, MDX). + +{/* prettier-ignore-start */} + +````md title="docs/_snippets/button-stories.md" +```ts filename="Button.stories.ts" language="js|ts|ts-4-9|mdx" ``` +```` -Select the option `Synchronize documentation (sync-docs)` and type the path of your `frontpage` project folder. Now every file change inside the monorepo `docs` folder will be reflected in the frontpage repo at `src/content/docs`. +{/* prettier-ignore-end */} -### Add your first snippet +### Framework-specific code -Now that you're familiar with how the documentation is structured, it's time to add the code snippets. First, go to the `docs/snippets/` folder and create a new directory for your framework of choice (e.g., `ember`). +Use the `renderer` attribute to indicate which of the [supported frameworks](../../configure/integration/frameworks-feature-support.mdx) the code snippet belongs to. -Browse the documentation and look for the code snippets you're willing to contribute. For example, on the [setup page](https://github.com/storybookjs/storybook/blob/next/docs/get-started/setup.mdx), you should see something similar to: +{/* prettier-ignore-start */} -```jsx -// /docs/get-started/setup.md +````md title="docs/_snippets/button-stories.md" +```ts filename="Button.stories.ts" language="ts" renderer="react|vue|angular|web-components|ember|html|svelte|preact|qwik|solid" +``` +```` + +{/* prettier-ignore-end */} + +Alternatively, if you're documenting examples that apply to multiple frameworks, use the `renderer` attribute with the `common` value to indicate that the code snippet is framework-agnostic. {/* prettier-ignore-start */} - +````md title="docs/_snippets/button-stories.md" +```ts filename="Button.stories.ts" language="ts" renderer="common" +``` +```` {/* prettier-ignore-end */} + +### Package manager configuration + +Use the `packageManager` attribute to configure the package manager used in the example from the following options: `npm`, `yarn`, or `pnpm`. + + +{/* prettier-ignore-start */} + +````md title="docs/_snippets/storybook-run-dev.md" +```shell renderer="common" language="js" packageManager="npm|yarn|pnpm" ``` +```` -Create the file `ember/your-component.js.mdx`, similar to the other frameworks, and reference it. +{/* prettier-ignore-end */} -```jsx -// /docs/get-started/setup.md +### Working with multiple snippets +Use the `tabTitle` attribute to indicate the tab title in which the code snippet will be displayed. This attribute should only be used when multiple examples are in a single code snippet file. + {/* prettier-ignore-start */} - +````md title="docs/_snippets/component-decorator.md" +```ts filename="YourConponent.stories.ts" language="ts" renderer="common" tabTitle="Story" +``` +```ts filename=".storybook/preview.ts" language="ts" renderer="common" tabTitle="Storybook configuration" +``` +```` {/* prettier-ignore-end */} + +## Contributing code snippets + +You can start contributing to the Storybook documentation by now that you're familiar with how the documentation is organized, the code snippet's structure, and available options. Assuming that you have already set up your [local development environment](../code.mdx#initial-setup) and are ready to contribute, the following steps will guide you through contributing code snippets to the Storybook documentation. + +Start by creating a new branch on your local Storybook monorepo with the following command: + +{/* prettier-ignore-start */} + +```shell +git checkout -b code-snippets-for-framework ``` + +{/* prettier-ignore-end */} - - Code snippets are divided into various file extensions, if you're contributing a TypeScript file use `.ts.mdx`, or if you're adding JavaScript files use `.js.mdx`. - +Browse the documentation and look for the code snippets you want to contribute. For example, on the [setup page](https://github.com/storybookjs/storybook/blob/next/docs/get-started/setup.mdx) you should see the following: + +{/* prettier-ignore-start */} + + ```jsx title="docs/get-started/setup.mdx" +{/* prettier-ignore-start */} + + + +{/* prettier-ignore-end */} +``` + +{/* prettier-ignore-end */} + + +Open the file inside the `docs/_snippets` directory and adjust the content to match the code snippet you're willing to contribute. For example: + +{/* prettier-ignore-start */} + +````md title="docs/_snippets/your-component.md" +```ts filename="YourComponent.stories.ts" renderer="qwik" language="ts-4-9" +import type { Meta, StoryObj } from 'storybook-framework-qwik'; + +import type { YourComponentProps } from './YourComponent'; + +import { YourComponent } from './YourComponent'; + +//๐Ÿ‘‡ This default export determines where your story goes in the story list +const meta = { + component: YourComponent, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const FirstStory: Story = { + args: { + //๐Ÿ‘‡ The args you need here will depend on your component + }, +}; +``` +```` + +{/* prettier-ignore-end */} Go through the rest of the documentation and repeat the process. -### Preview your work +## Preview your work + +Before submitting your contribution, we encourage you to check your work against the Storybook website. Doing this prevents last-minute issues with the documentation and is also an excellent way for the maintainers to merge faster once you submit the pull request. However, failing to do so will lead one of the maintainers to notify you that your contribution has an issue. -Before submitting your contribution, we advise you to check your work against the Storybook website. Doing this prevents last-minute issues with the documentation and is also an excellent way for the maintainers to merge faster once you submit the pull request. However, failing to do so will lead one of the maintainers to notify you that your contribution has an issue. +Start by forking the Storybook [website repository](https://github.com/storybookjs/web) and cloning it locally. -Start by forking [frontpage repo](https://github.com/storybookjs/frontpage) and cloning it locally. +{/* prettier-ignore-start */} ```shell -git clone https://github.com/your-username/frontpage.git +git clone https://github.com/your-username/web.git ``` + +{/* prettier-ignore-end */} + +Navigate to the `web` directory and install the required dependencies. -Navigate to the `frontpage` directory and install the required dependencies with the following command: +{/* prettier-ignore-start */} ```shell -yarn +npm install ``` -Next, make sure that you have running the `Synchronize documentation (sync-docs)` task from Storybook monorepo. Then, execute the following command to launch the Storybook website. +{/* prettier-ignore-end */} + +We recommend that you generate a website build first to ensure you can preview your changes locally and verify that everything is working as expected. To do so, run the following command: + +{/* prettier-ignore-start */} ```shell -yarn start:docs-only +npm run build:frontpage ``` - - During the start process if there's an issue with the documentation, the process will stop and you'll get a notification. +{/* prettier-ignore-end */} + + + +When executed, this command will retrieve the required files needed to successfully build the Storybook website, including current documentation versions (e.g., `6.5`, `7.6`, `8.x`), and copy them to the `apps/frontpage/docs/` directory, organized by version number. + -Open a browser window to `http://localhost:8000`, click the Docs link, and select your framework from the dropdown. +Run the `sync-docs` command to connect the documentation from the Storybook monorepo to the Storybook website. When prompted, provide the path to your local fork of the Storybook monorepo and the documentation version you're working on. + +{/* prettier-ignore-start */} + +```shell +npm run sync-docs +``` + +{/* prettier-ignore-end */} + +Finally, open a new terminal window and run the `dev` command to start the Storybook website. -![Storybook docs with dropdown](../../_assets/contribute/local-storybook-website-dropdown-optimized.png) +{/* prettier-ignore-start */} + +```shell +npm run dev +``` + +{/* prettier-ignore-end */} + +If all goes well, you should see the Storybook website running. Open a browser window to `http://localhost:3000`, click the Docs link to open the documentation, and select your framework from the dropdown. + +{/* TODO: Record video */} + +