+- [From version 9.x to 10.0.0](#from-version-9x-to-1000)
+ - [Core Changes](#core-changes)
+ - [Local addons must be fully resolved](#local-addons-must-be-fully-resolved)
+ - [The `.storybook/main.*`-file must be valid ESM](#the-storybookmain-file-must-be-valid-esm)
+ - [Node.js 20.19+ or 22.12+ required](#nodejs-2019-or-2212-required)
+ - [Require `tsconfig.json` `moduleResolution` set to value that supports `types` condition](#require-tsconfigjson-moduleresolution-set-to-value-that-supports-types-condition)
+ - [`core.builder` configuration must be a fully resolved path](#corebuilder-configuration-must-be-a-fully-resolved-path)
- [From version 8.x to 9.0.0](#from-version-8x-to-900)
- [Core Changes and Removals](#core-changes-and-removals)
- [Dropped support for legacy packages](#dropped-support-for-legacy-packages)
@@ -97,7 +104,7 @@
- [MDX is upgraded to v3](#mdx-is-upgraded-to-v3)
- [Dropping support for \*.stories.mdx (CSF in MDX) format and MDX1 support](#dropping-support-for-storiesmdx-csf-in-mdx-format-and-mdx1-support)
- [Dropping support for id, name and story in Story block](#dropping-support-for-id-name-and-story-in-story-block)
- - [Core changes](#core-changes)
+ - [Core changes](#core-changes-1)
- [`framework.options.builder.useSWC` for Webpack5-based projects removed](#frameworkoptionsbuilderuseswc-for-webpack5-based-projects-removed)
- [Removed `@babel/core` and `babel-loader` from `@storybook/builder-webpack5`](#removed-babelcore-and-babel-loader-from-storybookbuilder-webpack5)
- [`framework.options.fastRefresh` for Webpack5-based projects removed](#frameworkoptionsfastrefresh-for-webpack5-based-projects-removed)
@@ -477,6 +484,102 @@
- [Packages renaming](#packages-renaming)
- [Deprecated embedded addons](#deprecated-embedded-addons)
+## From version 9.x to 10.0.0
+
+### Core Changes
+
+#### Local addons must be fully resolved
+
+In Storybook 9 it was possible to do reference local addons by a relative path, like so:
+
+```ts
+// main.ts
+
+export default {
+ addons: ["./my-addon.ts"],
+};
+```
+
+In Storybook 10 this relative path, should be fully resolved, like so:
+
+```ts
+// main.ts
+
+export default {
+ addons: [import.meta.resolve("./my-addon.ts")],
+};
+```
+
+#### The `.storybook/main.*`-file must be valid ESM
+
+Storybook will load the `.storybook/main.*` file as an ESM file.
+Thus CJS constants (`require`, `__dirname`, `__filename`) will not be defined.
+
+You can define these constants yourself, like so:
+
+```ts
+import { createRequire } from "node:module";
+import { dirname } from "node:path";
+import { fileURLToPath } from "node:url";
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+const require = createRequire(import.meta.url);
+```
+
+A `main.ts` file that's CJS is no longer supported.
+
+#### Node.js 20.19+ or 22.12+ required
+
+Storybook 10 now requires Node.js version 20.19+ or 22.12+. We require these new ranges so Node.js supports `require(esm)` without a flag.
+
+#### Require `tsconfig.json` `moduleResolution` set to value that supports `types` condition
+
+Storybook 10 has removed all `typesVersions` fields from `package.json` files. This field was previously needed for older TypeScript module resolution strategies that didn't support the `types` condition in package.json exports.
+
+**Required action:** Update your `tsconfig.json` to use a `moduleResolution` that supports the `types` condition:
+
+```json
+{
+ "compilerOptions": {
+ "moduleResolution": "bundler" // or "node16"/"nodenext"
+ }
+}
+```
+
+**Supported values:**
+
+- `"bundler"` (recommended for modern bundler-based projects)
+- `"node16"` or `"nodenext"` (Node.js 16+ module resolution)
+
+**Note:** If you're currently using `moduleResolution: "node"` (the old Node.js 10-style resolution), you'll need to upgrade to one of the supported values above.
+
+This change simplifies our package structure and aligns with modern TypeScript standards. Only TypeScript projects are affected - JavaScript projects require no changes.
+
+#### `core.builder` configuration must be a fully resolved path
+
+> [!NOTE]
+> In the majority of cases, this is only relevant for authors of Storybook framework packages, as regular users very rarely set the `core.builder` property manually.
+
+When setting the `core.builder` or `core.builder.name` option in the main configuration, it must now be a fully resolved path to a builder's entry point, instead of just to the builder package's root directory.
+
+In a preset:
+
+```diff
+import { dirname, join } from 'node:path';
+
+const getAbsolutePath = (input) =>
+ dirname(require.resolve(join(input, 'package.json')));
+
+export const core = {
+- builder: getAbsolutePath('@storybook/builder-vite'),
+- // 👆 results in eg. `/absolute/path/node_modules/@storybook/builder-vite
++ builder: import.meta.resolve('@storybook/builder-vite'),
++ // 👆 results in eg. `/absolute/path/node_modules/@storybook/builder-vite/index.js
+ renderer: getAbsolutePath('@storybook/react'),
+};
+```
+
## From version 8.x to 9.0.0
### Core Changes and Removals
diff --git a/README.md b/README.md
index bb7a55352958..1bf638d98ba1 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-Storybook is a frontend workshop for building UI components and pages in isolation. Thousands of teams use it for UI development, testing, and documentation. Find out more at https://storybook.js.org!
+Storybook is a frontend workshop for building UI components and pages in isolation. Thousands of teams use it for UI development, testing, and documentation. Find out more at storybook.js.org!
-
+
@@ -74,19 +74,19 @@ Storybook is a frontend workshop for building UI components and pages in isolati
## Getting Started
-Visit [Storybook's website](https://storybook.js.org) to learn more about Storybook and to get started.
+Visit [Storybook's website](https://storybook.js.org/?ref=readme) to learn more about Storybook and to get started.
### Documentation
-Documentation can be found on [Storybook's docs site](https://storybook.js.org/docs).
+Documentation can be found on [Storybook's docs site](https://storybook.js.org/docs?ref=readme).
### Examples
-View [Component Encyclopedia](https://storybook.js.org/showcase) to see how leading teams use Storybook.
+View [Component Encyclopedia](https://storybook.js.org/showcase?ref=readme) to see how leading teams use Storybook.
Use [storybook.new](https://storybook.new) to quickly create an example project in Stackblitz.
-Storybook comes with a lot of [addons](https://storybook.js.org/docs/configure/user-interface/storybook-addons) for component design, documentation, testing, interactivity, and so on. Storybook's API makes it possible to configure and extend in various ways. It has even been extended to support React Native, Android, iOS, and Flutter development for mobile.
+Storybook comes with a lot of [addons](https://storybook.js.org/docs/configure/user-interface/storybook-addons?ref=readme) for component design, documentation, testing, interactivity, and so on. Storybook's API makes it possible to configure and extend in various ways. It has even been extended to support React Native, Android, iOS, and Flutter development for mobile.
### Community
@@ -108,7 +108,7 @@ For additional help, share your issue in [the repo's GitHub Discussions](https:/
| [Svelte](code/renderers/svelte) | [](https://next--630873996e4e3557791c069c.chromatic.com/) | [](code/renderers/svelte) |
| [Preact](code/renderers/preact) | [](https://next--63b588a512565bfaace15e7c.chromatic.com/) | [](code/renderers/preact) |
| [Qwik](https://github.com/literalpie/storybook-framework-qwik) | [](/) | [](https://github.com/literalpie/storybook-framework-qwik) |
-| [SolidJS](https://github.com/solidjs-community/storybook) | [](/) | [](https://github.com/solidjs-community/storybook) |
+| [SolidJS](https://github.com/solidjs-community/storybook) | [](/) | [](https://github.com/solidjs-community/storybook) |
| [Android, iOS, Flutter](https://github.com/storybookjs/native) | [](/) | [](https://github.com/storybookjs/native) |
### Addons
@@ -131,7 +131,7 @@ For additional help, share your issue in [the repo's GitHub Discussions](https:/
| [query params](https://github.com/storybookjs/addon-queryparams) | Mock query params |
| [viewport](code/core/src/viewport/) | Change display sizes and layouts for responsive components using Storybook |
-See [Addon / Framework Support Table](https://storybook.js.org/docs/configure/integration/frameworks-feature-support)
+See [Addon / Framework Support Table](https://storybook.js.org/docs/configure/integration/frameworks-feature-support?ref=readme)
To continue improving your experience, we have to eventually deprecate or remove certain addons in favor of new and better tools.
@@ -139,7 +139,7 @@ If you're using info/notes, we highly recommend you migrate to [docs](code/addon
If you're using contexts, we highly recommend you migrate to [toolbars](https://github.com/storybookjs/storybook/tree/next/code/addons/toolbars) and [here is a guide](https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#deprecated-addon-contexts) to help you.
-If you're using addon-storyshots, we highly recommend you migrate to the Storybook [test-runner](https://github.com/storybookjs/test-runner) and [here is a guide](https://storybook.js.org/docs/writing-tests/storyshots-migration-guide) to help you.
+If you're using addon-storyshots, we highly recommend you migrate to the Storybook [test-runner](https://github.com/storybookjs/test-runner) and [here is a guide](https://storybook.js.org/docs/writing-tests/storyshots-migration-guide?ref=readme) to help you.
## Badges & Presentation materials
@@ -156,7 +156,7 @@ If you're looking for material to use in your Storybook presentation, such as lo
## Community
- Tweeting via [@storybookjs](https://x.com/storybookjs)
-- Blogging at [storybook.js.org](https://storybook.js.org/blog/) and [Medium](https://medium.com/storybookjs)
+- Blogging at [storybook.js.org](https://storybook.js.org/blog/?ref=readme) and [Medium](https://medium.com/storybookjs)
- Chatting on [Discord](https://discord.gg/storybook)
- Videos and streams at [YouTube](https://www.youtube.com/channel/UCr7Quur3eIyA_oe8FNYexfg)
diff --git a/code/.storybook/bench.stories.tsx b/code/.storybook/bench.stories.tsx
deleted file mode 100644
index 09bb95c7f225..000000000000
--- a/code/.storybook/bench.stories.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-import React from 'react';
-
-import type { Meta } from '@storybook/react-vite';
-
-// @ts-expect-error - TS doesn't know about import.meta.glob from Vite
-const allMetafiles = import.meta.glob(['../bench/esbuild-metafiles/**/*.json']);
-
-const METAFILES_DIR = '../bench/esbuild-metafiles/';
-const PACKAGES_WITHOUT_ORG = ['storybook', 'sb', 'create-storybook'];
-
-// allows the metafile path to be used in the URL hash
-const safeMetafileArg = (path: string) =>
- path
- .replace(METAFILES_DIR, '')
- .replaceAll('/', '__')
- .replace(/(\w*).json/, '$1');
-
-export default {
- title: 'Bench',
- parameters: {
- layout: 'fullscreen',
- chromatic: { disableSnapshot: true },
- },
- args: {
- metafile: safeMetafileArg(Object.keys(allMetafiles)[0]),
- },
- argTypes: {
- metafile: {
- options: Object.keys(allMetafiles).map(safeMetafileArg).sort(),
- mapping: Object.fromEntries(
- Object.keys(allMetafiles).map((path) => [safeMetafileArg(path), path])
- ),
- control: {
- type: 'select',
- labels: Object.fromEntries(
- Object.keys(allMetafiles).map((path) => {
- const [, dirName, subEntry] = /esbuild-metafiles\/(.+)\/(.+).json/.exec(path)!;
- const pkgName = PACKAGES_WITHOUT_ORG.includes(dirName)
- ? dirName
- : `@storybook/${dirName}`;
-
- return [
- safeMetafileArg(path),
- subEntry !== 'metafile' ? `${pkgName} - ${subEntry}` : pkgName,
- ];
- })
- ),
- },
- },
- },
- loaders: [
- async ({ args }) => {
- if (!args.metafile) {
- return;
- }
- let metafile;
- try {
- metafile = await allMetafiles[args.metafile]();
- } catch (e) {
- return;
- }
- const encodedMetafile = btoa(JSON.stringify(metafile));
- return { encodedMetafile };
- },
- ],
- render: (args, { loaded }) => {
- const { encodedMetafile = '' } = loaded ?? {};
-
- return (
-
- );
- },
-} satisfies Meta;
-
-export const ESBuildAnalyzer = {
- name: 'ESBuild Metafiles',
-};
diff --git a/code/.storybook/bench/bench.stories.tsx b/code/.storybook/bench/bench.stories.tsx
new file mode 100644
index 000000000000..b5b84cacc75f
--- /dev/null
+++ b/code/.storybook/bench/bench.stories.tsx
@@ -0,0 +1,81 @@
+import React from 'react';
+
+import type { Meta } from '@storybook/react-vite';
+
+import { safeMetafileArg } from '../../../scripts/bench/safe-args';
+
+// @ts-expect-error - TS doesn't know about import.meta.glob from Vite
+const allMetafiles = import.meta.glob(['../../bench/esbuild-metafiles/**/*.json'], {
+ import: 'default',
+});
+
+export default {
+ title: 'Bench',
+ parameters: {
+ layout: 'fullscreen',
+ chromatic: { disableSnapshot: true },
+ },
+ args: {
+ // default to the core/node.json metafile
+ metafile: safeMetafileArg(
+ Object.keys(allMetafiles).find((path) => path.includes('/core/node.json'))!
+ ),
+ },
+ argTypes: {
+ metafile: {
+ options: Object.keys(allMetafiles).map(safeMetafileArg).sort(),
+ mapping: Object.fromEntries(
+ Object.keys(allMetafiles).map((path) => [safeMetafileArg(path), path])
+ ),
+ control: {
+ type: 'select',
+ labels: Object.fromEntries(
+ Object.keys(allMetafiles).map((path) => {
+ const [, dirname, subEntry] = /esbuild-metafiles\/(.+)\/(.+).json/.exec(path)!;
+
+ // most metafile directories are named exactly like their package name within the @storybook scope
+ let packageName = '@storybook/' + dirname;
+
+ // but some are not, so we need to map them to the correct package name
+ switch (dirname) {
+ case 'core': {
+ packageName = 'storybook';
+ break;
+ }
+ case 'eslint-plugin':
+ packageName = 'eslint-plugin-storybook';
+ break;
+ case 'create-storybook':
+ case 'storybook-addon-pseudo-states':
+ packageName = dirname;
+ }
+
+ return [safeMetafileArg(path), `${packageName} - ${subEntry}`];
+ })
+ ),
+ },
+ },
+ },
+ beforeEach: async ({ args }) => {
+ if (!args.metafile) {
+ globalThis.metafile = undefined;
+ return;
+ }
+ const metafile = await allMetafiles[args.metafile]();
+ // this is read by the bundle-analyzer iframe via parent.metafile, in bundle-analyzer/index.js
+ globalThis.metafile = JSON.stringify(metafile);
+ },
+ render: (args) => {
+ return (
+
+ );
+ },
+} satisfies Meta;
+
+export const ESBuildAnalyzer = {
+ name: 'ESBuild Metafiles',
+};
diff --git a/code/.storybook/bench/bundle-analyzer/index.css b/code/.storybook/bench/bundle-analyzer/index.css
new file mode 100644
index 000000000000..6dc6724dcec6
--- /dev/null
+++ b/code/.storybook/bench/bundle-analyzer/index.css
@@ -0,0 +1,511 @@
+html:not([data-theme='dark']) {
+ --bg: #fff;
+ --fg-on: #000;
+ --fg: #222;
+ --pre-dim: #777;
+ --pre-val: #870;
+ --pre: #222;
+ --bar-min-width: 1px;
+}
+html[data-theme='dark'] {
+ color-scheme: dark;
+ --bg: #191919;
+ --fg-on: #ddd;
+ --fg: #aaa;
+ --pre-dim: #999;
+ --pre-val: #cb8;
+ --pre: #ccc;
+ --bar-min-width: 3px;
+}
+@media (prefers-color-scheme: dark) {
+ html:not([data-theme='light']) {
+ color-scheme: dark;
+ --bg: #191919;
+ --fg-on: #ddd;
+ --fg: #aaa;
+ --pre-dim: #999;
+ --pre-val: #cb8;
+ --pre: #ccc;
+ --bar-min-width: 3px;
+ }
+}
+body {
+ margin: 50px;
+ background: var(--bg);
+ color: var(--fg);
+ font: 16px/20px sans-serif;
+}
+#startPanel {
+ margin: 10% auto 0;
+}
+#resultsPanel {
+ display: none;
+}
+section {
+ margin: auto;
+ max-width: 600px;
+}
+#logo {
+ display: inline-block;
+ margin-bottom: 10px;
+ text-align: left;
+}
+h1 {
+ margin: 0;
+ color: var(--fg-on);
+ font-size: 80px;
+ line-height: 1em;
+}
+blockquote {
+ margin: 5px 0;
+ font-style: italic;
+ font-size: 29px;
+ line-height: 1em;
+}
+p {
+ margin: 20px 0;
+}
+a,
+b {
+ color: var(--fg-on);
+}
+code {
+ margin-bottom: -2px;
+ border-radius: 3px;
+ background: rgba(127, 127, 127, 0.2);
+ padding: 2px 4px;
+ font:
+ 14px/20px Noto Sans Mono,
+ monospace;
+}
+pre {
+ margin: 0;
+ padding: 0;
+ color: var(--pre);
+ font:
+ 14px/20px Noto Sans Mono,
+ monospace;
+ white-space: pre-wrap;
+}
+.center {
+ text-align: center;
+}
+.noscript {
+ color: #e24834;
+}
+button {
+ cursor: pointer;
+ border: 1px solid var(--fg);
+ border-radius: 100px;
+ background: none;
+ padding: 5px 30px;
+ color: var(--fg);
+ line-height: 1.5em;
+}
+button:active,
+button:hover {
+ border: 1px solid var(--fg-on);
+ background: rgba(127, 127, 127, 0.2);
+ color: var(--fg-on);
+}
+button:active {
+ background: rgba(0, 0, 0, 0.2);
+ padding: 6px 30px 4px;
+}
+#settingsPanel {
+ text-align: center;
+}
+#settingsPanel .chartSwitcher {
+ display: flex;
+ position: relative;
+ justify-content: center;
+ margin: 50px 0;
+}
+#settingsPanel .chartSwitcher:after {
+ display: block;
+ position: absolute;
+ top: 0.5em;
+ right: 0;
+ left: 0;
+ z-index: -1;
+ border: 1px solid #ffcf00;
+ content: '';
+}
+#settingsPanel .chartSwitcher span {
+ border-left: 2px solid #ffcf00;
+ background: var(--bg);
+ padding: 0 1em;
+}
+#settingsPanel .chartSwitcher span:first-child {
+ border: none;
+}
+#settingsPanel a {
+ color: var(--fg);
+ text-decoration: none;
+}
+#settingsPanel a:hover {
+ text-decoration: underline;
+}
+#settingsPanel a.r {
+ color: var(--fg-on);
+ font-weight: 700;
+}
+.n {
+ display: none;
+ margin: auto;
+ max-width: 600px;
+}
+.p {
+ display: none;
+ position: absolute;
+ z-index: 1;
+ border-radius: 30px;
+ background: black;
+ padding: 0 10px;
+ touch-action: none;
+ pointer-events: none;
+ color: #999;
+ font-size: 14px;
+ line-height: 30px;
+ -webkit-user-select: none;
+ user-select: none;
+ white-space: pre;
+}
+.p b {
+ color: #fff;
+}
+#dragTarget {
+ display: none;
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 10;
+ box-shadow: inset 0 0 0 10px #ffcf00;
+ touch-action: none;
+ -webkit-user-select: none;
+ user-select: none;
+}
+#summaryPanel {
+ text-align: center;
+}
+#summaryPanel table {
+ margin: auto;
+ border-collapse: collapse;
+}
+#summaryPanel td:first-child {
+ text-align: right;
+}
+#summaryPanel h2 {
+ margin: 0;
+ color: var(--fg-on);
+ font-size: 30px;
+ line-height: 1em;
+}
+#summaryPanel .l {
+ padding: 0 30px;
+ font-size: 30px;
+}
+#summaryPanel .o {
+ display: inline-block;
+ margin: 20px auto 0;
+ text-decoration: none;
+ white-space: nowrap;
+}
+#summaryPanel .o:hover .d {
+ text-decoration: underline;
+}
+#summaryPanel .g {
+ display: inline-table;
+ vertical-align: middle;
+ margin: 0 0.8em;
+ border: 1px solid #222;
+}
+#summaryPanel .g > div {
+ display: table-cell;
+ height: 20px;
+}
+#x {
+ padding-left: 1.6em;
+ line-height: 1.1em;
+}
+#x .c {
+ display: inline-block;
+ vertical-align: middle;
+ box-sizing: border-box;
+ margin-right: 0.5em;
+ margin-left: -1.6em;
+ border: 1px solid #222;
+ width: 1.1em;
+ height: 1.1em;
+}
+#x small {
+ display: block;
+ opacity: 0.5;
+ margin-bottom: 1em;
+}
+#s {
+ margin: 0 -50px;
+ padding: 0 50px;
+}
+#s main {
+ position: relative;
+ margin-top: 60px;
+}
+#s canvas {
+ position: absolute;
+ top: 0;
+ right: 0;
+ left: 0;
+ margin: auto;
+ padding-bottom: 50px;
+}
+#s section {
+ display: none;
+ margin-top: 30px;
+ min-height: 200px;
+}
+#b {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ background: rgba(0, 0, 0, 0.6);
+ padding: 50px;
+ overflow-y: auto;
+}
+#b .m {
+ position: relative;
+ box-sizing: border-box;
+ margin: 0 auto;
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.6);
+ border-radius: 20px;
+ background: var(--bg);
+ padding: 50px;
+ max-width: 1000px;
+}
+#b .m:focus {
+ outline: none;
+}
+#b .h {
+ position: absolute;
+ top: 0;
+ right: 0;
+ width: 50px;
+ color: var(--fg);
+ font-size: 30px;
+ line-height: 50px;
+ text-align: center;
+ text-decoration: none;
+}
+#b .h:hover {
+ color: var(--fg-on);
+ font-size: 35px;
+}
+#b .h:active {
+ font-size: 30px;
+}
+#b h2 {
+ margin: 0;
+ border-bottom: 2px solid #ffcf00;
+ padding-bottom: 4px;
+ color: var(--fg-on);
+ font-size: 20px;
+ line-height: 1.2em;
+}
+#b .f {
+ position: relative;
+ margin-top: 10px;
+ border: 1px solid rgba(127, 127, 127, 0.5);
+ border-radius: 10px;
+ padding: 30px 10px 10px;
+ line-height: 22px;
+ white-space: pre-wrap;
+}
+#b .u {
+ position: absolute;
+ top: 0;
+ left: 0;
+ border-right: 1px solid rgba(127, 127, 127, 0.5);
+ border-bottom: 1px solid rgba(127, 127, 127, 0.5);
+ border-top-left-radius: 10px;
+ border-bottom-right-radius: 10px;
+ background: rgba(127, 127, 127, 0.1);
+ padding: 5px 10px;
+}
+#b pre {
+ padding: 0 0 0 30px;
+}
+#b .v {
+ color: var(--pre-dim);
+}
+#b .w {
+ color: var(--fg-on);
+}
+#b .k {
+ color: var(--pre-val);
+}
+#b .e,
+#b .i {
+ position: relative;
+}
+#b .e:after,
+#b .i:after {
+ position: absolute;
+ top: 0;
+ opacity: 0.5;
+ background: var(--fg);
+ width: 30px;
+ content: '';
+}
+#b .e:after {
+ height: 40px;
+ --icon: url('data:image/svg+xml,');
+ -webkit-mask-image: var(--icon);
+ mask-image: var(--icon);
+}
+#b .i:after {
+ height: 90px;
+ --icon: url('data:image/svg+xml,');
+ -webkit-mask-image: var(--icon);
+ mask-image: var(--icon);
+}
+#y main {
+ display: flex;
+ justify-content: center;
+ margin-top: 50px;
+ margin-bottom: 500px;
+}
+#y .z {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ margin-right: 20px;
+}
+#y canvas {
+ display: block;
+ margin-bottom: 30px;
+}
+#y .F {
+ position: relative;
+ flex: 1;
+ max-width: 800px;
+}
+#y .P {
+ display: flex;
+ margin: 0 10px 20px;
+ font-size: 20px;
+ line-height: 1.2em;
+ white-space: pre;
+}
+#y .P .C {
+ flex: 1;
+ height: 20px;
+}
+#y .P a {
+ opacity: 0.5;
+ text-decoration: none;
+}
+#y .P a[href]:hover {
+ text-decoration: underline;
+}
+#y .P a:last-child {
+ opacity: 1;
+ color: var(--fg-on);
+ font-weight: 700;
+}
+#y .S {
+ width: 100%;
+}
+#y .S .j {
+ display: table-row;
+ color: var(--fg);
+ line-height: 26px;
+ text-decoration: none;
+}
+#y .S .j.a {
+ background: rgba(127, 127, 127, 0.2);
+ color: var(--fg-on);
+}
+#y .S .j.a .t:after {
+ position: absolute;
+ top: 1px;
+ right: 1px;
+ bottom: 1px;
+ left: 1px;
+ background: rgba(255, 255, 255, 0.3);
+ content: '';
+}
+#y .S .j > div {
+ display: table-cell;
+}
+#y .S .M {
+ vertical-align: top;
+ padding: 0 10px;
+ white-space: pre;
+}
+#y .S .N {
+ padding-right: 100px;
+ width: 100%;
+}
+#y .S .t {
+ position: relative;
+ margin-bottom: -1px;
+ box-shadow: inset 0 0 0 1px #222;
+ min-width: var(--bar-min-width);
+ height: 27px;
+}
+#y .S .t.q {
+ min-width: 0;
+}
+#y .S .T {
+ position: absolute;
+ right: -10px;
+ width: 0;
+ white-space: nowrap;
+}
+#A main {
+ position: relative;
+ margin-top: 60px;
+}
+#A canvas {
+ position: absolute;
+ top: 0;
+ left: -50px;
+}
+#A section {
+ display: none;
+ margin-top: 30px;
+ min-height: 500px;
+}
+#warningsPanel {
+ margin: auto;
+ max-width: 1000px;
+}
+#warningsPanel .B {
+ margin-top: 20px;
+ text-align: center;
+}
+#warningsPanel .D {
+ display: none;
+}
+#warningsPanel .E {
+ margin: 30px 0;
+ color: var(--fg-on);
+ white-space: pre-wrap;
+}
+#warningsPanel ul {
+ margin: 5px 0 0;
+ color: var(--fg);
+}
+#warningsPanel .G {
+ opacity: 0.5;
+}
+#warningsPanel pre a {
+ text-decoration: none;
+}
+#warningsPanel pre a:hover {
+ text-decoration: underline;
+}
diff --git a/code/.storybook/bench/bundle-analyzer/index.html b/code/.storybook/bench/bundle-analyzer/index.html
new file mode 100644
index 000000000000..460a41301723
--- /dev/null
+++ b/code/.storybook/bench/bundle-analyzer/index.html
@@ -0,0 +1,58 @@
+
+
+
+ This is a modified version of the esbuild bundle analyzer, for storybook's purpose we
+ added a postMessage listener to receive the metafile from the parent window.
+
+
+ If you see this message, it means that the iframe hasn't received the metafile yet. It can
+ take a little while to load, for large meta-files, but check the browser console for any
+ errors, if it persists.
+
+
+
+
+
+ Or you can load an example to play around
+ with the visualization.
+
'),
+ nr(s, Pt, t);
+ let n = m.createElement('a');
+ (n.className = yl),
+ (n.href = 'javascript:void 0'),
+ (n.onclick = tt),
+ (n.innerHTML = '×'),
+ s.append(n),
+ (s.tabIndex = 0),
+ (De.id = gl),
+ (De.innerHTML = ''),
+ De.append(s),
+ (De.onmousedown = (r) => {
+ r.target === De && tt();
+ }),
+ m.body.append(De),
+ s.focus(),
+ (s.onkeydown = (r) => {
+ r.key === 'Escape' &&
+ !r.shiftKey &&
+ !r.metaKey &&
+ !r.ctrlKey &&
+ !r.altKey &&
+ (r.preventDefault(), tt());
+ });
+ },
+ nr = (e, t, i) => {
+ let p = t.E,
+ o = i,
+ s = [{ e: i, x: null }];
+ for (;;) {
+ let a = p[o];
+
+ if (!a) {
+ return;
+ }
+
+ if (o === a.e) {
+ break;
+ }
+ s.push({ e: a.e, x: { e: o, h: a.h, o: a.o, s: a.s } }), (o = a.e);
+ }
+ s.reverse();
+ let n = t.v,
+ r,
+ l = 'Entry point';
+ e.append('This file is included in the bundle because:');
+ for (let a of s) {
+ if (Pe.call(n, a.e)) {
+ let H = m.createElement('div');
+ (r = m.createElement('div')),
+ (r.className = Tl),
+ (H.className = xl),
+ (H.textContent = 'Output file '),
+ H.append(xt(n[a.e])),
+ r.append(H),
+ e.appendChild(r);
+ } else if (!r) {
+ return;
+ }
+ let f = Tt(l + ' '),
+ u = Tt(` is included in the bundle.
+`);
+ r.firstChild &&
+ r.append(`
+`),
+ r.append(f, xt(a.e), u);
+ let N = a.x;
+
+ if (N) {
+ let H = N.h || Zt(N.e) || Kt(N.e, qt(a.e)),
+ x = m.createElement('pre'),
+ P = m.createElement('span');
+ if (((P.className = Pe.call(n, N.e) ? El : vl), N.o === 'import-statement')) {
+ x.append(xe(Lt, 'import '), xe(Ae, JSON.stringify(H))),
+ N.s && (x.append(xe(Lt, ' with ')), Rt(x, N.s)),
+ x.append(';'),
+ (l = 'Imported file');
+ } else if (N.o === 'require-call') {
+ x.append('require(', xe(Ae, JSON.stringify(H)), ');'), (l = 'Required file');
+ } else if (N.o === 'dynamic-import') {
+ x.append('import(', xe(Ae, JSON.stringify(H))),
+ N.s && (x.append(', '), Rt(x, { with: N.s })),
+ x.append(');'),
+ (l = 'Dynamically-imported file');
+ } else if (N.o === 'import-rule') {
+ x.append('@import ', xe(Ae, JSON.stringify(H)), ';'), (l = 'Imported stylesheet');
+ } else if (N.o === 'url-token') {
+ x.append('url(', xe(Ae, JSON.stringify(H)), ')'), (l = 'URL reference');
+ } else {
+ return;
+ }
+ (u.textContent = ` contains:
+`),
+ x.append(
+ P,
+ `
+`
+ ),
+ r.append(x);
+ } else {
+ f.textContent = 'So ' + f.textContent.toLowerCase();
+ }
+ }
+ },
+ Rt = (e, t) => {
+ let i = Object.keys(t);
+ if (i.length === 0) {
+ e.append('{}');
+ return;
+ }
+ e.append('{ ');
+ for (let p = 0; p < i.length; p++) {
+ let o = i[p],
+ s = t[o];
+ p > 0 && e.append(', '),
+ e.append(Sl.test(o) ? o : xe(Ae, JSON.stringify(o)), ': '),
+ typeof s == 'string' ? e.append(xe(Ae, JSON.stringify(s))) : Rt(e, s);
+ }
+ e.append(' }');
+ };
+ var ir = (e) => {
+ let t = e.outputs,
+ i = 0,
+ p = 0,
+ o = [],
+ s,
+ n = (r, l) => {
+ let a = r.n,
+ f = [];
+
+ for (let u in a) {
+ f.push(n(a[u], !1));
+ }
+ return { r: r.r, e: r.e, p: ae(r.t), t: r.t, l: f.sort(ve), g: l };
+ };
+ for (let r in t) {
+ let l = ge(r);
+ l.pop(), (s = Ue(l.join('/'), s));
+ }
+ for (let r in t) {
+ if (Te(r)) {
+ continue;
+ }
+ let a = { r: s ? ge(r).slice(s.length).join('/') : r, e: '', t: 0, n: {} },
+ f = t[r],
+ u = f.inputs,
+ N = f.bytes;
+ for (let H in u) {
+ let x = we(a, Se(H), u[H].bytesInOutput);
+ x > p && (p = x);
+ }
+ (a.t = N), (i += N), o.push(n(a, !0));
+ }
+ e: for (;;) {
+ let r;
+ for (let l of o) {
+ let a = l.l;
+
+ if (!a.length) {
+ continue;
+ }
+
+ if (a.length > 1 || a[0].l.length !== 1) {
+ break e;
+ }
+ let f = a[0].r;
+
+ if (r === void 0) {
+ r = f;
+ } else if (r !== f) {
+ break e;
+ }
+ }
+
+ if (r === void 0) {
+ break;
+ }
+ for (let l of o) {
+ let a = l.l;
+ if (a.length) {
+ a = a[0].l;
+
+ for (let f of a) {
+ f.r = r + f.r;
+ }
+ l.l = a;
+ }
+ }
+ p--;
+ }
+ for (let r of o) {
+ let l = 0;
+
+ for (let a of r.l) {
+ l += a.t;
+ }
+ l < r.t && r.l.push({ r: '(unassigned)', e: '', p: ae(r.t - l), t: r.t - l, l: [], g: !1 });
+ }
+ return o.sort(ve), { u: { r: '', e: '', p: '', t: i, l: o, g: !1 }, b: p + 1 };
+ },
+ Ht = (e, t, i, p, o) => {
+ let s = [],
+ n = (l, a, f, u, N) => {
+ let H = e[l].t * N,
+ x = e[a].t * N;
+ return h.max((f * f * H) / (u * u), (u * u) / (f * f * x));
+ };
+ return (
+ ((l, a, f, u, N) => {
+ for (; l < e.length; ) {
+ let H = 0;
+
+ for (let C = l; C < e.length; C++) {
+ H += e[C].t;
+ }
+ let x = h.min(u, N),
+ P = (u * N) / H,
+ I = l,
+ G = 0,
+ se = 0;
+ for (; I < e.length; ) {
+ let C = e[I].t * P,
+ k = n(l, I, x, G + C, P);
+
+ if (I > l && se < k) {
+ break;
+ }
+ (G += C), (se = k), I++;
+ }
+ let X = h.round(G / x),
+ J = 0;
+ for (let C = l; C < I; C++) {
+ let k = e[C],
+ U = k.t * P,
+ B = h.round((x * J) / G),
+ ee = h.round((x * (J + U)) / G),
+ [D, E, F, j] = u >= N ? [a, f + B, X, ee - B] : [a + B, f, ee - B, X];
+ s.push({
+ f: k,
+ m: [D, E, F, j],
+ n: F > 8 && j > 24 ? Ht(k.l, D + 4, E + 20, F - 8, j - 24) : [],
+ }),
+ (J += U);
+ }
+ (l = I), u >= N ? ((a += X), (u -= X)) : ((f += X), (N -= X));
+ }
+ })(0, t, i, p, o),
+ s
+ );
+ },
+ wl = (e) => {
+ let t = ir(e),
+ i = [],
+ p = m.createElement('div'),
+ o = m.createElement('main'),
+ s = m.createElement('canvas'),
+ n = s.getContext('2d'),
+ r = 0,
+ l = 0,
+ a = null,
+ f = null,
+ u = 0,
+ N = 0,
+ H = '',
+ x = '',
+ P = '14px sans-serif',
+ I = {},
+ G = 'bold ' + P,
+ se = {},
+ X = 0,
+ J = se,
+ C = null,
+ k = null,
+ U = 0,
+ B = 0,
+ ee = 0,
+ D = 1,
+ E = null,
+ F = null,
+ j = () => {
+ if (C) {
+ let [d, c, b, v] = C.m,
+ T = d + b,
+ S = c + v,
+ R = h.round(r / 10),
+ _ = h.round(l / 10),
+ W = r - R - 1,
+ A = l - _ - 1,
+ Q = F ? D : 1 - D,
+ le = h.round(d + (R - d) * Q),
+ pe = h.round(c + (_ - c) * Q),
+ $ = h.round(T + (W - T) * Q),
+ ue = h.round(S + (A - S) * Q),
+ Ne = (jt) => jt - h.floor(jt / 64 - 0.5) * 64;
+ (k = Ht([C.f], le, pe, $ - le, ue - pe)[0]),
+ (U = Ne(-(d + T) / 2) * (1 - Q) + (le + $) / 2),
+ (B = Ne(-(c + S) / 2) * (1 - Q) + (pe + ue) / 2);
+ } else {
+ (k = null), (U = 0), (B = 0);
+ }
+ },
+ Y = () => {
+ let d = r,
+ c = l,
+ b = te.devicePixelRatio || 1;
+ (r = h.min(o.clientWidth, 1600)),
+ (l = h.max(h.round(r / 2), innerHeight - 380)),
+ (s.style.width = r + 'px'),
+ (s.style.height = l + 'px'),
+ (o.style.height = l + 'px'),
+ (s.width = h.round(r * b)),
+ (s.height = h.round(l * b)),
+ n.scale(b, b),
+ (r !== d || l !== c) && ((i = Ht(t.u.l, 0, 0, r - 1, l - 1)), j()),
+ fe();
+ },
+ ce = () => {
+ let d = D,
+ c = C;
+ (D = (Re() - ee) / 350),
+ D < 0 || D > 1
+ ? ((C = F), (D = 1), (a = null))
+ : ((D = 1 - D), (D *= D * D), (D = 1 - D), (a = ie(ce))),
+ (D !== d || C !== c) && j(),
+ fe();
+ },
+ q = () => {
+ a === null && (a = ie(ce));
+ },
+ re = (d) => {
+ let c = J[d];
+ return c === void 0 && ((c = n.measureText(String.fromCharCode(d)).width), (J[d] = c)), c;
+ },
+ K = (d, c) => {
+ if (c < X) {
+ return ['', 0];
+ }
+ let b = 0,
+ v = d.length,
+ T = 0;
+ for (; T < v; ) {
+ let S = re(d.charCodeAt(T));
+
+ if (c < b + X + S) {
+ return [d.slice(0, T) + '...', b + X];
+ }
+ (b += S), T++;
+ }
+ return [d, b];
+ },
+ Z = (d, c) => {
+ let b = d.f,
+ [v, T, S, R] = d.m,
+ _ = (b === f ? 1 : 0) | (d === F ? 2 : 0);
+ if (c === 1 && k) {
+ let [W, A, Q, le] = k.m;
+ v >= W && T >= A && v + S <= W + Q && T + R <= A + le && (c = 2);
+ }
+
+ for (let W of d.n) {
+ _ |= Z(W, c);
+ }
+ return (
+ c !== 2 &&
+ !b.g &&
+ ((n.fillStyle = Je(n, b.e, u, N, 1)),
+ d.n.length
+ ? (n.fillRect(v, T, S, 20),
+ n.fillRect(v, T + R - 4, S, 4),
+ n.fillRect(v, T + 20, 4, R - 24),
+ n.fillRect(v + S - 4, T + 20, 4, R - 24))
+ : n.fillRect(v, T, S, R)),
+ _
+ );
+ },
+ de = (d, c) => {
+ let b = d.f,
+ [v, T, S, R] = d.m,
+ _ = b.g;
+ if (
+ (f === b &&
+ !_ &&
+ (!C || c) &&
+ ((n.fillStyle = 'rgba(255,255,255,0.5)'), n.fillRect(v, T, S, R)),
+ _ || ot(n, '#222', v + 0.5, T + 0.5, S, R),
+ R >= 20)
+ ) {
+ (n.fillStyle = _ ? x : '#000'), _ && ((n.font = G), (J = I), (X = 3 * re(46)));
+ let W = S - 8,
+ A = T + h.round(24 / 2),
+ [Q, le] = K(b.r, W),
+ pe = v + h.round((S - le) / 2);
+ if ((_ && ((n.font = P), (J = se), (X = 3 * re(46))), Q === b.r && b.l.length)) {
+ let $ = ' \u2013 ' + (me === 2 ? Ee(b.e, '') : b.p),
+ [ue, Ne] = K($, W - le);
+ (pe = v + h.round((S - le - Ne) / 2)),
+ (n.globalAlpha = 0.5),
+ n.fillText(ue, pe + le, A),
+ (n.globalAlpha = 1);
+ }
+ if (
+ (_ && ((n.font = G), (J = I), (X = 3 * re(46))),
+ n.fillText(Q, pe, A),
+ _ && ((n.font = P), (J = se), (X = 3 * re(46))),
+ R > 40 && !b.l.length)
+ ) {
+ let $ = me === 2 ? Ee(b.e, '') : b.p,
+ [ue, Ne] = K($, W);
+ (n.globalAlpha = 0.5),
+ n.fillText(ue, v + h.round((S - Ne) / 2), T + 20 + h.round(R - 24) / 2),
+ (n.globalAlpha = 1);
+ }
+
+ for (let $ of d.n) {
+ de($, c);
+ }
+ }
+ },
+ fe = () => {
+ let d = getComputedStyle(m.body);
+ (H = d.getPropertyValue('--bg')),
+ (x = d.getPropertyValue('--fg-on')),
+ (a = null),
+ n.clearRect(0, 0, r, l),
+ (n.textBaseline = 'middle'),
+ (X = n.measureText('...').width);
+ let c = null,
+ b = null,
+ v = k ? (E ? (F ? 1 : 1 - D) : D) : 0;
+ u = N = 0;
+ for (let T of i) {
+ let S = Z(T, 1);
+ S & 1 && (c = T), S & 2 && (b = T);
+ }
+
+ for (let T of i) {
+ if ((de(T, !1), k || (c && T !== c))) {
+ let [S, R, _, W] = T.m;
+ (n.globalAlpha = 0.6 * (!k || (!E && b && T !== b) ? 1 : v)),
+ (n.fillStyle = H),
+ n.fillRect(S, R, _, W),
+ (n.globalAlpha = 1);
+ }
+ }
+ if (k) {
+ let [T, S, R, _] = k.m,
+ W = n.getTransform(),
+ A = h.sqrt(W.a * W.d);
+ n.save(),
+ (n.shadowColor = 'rgba(0,0,0,0.5)'),
+ (n.shadowBlur = A * (30 * v)),
+ (n.shadowOffsetX = A * (2 * r)),
+ (n.shadowOffsetY = A * (2 * l + 15 * v)),
+ n.fillRect(T - 2 * r, S - 2 * l, R, _),
+ n.restore(),
+ (u = U),
+ (N = B),
+ Z(k, 0),
+ de(k, !0);
+ }
+ },
+ ne = m.createElement('div'),
+ Le = (d, c, b) => {
+ (ne.style.display = 'block'),
+ (ne.style.left = d + 'px'),
+ (ne.style.top = c + 'px'),
+ (ne.innerHTML = b);
+ let v = ne.offsetWidth;
+
+ for (let T = ne; T; T = T.offsetParent) {
+ v += T.offsetLeft;
+ }
+ v > r && (ne.style.left = d + r - v + 'px');
+ },
+ y = () => {
+ ne.style.display = 'none';
+ },
+ w = (d) => {
+ let c = (T, S) => {
+ for (let R of T) {
+ let [_, W, A, Q] = R.m;
+
+ if (b >= _ && v >= W && b < _ + A && v < W + Q) {
+ return c(R.n, !1) || (S ? null : R);
+ }
+ }
+ return null;
+ },
+ b = d.pageX,
+ v = d.pageY;
+
+ for (let T = s; T; T = T.offsetParent) {
+ (b -= T.offsetLeft), (v -= T.offsetTop);
+ }
+ return k ? c([k], !1) : c(i, !0);
+ },
+ L = (d) => {
+ let c = w(d);
+
+ if ((O(c && c.f), c)) {
+ let b = c.f,
+ v = b.r === b.e ? We(b.e) : b.e,
+ T = v.length - b.r.length;
+ (v = z(v.slice(0, T)) + '' + z(v.slice(T)) + ''),
+ (v += me === 2 ? z(Ee(b.e, ' \u2013 ')) : ' \u2013 ' + z(ae(b.t))),
+ Le(d.pageX, d.pageY + 20, v);
+ } else {
+ y();
+ }
+ },
+ O = (d) => {
+ f !== d && ((f = d), (s.style.cursor = d && !d.l.length ? 'pointer' : 'auto'), q());
+ },
+ M = (d, c) => {
+ for (let b of d) {
+ let v = b.f === c ? b : M(b.n, c);
+
+ if (v) {
+ return v;
+ }
+ }
+ return null;
+ },
+ V = (d) => {
+ C !== d && ((D = 0), (ee = Re()), (E = C), (F = d), (C = d || M(i, C.f)), j(), q());
+ };
+ (s.onmousemove = (d) => {
+ L(d);
+ }),
+ (s.onmouseout = (d) => {
+ O(null), y();
+ }),
+ (p.onclick = (d) => {
+ let c = w(d);
+
+ if (c) {
+ let b = c.f;
+ b.l.length ? (c !== k ? (V(c), O(null), y()) : L(d)) : (Oe(e, b.e, b.t), L(d));
+ } else {
+ C && (V(null), L(d));
+ }
+ }),
+ je((d) => {
+ Ze() || L(d);
+ }),
+ Y(),
+ Promise.resolve().then(Y),
+ Xe(fe),
+ $e(fe),
+ Ye(Y),
+ (p.id = hl),
+ (p.innerHTML = `
This visualization shows which input files were placed into each output file in the bundle. Click on a node to expand and focus it.
Benefit of this chart type: Makes the most of available screen area.
`),
+ (ne.className = ze),
+ o.append(s),
+ p.append(o, ne);
+ let g = m.createElement('section');
+ return g.append(Me), p.append(g), p;
+ };
+ var Ml = 'y',
+ Il = 'z',
+ Ol = 'F',
+ Cl = 'P',
+ Ll = 'C',
+ Pl = 'S',
+ At = 'j';
+ var Rl = 't',
+ Dt = 'M',
+ Ft = 'N',
+ Hl = 'q',
+ kl = 'T';
+ var Al = (e, t) => {
+ for (; t; ) {
+ if (t === e) {
+ return !0;
+ }
+ t = t.i;
+ }
+ return !1;
+ },
+ ar = (e) => {
+ let t = e.inputs,
+ i = e.outputs,
+ p = { r: '', e: '', t: 0, n: {} },
+ o = (r) => {
+ let l = r.n,
+ a = [];
+
+ for (let f in l) {
+ a.push(o(l[f]));
+ }
+ return { e: r.e, t: r.t, l: a.sort(ve), i: null };
+ },
+ s = (r, l) => {
+ let a = 0;
+ for (let f of r.l) {
+ let u = s(f, l + 1);
+ (f.i = r), u > a && (a = u);
+ }
+ return a + 1;
+ };
+
+ for (let r in t) {
+ we(p, Se(r), 0);
+ }
+ for (let r in i) {
+ if (Te(r)) {
+ continue;
+ }
+ let a = i[r].inputs;
+
+ for (let f in a) {
+ we(p, Se(f), a[f].bytesInOutput);
+ }
+ }
+ let n = o(p);
+
+ for (; n.l.length === 1; ) {
+ n = n.l[0];
+ }
+ return { u: n, b: s(n, 0) };
+ },
+ _t = (e, t, i) => {
+ if (e === t) {
+ return;
+ }
+ let p = t.i,
+ o = p.t || 1,
+ s = 0;
+ _t(e, p, i);
+ for (let n of p.l) {
+ if (n === t) {
+ (i.y += (i.c * s) / o), (i.c = (n.t / o) * i.c);
+ break;
+ }
+ s += n.t;
+ }
+ i.T += 1;
+ },
+ Qe = (e) => 50 * 8 * h.log(1 + h.log(1 + e / 8)),
+ Dl = (e) => {
+ let t = m.createElement('div'),
+ i = m.createElement('main'),
+ p = ar(e),
+ o = p.u,
+ s = null,
+ n = (x) => {
+ o !== x && ((o = x), u(), H());
+ },
+ r = (x) => {
+ s !== x && ((s = x), u(), H());
+ },
+ l = () => {
+ let x = m.createElement('div'),
+ P = m.createElement('canvas'),
+ I = P.getContext('2d'),
+ G = () => {
+ let g = 2 * h.ceil(Qe(p.b)),
+ d = te.devicePixelRatio || 1;
+ (C = h.min(h.round(innerWidth * 0.4), g)),
+ (k = C),
+ (U = C >> 1),
+ (B = k >> 1),
+ (P.style.width = C + 'px'),
+ (P.style.height = k + 'px'),
+ (P.width = h.round(C * d)),
+ (P.height = h.round(k * d)),
+ I.scale(d, d),
+ X();
+ },
+ se = (g, d, c, b, v, T, S) => {
+ let R = Qe(d + 1);
+
+ if (R > B) {
+ return S;
+ }
+ g === s && (T |= 8);
+ let _ = (c + R) / 2,
+ W = b + v;
+
+ if (W - S < 1.5 / _) {
+ return S;
+ }
+ let A = 2 / _;
+
+ if ((v > A && (A = v), T & 2)) {
+ (I.fillStyle = Je(I, g.e, U, B, 1)),
+ I.beginPath(),
+ I.arc(U, B, c, b, b + A, !1),
+ I.arc(U, B, R, b + A, b, !0),
+ I.fill(),
+ s &&
+ (T & 8 || g.i === s) &&
+ ((I.fillStyle = 'rgba(255, 255, 255, 0.3)'), I.fill());
+ } else {
+ let ue = A === h.PI * 2,
+ Ne = T & 4 || ue ? R : c;
+ T & 1 && c > 0 && I.arc(U, B, c, b + A, b, !0),
+ I.moveTo(U + Ne * h.cos(b), B + Ne * h.sin(b)),
+ I.arc(U, B, R, b, b + A, !1),
+ ue || I.lineTo(U + c * h.cos(b + A), B + c * h.sin(b + A));
+ }
+ let Q = g.t,
+ le = T & 10,
+ pe = 0,
+ $ = -1 / 0;
+
+ for (let ue of g.l) {
+ ($ = se(ue, d + 1, R, b + (v * pe) / Q, (ue.t / Q) * v, le, $)),
+ (pe += ue.t),
+ (le |= 4);
+ }
+ return W;
+ },
+ X = () => {
+ I.clearRect(0, 0, C, k),
+ se(K, Z, Qe(Z), de, fe, 3, -1 / 0),
+ (I.strokeStyle = '#222'),
+ I.beginPath(),
+ se(K, Z, Qe(Z), de, fe, 1, -1 / 0),
+ I.stroke(),
+ Z === 0 &&
+ ((I.fillStyle = '#222'),
+ (I.font = 'bold 16px sans-serif'),
+ (I.textAlign = 'center'),
+ (I.textBaseline = 'middle'),
+ I.fillText(ae(Y.t), U, B));
+ },
+ J = -h.PI / 2,
+ C = 0,
+ k = 0,
+ U = 0,
+ B = 0,
+ ee = null,
+ D = 0,
+ E = 0,
+ F = J,
+ j = h.PI * 2,
+ Y = o,
+ ce = E,
+ q = F,
+ re = j,
+ K = o,
+ Z = E,
+ de = F,
+ fe = j,
+ ne = (g) => {
+ let d = (S, R, _, W, A) => {
+ let Q = Qe(R + 1);
+
+ if (Q > B) {
+ return null;
+ }
+ if (v >= _ && v < Q) {
+ let $ = T - W;
+
+ if ((($ /= h.PI * 2), ($ -= h.floor($)), ($ *= h.PI * 2), $ < A)) {
+ return S === K ? S.i : S;
+ }
+ }
+ let le = S.t,
+ pe = 0;
+ for (let $ of S.l) {
+ let ue = d($, R + 1, Q, W + (A * pe) / le, ($.t / le) * A);
+
+ if (ue) {
+ return ue;
+ }
+ pe += $.t;
+ }
+ return null;
+ },
+ c = g.pageX,
+ b = g.pageY;
+
+ for (let S = P; S; S = S.offsetParent) {
+ (c -= S.offsetLeft), (b -= S.offsetTop);
+ }
+ (c -= U), (b -= B);
+ let v = h.sqrt(c * c + b * b),
+ T = h.atan2(b, c);
+ return d(K, Z, Qe(Z), de, fe);
+ },
+ Le = () => {
+ let g = (Re() - D) / 350;
+ g < 0 || g > 1
+ ? ((g = 1), (ee = null), (K = Y), (ce = 0), (q = J), (re = h.PI * 2))
+ : (g < 0.5 ? (g *= 4 * g * g) : ((g = 1 - g), (g *= 4 * g * g), (g = 1 - g)),
+ (ee = ie(Le))),
+ (Z = E + (ce - E) * g),
+ (de = F + (q - F) * g),
+ (fe = j + (re - j) * g),
+ X();
+ },
+ y = m.createElement('div'),
+ w = (g, d, c) => {
+ (y.style.display = 'block'),
+ (y.style.left = g + 'px'),
+ (y.style.top = d + 'px'),
+ (y.innerHTML = c);
+ },
+ L = () => {
+ y.style.display = 'none';
+ },
+ O = null,
+ M = [],
+ V = (g) => {
+ let d = ne(g);
+
+ if ((r(d), d && d !== K.i)) {
+ let c = d.e;
+ if (d.i && d.i.e !== '') {
+ let b = d.i.e.length;
+ c = z(c.slice(0, b)) + '' + z(c.slice(b)) + '';
+ } else {
+ c = '' + z(We(c)) + '';
+ }
+ me === 2 ? (c += z(Ee(d.e, ' \u2013 '))) : (c += ' \u2013 ' + z(ae(d.t))),
+ w(g.pageX, g.pageY + 20, c),
+ (P.style.cursor = 'pointer');
+ } else {
+ L();
+ }
+ };
+ return (
+ G(),
+ Xe(X),
+ Ye(G),
+ je((g) => {
+ Ze() || V(g);
+ }),
+ (P.onmousemove = (g) => {
+ V(g);
+ }),
+ (P.onmouseout = () => {
+ r(null), L();
+ }),
+ (P.onclick = (g) => {
+ let d = ne(g);
+
+ if (!d) {
+ return;
+ }
+ L();
+ let c = [];
+ d !== K.i ? (c = M.concat(o)) : M.length > 0 && ((d = M.pop()), (c = M.slice())),
+ d.l.length > 0 ? (n(d), (M = c)) : (g.preventDefault(), Oe(e, d.e, d.t));
+ }),
+ (x.className = Il),
+ x.append(P, Me),
+ (y.className = ze),
+ i.append(y, x),
+ [
+ X,
+ () => {
+ if (
+ (O !== s &&
+ ((O = s), s || ((P.style.cursor = 'auto'), L()), ee === null && (ee = ie(Le))),
+ Y !== o)
+ ) {
+ if (((M.length = 0), ee === null && (ee = ie(Le)), (D = Re()), Al(K, o))) {
+ let g = { T: Z, y: de, c: fe };
+ _t(K, o, g),
+ (Z = g.T),
+ (de = g.y),
+ (fe = g.c),
+ (ce = 0),
+ (q = J),
+ (re = h.PI * 2),
+ (K = o);
+ } else if (Al(o, K)) {
+ let g = { T: 0, y: J, c: h.PI * 2 };
+ _t(o, K, g), (ce = g.T), (q = g.y), (re = g.c);
+ } else {
+ (D = -1 / 0), (K = o);
+ }
+ (E = Z), (F = de), (j = fe), (Y = o);
+ }
+ },
+ ]
+ );
+ },
+ a = () => {
+ let x = m.createElement('div'),
+ P = () => {
+ let C = o.i,
+ k = o.l,
+ U = m.createElement('div'),
+ B = 1;
+ U.className = Pl;
+ for (let E of k) {
+ let F = E.t;
+ F > B && (B = F);
+ }
+ if (((I.length = 0), (G.length = 0), C)) {
+ let E = m.createElement('a');
+ (E.className = At), (E.tabIndex = 0), U.append(E);
+ let F = m.createElement('div');
+ (F.className = Dt), E.append(F);
+ let j = m.createElement('div');
+ (j.className = Ft),
+ E.append(j),
+ (E.href = 'javascript:void 0'),
+ (F.textContent = '../'),
+ (E.onclick = () => {
+ n(C), He && G.length > 0 && G[0].focus();
+ }),
+ (E.onfocus = E.onmouseover = () => r(C)),
+ (E.onblur = E.onmouseout = () => r(null)),
+ I.push(C),
+ G.push(E);
+ }
+ for (let E of k) {
+ let F = E.e.slice(o.e.length),
+ j = ae(E.t),
+ Y = m.createElement('a');
+ (Y.className = At), (Y.tabIndex = 0), U.append(Y);
+ let ce = m.createElement('div');
+ (ce.className = Dt), (ce.innerHTML = z(F === E.e ? We(F) : F)), Y.append(ce);
+ let q = m.createElement('div');
+ (q.className = Ft), Y.append(q);
+ let re = m.createElement('div'),
+ K = sl(E.e);
+ (re.className = Rl + (E.t ? '' : ' ' + Hl)),
+ (re.style.background = K),
+ (re.style.width = (100 * E.t) / B + '%'),
+ q.append(re);
+ let Z = m.createElement('div');
+ (Z.className = kl),
+ (Z.textContent = me === 2 ? Ee(E.e, '') : j),
+ re.append(Z),
+ (Y.href = 'javascript:void 0'),
+ (Y.onclick = (de) => {
+ de.preventDefault(),
+ E.l.length > 0 ? (n(E), He && G.length > 0 && G[0].focus()) : Oe(e, E.e, E.t);
+ }),
+ (Y.onfocus = Y.onmouseover = () => r(E)),
+ (Y.onblur = Y.onmouseout = () => r(null)),
+ I.push(E),
+ G.push(Y);
+ }
+ let ee = m.createElement('div');
+ (ee.className = Cl), (ee.textContent = 'Directory: ');
+ let D = m.createElement('div');
+ (D.className = Ll), ee.append(D);
+ for (let E = o; E; E = E.i) {
+ let F = E.e || '/',
+ j = m.createElement('a');
+ E.i && (F = F.slice(E.i.e.length)),
+ (j.textContent = F),
+ E !== o &&
+ ((j.href = 'javascript:void 0'),
+ (j.onclick = (Y) => {
+ Y.preventDefault(),
+ n(E),
+ He && G.length > 0 && G[!I[0] && G.length > 1 ? 1 : 0].focus();
+ })),
+ D.insertBefore(j, D.firstChild),
+ o == p.u && ((j.tabIndex = -1), I.unshift(o), G.unshift(j));
+ }
+ (x.innerHTML = ''), x.append(ee, U);
+ },
+ I = [],
+ G = [],
+ se = o,
+ X = null,
+ J = null;
+ return (
+ (x.className = Ol),
+ i.append(x),
+ P(),
+ [
+ P,
+ () => {
+ if ((se !== o && ((se = o), P()), X !== s)) {
+ (X = s), J && (J.classList.remove('hover'), (J = null));
+ for (let C = s; C; C = C.i) {
+ let k = I.indexOf(C);
+ if (k >= 0) {
+ (J = G[k]), J.classList.add('hover');
+ break;
+ }
+ }
+ }
+ },
+ ]
+ );
+ },
+ [f, u] = l(),
+ [N, H] = a();
+ return (
+ $e(() => {
+ f(), N();
+ }),
+ (t.id = Ml),
+ (t.innerHTML = `
This visualization shows how much space each input file takes up in the final bundle. Input files that take up 0 bytes have been completely eliminated by tree-shaking.
Benefit of this chart type: Can be navigated with the keyboard.
`),
+ t.append(i),
+ t
+ );
+ };
+ var Fl = 'A';
+ var pr = (e) => {
+ let t = e.outputs,
+ i = 0,
+ p = 0,
+ o = [],
+ s,
+ n = (l) => ' \u2013 ' + ae(l),
+ r = (l) => {
+ let a = l.n,
+ f = [];
+
+ for (let u in a) {
+ f.push(r(a[u]));
+ }
+ return { r: l.r, e: l.e, p: n(l.t), t: l.t, l: f.sort(ve) };
+ };
+ for (let l in t) {
+ let a = ge(l);
+ a.pop(), (s = Ue(a.join('/'), s));
+ }
+ for (let l in t) {
+ if (Te(l)) {
+ continue;
+ }
+ let f = { r: s ? ge(l).slice(s.length).join('/') : l, e: '', t: 0, n: {} },
+ u = t[l],
+ N = u.inputs,
+ H = u.bytes;
+ for (let x in N) {
+ let P = we(f, Se(x), N[x].bytesInOutput);
+ P > p && (p = P);
+ }
+ (f.t = H), (i += H), o.push(r(f));
+ }
+ e: for (;;) {
+ let l;
+ for (let a of o) {
+ let f = a.l;
+
+ if (!f.length) {
+ continue;
+ }
+
+ if (f.length > 1 || f[0].l.length !== 1) {
+ break e;
+ }
+ let u = f[0].r;
+
+ if (l === void 0) {
+ l = u;
+ } else if (l !== u) {
+ break e;
+ }
+ }
+
+ if (l === void 0) {
+ break;
+ }
+ for (let a of o) {
+ let f = a.l;
+ if (f.length) {
+ f = f[0].l;
+
+ for (let u of f) {
+ u.r = l + u.r;
+ }
+ a.l = f;
+ }
+ }
+ p--;
+ }
+ for (let l of o) {
+ let a = 0;
+
+ for (let f of l.l) {
+ a += f.t;
+ }
+ a < l.t && l.l.push({ r: '(unassigned)', e: '', p: n(l.t - a), t: l.t - a, l: [] });
+ }
+ return o.sort(ve), { u: { r: '', e: '', p: '', t: i, l: o }, b: p + 1 };
+ },
+ _l = (e) => {
+ let t = pr(e),
+ i = t.u.t,
+ p = 0,
+ o = i,
+ s = m.createElement('div'),
+ n = m.createElement('main'),
+ r = m.createElement('canvas'),
+ l = r.getContext('2d'),
+ a = 0,
+ f = 0,
+ u = 0,
+ N = 0,
+ H = 0,
+ x = !1,
+ P = 1,
+ I = null,
+ G = null,
+ se = '',
+ X = '14px sans-serif',
+ J = {},
+ C = 'bold ' + X,
+ k = {},
+ U = 0,
+ B = k,
+ ee = (y) => {
+ G !== y &&
+ ((G = y), (r.style.cursor = y && !y.l.length ? 'pointer' : 'auto'), y || K(), ce());
+ },
+ D = (y) => {
+ let w = B[y];
+ return w === void 0 && ((w = l.measureText(String.fromCharCode(y)).width), (B[y] = w)), w;
+ },
+ E = () => {
+ let y = te.devicePixelRatio || 1;
+ (a = s.clientWidth + 2 * 50),
+ (f = t.b * 24 + 1),
+ (u = (a - 1e3) >> 1),
+ (N = u + 1e3),
+ u < 0 && (u = 0),
+ N > a && (N = a),
+ (N -= u),
+ (P = i / N),
+ (r.style.width = a + 'px'),
+ (r.style.height = f + 'px'),
+ (n.style.height = f + 'px'),
+ (r.width = h.round(a * y)),
+ (r.height = h.round(f * y)),
+ l.scale(y, y),
+ Y();
+ },
+ F = (y, w) => {
+ let L = U,
+ O = y.length,
+ M = 0;
+
+ for (; M < O && ((L += D(y.charCodeAt(M))), !(L > w)); ) {
+ M++;
+ }
+ return y.slice(0, M) + '...';
+ },
+ j = (y, w, L, O, M) => {
+ let V = N / (o - p),
+ g = u + (L - p) * V,
+ d = y.t * V,
+ c = g + d;
+
+ if (c < O + 1.5) {
+ return O;
+ }
+
+ if (g + d < 0 || g > a) {
+ return c;
+ }
+ let b = d < 2 ? 2 : d,
+ v = (g > 0 ? g : 0) + 5,
+ T = w + 24 / 2,
+ S = '',
+ R = '',
+ _,
+ W = 0,
+ A = d + g - v,
+ Q = y.e ? Je(l, y.e, u - p * V, 24, V * P) : ke,
+ le = 'black',
+ pe = -1 / 0;
+ M & 1
+ ? ((le = se), (l.font = C), (B = J), (U = 3 * D(46)))
+ : ((l.fillStyle = Q),
+ l.fillRect(g, w, b, 24),
+ (M & 2 || (G && y.e === G.e)) &&
+ ((l.fillStyle = 'rgba(255, 255, 255, 0.3)'), l.fillRect(g, w, b, 24), (M |= 2))),
+ U < A &&
+ ((S = y.r),
+ (_ = l.measureText(S).width),
+ _ <= A ? (W += _) : ((S = F(S, A)), (W = A)),
+ (l.fillStyle = le),
+ l.fillText(S, v, T)),
+ M & 1 && ((l.font = X), (B = k), (U = 3 * D(46))),
+ W + U < A &&
+ ((R = me === 2 ? Ee(y.e, ' \u2013 ') : y.p),
+ (_ = l.measureText(R).width),
+ W + _ > A && (R = F(R, A - W)),
+ (l.globalAlpha = 0.5),
+ l.fillText(R, v + W, T),
+ (l.globalAlpha = 1));
+
+ for (let $ of y.l) {
+ (pe = j($, w + 24, L, pe, M & -2)), (L += $.t);
+ }
+ return M & 1 || ot(l, '#222', g + 0.5, w + 0.5, b, 24), c;
+ },
+ Y = () => {
+ let y = getComputedStyle(m.body),
+ w = 0,
+ L = -1 / 0;
+ (I = null),
+ (se = y.getPropertyValue('--fg-on')),
+ l.clearRect(0, 0, a, f),
+ (l.textBaseline = 'middle');
+
+ for (let O of t.u.l) {
+ (L = j(O, 0, w, L, 1)), (w += O.t);
+ }
+ },
+ ce = () => {
+ I === null && (I = ie(Y));
+ },
+ q = m.createElement('div'),
+ re = (y, w, L) => {
+ (q.style.display = 'block'),
+ (q.style.left = y + 'px'),
+ (q.style.top = w + 'px'),
+ (q.innerHTML = L);
+ let O = q.offsetWidth;
+
+ for (let M = q; M; M = M.offsetParent) {
+ O += M.offsetLeft;
+ }
+ O > a && (q.style.left = y + a - O + 'px');
+ },
+ K = () => {
+ q.style.display = 'none';
+ },
+ Z = (y) => {
+ let w = (g, d, c) => {
+ if (M >= c && M < c + g.t) {
+ if (O >= d && O < d + 24 && g.e) {
+ return g;
+ }
+
+ if (O >= d + 24) {
+ for (let b of g.l) {
+ let v = w(b, d + 24, c);
+ if (v) {
+ return v;
+ }
+ c += b.t;
+ }
+ }
+ }
+ return null;
+ },
+ L = y.pageX,
+ O = y.pageY;
+
+ for (let g = r; g; g = g.offsetParent) {
+ (L -= g.offsetLeft), (O -= g.offsetTop);
+ }
+ let M = p + ((o - p) / N) * (L - u),
+ V = 0;
+ for (let g of t.u.l) {
+ let d = w(g, 0, V);
+
+ if (d) {
+ return d;
+ }
+ V += g.t;
+ }
+ return null;
+ },
+ de = (y, w, L) => {
+ let O = p,
+ M = o,
+ V = 0;
+
+ if (L !== null) {
+ let g = O + ((M - O) / N) * (L - u),
+ d = h.pow(1.01, w);
+ (O = g + (O - g) * d), (M = g + (M - g) * d);
+ } else {
+ V = (y * (M - O)) / N;
+ }
+ O + V < 0 ? (V = -O) : M + V > i && (V = i - M),
+ (O += V),
+ (M += V),
+ O < 0 && (O = 0),
+ M > i && (M = i),
+ (p !== O || o !== M) && ((p = O), (o = M), ce());
+ },
+ fe = (y) => {
+ let w = Z(y);
+
+ if ((ee(w), w)) {
+ let L = w.r === w.e ? We(w.e) : w.e,
+ O = L.length - w.r.length;
+ (L = z(L.slice(0, O)) + '' + z(L.slice(O)) + ''),
+ (L += me === 2 ? z(Ee(w.e, ' \u2013 ')) : ' \u2013 ' + z(ae(w.t))),
+ re(y.pageX, y.pageY + 20, L);
+ } else {
+ K();
+ }
+ },
+ ne = !1;
+ (r.onmousedown = (y) => {
+ if (((ne = !1), y.button !== 2)) {
+ let w = y.pageX,
+ L = (M) => {
+ let V = M.pageX - w;
+ (!ne && h.abs(V) < 3) || ((ne = !0), de(-V, 0, null), (w = M.pageX));
+ },
+ O = () => {
+ m.removeEventListener('mousemove', L), m.removeEventListener('mouseup', O);
+ };
+ y.preventDefault(), m.addEventListener('mousemove', L), m.addEventListener('mouseup', O);
+ }
+ }),
+ (r.onmousemove = (y) => {
+ fe(y);
+ }),
+ (r.onmouseout = (y) => {
+ ee(null);
+ }),
+ (r.onclick = (y) => {
+ if (ne) {
+ return;
+ }
+ let w = Z(y);
+ ee(w), w && !w.l.length && Oe(e, w.e, w.t);
+ }),
+ je((y) => {
+ if (Ze()) {
+ return;
+ }
+ let w = y.deltaX,
+ L = y.deltaY,
+ O = Re(),
+ M = O - H < 50 ? x : y.ctrlKey || y.metaKey;
+ (H = O),
+ (x = M),
+ (M || h.abs(w) >= h.abs(L)) && y.preventDefault(),
+ de(w, L, M ? y.pageX : null),
+ fe(y);
+ }),
+ E(),
+ Promise.resolve().then(E),
+ Xe(Y),
+ $e(Y),
+ Ye(E),
+ (s.id = Fl),
+ (s.innerHTML =
+ `
This visualization shows which input files were placed into each output file in the bundle. Use the scroll wheel with the ` +
+ ($t ? 'command' : 'control') +
+ ' key to zoom in and out.
Benefit of this chart type: Best chart for quick mouse navigation.
'),
+ (q.className = ze),
+ n.append(r),
+ s.append(n, q);
+ let Le = m.createElement('section');
+ return Le.append(Me), s.append(Le), s;
+ };
+ var Wl = 'B',
+ Bl = 'D',
+ zl = 'E',
+ Wt = 'G';
+ var Gl,
+ fr = (e) => {
+ let t = e.inputs,
+ i = {},
+ p = [];
+ for (let o in t) {
+ let s = t[o];
+
+ for (let n of s.imports) {
+ if (n.original && n.original[0] !== '.') {
+ let r = i[n.original] || (i[n.original] = []);
+ r.includes(n.path) || r.push(n.path);
+ }
+ }
+ }
+ for (let o in i) {
+ let s = i[o];
+ if (s.length > 1) {
+ let n = m.createElement('div'),
+ r = m.createElement('ul'),
+ l,
+ a;
+ (n.className = zl),
+ (n.innerHTML =
+ 'The import path ' +
+ z(o) +
+ ' resolves to multiple files in the bundle:');
+
+ for (let f of s) {
+ l = Ue(f, l);
+ }
+ for (let f of s) {
+ let u = ge(f);
+ l && (u = u.slice(l.length)), (a = Jt(u.join('/'), a));
+ }
+ for (let f of s.sort()) {
+ let u = ge(f).map(z),
+ N = m.createElement('li'),
+ H = '
-The `@storybook/core` package is the core of Storybook. It is responsible for the following:
+
Build bulletproof UI components faster
-- the main UI of storybook
-- the UI used by addons
-- the API used by addons
-- the API used by the CLI
-- the API used by the server
-- prebundled code used by the browser
-- static assets used by the browser
-- utilities for CSF, MDX & Docs
+
-## Private package
+
+Storybook is a frontend workshop for building UI components and pages in isolation. Thousands of teams use it for UI development, testing, and documentation. Find out more at storybook.js.org!
+
-This package is not intended to be used by anyone but storybook internally.
+
+
+
-Even though this is where all of the code is located, it is NOT to be the entry point when using functionality within!
+---
-Consumers of the code should import like so:
+# Storybook
-```ts
-import { addons } from 'storybook/manager-api';
-```
+The `storybook` package contains Storybook's core. It includes:
-Importing from `@storybook/core` is explicitly NOT supported; it WILL break in a future version of storybook, very likely in a non-major version bump.
+- Storybook's CLI and development server
+- Storybook's main UI (aka "the manager")
+- Core functionality including component controls, toolbar controls, action logging, viewport control, and interaction debugger
+- User-facing utility libraries such as `storybook/test`, `theming`, and `viewport`
+- Libraries used by Storybook's ecosystem of frameworks, addons, and builders
-# For maintainers
+It also contains a variety of other libraries and utilities under the `stroybook/internal` namespace, such as utilities for CSF, MDX & Docs.
-## When to use `@storybook/core`
-
-In the following packages you should import from `@storybook/core` (and ONLY from `@storybook/core`):
-
-- `@storybook/core`
-- `@storybook/codemod`
-
-To prevent cyclical dependencies, these packages cannot depend on the `storybook` package.
-
-## When to use `storybook/internal`
-
-In every other package you should import from `storybook/internal` (and ONLY from `storybook/internal`).
-
-The heuristic is simple:
-
-> If you see a peerDependency on `storybook` in the `package.json` of the package you are working on, you should import from `storybook/internal`.
-
-## The 1 exception: the `storybook` package itself
-
-The sole exception is the `storybook` package itself.
-
-Obviously, the `storybook` package cannot depend on itself, so it must import from `@storybook/core`.
+Learn more about Storybook at [storybook.js.org](https://storybook.js.org/?ref=readme).
diff --git a/code/core/templates/mocker-runtime.template.js b/code/core/assets/server/mocker-runtime.template.js
similarity index 100%
rename from code/core/templates/mocker-runtime.template.js
rename to code/core/assets/server/mocker-runtime.template.js
diff --git a/code/core/bin/index.cjs b/code/core/bin/index.cjs
deleted file mode 100755
index d0395f4f3f9e..000000000000
--- a/code/core/bin/index.cjs
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/usr/bin/env node
-
-const majorNodeVersion = parseInt(process.versions.node, 10);
-if (majorNodeVersion < 20) {
- console.error('To run Storybook you need to have Node.js 20 or higher');
- process.exit(1);
-}
-
-// The Storybook CLI has a catch block for all of its commands, but if an error
-// occurs before the command even runs, for instance, if an import fails, then
-// such error will fall under the uncaughtException handler.
-// This is the earliest moment we can catch such errors.
-process.once('uncaughtException', (error) => {
- if (error.message.includes('string-width')) {
- console.error(
- [
- '🔴 Error: It looks like you are having a known issue with package hoisting.',
- 'Please check the following issue for details and solutions: https://github.com/storybookjs/storybook/issues/22431#issuecomment-1630086092\n\n',
- ].join('\n')
- );
- }
-
- throw error;
-});
-
-require('../dist/bin/index.cjs');
diff --git a/code/core/build-config.ts b/code/core/build-config.ts
new file mode 100644
index 000000000000..76c21ac6b105
--- /dev/null
+++ b/code/core/build-config.ts
@@ -0,0 +1,206 @@
+import fs from 'node:fs/promises';
+import path from 'node:path';
+
+import { x as exec } from 'tinyexec';
+
+import type { BuildEntries } from '../../scripts/build/utils/entry-utils';
+
+const config: BuildEntries = {
+ prebuild: async (cwd) => {
+ await exec('jiti', [path.join(import.meta.dirname, 'scripts', 'generate-source-files.ts')], {
+ nodeOptions: {
+ cwd,
+ env: {
+ ...process.env,
+ NODE_ENV: 'production',
+ FORCE_COLOR: '1',
+ },
+ stdio: 'inherit',
+ },
+ throwOnError: true,
+ });
+ },
+ entries: {
+ node: [
+ {
+ exportEntries: ['./internal/node-logger'],
+ entryPoint: './src/node-logger/index.ts',
+ },
+ {
+ exportEntries: ['./internal/server-errors'],
+ entryPoint: './src/server-errors.ts',
+ },
+ {
+ exportEntries: ['./internal/core-server'],
+ entryPoint: './src/core-server/index.ts',
+ },
+ {
+ entryPoint: './src/core-server/presets/common-preset.ts',
+ dts: false,
+ },
+ {
+ entryPoint: './src/core-server/presets/common-override-preset.ts',
+ exportEntries: ['./internal/core-server/presets/common-override-preset'],
+ dts: false,
+ },
+ {
+ exportEntries: ['./internal/telemetry'],
+ entryPoint: './src/telemetry/index.ts',
+ },
+ {
+ exportEntries: ['./internal/csf-tools'],
+ entryPoint: './src/csf-tools/index.ts',
+ },
+ {
+ exportEntries: ['./internal/babel'],
+ entryPoint: './src/babel/index.ts',
+ },
+ {
+ exportEntries: ['./internal/bin/dispatcher'],
+ entryPoint: './src/bin/dispatcher.ts',
+ dts: false,
+ },
+ {
+ entryPoint: './src/bin/core.ts',
+ dts: false,
+ },
+ {
+ exportEntries: ['./internal/bin/loader'],
+ entryPoint: './src/bin/loader.ts',
+ dts: false,
+ },
+ {
+ exportEntries: ['./internal/common'],
+ entryPoint: './src/common/index.ts',
+ },
+ {
+ entryPoint: './src/cli/index.ts',
+ exportEntries: ['./internal/cli'],
+ },
+ {
+ entryPoint: './src/core-server/presets/webpack/loaders/webpack-automock-loader.ts',
+ exportEntries: ['./webpack/loaders/webpack-automock-loader'],
+ dts: false,
+ },
+ {
+ entryPoint: './src/core-server/presets/webpack/loaders/storybook-mock-transform-loader.ts',
+ exportEntries: ['./webpack/loaders/storybook-mock-transform-loader'],
+ dts: false,
+ },
+ ],
+ browser: [
+ {
+ exportEntries: ['./internal/client-logger'],
+ entryPoint: './src/client-logger/index.ts',
+ },
+ {
+ exportEntries: ['./internal/instrumenter'],
+ entryPoint: './src/instrumenter/index.ts',
+ },
+ {
+ exportEntries: ['./test', './internal/test'],
+ entryPoint: './src/test/index.ts',
+ },
+ {
+ exportEntries: ['./preview-api', './internal/preview-api'],
+ entryPoint: './src/preview-api/index.ts',
+ },
+ {
+ exportEntries: ['./highlight', './internal/highlight'],
+ entryPoint: './src/highlight/index.ts',
+ },
+ {
+ exportEntries: ['./actions', './internal/actions'],
+ entryPoint: './src/actions/index.ts',
+ },
+ {
+ exportEntries: ['./actions/decorator', './internal/actions/decorator'],
+ entryPoint: './src/actions/decorator.ts',
+ },
+ {
+ exportEntries: ['./viewport', './internal/viewport'],
+ entryPoint: './src/viewport/index.ts',
+ },
+ {
+ exportEntries: ['./internal/preview/globals'],
+ entryPoint: './src/preview/globals.ts',
+ },
+ {
+ exportEntries: ['./internal/csf'],
+ entryPoint: './src/csf/index.ts',
+ },
+ {
+ exportEntries: ['./internal/manager-errors'],
+ entryPoint: './src/manager-errors.ts',
+ },
+ {
+ exportEntries: ['./internal/preview-errors'],
+ entryPoint: './src/preview-errors.ts',
+ },
+ {
+ exportEntries: ['./internal/manager/globals'],
+ entryPoint: './src/manager/globals.ts',
+ },
+ {
+ entryPoint: './src/core-server/presets/common-manager.ts',
+ dts: false,
+ },
+ {
+ exportEntries: ['./theming', './internal/theming'],
+ entryPoint: './src/theming/index.ts',
+ },
+ {
+ exportEntries: ['./theming/create', './internal/theming/create'],
+ entryPoint: './src/theming/create.ts',
+ },
+ {
+ exportEntries: ['./internal/components'],
+ entryPoint: './src/components/index.ts',
+ },
+ {
+ exportEntries: ['./manager-api', './internal/manager-api'],
+ entryPoint: './src/manager-api/index.ts',
+ },
+ {
+ exportEntries: ['./internal/router'],
+ entryPoint: './src/router/index.ts',
+ },
+ {
+ exportEntries: ['./internal/docs-tools'],
+ entryPoint: './src/docs-tools/index.ts',
+ },
+ {
+ exportEntries: ['./internal/core-events'],
+ entryPoint: './src/core-events/index.ts',
+ },
+ {
+ exportEntries: ['./internal/channels'],
+ entryPoint: './src/channels/index.ts',
+ },
+ {
+ exportEntries: ['./internal/types'],
+ entryPoint: './src/types/index.ts',
+ },
+ ],
+ runtime: [
+ {
+ exportEntries: ['./internal/preview/runtime'],
+ entryPoint: './src/preview/runtime.ts',
+ dts: false,
+ },
+ {
+ exportEntries: ['./internal/manager/globals-runtime'],
+ entryPoint: './src/manager/globals-runtime.ts',
+ dts: false,
+ },
+ ],
+ globalizedRuntime: [
+ {
+ entryPoint: './src/manager/runtime.tsx',
+ dts: false,
+ },
+ ],
+ },
+};
+
+export default config;
diff --git a/code/core/package.json b/code/core/package.json
index da0016a688b7..d087e30d858b 100644
--- a/code/core/package.json
+++ b/code/core/package.json
@@ -1,9 +1,23 @@
{
"name": "storybook",
"version": "9.2.0-alpha.3",
- "description": "Storybook framework-agnostic API",
+ "description": "Storybook: Develop, document, and test UI components in isolation",
"keywords": [
- "storybook"
+ "storybook",
+ "component",
+ "components",
+ "design-systems",
+ "component-testing",
+ "react",
+ "next",
+ "next.js",
+ "react-native",
+ "react-native-web",
+ "vue",
+ "angular",
+ "svelte",
+ "sveltekit",
+ "web-components"
],
"homepage": "https://storybook.js.org",
"bugs": {
@@ -32,419 +46,189 @@
}
},
"exports": {
- ".": {
- "types": "./dist/index.d.ts",
- "import": "./dist/index.js",
- "require": "./dist/index.cjs"
- },
- "./internal/node-logger": {
- "types": "./dist/node-logger/index.d.ts",
- "import": "./dist/node-logger/index.js",
- "require": "./dist/node-logger/index.cjs"
- },
- "./internal/client-logger": {
- "types": "./dist/client-logger/index.d.ts",
- "import": "./dist/client-logger/index.js",
- "require": "./dist/client-logger/index.cjs"
- },
- "./internal/theming": {
- "types": "./dist/theming/index.d.ts",
- "import": "./dist/theming/index.js",
- "require": "./dist/theming/index.cjs"
- },
- "./theming": {
- "types": "./dist/theming/index.d.ts",
- "import": "./dist/theming/index.js",
- "require": "./dist/theming/index.cjs"
- },
- "./internal/theming/create": {
- "types": "./dist/theming/create.d.ts",
- "import": "./dist/theming/create.js",
- "require": "./dist/theming/create.cjs"
- },
- "./theming/create": {
- "types": "./dist/theming/create.d.ts",
- "import": "./dist/theming/create.js",
- "require": "./dist/theming/create.cjs"
- },
- "./internal/core-server": {
- "types": "./dist/core-server/index.d.ts",
- "import": "./dist/core-server/index.js",
- "require": "./dist/core-server/index.cjs"
- },
- "./internal/core-server/presets/common-preset": {
- "import": "./dist/core-server/presets/common-preset.js",
- "require": "./dist/core-server/presets/common-preset.cjs"
- },
- "./internal/core-server/presets/common-manager": {
- "import": "./dist/core-server/presets/common-manager.js"
- },
- "./internal/core-server/presets/common-override-preset": {
- "import": "./dist/core-server/presets/common-override-preset.js",
- "require": "./dist/core-server/presets/common-override-preset.cjs"
- },
- "./internal/core-server/presets/webpack/loaders/webpack-automock-loader": {
- "import": "./dist/core-server/presets/webpack/loaders/webpack-automock-loader.js",
- "require": "./dist/core-server/presets/webpack/loaders/webpack-automock-loader.cjs"
- },
- "./internal/core-server/presets/webpack/loaders/storybook-mock-transform-loader": {
- "import": "./dist/core-server/presets/webpack/loaders/storybook-mock-transform-loader.js",
- "require": "./dist/core-server/presets/webpack/loaders/storybook-mock-transform-loader.cjs"
+ "./actions": {
+ "types": "./dist/actions/index.d.ts",
+ "default": "./dist/actions/index.js"
},
- "./internal/highlight": {
- "types": "./dist/highlight/index.d.ts",
- "import": "./dist/highlight/index.js",
- "require": "./dist/highlight/index.cjs"
+ "./actions/decorator": {
+ "types": "./dist/actions/decorator.d.ts",
+ "default": "./dist/actions/decorator.js"
},
"./highlight": {
"types": "./dist/highlight/index.d.ts",
- "import": "./dist/highlight/index.js",
- "require": "./dist/highlight/index.cjs"
+ "default": "./dist/highlight/index.js"
},
"./internal/actions": {
"types": "./dist/actions/index.d.ts",
- "import": "./dist/actions/index.js",
- "require": "./dist/actions/index.cjs"
- },
- "./actions": {
- "types": "./dist/actions/index.d.ts",
- "import": "./dist/actions/index.js",
- "require": "./dist/actions/index.cjs"
+ "default": "./dist/actions/index.js"
},
"./internal/actions/decorator": {
"types": "./dist/actions/decorator.d.ts",
- "import": "./dist/actions/decorator.js"
+ "default": "./dist/actions/decorator.js"
},
- "./actions/decorator": {
- "types": "./dist/actions/decorator.d.ts",
- "import": "./dist/actions/decorator.js"
+ "./internal/babel": {
+ "types": "./dist/babel/index.d.ts",
+ "default": "./dist/babel/index.js"
},
- "./internal/viewport": {
- "types": "./dist/viewport/index.d.ts",
- "import": "./dist/viewport/index.js",
- "require": "./dist/viewport/index.cjs"
+ "./internal/bin/dispatcher": "./dist/bin/dispatcher.js",
+ "./internal/bin/loader": "./dist/bin/loader.js",
+ "./internal/channels": {
+ "types": "./dist/channels/index.d.ts",
+ "default": "./dist/channels/index.js"
},
- "./viewport": {
- "types": "./dist/viewport/index.d.ts",
- "import": "./dist/viewport/index.js",
- "require": "./dist/viewport/index.cjs"
+ "./internal/cli": {
+ "types": "./dist/cli/index.d.ts",
+ "default": "./dist/cli/index.js"
},
- "./internal/controls": {
- "types": "./dist/controls/index.d.ts",
- "import": "./dist/controls/index.js",
- "require": "./dist/controls/index.cjs"
+ "./internal/client-logger": {
+ "types": "./dist/client-logger/index.d.ts",
+ "default": "./dist/client-logger/index.js"
},
- "./internal/controls/decorator": {
- "types": "./dist/controls/decorator.d.ts",
- "import": "./dist/controls/decorator.js"
+ "./internal/common": {
+ "types": "./dist/common/index.d.ts",
+ "default": "./dist/common/index.js"
+ },
+ "./internal/components": {
+ "types": "./dist/components/index.d.ts",
+ "default": "./dist/components/index.js"
},
"./internal/core-events": {
"types": "./dist/core-events/index.d.ts",
- "import": "./dist/core-events/index.js",
- "require": "./dist/core-events/index.cjs"
+ "default": "./dist/core-events/index.js"
},
- "./internal/manager-errors": {
- "types": "./dist/manager-errors.d.ts",
- "import": "./dist/manager-errors.js"
+ "./internal/core-server": {
+ "types": "./dist/core-server/index.d.ts",
+ "default": "./dist/core-server/index.js"
},
- "./internal/preview-errors": {
- "types": "./dist/preview-errors.d.ts",
- "import": "./dist/preview-errors.js",
- "require": "./dist/preview-errors.cjs"
+ "./internal/core-server/presets/common-override-preset": "./dist/core-server/presets/common-override-preset.js",
+ "./internal/csf": {
+ "types": "./dist/csf/index.d.ts",
+ "default": "./dist/csf/index.js"
},
- "./internal/server-errors": {
- "types": "./dist/server-errors.d.ts",
- "import": "./dist/server-errors.js",
- "require": "./dist/server-errors.cjs"
+ "./internal/csf-tools": {
+ "types": "./dist/csf-tools/index.d.ts",
+ "default": "./dist/csf-tools/index.js"
},
- "./internal/channels": {
- "types": "./dist/channels/index.d.ts",
- "import": "./dist/channels/index.js",
- "require": "./dist/channels/index.cjs"
+ "./internal/docs-tools": {
+ "types": "./dist/docs-tools/index.d.ts",
+ "default": "./dist/docs-tools/index.js"
},
- "./internal/types": {
- "types": "./dist/types/index.d.ts",
- "import": "./dist/types/index.js",
- "require": "./dist/types/index.cjs"
+ "./internal/highlight": {
+ "types": "./dist/highlight/index.d.ts",
+ "default": "./dist/highlight/index.js"
},
- "./internal/csf-tools": {
- "types": "./dist/csf-tools/index.d.ts",
- "import": "./dist/csf-tools/index.js",
- "require": "./dist/csf-tools/index.cjs"
+ "./internal/instrumenter": {
+ "types": "./dist/instrumenter/index.d.ts",
+ "default": "./dist/instrumenter/index.js"
},
- "./internal/csf": {
- "types": "./dist/csf/index.d.ts",
- "import": "./dist/csf/index.js",
- "require": "./dist/csf/index.cjs"
+ "./internal/manager-api": {
+ "types": "./dist/manager-api/index.d.ts",
+ "default": "./dist/manager-api/index.js"
},
- "./internal/common": {
- "types": "./dist/common/index.d.ts",
- "import": "./dist/common/index.js",
- "require": "./dist/common/index.cjs"
+ "./internal/manager-errors": {
+ "types": "./dist/manager-errors.d.ts",
+ "default": "./dist/manager-errors.js"
},
- "./internal/builder-manager": {
- "types": "./dist/builder-manager/index.d.ts",
- "import": "./dist/builder-manager/index.js",
- "require": "./dist/builder-manager/index.cjs"
+ "./internal/manager/globals": {
+ "types": "./dist/manager/globals.d.ts",
+ "default": "./dist/manager/globals.js"
},
- "./internal/telemetry": {
- "types": "./dist/telemetry/index.d.ts",
- "import": "./dist/telemetry/index.js",
- "require": "./dist/telemetry/index.cjs"
+ "./internal/manager/globals-runtime": "./dist/manager/globals-runtime.js",
+ "./internal/node-logger": {
+ "types": "./dist/node-logger/index.d.ts",
+ "default": "./dist/node-logger/index.js"
},
"./internal/preview-api": {
"types": "./dist/preview-api/index.d.ts",
- "import": "./dist/preview-api/index.js",
- "require": "./dist/preview-api/index.cjs"
- },
- "./preview-api": {
- "types": "./dist/preview-api/index.d.ts",
- "import": "./dist/preview-api/index.js",
- "require": "./dist/preview-api/index.cjs"
+ "default": "./dist/preview-api/index.js"
},
- "./internal/manager-api": {
- "types": "./dist/manager-api/index.d.ts",
- "import": "./dist/manager-api/index.js",
- "require": "./dist/manager-api/index.cjs"
+ "./internal/preview-errors": {
+ "types": "./dist/preview-errors.d.ts",
+ "default": "./dist/preview-errors.js"
},
- "./manager-api": {
- "types": "./dist/manager-api/index.d.ts",
- "import": "./dist/manager-api/index.js",
- "require": "./dist/manager-api/index.cjs"
+ "./internal/preview/globals": {
+ "types": "./dist/preview/globals.d.ts",
+ "default": "./dist/preview/globals.js"
},
+ "./internal/preview/runtime": "./dist/preview/runtime.js",
"./internal/router": {
"types": "./dist/router/index.d.ts",
- "import": "./dist/router/index.js",
- "require": "./dist/router/index.cjs"
+ "default": "./dist/router/index.js"
},
- "./internal/components": {
- "types": "./dist/components/index.d.ts",
- "import": "./dist/components/index.js",
- "require": "./dist/components/index.cjs"
- },
- "./internal/docs-tools": {
- "types": "./dist/docs-tools/index.d.ts",
- "import": "./dist/docs-tools/index.js",
- "require": "./dist/docs-tools/index.cjs"
- },
- "./internal/manager/globals-module-info": {
- "types": "./dist/manager/globals-module-info.d.ts",
- "import": "./dist/manager/globals-module-info.js",
- "require": "./dist/manager/globals-module-info.cjs"
+ "./internal/server-errors": {
+ "types": "./dist/server-errors.d.ts",
+ "default": "./dist/server-errors.js"
},
- "./internal/manager/globals": {
- "types": "./dist/manager/globals.d.ts",
- "import": "./dist/manager/globals.js",
- "require": "./dist/manager/globals.cjs"
+ "./internal/telemetry": {
+ "types": "./dist/telemetry/index.d.ts",
+ "default": "./dist/telemetry/index.js"
},
- "./internal/preview/globals": {
- "types": "./dist/preview/globals.d.ts",
- "import": "./dist/preview/globals.js",
- "require": "./dist/preview/globals.cjs"
+ "./internal/test": {
+ "types": "./dist/test/index.d.ts",
+ "default": "./dist/test/index.js"
},
- "./internal/cli": {
- "types": "./dist/cli/index.d.ts",
- "import": "./dist/cli/index.js",
- "require": "./dist/cli/index.cjs"
+ "./internal/theming": {
+ "types": "./dist/theming/index.d.ts",
+ "default": "./dist/theming/index.js"
},
- "./internal/babel": {
- "types": "./dist/babel/index.d.ts",
- "import": "./dist/babel/index.js",
- "require": "./dist/babel/index.cjs"
+ "./internal/theming/create": {
+ "types": "./dist/theming/create.d.ts",
+ "default": "./dist/theming/create.js"
},
- "./internal/cli/bin": {
- "types": "./dist/cli/bin/index.d.ts",
- "import": "./dist/cli/bin/index.js",
- "require": "./dist/cli/bin/index.cjs"
+ "./internal/types": {
+ "types": "./dist/types/index.d.ts",
+ "default": "./dist/types/index.js"
},
- "./internal/bin": {
- "import": "./dist/bin/index.js",
- "require": "./dist/bin/index.cjs"
+ "./internal/viewport": {
+ "types": "./dist/viewport/index.d.ts",
+ "default": "./dist/viewport/index.js"
},
- "./internal/instrumenter": {
- "types": "./dist/instrumenter/index.d.ts",
- "import": "./dist/instrumenter/index.js",
- "require": "./dist/instrumenter/index.cjs"
+ "./manager-api": {
+ "types": "./dist/manager-api/index.d.ts",
+ "default": "./dist/manager-api/index.js"
},
- "./internal/test": {
- "types": "./dist/test/index.d.ts",
- "import": "./dist/test/index.js",
- "require": "./dist/test/index.cjs"
+ "./package.json": "./package.json",
+ "./preview-api": {
+ "types": "./dist/preview-api/index.d.ts",
+ "default": "./dist/preview-api/index.js"
},
"./test": {
"types": "./dist/test/index.d.ts",
- "import": "./dist/test/index.js",
- "require": "./dist/test/index.cjs"
+ "default": "./dist/test/index.js"
},
- "./internal/preview/runtime": {
- "import": "./dist/preview/runtime.js"
+ "./theming": {
+ "types": "./dist/theming/index.d.ts",
+ "default": "./dist/theming/index.js"
},
- "./internal/manager/globals-runtime": {
- "import": "./dist/manager/globals-runtime.js"
+ "./theming/create": {
+ "types": "./dist/theming/create.d.ts",
+ "default": "./dist/theming/create.js"
},
- "./bin/index.cjs": "./bin/index.cjs",
- "./package.json": "./package.json",
- "./internal/package.json": "./package.json"
- },
- "main": "dist/index.cjs",
- "module": "dist/index.js",
- "types": "dist/index.d.ts",
- "typesVersions": {
- "*": {
- "*": [
- "./dist/index.d.ts"
- ],
- "internal/node-logger": [
- "./dist/node-logger/index.d.ts"
- ],
- "internal/client-logger": [
- "./dist/client-logger/index.d.ts"
- ],
- "internal/theming": [
- "./dist/theming/index.d.ts"
- ],
- "theming": [
- "./dist/theming/index.d.ts"
- ],
- "internal/theming/create": [
- "./dist/theming/create.d.ts"
- ],
- "theming/create": [
- "./dist/theming/create.d.ts"
- ],
- "internal/core-server": [
- "./dist/core-server/index.d.ts"
- ],
- "internal/highlight": [
- "./dist/highlight/index.d.ts"
- ],
- "highlight": [
- "./dist/highlight/index.d.ts"
- ],
- "internal/actions": [
- "./dist/actions/index.d.ts"
- ],
- "actions": [
- "./dist/actions/index.d.ts"
- ],
- "internal/actions/decorator": [
- "./dist/actions/decorator.d.ts"
- ],
- "actions/decorator": [
- "./dist/actions/decorator.d.ts"
- ],
- "internal/viewport": [
- "./dist/viewport/index.d.ts"
- ],
- "viewport": [
- "./dist/viewport/index.d.ts"
- ],
- "internal/controls": [
- "./dist/controls/index.d.ts"
- ],
- "internal/controls/decorator": [
- "./dist/controls/decorator.d.ts"
- ],
- "internal/core-events": [
- "./dist/core-events/index.d.ts"
- ],
- "internal/manager-errors": [
- "./dist/manager-errors.d.ts"
- ],
- "internal/preview-errors": [
- "./dist/preview-errors.d.ts"
- ],
- "internal/server-errors": [
- "./dist/server-errors.d.ts"
- ],
- "internal/channels": [
- "./dist/channels/index.d.ts"
- ],
- "internal/types": [
- "./dist/types/index.d.ts"
- ],
- "internal/csf-tools": [
- "./dist/csf-tools/index.d.ts"
- ],
- "internal/csf": [
- "./dist/csf/index.d.ts"
- ],
- "internal/common": [
- "./dist/common/index.d.ts"
- ],
- "internal/builder-manager": [
- "./dist/builder-manager/index.d.ts"
- ],
- "internal/telemetry": [
- "./dist/telemetry/index.d.ts"
- ],
- "internal/preview-api": [
- "./dist/preview-api/index.d.ts"
- ],
- "preview-api": [
- "./dist/preview-api/index.d.ts"
- ],
- "internal/manager-api": [
- "./dist/manager-api/index.d.ts"
- ],
- "manager-api": [
- "./dist/manager-api/index.d.ts"
- ],
- "internal/router": [
- "./dist/router/index.d.ts"
- ],
- "internal/components": [
- "./dist/components/index.d.ts"
- ],
- "internal/docs-tools": [
- "./dist/docs-tools/index.d.ts"
- ],
- "internal/manager/globals-module-info": [
- "./dist/manager/globals-module-info.d.ts"
- ],
- "internal/manager/globals": [
- "./dist/manager/globals.d.ts"
- ],
- "internal/preview/globals": [
- "./dist/preview/globals.d.ts"
- ],
- "internal/cli": [
- "./dist/cli/index.d.ts"
- ],
- "internal/babel": [
- "./dist/babel/index.d.ts"
- ],
- "internal/cli/bin": [
- "./dist/cli/bin/index.d.ts"
- ],
- "internal/instrumenter": [
- "./dist/instrumenter/index.d.ts"
- ],
- "internal/test": [
- "./dist/test/index.d.ts"
- ],
- "test": [
- "./dist/test/index.d.ts"
- ]
- }
+ "./viewport": {
+ "types": "./dist/viewport/index.d.ts",
+ "default": "./dist/viewport/index.js"
+ },
+ "./webpack/loaders/storybook-mock-transform-loader": "./dist/core-server/presets/webpack/loaders/storybook-mock-transform-loader.js",
+ "./webpack/loaders/webpack-automock-loader": "./dist/core-server/presets/webpack/loaders/webpack-automock-loader.js"
},
- "bin": "./bin/index.cjs",
+ "bin": "./dist/bin/dispatcher.js",
"files": [
+ "bin/**/*",
"dist/**/*",
"assets/**/*",
- "templates/**/*",
"README.md",
"!src/**/*"
],
"scripts": {
- "check": "jiti ./scripts/check.ts",
- "prep": "jiti ./scripts/prep.ts"
+ "check": "jiti ../../scripts/check/check-package.ts",
+ "prep": "jiti ../../scripts/build/build-package.ts"
},
"resolutions": {
"@testing-library/user-event": "patch:@testing-library/user-event@npm%3A14.6.1#~/../.yarn/patches/@testing-library-user-event-npm-14.6.1-5da7e1d4e2.patch"
},
"dependencies": {
"@storybook/global": "^5.0.0",
+ "@storybook/icons": "^1.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/user-event": "^14.6.1",
"@vitest/expect": "3.2.4",
@@ -452,7 +236,6 @@
"@vitest/spy": "3.2.4",
"better-opn": "^3.0.2",
"esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0",
- "esbuild-register": "^3.5.0",
"recast": "^0.23.5",
"semver": "^7.6.2",
"ws": "^8.18.0"
@@ -472,6 +255,7 @@
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@fal-works/esbuild-plugin-global-externals": "^2.1.2",
+ "@happy-dom/global-registrator": "^18.0.1",
"@ndelangen/get-tarball": "^3.0.7",
"@ngard/tiny-isequal": "^1.1.0",
"@polka/compression": "^1.0.0-next.28",
@@ -480,7 +264,6 @@
"@radix-ui/react-slot": "^1.0.2",
"@rolldown/pluginutils": "1.0.0-beta.18",
"@storybook/docs-mdx": "4.0.0-next.1",
- "@storybook/icons": "^1.4.0",
"@tanstack/react-virtual": "^3.3.0",
"@testing-library/dom": "10.4.0",
"@testing-library/react": "^14.0.0",
@@ -535,7 +318,7 @@
"get-npm-tarball-url": "^2.0.3",
"glob": "^10.0.0",
"globby": "^14.0.1",
- "jiti": "^1.21.6",
+ "jiti": "^2.4.2",
"js-yaml": "^4.1.0",
"jsdoc-type-pratt-parser": "^4.0.0",
"lazy-universal-dotenv": "^4.0.0",
@@ -543,6 +326,7 @@
"lodash": "^4.17.21",
"memfs": "^4.11.1",
"memoizerific": "^1.11.3",
+ "mlly": "^1.7.4",
"nanoid": "^4.0.2",
"npmlog": "^7.0.0",
"open": "^8.4.0",
@@ -579,7 +363,6 @@
"tinyspy": "^3.0.2",
"ts-dedent": "^2.0.0",
"tsconfig-paths": "^4.2.0",
- "tsup": "^6.7.0",
"type-fest": "^4.18.1",
"typescript": "^5.8.3",
"unique-string": "^3.0.0",
@@ -598,5 +381,5 @@
"publishConfig": {
"access": "public"
},
- "gitHead": "e6a7fd8a655c69780bc20b9749c2699e44beae17"
+ "gitHead": "a8e7fd8a655c69780bc20b9749d2699e45beae16"
}
diff --git a/code/core/scripts/dts.ts b/code/core/scripts/dts.ts
deleted file mode 100644
index f52b1a6508a6..000000000000
--- a/code/core/scripts/dts.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import { join } from 'node:path';
-
-import { dts, nodeInternals, process } from '../../../scripts/prepare/tools';
-import pkg from '../package.json';
-import { getEntries } from './entries';
-
-async function run() {
- const cwd = process.cwd();
-
- const flags = process.argv.slice(2);
-
- const selection = flags[0] || 'all';
-
- const entries = getEntries(cwd);
- const external = [
- ...Object.keys((pkg as any).dependencies || {}),
- ...Object.keys((pkg as any).peerDependencies || {}),
- ...nodeInternals,
- 'typescript',
- 'storybook',
-
- 'storybook/manager-api',
- 'storybook/preview-api',
- 'storybook/theming',
-
- 'storybook/test',
- 'storybook/highlight',
- 'storybook/actions',
- 'storybook/actions/decorator',
- 'storybook/viewport',
-
- 'storybook/internal/builder-manager',
- 'storybook/internal/channels',
- 'storybook/internal/client-logger',
- 'storybook/internal/common',
- 'storybook/internal/component-testing',
- 'storybook/internal/components',
- 'storybook/internal/core-events',
- 'storybook/internal/core-server',
- 'storybook/internal/csf-tools',
- 'storybook/internal/docs-tools',
- 'storybook/internal/node-logger',
- 'storybook/internal/router',
- 'storybook/internal/telemetry',
- 'storybook/internal/types',
- 'storybook/internal/instrumenter',
- ];
-
- const all = entries.filter((e) => e.dts);
- const list = selection === 'all' ? all : [all[Number(selection)]];
-
- console.log('Generating d.ts files for', list.map((i) => i.file).join(', '));
-
- await Promise.all(
- list.map(async (i) => {
- await dts(i.file, [...external, ...i.externals], join(__dirname, '..', 'tsconfig.json'));
- })
- );
-}
-
-run().catch((e) => {
- process.stderr.write(e.toString());
- process.exit(1);
-});
diff --git a/code/core/scripts/entries.ts b/code/core/scripts/entries.ts
deleted file mode 100644
index bbfea61ee146..000000000000
--- a/code/core/scripts/entries.ts
+++ /dev/null
@@ -1,114 +0,0 @@
-import { defineEntry } from '../../../scripts/prepare/tools';
-
-export const getEntries = (cwd: string) => {
- const define = defineEntry(cwd);
- return [
- // empty, right now, TDB what to do with this
- define('src/index.ts', ['node', 'browser'], true),
-
- define('src/node-logger/index.ts', ['node'], true),
- define('src/client-logger/index.ts', ['browser', 'node'], true),
-
- define('src/theming/index.ts', ['browser', 'node'], true, ['react'], [], [], true),
- define('src/theming/create.ts', ['browser', 'node'], true, ['react'], [], [], true),
-
- define('src/core-server/index.ts', ['node'], true, ['react']),
- define('src/core-server/presets/common-preset.ts', ['node'], false),
- define('src/core-server/presets/common-manager.ts', ['browser'], false, [
- 'react',
- '@storybook/icons',
- ]),
-
- define('src/core-server/presets/common-override-preset.ts', ['node'], false),
-
- define('src/core-server/presets/webpack/loaders/webpack-automock-loader.ts', ['node'], false),
- define(
- 'src/core-server/presets/webpack/loaders/storybook-mock-transform-loader.ts',
- ['node'],
- false
- ),
-
- define('src/highlight/index.ts', ['browser', 'node'], true, ['react'], [], [], true),
-
- define('src/actions/index.ts', ['browser', 'node'], true, ['react'], [], [], true),
- define('src/actions/decorator.ts', ['browser'], true, ['react'], [], [], true),
-
- define('src/viewport/index.ts', ['browser', 'node'], true, ['react'], [], [], true),
-
- define('src/controls/index.ts', ['browser', 'node'], true, ['react']),
- define('src/controls/decorator.ts', ['browser'], true, ['react']),
-
- define('src/core-events/index.ts', ['browser', 'node'], true),
- define('src/manager-errors.ts', ['browser'], true),
- define('src/preview-errors.ts', ['browser', 'node'], true),
- define('src/server-errors.ts', ['node'], true),
-
- define('src/channels/index.ts', ['browser', 'node'], true),
- define('src/types/index.ts', ['browser', 'node'], true, ['react']),
- define('src/csf-tools/index.ts', ['node'], true),
- define('src/csf/index.ts', ['browser', 'node'], true),
- define('src/common/index.ts', ['node'], true),
- define('src/builder-manager/index.ts', ['node'], true),
- define('src/telemetry/index.ts', ['node'], true),
- define('src/preview-api/index.ts', ['browser', 'node'], true, ['react'], [], [], true),
- define(
- 'src/manager-api/index.ts',
- ['browser', 'node'],
- true,
- ['react', 'react-dom'],
- [],
- [],
- true
- ),
- define('src/router/index.ts', ['browser', 'node'], true, ['react']),
- define('src/components/index.ts', ['browser', 'node'], true, ['react', 'react-dom'], []),
- define('src/docs-tools/index.ts', ['browser', 'node'], true),
-
- define('src/manager/globals-module-info.ts', ['node'], true),
- define('src/manager/globals.ts', ['node'], true),
- define('src/preview/globals.ts', ['node'], true),
- define('src/cli/index.ts', ['node'], true),
- define('src/babel/index.ts', ['node'], true),
- define('src/cli/bin/index.ts', ['node'], true),
- define('src/bin/index.ts', ['node'], false),
-
- define('src/instrumenter/index.ts', ['browser', 'node'], true),
- define(
- 'src/test/index.ts',
- ['browser', 'node'],
- true,
- ['util', 'react'],
- [],
- [
- '@testing-library/jest-dom',
- '@testing-library/user-event',
- 'chai',
- '@vitest/expect',
- '@vitest/spy',
- '@vitest/utils',
- ],
- true
- ),
- ];
-};
-
-// entries for injecting globals into the preview and manager
-export const getBundles = (cwd: string) => {
- const define = defineEntry(cwd);
-
- return [
- //
- define('src/preview/runtime.ts', ['browser'], false),
- define('src/manager/globals-runtime.ts', ['browser'], false),
- ];
-};
-
-// the runtime for the manager
-export const getFinals = (cwd: string) => {
- const define = defineEntry(cwd);
-
- return [
- //
- define('src/manager/runtime.tsx', ['browser'], false),
- ];
-};
diff --git a/code/core/scripts/helpers/sourcefiles.ts b/code/core/scripts/generate-source-files.ts
similarity index 67%
rename from code/core/scripts/helpers/sourcefiles.ts
rename to code/core/scripts/generate-source-files.ts
index b7f8b28f2044..acb03af7574a 100644
--- a/code/core/scripts/helpers/sourcefiles.ts
+++ b/code/core/scripts/generate-source-files.ts
@@ -3,31 +3,30 @@ import { mkdirSync } from 'node:fs';
import { readdir, realpath, writeFile } from 'node:fs/promises';
import os from 'node:os';
import { join } from 'node:path';
+import { pathToFileURL } from 'node:url';
import { GlobalRegistrator } from '@happy-dom/global-registrator';
-import { isNotNil } from 'es-toolkit';
-import uniqueString from 'unique-string';
+import { isNotNil } from 'es-toolkit/predicate';
+import * as esbuild from 'esbuild';
+import * as prettier from 'prettier';
+import { dedent } from 'ts-dedent';
-import { dedent, esbuild, getWorkspace, prettier } from '../../../../scripts/prepare/tools';
-import {
- BROWSER_TARGETS,
- SUPPORTED_FEATURES,
-} from '../../src/shared/constants/environments-support';
+import { getWorkspace } from '../../../scripts/utils/tools';
+import { BROWSER_TARGETS, SUPPORTED_FEATURES } from '../src/shared/constants/environments-support';
GlobalRegistrator.register({ url: 'http://localhost:3000', width: 1920, height: 1080 });
+const CORE_ROOT_DIR = join(import.meta.dirname, '..', '..', '..', '..', 'code', 'core');
const tempDir = () => realpath(os.tmpdir());
-const getPath = async (prefix = '') => join(await tempDir(), prefix + uniqueString());
+const getPath = async (prefix = '') =>
+ join(await tempDir(), prefix + (Math.random() + 1).toString(36).substring(7));
-export async function temporaryDirectory({ prefix = '' } = {}) {
+async function temporaryDirectory({ prefix = '' } = {}) {
const directory = await getPath(prefix);
mkdirSync(directory);
return directory;
}
-export async function temporaryFile({
- name,
- extension,
-}: { name?: string; extension?: string } = {}) {
+async function temporaryFile({ name, extension }: { name?: string; extension?: string } = {}) {
if (name) {
if (extension !== undefined && extension !== null) {
// eslint-disable-next-line local-rules/no-uncategorized-errors
@@ -47,11 +46,9 @@ export async function temporaryFile({
// save this list into ./code/core/src/types/frameworks.ts and export it as a union type.
// The name of the type is `SupportedFrameworks`. Add additionally 'qwik' and `solid` to that list.
export const generateSourceFiles = async () => {
- const location = join(__dirname, '..', '..', 'src');
- const prettierConfig = await prettier.resolveConfig(location);
+ const prettierConfig = await prettier.resolveConfig(join(CORE_ROOT_DIR, 'src'));
await Promise.all([
- //
generateFrameworksFile(prettierConfig),
generateVersionsFile(prettierConfig),
generateExportsFile(prettierConfig),
@@ -59,7 +56,7 @@ export const generateSourceFiles = async () => {
};
async function generateVersionsFile(prettierConfig: prettier.Options | null): Promise {
- const location = join(__dirname, '..', '..', 'src', 'common', 'versions.ts');
+ const destination = join(CORE_ROOT_DIR, 'src', 'common', 'versions.ts');
const workspace = (await getWorkspace()).filter(isNotNil);
@@ -75,7 +72,7 @@ async function generateVersionsFile(prettierConfig: prettier.Options | null): Pr
);
await writeFile(
- location,
+ destination,
await prettier.format(
dedent`
// auto generated file, do not edit
@@ -91,18 +88,26 @@ async function generateVersionsFile(prettierConfig: prettier.Options | null): Pr
async function generateFrameworksFile(prettierConfig: prettier.Options | null): Promise {
const thirdPartyFrameworks = ['qwik', 'solid', 'nuxt', 'react-rsbuild', 'vue3-rsbuild'];
- const location = join(__dirname, '..', '..', 'src', 'types', 'modules', 'frameworks.ts');
- const frameworksDirectory = join(__dirname, '..', '..', '..', 'frameworks');
+ const destination = join(CORE_ROOT_DIR, 'src', 'types', 'modules', 'frameworks.ts');
+ const frameworksDirectory = join(
+ import.meta.dirname,
+ '..',
+ '..',
+ '..',
+ '..',
+ 'code',
+ 'frameworks'
+ );
const readFrameworks = (await readdir(frameworksDirectory)).filter((framework) =>
- existsSync(join(frameworksDirectory, framework, 'project.json'))
+ existsSync(join(frameworksDirectory, framework, 'package.json'))
);
const frameworks = [...readFrameworks.sort(), ...thirdPartyFrameworks]
.map((framework) => `'${framework}'`)
.join(' | ');
await writeFile(
- location,
+ destination,
await prettier.format(
dedent`
// auto generated file, do not edit
@@ -117,24 +122,23 @@ async function generateFrameworksFile(prettierConfig: prettier.Options | null):
}
const localAlias = {
- '@storybook/core': join(__dirname, '..', '..', 'src'),
- 'storybook/internal': join(__dirname, '..', '..', 'src'),
- 'storybook/theming': join(__dirname, '..', '..', 'src', 'theming'),
- 'storybook/test': join(__dirname, '..', '..', 'src', 'test'),
- 'storybook/test/preview': join(__dirname, '..', '..', 'src', 'test', 'preview'),
- 'storybook/actions': join(__dirname, '..', '..', 'src', 'actions'),
- 'storybook/preview-api': join(__dirname, '..', '..', 'src', 'preview-api'),
- 'storybook/manager-api': join(__dirname, '..', '..', 'src', 'manager-api'),
- storybook: join(__dirname, '..', '..', 'src'),
+ 'storybook/internal': join(CORE_ROOT_DIR, 'src'),
+ 'storybook/theming': join(CORE_ROOT_DIR, 'src', 'theming'),
+ 'storybook/test': join(CORE_ROOT_DIR, 'src', 'test'),
+ 'storybook/test/preview': join(CORE_ROOT_DIR, 'src', 'test', 'preview'),
+ 'storybook/actions': join(CORE_ROOT_DIR, 'src', 'actions'),
+ 'storybook/preview-api': join(CORE_ROOT_DIR, 'src', 'preview-api'),
+ 'storybook/manager-api': join(CORE_ROOT_DIR, 'src', 'manager-api'),
+ storybook: join(CORE_ROOT_DIR, 'src'),
};
async function generateExportsFile(prettierConfig: prettier.Options | null): Promise {
function removeDefault(input: string) {
return input !== 'default';
}
- const location = join(__dirname, '..', '..', 'src', 'manager', 'globals', 'exports.ts');
+ const destination = join(CORE_ROOT_DIR, 'src', 'manager', 'globals', 'exports.ts');
- const entryFile = join(__dirname, '..', '..', 'src', 'manager', 'globals', 'runtime.ts');
+ const entryFile = join(CORE_ROOT_DIR, 'src', 'manager', 'globals', 'runtime.ts');
const outFile = await temporaryFile({ extension: 'js' });
await esbuild.build({
@@ -151,7 +155,7 @@ async function generateExportsFile(prettierConfig: prettier.Options | null): Pro
supported: SUPPORTED_FEATURES,
});
- const { globalsNameValueMap: data } = await import(outFile);
+ const { globalsNameValueMap: data } = await import(pathToFileURL(outFile).href);
// loop over all values of the keys of the data object and remove the default key
for (const key in data) {
@@ -164,7 +168,7 @@ async function generateExportsFile(prettierConfig: prettier.Options | null): Pro
}
await writeFile(
- location,
+ destination,
await prettier.format(
dedent`
// this file is generated by sourcefiles.ts
diff --git a/code/core/scripts/helpers/dependencies.ts b/code/core/scripts/helpers/dependencies.ts
deleted file mode 100644
index 89e67fc554f3..000000000000
--- a/code/core/scripts/helpers/dependencies.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { readFile } from 'node:fs/promises';
-import { join } from 'node:path';
-
-export async function flattenDependencies(
- list: string[],
- output: string[] = [],
- ignore: string[] = []
-): Promise {
- output.push(...list);
-
- await Promise.all(
- list.map(async (dep) => {
- let path;
- try {
- path = require.resolve(join(dep, 'package.json'));
- } catch (e) {
- console.log(dep + ' not found');
- return;
- }
- const { dependencies = {}, peerDependencies = {} } = JSON.parse(
- await readFile(path, { encoding: 'utf8' })
- );
- const all: string[] = [
- ...new Set([...Object.keys(dependencies), ...Object.keys(peerDependencies)]),
- ]
- .filter((d) => !output.includes(d))
- .filter((d) => !ignore.includes(d));
-
- await flattenDependencies(all, output, ignore);
- })
- );
-
- return output;
-}
diff --git a/code/core/scripts/helpers/generatePackageJsonFile.ts b/code/core/scripts/helpers/generatePackageJsonFile.ts
deleted file mode 100644
index f6677adfb9a0..000000000000
--- a/code/core/scripts/helpers/generatePackageJsonFile.ts
+++ /dev/null
@@ -1,128 +0,0 @@
-import { readFile, writeFile } from 'node:fs/promises';
-import { join, relative } from 'node:path';
-
-import slash from 'slash';
-
-import { sortPackageJson } from '../../../../scripts/prepare/tools';
-import type { getEntries } from '../entries';
-
-const cwd = process.cwd();
-
-export async function generatePackageJsonFile(entries: ReturnType) {
- const location = join(cwd, 'package.json');
- const pkgJson = JSON.parse(await readFile(location, { encoding: 'utf8' }));
-
- /**
- * Re-create the `exports` field in `code/core/package.json` This way we only need to update the
- * `./scripts/entries.ts` file to ensure all things we create actually exist and are mapped to the
- * correct path.
- */
- pkgJson.exports = entries.reduce>>((acc, entry) => {
- let main = './' + slash(relative(cwd, entry.file).replace('src', 'dist'));
-
- const content: Record = {};
- if (entry.dts) {
- content.types = main.replace(/\.tsx?/, '.d.ts');
- }
- if (entry.browser) {
- content.import = main.replace(/\.tsx?/, '.js');
- }
- if (entry.node && !entry.browser) {
- content.import = main.replace(/\.tsx?/, '.js');
- }
- if (entry.node) {
- content.require = main.replace(/\.tsx?/, '.cjs');
- }
- if (main === './dist/index.ts' || main === './dist/index.tsx') {
- main = '.';
- }
- /**
- * We always write an entry for /internal/X, even when it's isPublic is true, this is for
- * compatibility reasons. We should remove this once everything stops referencing public APIs as
- * internal.
- *
- * Known references:
- *
- * - VTA
- * - Design addon
- * - Addon kit
- *
- * I expect that we should be able to drop it in the process of of the release of 9.0, or keep
- * it for now, and drop it in the release of 9.1.
- */
- acc[
- main
- .replace(/\/index\.tsx?/, '')
- .replace(/\.tsx?/, '')
- .replace('dist/', 'internal/')
- ] = content;
-
- if (entry.isPublic) {
- acc[
- main
- .replace(/\/index\.tsx?/, '')
- .replace(/\.tsx?/, '')
- .replace('dist/', '')
- ] = content;
- }
- return acc;
- }, {});
-
- // Add CLI entry so `sb` can require it.
- pkgJson.exports['./bin/index.cjs'] = './bin/index.cjs';
-
- // Add the package.json file to the exports, so we can use it to `require.resolve` the package's root easily
- pkgJson.exports['./package.json'] = './package.json';
- pkgJson.exports['./internal/package.json'] = './package.json';
-
- /**
- * Add the `typesVersion` field to `code/core/package.json`, to make typescript respect and find
- * the correct type annotation files, even when not configured with `"moduleResolution":
- * "Bundler"` If we even decide to only support `"moduleResolution": "Bundler"`, we should be able
- * to remove this part, but that would be a breaking change.
- */
- pkgJson.typesVersions = {
- '*': {
- '*': ['./dist/index.d.ts'],
- ...entries.reduce>((acc, entry) => {
- if (!entry.dts) {
- return acc;
- }
-
- let main = slash(relative(cwd, entry.file).replace('src', 'dist'));
- if (main === './dist/index.ts' || main === './dist/index.tsx') {
- main = '.';
- }
- const key = main.replace(/\/index\.tsx?/, '').replace(/\.tsx?/, '');
-
- if (key === 'dist') {
- return acc;
- }
-
- const content = ['./' + main.replace(/\.tsx?/, '.d.ts')];
-
- /**
- * We always write an entry for /internal/X, even when it's isPublic is true, this is for
- * compatibility reasons. We should remove this once everything stops referencing public
- * APIs as internal.
- *
- * Known references:
- *
- * - VTA
- * - Design addon
- * - Addon kit
- *
- * I expect that we should be able to drop it in the process of of the release of 9.0, or
- * keep it for now, and drop it in the release of 9.1.
- */
- acc[key.replace('dist/', 'internal/')] = content;
- if (entry.isPublic) {
- acc[key.replace('dist/', '')] = content;
- }
- return acc;
- }, {}),
- },
- };
-
- await writeFile(location, `${sortPackageJson(JSON.stringify(pkgJson, null, 2))}\n`, {});
-}
diff --git a/code/core/scripts/helpers/generateTypesFiles.ts b/code/core/scripts/helpers/generateTypesFiles.ts
deleted file mode 100644
index a144600c8514..000000000000
--- a/code/core/scripts/helpers/generateTypesFiles.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-import { join, relative } from 'node:path';
-
-import { spawn } from '../../../../scripts/prepare/tools';
-import { limit, picocolors, process } from '../../../../scripts/prepare/tools';
-import type { getEntries } from '../entries';
-import { modifyThemeTypes } from './modifyThemeTypes';
-
-export async function generateTypesFiles(
- entries: ReturnType,
- isOptimized: boolean,
- cwd: string
-) {
- const dtsEntries = entries.filter((e) => e.dts).map((e) => e.file);
-
- if (isOptimized) {
- // Spawn each entry in it's own separate process, because they are slow & synchronous
- // ...this way we do not bog down the main process/esbuild and can run them in parallel
- // we limit the number of concurrent processes to 3, because we don't want to overload the host machine
- // by trial and error, 3 seems to be the sweet spot between perf and consistency
- const limited = limit(10);
- let processes: ReturnType[] = [];
-
- await Promise.all(
- dtsEntries.map(async (fileName, index) => {
- return limited(async () => {
- const getDtsProcess = () =>
- spawn(
- join(__dirname, '../../../../scripts/node_modules/.bin/jiti'),
- ['./scripts/dts.ts', index.toString()],
- {
- cwd,
- stdio: ['ignore', 'pipe', 'inherit'],
- }
- );
- let timer: ReturnType | undefined;
- const dtsProcess = getDtsProcess();
- processes.push(dtsProcess);
-
- await Promise.race([
- new Promise((resolve) => {
- dtsProcess.on('exit', () => {
- resolve(void 0);
- });
- dtsProcess.on('error', () => {
- resolve(void 0);
- });
- dtsProcess.on('close', () => {
- resolve(void 0);
- });
- }),
- new Promise((resolve) => {
- timer = setTimeout(() => {
- console.log(index, fileName);
-
- dtsProcess.kill(408); // timed out
- resolve(void 0);
- }, 60000);
- }),
- ]);
-
- if (timer) {
- clearTimeout(timer);
- }
-
- if (dtsProcess.exitCode !== 0) {
- console.error(
- '\nGenerating types for',
- picocolors.cyan(relative(cwd, dtsEntries[index])),
- ' failed'
- );
- console.log(dtsProcess.exitCode);
- // If any fail, kill all the other processes and exit (bail)
- processes.forEach((p) => p.kill());
- processes = [];
- console.log(index, fileName);
- process.exit(dtsProcess.exitCode || 1);
- } else {
- console.log('Generated types for', picocolors.cyan(relative(cwd, dtsEntries[index])));
-
- if (dtsEntries[index].includes('src/theming/index')) {
- console.log('Modifying theme types');
- await modifyThemeTypes();
- }
- }
- });
- })
- );
- }
-}
diff --git a/code/core/scripts/helpers/isEntryType.ts b/code/core/scripts/helpers/isEntryType.ts
deleted file mode 100644
index a2e6da72f48c..000000000000
--- a/code/core/scripts/helpers/isEntryType.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import type { getEntries } from '../entries';
-
-export function noExternals(entry: ReturnType[0]): boolean {
- return entry.externals.length === 0 && entry.internals.length === 0;
-}
-export function isNode(entry: ReturnType[0]): boolean {
- return !!entry.node;
-}
-export function isBrowser(entry: ReturnType[0]): boolean {
- return !!entry.browser;
-}
diff --git a/code/core/scripts/helpers/modifyThemeTypes.ts b/code/core/scripts/helpers/modifyThemeTypes.ts
deleted file mode 100644
index 85d78a2cb07b..000000000000
--- a/code/core/scripts/helpers/modifyThemeTypes.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { readFile, writeFile } from 'node:fs/promises';
-import { join } from 'node:path';
-
-import { dedent } from '../../../../scripts/prepare/tools';
-
-export async function modifyThemeTypes() {
- /**
- * This is a unique hack (pre-existing the CPC project) because the only way to set a custom Theme
- * interface with emotion, is by module enhancement. This is not an option for us, because we
- * pre-bundle emotion in. The little hack work to ensure the `Theme` export is overloaded with our
- * `StorybookTheme` interface. (in both development and production builds)
- */
- const target = join(__dirname, '..', '..', 'dist', 'theming', 'index.d.ts');
- const contents = await readFile(target, 'utf-8');
-
- const footer = contents.includes('// auto generated file')
- ? `export { StorybookTheme as Theme } from '../../src/theming/index';`
- : dedent`
- interface Theme extends StorybookTheme {}
- export type { Theme };
- `;
-
- const newContents = dedent`
- ${contents}
- ${footer}
- `;
-
- await writeFile(target, newContents);
-}
diff --git a/code/core/scripts/no-externals-plugin.ts b/code/core/scripts/no-externals-plugin.ts
deleted file mode 100644
index 43e8e45aeb07..000000000000
--- a/code/core/scripts/no-externals-plugin.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { match } from 'bundle-require';
-import type { Plugin } from 'esbuild';
-
-// Must not start with "/" or "./" or "../" or "C:\" or be the exact strings ".." or "."
-const NON_NODE_MODULE_RE = /^[A-Z]:[/\\]|^\.{0,2}\/|^\.{1,2}$/;
-
-export const externalPlugin = ({ noExternal }: { noExternal?: (string | RegExp)[] }): Plugin => {
- return {
- name: `external`,
-
- setup(build) {
- build.onResolve({ filter: /.*/ }, (args) => {
- // Respect explicit external/noExternal conditions
- if (match(args.path, noExternal)) {
- return undefined;
- }
- });
- },
- };
-};
diff --git a/code/core/scripts/prep.ts b/code/core/scripts/prep.ts
deleted file mode 100644
index b81482446173..000000000000
--- a/code/core/scripts/prep.ts
+++ /dev/null
@@ -1,426 +0,0 @@
-/* eslint-disable local-rules/no-uncategorized-errors */
-import { existsSync, watch } from 'node:fs';
-import { mkdir, rm, writeFile } from 'node:fs/promises';
-import { dirname, join } from 'node:path';
-
-import type { Metafile } from 'esbuild';
-
-import {
- dedent,
- esbuild,
- globalExternals,
- measure,
- merge,
- nodeInternals,
- picocolors,
- prettyTime,
- process,
-} from '../../../scripts/prepare/tools';
-import pkg from '../package.json';
-import { globalsModuleInfoMap } from '../src/manager/globals-module-info';
-import {
- BROWSER_TARGETS,
- NODE_TARGET,
- SUPPORTED_FEATURES,
-} from '../src/shared/constants/environments-support';
-import { getBundles, getEntries, getFinals } from './entries';
-import { generatePackageJsonFile } from './helpers/generatePackageJsonFile';
-import { generateTypesFiles } from './helpers/generateTypesFiles';
-import { generateTypesMapperFiles } from './helpers/generateTypesMapperFiles';
-import { isBrowser, isNode, noExternals } from './helpers/isEntryType';
-import { modifyThemeTypes } from './helpers/modifyThemeTypes';
-import { generateSourceFiles } from './helpers/sourcefiles';
-import { externalPlugin } from './no-externals-plugin';
-
-async function run() {
- const flags = process.argv.slice(2);
- const cwd = process.cwd();
-
- const isOptimized = flags.includes('--optimized');
- const isWatch = flags.includes('--watch');
- const isReset = flags.includes('--reset');
-
- const external = [
- ...new Set([
- ...Object.keys(pkg.dependencies),
- ...Object.keys((pkg as any).peerDependencies || {}),
- ]),
- ];
-
- if (isOptimized && isWatch) {
- throw new Error('Cannot watch and optimize at the same time');
- }
-
- if (isReset) {
- await rm(join(cwd, 'dist'), { recursive: true }).catch(() => {});
- await mkdir(join(cwd, 'dist'));
- }
-
- const entries = getEntries(cwd);
- const bundles = getBundles(cwd);
- const finals = getFinals(cwd);
-
- type EsbuildContextOptions = Parameters<(typeof esbuild)['context']>[0];
-
- console.log(isWatch ? 'Watching...' : 'Bundling...');
-
- const files = measure(generateSourceFiles);
- const packageJson = measure(() => generatePackageJsonFile(entries.concat(bundles)));
- const dist = files.then(() => measure(generateDistFiles));
- const types = files.then(() =>
- measure(async () => {
- await generateTypesMapperFiles(entries);
- await modifyThemeTypes();
- await generateTypesFiles(entries, isOptimized, cwd);
- })
- );
-
- const [filesTime, packageJsonTime, distTime, typesTime] = await Promise.all([
- files,
- packageJson,
- dist,
- types,
- ]);
-
- console.log('Files generated in', picocolors.yellow(prettyTime(filesTime)));
- console.log('Package.json generated in', picocolors.yellow(prettyTime(packageJsonTime)));
- console.log(
- isWatch ? 'Watcher started in' : 'Bundled in',
- picocolors.yellow(prettyTime(distTime))
- );
- console.log(
- isOptimized ? 'Generated types in' : 'Generated type mappers in',
- picocolors.yellow(prettyTime(typesTime))
- );
-
- async function generateDistFiles() {
- const esbuildDefaultOptions = {
- absWorkingDir: cwd,
- allowOverwrite: false,
- assetNames: 'assets/[name]-[hash]',
- bundle: true,
- chunkNames: 'chunks/[name]-[hash]',
- external: ['storybook', ...external],
- keepNames: true,
- legalComments: 'none',
- lineLimit: 140,
- metafile: true,
- minifyIdentifiers: isOptimized,
- minifySyntax: isOptimized,
- minifyWhitespace: false,
- outdir: 'dist',
- sourcemap: false,
- treeShaking: true,
- supported: {
- // This is an ES2018 feature, but esbuild is really strict here.
- // Since not all browser support the latest Unicode characters.
- //
- // Also this feature only works in combination with a Regex polyfill that we don't load.
- //
- // The Hermes engine of React Native doesn't support this feature,
- // but leaving the regex alone, actually allows Hermes to do its own thing,
- // without us having to load a RegExp polyfill.
- 'regexp-unicode-property-escapes': true,
- },
- } satisfies EsbuildContextOptions;
-
- const browserEsbuildOptions = {
- ...esbuildDefaultOptions,
- format: 'esm',
- target: BROWSER_TARGETS,
- supported: SUPPORTED_FEATURES,
- splitting: false,
- platform: 'browser',
-
- conditions: ['browser', 'module', 'import', 'default'],
- } satisfies EsbuildContextOptions;
-
- const nodeEsbuildOptions = {
- ...esbuildDefaultOptions,
- target: NODE_TARGET,
- splitting: false,
- platform: 'neutral',
- mainFields: ['main', 'module', 'node'],
- conditions: ['node', 'module', 'import', 'require'],
- } satisfies EsbuildContextOptions;
-
- const compile = await Promise.all([
- esbuild.context(
- merge(nodeEsbuildOptions, {
- entryPoints: entries
- .filter(isNode)
- .filter(noExternals)
- .map((e) => e.file),
- external: [...nodeInternals, ...esbuildDefaultOptions.external],
- format: 'cjs',
- outExtension: {
- '.js': '.cjs',
- },
- })
- ),
- esbuild.context(
- merge(browserEsbuildOptions, {
- entryPoints: entries
- .filter(isBrowser)
- .filter(noExternals)
- .map((entry) => entry.file),
- outExtension: {
- '.js': '.js',
- },
- })
- ),
- esbuild.context(
- merge(nodeEsbuildOptions, {
- banner: {
- js: dedent`
- import ESM_COMPAT_Module from "node:module";
- import { fileURLToPath as ESM_COMPAT_fileURLToPath } from 'node:url';
- import { dirname as ESM_COMPAT_dirname } from 'node:path';
- const __filename = ESM_COMPAT_fileURLToPath(import.meta.url);
- const __dirname = ESM_COMPAT_dirname(__filename);
- const require = ESM_COMPAT_Module.createRequire(import.meta.url);
- `,
- },
- entryPoints: entries
- .filter(isNode)
- .filter(noExternals)
- .filter((i) => !isBrowser(i))
- .map((entry) => entry.file),
- external: [...nodeInternals, ...esbuildDefaultOptions.external],
- format: 'esm',
- outExtension: {
- '.js': '.js',
- },
- })
- ),
- ...bundles.flatMap((entry) => {
- const results = [];
- results.push(
- esbuild.context(
- merge(browserEsbuildOptions, {
- outdir: dirname(entry.file).replace('src', 'dist'),
- entryPoints: [entry.file],
- outExtension: { '.js': '.js' },
- alias: {
- 'storybook/preview-api': join(cwd, 'src', 'preview-api'),
- 'storybook/manager-api': join(cwd, 'src', 'manager-api'),
- 'storybook/theming': join(cwd, 'src', 'theming'),
- 'storybook/test': join(cwd, 'src', 'test'),
- 'storybook/internal': join(cwd, 'src'),
- 'storybook/outline': join(cwd, 'src', 'outline'),
- 'storybook/backgrounds': join(cwd, 'src', 'backgrounds'),
- 'storybook/highlight': join(cwd, 'src', 'highlight'),
- 'storybook/measure': join(cwd, 'src', 'measure'),
- 'storybook/actions': join(cwd, 'src', 'actions'),
- 'storybook/viewport': join(cwd, 'src', 'viewport'),
- react: dirname(require.resolve('react/package.json')),
- 'react-dom': dirname(require.resolve('react-dom/package.json')),
- 'react-dom/client': join(
- dirname(require.resolve('react-dom/package.json')),
- 'client'
- ),
- },
- define: {
- // This should set react in prod mode for the manager
- 'process.env.NODE_ENV': JSON.stringify('production'),
- },
- external: [],
- })
- )
- );
-
- return results;
- }),
- ...finals.flatMap((entry) => {
- const results = [];
- results.push(
- esbuild.context(
- merge(browserEsbuildOptions, {
- alias: {
- 'storybook/preview-api': join(cwd, 'src', 'preview-api'),
- 'storybook/manager-api': join(cwd, 'src', 'manager-api'),
- 'storybook/theming': join(cwd, 'src', 'theming'),
- 'storybook/test': join(cwd, 'src', 'test'),
- 'storybook/actions': join(cwd, 'src', 'actions'),
- 'storybook/outline': join(cwd, 'src', 'outline'),
- 'storybook/backgrounds': join(cwd, 'src', 'backgrounds'),
- 'storybook/measure': join(cwd, 'src', 'measure'),
- 'storybook/viewport': join(cwd, 'src', 'viewport'),
- 'storybook/highlight': join(cwd, 'src', 'highlight'),
-
- 'storybook/internal': join(cwd, 'src'),
- react: dirname(require.resolve('react/package.json')),
- 'react-dom': dirname(require.resolve('react-dom/package.json')),
- 'react-dom/client': join(
- dirname(require.resolve('react-dom/package.json')),
- 'client'
- ),
- },
- define: {
- // This should set react in prod mode for the manager
- 'process.env.NODE_ENV': JSON.stringify('production'),
- },
- entryPoints: [entry.file],
- external: [],
- outdir: dirname(entry.file).replace('src', 'dist'),
- outExtension: {
- '.js': '.js',
- },
- plugins: [globalExternals(globalsModuleInfoMap)],
- })
- )
- );
-
- return results;
- }),
- ...entries
- .filter((entry) => !noExternals(entry))
- .flatMap((entry) => {
- const results = [];
- if (entry.node) {
- results.push(
- esbuild.context(
- merge(nodeEsbuildOptions, {
- entryPoints: [entry.file],
- external: [
- ...nodeInternals,
- ...esbuildDefaultOptions.external.filter((e) => !entry.noExternal.includes(e)),
- ...entry.externals,
- ].filter((e) => !entry.internals.includes(e)),
- plugins: [
- externalPlugin({
- noExternal: entry.noExternal,
- }),
- ],
- format: 'cjs',
- outdir: dirname(entry.file).replace('src', 'dist'),
- outExtension: {
- '.js': '.cjs',
- },
- })
- )
- );
- }
- if (entry.browser) {
- results.push(
- esbuild.context(
- merge(browserEsbuildOptions, {
- entryPoints: [entry.file],
- external: [
- ...nodeInternals,
- ...esbuildDefaultOptions.external.filter((e) => !entry.noExternal.includes(e)),
- ...entry.externals,
- ].filter((e) => !entry.internals.includes(e)),
- outdir: dirname(entry.file).replace('src', 'dist'),
- plugins: [
- externalPlugin({
- noExternal: entry.noExternal,
- }),
- ],
- outExtension: {
- '.js': '.js',
- },
- })
- )
- );
- } else if (entry.node) {
- results.push(
- esbuild.context(
- merge(nodeEsbuildOptions, {
- entryPoints: [entry.file],
- external: [
- ...nodeInternals,
- ...esbuildDefaultOptions.external.filter((e) => !entry.noExternal.includes(e)),
- ...entry.externals,
- ].filter((e) => !entry.internals.includes(e)),
- plugins: [
- externalPlugin({
- noExternal: entry.noExternal,
- }),
- ],
- format: 'esm',
- outdir: dirname(entry.file).replace('src', 'dist'),
- outExtension: {
- '.js': '.js',
- },
- })
- )
- );
- }
-
- return results;
- }),
- ]);
-
- if (isWatch) {
- await Promise.all(
- compile.map(async (context) => {
- await context.watch();
- })
- );
-
- // show a log message when a file is compiled
- watch(join(cwd, 'dist'), { recursive: true }, (event, filename) => {
- console.log(`compiled ${picocolors.cyan(filename)}`);
- });
- } else {
- // repo root/bench/esbuild-metafiles/core
- const metafilesDir = join(__dirname, '..', '..', 'bench', 'esbuild-metafiles', 'core');
- if (existsSync(metafilesDir)) {
- await rm(metafilesDir, { recursive: true });
- }
- await mkdir(metafilesDir, { recursive: true });
- const outputs = await Promise.all(
- compile.map(async (context) => {
- const output = await context.rebuild();
- await context.dispose();
- return output;
- })
- );
- const metafileByModule: Record = {};
- for (const currentOutput of outputs) {
- if (!currentOutput.metafile) {
- continue;
- }
- for (const key of Object.keys(currentOutput.metafile.outputs)) {
- const moduleName = dirname(key).replace('dist/', '');
- const existingMetafile = metafileByModule[moduleName];
- if (existingMetafile) {
- existingMetafile.inputs = {
- ...existingMetafile.inputs,
- ...currentOutput.metafile.inputs,
- };
- existingMetafile.outputs = {
- ...existingMetafile.outputs,
- [key]: currentOutput.metafile.outputs[key],
- };
- } else {
- metafileByModule[moduleName] = {
- ...currentOutput.metafile,
- outputs: { [key]: currentOutput.metafile.outputs[key] },
- };
- }
- }
- }
- await Promise.all(
- Object.entries(metafileByModule).map(async ([moduleName, metafile]) => {
- console.log('saving metafiles', moduleName);
- const sanitizedModuleName = moduleName.replaceAll('/', '-');
- await writeFile(
- join(metafilesDir, `${sanitizedModuleName}.json`),
- JSON.stringify(metafile, null, 2)
- );
- await writeFile(
- join(metafilesDir, `${sanitizedModuleName}.txt`),
- await esbuild.analyzeMetafile(metafile, { color: false, verbose: false })
- );
- })
- );
- }
- }
-}
-
-run().catch((err) => {
- console.error(err);
- process.exit(1);
-});
diff --git a/code/core/src/__tests/storybook-error.test.ts b/code/core/src/__tests/storybook-error.test.ts
index 770997b00fe9..3af3244d0d35 100644
--- a/code/core/src/__tests/storybook-error.test.ts
+++ b/code/core/src/__tests/storybook-error.test.ts
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest';
-import { StorybookError } from '../storybook-error';
+import { StorybookError, appendErrorRef } from '../storybook-error';
describe('StorybookError', () => {
class TestError extends StorybookError {
@@ -28,7 +28,7 @@ describe('StorybookError', () => {
it('should generate the correct message with internal documentation link', () => {
const error = new TestError(true);
const expectedMessage =
- 'This is a test error.\n\nMore info: https://storybook.js.org/error/SB_TEST_CATEGORY_0123\n';
+ 'This is a test error.\n\nMore info: https://storybook.js.org/error/SB_TEST_CATEGORY_0123?ref=error\n';
expect(error.message).toBe(expectedMessage);
});
@@ -45,14 +45,14 @@ describe('StorybookError', () => {
it('should generate the correct message with multiple external documentation links', () => {
const error = new TestError([
'https://example.com/docs/first-error',
- 'https://example.com/docs/second-error',
+ 'https://storybook.js.org/docs/second-error',
]);
expect(error.message).toMatchInlineSnapshot(`
"This is a test error.
More info:
- https://example.com/docs/first-error
- - https://example.com/docs/second-error
+ - https://storybook.js.org/docs/second-error?ref=error
"
`);
});
@@ -62,3 +62,83 @@ describe('StorybookError', () => {
expect(error.documentation).toBe(false);
});
});
+
+describe('appendErrorRef', () => {
+ it('should append ref=error to storybook.js.org URLs without query parameters', () => {
+ const url = 'https://storybook.js.org/docs/example';
+ const result = appendErrorRef(url);
+ expect(result).toBe('https://storybook.js.org/docs/example?ref=error');
+ });
+
+ it('should append ref=error to storybook.js.org URLs with existing query parameters', () => {
+ const url = 'https://storybook.js.org/docs/example?foo=bar';
+ const result = appendErrorRef(url);
+ expect(result).toBe('https://storybook.js.org/docs/example?foo=bar&ref=error');
+ });
+
+ it('should append ref=error to storybook.js.org URLs with multiple existing query parameters', () => {
+ const url = 'https://storybook.js.org/docs/example?foo=bar&baz=qux';
+ const result = appendErrorRef(url);
+ expect(result).toBe('https://storybook.js.org/docs/example?foo=bar&baz=qux&ref=error');
+ });
+
+ it('should handle storybook.js.org URLs with hash fragments', () => {
+ const url = 'https://storybook.js.org/docs/example#section';
+ const result = appendErrorRef(url);
+ expect(result).toBe('https://storybook.js.org/docs/example?ref=error#section');
+ });
+
+ it('should handle storybook.js.org URLs with query parameters and hash fragments', () => {
+ const url = 'https://storybook.js.org/docs/example?foo=bar#section';
+ const result = appendErrorRef(url);
+ expect(result).toBe('https://storybook.js.org/docs/example?foo=bar&ref=error#section');
+ });
+
+ it('should not modify non-storybook.js.org URLs', () => {
+ const url = 'https://example.com/docs/test-error';
+ const result = appendErrorRef(url);
+ expect(result).toBe('https://example.com/docs/test-error');
+ });
+
+ it('should not modify relative URLs', () => {
+ const url = '/docs/example';
+ const result = appendErrorRef(url);
+ expect(result).toBe('/docs/example');
+ });
+
+ it('should not modify empty string URLs', () => {
+ const url = '';
+ const result = appendErrorRef(url);
+ expect(result).toBe('');
+ });
+
+ it('should not modify other domain URLs', () => {
+ const url = 'https://github.com/storybookjs/storybook/issues/123';
+ const result = appendErrorRef(url);
+ expect(result).toBe('https://github.com/storybookjs/storybook/issues/123');
+ });
+
+ it('should not append ref=error if it already exists in the URL', () => {
+ const url = 'https://storybook.js.org/docs/example?ref=error';
+ const result = appendErrorRef(url);
+ expect(result).toBe('https://storybook.js.org/docs/example?ref=error');
+ });
+
+ it('should not append ref=error if it already exists with other parameters', () => {
+ const url = 'https://storybook.js.org/docs/example?foo=bar&ref=error&baz=qux';
+ const result = appendErrorRef(url);
+ expect(result).toBe('https://storybook.js.org/docs/example?foo=bar&ref=error&baz=qux');
+ });
+
+ it('should not append ref=error if it already exists in URL with hash fragment', () => {
+ const url = 'https://storybook.js.org/docs/example?ref=error#target';
+ const result = appendErrorRef(url);
+ expect(result).toBe('https://storybook.js.org/docs/example?ref=error#target');
+ });
+
+ it('should append ref=error before hash fragment when no existing ref parameter', () => {
+ const url = 'https://storybook.js.org/docs/example#target';
+ const result = appendErrorRef(url);
+ expect(result).toBe('https://storybook.js.org/docs/example?ref=error#target');
+ });
+});
diff --git a/code/core/src/cli/bin/index.ts b/code/core/src/bin/core.ts
similarity index 83%
rename from code/core/src/cli/bin/index.ts
rename to code/core/src/bin/core.ts
index 6613e80ef6a3..effa6ec1d5a1 100644
--- a/code/core/src/cli/bin/index.ts
+++ b/code/core/src/bin/core.ts
@@ -3,19 +3,28 @@ import { logTracker, logger } from 'storybook/internal/node-logger';
import { addToGlobalContext } from 'storybook/internal/telemetry';
import { program } from 'commander';
-import { findPackage } from 'fd-package-json';
import leven from 'leven';
import picocolors from 'picocolors';
-import invariant from 'tiny-invariant';
-import { version } from '../../../package.json';
-import { build } from '../build';
-import { buildIndex as index } from '../buildIndex';
-import { dev } from '../dev';
-import { globalSettings } from '../globalSettings';
+import { version } from '../../package.json';
+import { build } from '../cli/build';
+import { buildIndex as index } from '../cli/buildIndex';
+import { dev } from '../cli/dev';
+import { globalSettings } from '../cli/globalSettings';
addToGlobalContext('cliVersion', version);
+/**
+ * Core CLI for Storybook.
+ *
+ * This module provides the core CLI for Storybook, handling the following commands:
+ *
+ * - `dev`: Start the Storybook development server
+ * - `build`: Build the Storybook static files
+ * - `index`: Generate the Storybook index file
+ *
+ * The dispatch CLI at ./dispatcher.ts routes commands to this core CLI.
+ */
const command = (name: string) =>
program
.command(name)
@@ -90,10 +99,13 @@ command('dev')
)
.option('--preview-only', 'Use the preview without the manager UI')
.action(async (options) => {
- const pkg = await findPackage(__dirname);
- invariant(pkg, 'Failed to find the closest package.json file.');
+ const { default: packageJson } = await import('storybook/package.json', {
+ with: { type: 'json' },
+ });
- logger.log(picocolors.bold(`${pkg.name} v${pkg.version}`) + picocolors.reset('\n'));
+ logger.log(
+ picocolors.bold(`${packageJson.name} v${packageJson.version}`) + picocolors.reset('\n')
+ );
// The key is the field created in `options` variable for
// each command line argument. Value is the env variable.
@@ -109,7 +121,7 @@ command('dev')
options.port = parseInt(`${options.port}`, 10);
}
- await dev({ ...options, packageJson: pkg }).catch(() => process.exit(1));
+ await dev({ ...options, packageJson }).catch(() => process.exit(1));
});
command('build')
@@ -134,10 +146,11 @@ command('build')
const { env } = process;
env.NODE_ENV = env.NODE_ENV || 'production';
- const pkg = await findPackage(__dirname);
- invariant(pkg, 'Failed to find the closest package.json file.');
+ const { default: packageJson } = await import('storybook/package.json', {
+ with: { type: 'json' },
+ });
- logger.log(picocolors.bold(`${pkg.name} v${pkg.version}\n`));
+ logger.log(picocolors.bold(`${packageJson.name} v${packageJson.version}\n`));
// The key is the field created in `options` variable for
// each command line argument. Value is the env variable.
@@ -149,7 +162,7 @@ command('build')
await build({
...options,
- packageJson: pkg,
+ packageJson,
test: !!options.test || optionalEnvToBoolean(process.env.SB_TESTBUILD),
}).catch(() => process.exit(1));
});
@@ -162,10 +175,11 @@ command('index')
const { env } = process;
env.NODE_ENV = env.NODE_ENV || 'production';
- const pkg = await findPackage(__dirname);
- invariant(pkg, 'Failed to find the closest package.json file.');
+ const { default: packageJson } = await import('storybook/package.json', {
+ with: { type: 'json' },
+ });
- logger.log(picocolors.bold(`${pkg.name} v${pkg.version}\n`));
+ logger.log(picocolors.bold(`${packageJson.name} v${packageJson.version}\n`));
// The key is the field created in `options` variable for
// each command line argument. Value is the env variable.
@@ -176,7 +190,7 @@ command('index')
await index({
...options,
- packageJson: pkg,
+ packageJson,
}).catch(() => process.exit(1));
});
diff --git a/code/core/src/bin/dispatcher.ts b/code/core/src/bin/dispatcher.ts
new file mode 100644
index 000000000000..71cc19f5e076
--- /dev/null
+++ b/code/core/src/bin/dispatcher.ts
@@ -0,0 +1,79 @@
+#!/usr/bin/env node
+import { spawn } from 'node:child_process';
+import { pathToFileURL } from 'node:url';
+
+import { logger } from 'storybook/internal/node-logger';
+
+import { join } from 'pathe';
+import { dedent } from 'ts-dedent';
+
+import versions from '../common/versions';
+import { resolvePackageDir } from '../shared/utils/module';
+
+/**
+ * Dispatches Storybook CLI commands to the appropriate handler.
+ *
+ * This function serves as the main entry point for Storybook CLI operations.
+ *
+ * - Core Storybook commands (dev, build, index) are routed to the core binary at
+ * storybook/dist/bin/core.js
+ * - Init is routed to the create-storybook package via npx
+ * - External CLI tools (upgrade, doctor, etc.) are routed to @storybook/cli via npx
+ */
+const [majorNodeVersion, minorNodeVersion] = process.versions.node.split('.').map(Number);
+if (
+ majorNodeVersion < 20 ||
+ (majorNodeVersion === 20 && minorNodeVersion < 19) ||
+ (majorNodeVersion === 22 && minorNodeVersion < 12)
+) {
+ logger.error(
+ dedent`To run Storybook, you need Node.js version 20.19+ or 22.12+.
+ You are currently running Node.js ${process.version}. Please upgrade your Node.js installation.`
+ );
+ process.exit(1);
+}
+
+async function run() {
+ const args = process.argv.slice(2);
+
+ if (['dev', 'build', 'index'].includes(args[0])) {
+ const coreBin = pathToFileURL(join(resolvePackageDir('storybook'), 'dist/bin/core.js')).href;
+ await import(coreBin);
+ return;
+ }
+
+ const targetCli =
+ args[0] === 'init'
+ ? ({
+ pkg: 'create-storybook',
+ args: args.slice(1),
+ } as const)
+ : ({
+ pkg: '@storybook/cli',
+ args,
+ } as const);
+
+ let command;
+ try {
+ const { default: targetCliPackageJson } = await import(`${targetCli.pkg}/package.json`, {
+ with: { type: 'json' },
+ });
+ if (targetCliPackageJson.version === versions[targetCli.pkg]) {
+ command = [
+ 'node',
+ `"${join(resolvePackageDir(targetCli.pkg), 'dist/bin/index.js')}"`,
+ ...targetCli.args,
+ ];
+ }
+ } catch (e) {
+ // the package couldn't be imported, use npx to install and run it instead
+ }
+ command ??= ['npx', '--yes', `${targetCli.pkg}@${versions[targetCli.pkg]}`, ...targetCli.args];
+
+ const child = spawn(command[0], command.slice(1), { stdio: 'inherit', shell: true });
+ child.on('exit', (code) => {
+ process.exit(code);
+ });
+}
+
+run();
diff --git a/code/core/src/bin/index.ts b/code/core/src/bin/index.ts
deleted file mode 100644
index da804a272677..000000000000
--- a/code/core/src/bin/index.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import { spawn } from 'node:child_process';
-import { readFileSync } from 'node:fs';
-import { dirname, join } from 'node:path';
-
-import versions from '../common/versions';
-
-const args = process.argv.slice(2);
-
-if (['dev', 'build', 'index'].includes(args[0])) {
- require('storybook/internal/cli/bin');
-} else {
- let command;
- if (args[0] === 'init') {
- let foundCreateStorybook;
- try {
- foundCreateStorybook = require.resolve('create-storybook/package.json');
- } catch (e) {
- // ignore
- }
- if (foundCreateStorybook) {
- const json = JSON.parse(readFileSync(foundCreateStorybook, 'utf-8'));
- if (json.version === versions['create-storybook']) {
- command = [
- 'node',
- join(dirname(foundCreateStorybook), 'bin', 'index.cjs'),
- ...args.slice(1),
- ];
- }
- } else {
- command = ['npx', '--yes', `create-storybook@${versions.storybook}`, ...args.slice(1)];
- }
- } else {
- let foundStorybookCLI;
- try {
- foundStorybookCLI = require.resolve('@storybook/cli/package.json');
- } catch (e) {
- // ignore
- }
-
- if (foundStorybookCLI) {
- const json = JSON.parse(readFileSync(foundStorybookCLI, 'utf-8'));
- if (json.version === versions['@storybook/cli']) {
- command = ['node', join(dirname(foundStorybookCLI), 'bin', 'index.cjs'), ...args];
- }
- } else {
- command = ['npx', '--yes', `@storybook/cli@${versions.storybook}`, ...args];
- }
- }
-
- if (!command) {
- console.error('Could not run storybook cli, please report this as a bug');
- process.exit(1);
- }
-
- const child = spawn(command[0], command.slice(1), { stdio: 'inherit', shell: true });
- child.on('exit', (code) => {
- if (code != null) {
- process.exit(code);
- }
- process.exit(1);
- });
-}
diff --git a/code/core/src/bin/loader.ts b/code/core/src/bin/loader.ts
new file mode 100644
index 000000000000..909761c374cd
--- /dev/null
+++ b/code/core/src/bin/loader.ts
@@ -0,0 +1,40 @@
+/**
+ * This is an isolated file that is registered as a loader in Node. It is used to convert TS to ESM
+ * using esbuild. Do _not_ import from other modules in core unless strictly necessary, as it will
+ * cause the dist to get huge.
+ */
+import { readFile } from 'node:fs/promises';
+import type { LoadHook } from 'node:module';
+import { fileURLToPath } from 'node:url';
+
+import { transform } from 'esbuild';
+
+import { NODE_TARGET } from '../shared/constants/environments-support';
+
+export const load: LoadHook = async (url, context, nextLoad) => {
+ /** Convert TS to ESM using esbuild */
+ if (
+ url.endsWith('.ts') ||
+ url.endsWith('.tsx') ||
+ url.endsWith('.mts') ||
+ url.endsWith('.cts') ||
+ url.endsWith('.mtsx') ||
+ url.endsWith('.ctsx')
+ ) {
+ const rawSource = await readFile(fileURLToPath(url), 'utf-8');
+ const transformedSource = await transform(rawSource, {
+ loader: 'ts',
+ target: NODE_TARGET,
+ format: 'esm',
+ platform: 'neutral',
+ });
+
+ return {
+ format: 'module',
+ shortCircuit: true,
+ source: transformedSource.code,
+ };
+ }
+
+ return nextLoad(url, context);
+};
diff --git a/code/core/src/builder-manager/index.ts b/code/core/src/builder-manager/index.ts
index aa4e35913d2a..113ebfc56d9f 100644
--- a/code/core/src/builder-manager/index.ts
+++ b/code/core/src/builder-manager/index.ts
@@ -1,15 +1,17 @@
import { cp, rm, writeFile } from 'node:fs/promises';
-import { dirname, join, parse } from 'node:path';
import { stringifyProcessEnvs } from 'storybook/internal/common';
-import { globalsModuleInfoMap } from 'storybook/internal/manager/globals-module-info';
import { logger } from 'storybook/internal/node-logger';
import { globalExternals } from '@fal-works/esbuild-plugin-global-externals';
import { pnpPlugin } from '@yarnpkg/esbuild-plugin-pnp';
+import { resolvePathSync } from 'mlly';
+import { join, parse } from 'pathe';
import sirv from 'sirv';
+import { globalsModuleInfoMap } from '../manager/globals/globals-module-info';
import { BROWSER_TARGETS, SUPPORTED_FEATURES } from '../shared/constants/environments-support';
+import { resolvePackageDir } from '../shared/utils/module';
import type {
BuilderBuildResult,
BuilderFunction,
@@ -22,26 +24,35 @@ import { getData } from './utils/data';
import { readOrderedFiles } from './utils/files';
import { buildFrameworkGlobalsFromOptions } from './utils/framework';
import { wrapManagerEntries } from './utils/managerEntries';
-import { safeResolve } from './utils/safeResolve';
import { getTemplatePath, renderHTML } from './utils/template';
export { BROWSER_TARGETS, NODE_TARGET } from '../shared/constants/environments-support';
+const CORE_DIR_ORIGIN = join(resolvePackageDir('storybook'), 'dist/manager');
+
const isRootPath = /^\/($|\?)/;
let compilation: Compilation;
let asyncIterator: ReturnType | ReturnType;
export const getConfig: ManagerBuilder['getConfig'] = async (options) => {
- const [addonsEntryPoints, customManagerEntryPoint, tsconfigPath, envs] = await Promise.all([
+ const [managerEntriesFromPresets, envs] = await Promise.all([
options.presets.apply('managerEntries', []),
- safeResolve(join(options.configDir, 'manager')),
- getTemplatePath('addon.tsconfig.json'),
options.presets.apply>('env'),
]);
+ const tsconfigPath = getTemplatePath('addon.tsconfig.json');
+ let configDirManagerEntry;
+ try {
+ configDirManagerEntry = resolvePathSync('./manager', {
+ url: options.configDir,
+ extensions: ['.js', '.mjs', '.jsx', '.ts', '.mts', '.tsx'],
+ });
+ } catch (e) {
+ // no manager entry found in config directory, that's fine
+ }
- const entryPoints = customManagerEntryPoint
- ? [...addonsEntryPoints, customManagerEntryPoint]
- : addonsEntryPoints;
+ const entryPoints = configDirManagerEntry
+ ? [...managerEntriesFromPresets, configDirManagerEntry]
+ : managerEntriesFromPresets;
return {
entryPoints: await wrapManagerEntries(entryPoints, options.cacheKey),
@@ -161,12 +172,6 @@ const starter: StarterFunction = async function* starterGeneratorFn({
yield;
- const coreDirOrigin = join(
- dirname(require.resolve('storybook/internal/package.json')),
- 'dist',
- 'manager'
- );
-
router.use(
'/sb-addons',
sirv(addonsDir, {
@@ -177,7 +182,7 @@ const starter: StarterFunction = async function* starterGeneratorFn({
);
router.use(
'/sb-manager',
- sirv(coreDirOrigin, {
+ sirv(CORE_DIR_ORIGIN, {
maxAge: 300000,
dev: true,
immutable: true,
@@ -269,11 +274,6 @@ const builder: BuilderFunction = async function* builderGeneratorFn({ startTime,
yield;
const addonsDir = config.outdir;
- const coreDirOrigin = join(
- dirname(require.resolve('storybook/internal/package.json')),
- 'dist',
- 'manager'
- );
const coreDirTarget = join(options.outputDir, `sb-manager`);
// TODO: this doesn't watch, we should change this to use the esbuild watch API: https://esbuild.github.io/api/#watch
@@ -284,7 +284,7 @@ const builder: BuilderFunction = async function* builderGeneratorFn({ startTime,
yield;
- const managerFiles = cp(coreDirOrigin, coreDirTarget, {
+ const managerFiles = cp(CORE_DIR_ORIGIN, coreDirTarget, {
filter: (src) => {
const { ext } = parse(src);
if (ext) {
diff --git a/code/core/src/builder-manager/utils/files.ts b/code/core/src/builder-manager/utils/files.ts
index 352d558dd981..f4d405319fe6 100644
--- a/code/core/src/builder-manager/utils/files.ts
+++ b/code/core/src/builder-manager/utils/files.ts
@@ -1,6 +1,6 @@
import { existsSync } from 'node:fs';
import { mkdir, writeFile } from 'node:fs/promises';
-import { dirname, join, normalize } from 'node:path';
+import { dirname, join, normalize, relative } from 'node:path';
import type { OutputFile } from 'esbuild';
import slash from 'slash';
@@ -31,9 +31,9 @@ export async function readOrderedFiles(
}
export function sanitizePath(file: OutputFile, addonsDir: string) {
- const filePath = file.path.replace(addonsDir, '');
+ const filePath = relative(addonsDir, file.path);
const location = normalize(join(addonsDir, filePath));
- const url = `./sb-addons${slash(filePath).split('/').map(encodeURIComponent).join('/')}`;
+ const url = `./sb-addons/${slash(filePath).split('/').map(encodeURIComponent).join('/')}`;
return { location, url };
}
diff --git a/code/core/src/builder-manager/utils/framework.ts b/code/core/src/builder-manager/utils/framework.ts
index 085fe63587dd..8362e423813f 100644
--- a/code/core/src/builder-manager/utils/framework.ts
+++ b/code/core/src/builder-manager/utils/framework.ts
@@ -43,10 +43,11 @@ export const buildFrameworkGlobalsFromOptions = async (options: Options) => {
(await extractProperRendererNameFromFramework(frameworkName)) ?? undefined;
}
- const builderName = pluckNameFromConfigProperty(builder);
- if (builderName) {
+ const resolvedPreviewBuilder = pluckNameFromConfigProperty(builder);
+ if (resolvedPreviewBuilder) {
globals.STORYBOOK_BUILDER =
- pluckStorybookPackageFromPath(builderName) ?? pluckThirdPartyPackageFromPath(builderName);
+ pluckStorybookPackageFromPath(resolvedPreviewBuilder) ??
+ pluckThirdPartyPackageFromPath(resolvedPreviewBuilder);
}
const framework = pluckNameFromConfigProperty(await options.presets.apply('framework'));
diff --git a/code/core/src/builder-manager/utils/safeResolve.ts b/code/core/src/builder-manager/utils/safeResolve.ts
deleted file mode 100644
index b166a22bced4..000000000000
--- a/code/core/src/builder-manager/utils/safeResolve.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export const safeResolve = (path: string) => {
- try {
- return Promise.resolve(require.resolve(path));
- } catch (e) {
- return Promise.resolve(false as const);
- }
-};
diff --git a/code/core/src/builder-manager/utils/template.ts b/code/core/src/builder-manager/utils/template.ts
index aa321a080514..c31088c39d41 100644
--- a/code/core/src/builder-manager/utils/template.ts
+++ b/code/core/src/builder-manager/utils/template.ts
@@ -1,20 +1,18 @@
import { readFile } from 'node:fs/promises';
-import { dirname, join } from 'node:path';
import type { DocsOptions, Options, Ref, TagsOptions } from 'storybook/internal/types';
import { render } from 'ejs';
+import { join } from 'pathe';
-export const getTemplatePath = async (template: string) => {
- return join(
- dirname(require.resolve('storybook/internal/package.json')),
- 'assets/server',
- template
- );
+import { resolvePackageDir } from '../../shared/utils/module';
+
+export const getTemplatePath = (template: string) => {
+ return join(resolvePackageDir('storybook'), 'assets/server', template);
};
export const readTemplate = async (template: string) => {
- const path = await getTemplatePath(template);
+ const path = getTemplatePath(template);
return readFile(path, { encoding: 'utf8' });
};
diff --git a/code/core/src/cli/build.ts b/code/core/src/cli/build.ts
index a27fe6f27139..6f3a0e62a2a2 100644
--- a/code/core/src/cli/build.ts
+++ b/code/core/src/cli/build.ts
@@ -1,12 +1,10 @@
import { cache } from 'storybook/internal/common';
import { buildStaticStandalone, withTelemetry } from 'storybook/internal/core-server';
-import { findPackage } from 'fd-package-json';
-import invariant from 'tiny-invariant';
-
export const build = async (cliOptions: any) => {
- const packageJson = await findPackage(__dirname);
- invariant(packageJson, 'Failed to find the closest package.json file.');
+ const { default: packageJson } = await import('storybook/package.json', {
+ with: { type: 'json' },
+ });
const options = {
...cliOptions,
configDir: cliOptions.configDir || './.storybook',
diff --git a/code/core/src/cli/detect.test.ts b/code/core/src/cli/detect.test.ts
index d5f81f531a63..ad58aac30d66 100644
--- a/code/core/src/cli/detect.test.ts
+++ b/code/core/src/cli/detect.test.ts
@@ -241,7 +241,7 @@ describe('Detect', () => {
operationDir: 'some/path',
},
getAllDependencies: () => ({}),
- getModulePackageJSON: () => null,
+ getModulePackageJSON: () => Promise.resolve(null),
} as Partial;
await expect(detect(packageManager as any, { html: true })).resolves.toBe(ProjectType.HTML);
@@ -254,12 +254,12 @@ describe('Detect', () => {
getAllDependencies: () => ({
typescript: '1.0.0',
}),
- getModulePackageJSON: (packageName) => {
+ getModulePackageJSON: (packageName: string) => {
switch (packageName) {
case 'typescript':
- return {
+ return Promise.resolve({
version: '1.0.0',
- };
+ });
default:
return null;
}
@@ -280,9 +280,9 @@ describe('Detect', () => {
getModulePackageJSON: (packageName: string) => {
switch (packageName) {
case 'typescript':
- return {
+ return Promise.resolve({
version: '4.8.0',
- };
+ });
default:
return null;
}
@@ -299,9 +299,9 @@ describe('Detect', () => {
getModulePackageJSON: (packageName: string) => {
switch (packageName) {
case 'typescript':
- return {
+ return Promise.resolve({
version: '4.9.1',
- };
+ });
default:
return null;
}
@@ -318,9 +318,9 @@ describe('Detect', () => {
getModulePackageJSON: (packageName: string) => {
switch (packageName) {
case 'typescript':
- return {
+ return Promise.resolve({
version: '4.9.0',
- };
+ });
default:
return null;
}
@@ -337,9 +337,9 @@ describe('Detect', () => {
getModulePackageJSON: (packageName: string) => {
switch (packageName) {
case 'typescript':
- return {
+ return Promise.resolve({
version: '4.9.0-beta',
- };
+ });
default:
return null;
}
@@ -352,7 +352,7 @@ describe('Detect', () => {
it(`should return language javascript by default`, async () => {
const packageManager = {
getAllDependencies: () => ({}),
- getModulePackageJSON: () => null,
+ getModulePackageJSON: () => Promise.resolve(null),
} as Partial;
await expect(detectLanguage(packageManager as any)).resolves.toBe(SupportedLanguage.JAVASCRIPT);
@@ -361,12 +361,12 @@ describe('Detect', () => {
it(`should return language Javascript even when Typescript is detected in the node_modules but not listed as a direct dependency`, async () => {
const packageManager = {
getAllDependencies: () => ({}),
- getModulePackageJSON: (packageName) => {
+ getModulePackageJSON: (packageName: string) => {
switch (packageName) {
case 'typescript':
- return {
+ return Promise.resolve({
version: '4.9.0',
- };
+ });
default:
return null;
}
diff --git a/code/core/src/cli/detect.ts b/code/core/src/cli/detect.ts
index 487e1f67bd92..8eac757bc3c6 100644
--- a/code/core/src/cli/detect.ts
+++ b/code/core/src/cli/detect.ts
@@ -183,17 +183,23 @@ export async function detectLanguage(packageManager: JsPackageManager) {
const isTypescriptDirectDependency = !!packageManager.getAllDependencies().typescript;
- const getModulePackageJSONVersion = (pkg: string) => {
- return packageManager.getModulePackageJSON(pkg)?.version ?? null;
+ const getModulePackageJSONVersion = async (pkg: string) => {
+ return (await packageManager.getModulePackageJSON(pkg))?.version ?? null;
};
- const typescriptVersion = getModulePackageJSONVersion('typescript');
- const prettierVersion = getModulePackageJSONVersion('prettier');
- const babelPluginTransformTypescriptVersion = getModulePackageJSONVersion(
- '@babel/plugin-transform-typescript'
- );
- const typescriptEslintParserVersion = getModulePackageJSONVersion('@typescript-eslint/parser');
- const eslintPluginStorybookVersion = getModulePackageJSONVersion('eslint-plugin-storybook');
+ const [
+ typescriptVersion,
+ prettierVersion,
+ babelPluginTransformTypescriptVersion,
+ typescriptEslintParserVersion,
+ eslintPluginStorybookVersion,
+ ] = await Promise.all([
+ getModulePackageJSONVersion('typescript'),
+ getModulePackageJSONVersion('prettier'),
+ getModulePackageJSONVersion('@babel/plugin-transform-typescript'),
+ getModulePackageJSONVersion('@typescript-eslint/parser'),
+ getModulePackageJSONVersion('eslint-plugin-storybook'),
+ ]);
if (isTypescriptDirectDependency && typescriptVersion) {
if (
diff --git a/code/core/src/cli/dev.ts b/code/core/src/cli/dev.ts
index c885c60430f0..87e637c408c4 100644
--- a/code/core/src/cli/dev.ts
+++ b/code/core/src/cli/dev.ts
@@ -1,10 +1,8 @@
import { cache } from 'storybook/internal/common';
import { buildDevStandalone, withTelemetry } from 'storybook/internal/core-server';
import { logger, instance as npmLog } from 'storybook/internal/node-logger';
-import type { CLIOptions } from 'storybook/internal/types';
+import type { CLIOptions, PackageJson } from 'storybook/internal/types';
-import { findPackage } from 'fd-package-json';
-import invariant from 'tiny-invariant';
import { dedent } from 'ts-dedent';
function printError(error: any) {
@@ -42,8 +40,9 @@ export const dev = async (cliOptions: CLIOptions) => {
const { env } = process;
env.NODE_ENV = env.NODE_ENV || 'development';
- const packageJson = await findPackage(__dirname);
- invariant(packageJson, 'Failed to find the closest package.json file.');
+ const { default: packageJson } = await import('storybook/package.json', {
+ with: { type: 'json' },
+ });
type Options = Parameters[0];
const options = {
@@ -52,7 +51,7 @@ export const dev = async (cliOptions: CLIOptions) => {
configType: 'DEVELOPMENT',
ignorePreview: !!cliOptions.previewUrl && !cliOptions.forceBuildPreview,
cache: cache as any,
- packageJson,
+ packageJson: packageJson as unknown as PackageJson, // type-fest types are wrong here because we're on an outdated version of the package
} as Options;
await withTelemetry(
diff --git a/code/core/src/cli/dirs.ts b/code/core/src/cli/dirs.ts
index 8d6f2af89e92..c855125e566e 100644
--- a/code/core/src/cli/dirs.ts
+++ b/code/core/src/cli/dirs.ts
@@ -1,4 +1,4 @@
-import { dirname, join } from 'node:path';
+import { join } from 'node:path';
import { temporaryDirectory, versions } from 'storybook/internal/common';
import type { JsPackageManager } from 'storybook/internal/common';
@@ -8,6 +8,7 @@ import downloadTarballDefault from '@ndelangen/get-tarball';
import getNpmTarballUrlDefault from 'get-npm-tarball-url';
import invariant from 'tiny-invariant';
+import { resolvePackageDir } from '../shared/utils/module';
import { externalFrameworks } from './project_types';
const resolveUsingBranchInstall = async (packageManager: JsPackageManager, request: string) => {
@@ -46,11 +47,7 @@ export async function getRendererDir(
const errors: Error[] = [];
try {
- return dirname(
- require.resolve(packageJsonPath, {
- paths: [process.cwd()],
- })
- );
+ return resolvePackageDir(frameworkPackageName, process.cwd());
} catch (e) {
invariant(e instanceof Error);
errors.push(e);
diff --git a/code/core/src/common/index.ts b/code/core/src/common/index.ts
index 35cbc5a51e86..e0c73e661ab0 100644
--- a/code/core/src/common/index.ts
+++ b/code/core/src/common/index.ts
@@ -21,7 +21,6 @@ export * from './utils/HandledError';
export * from './utils/interpolate';
export * from './utils/interpret-files';
export * from './utils/interpret-require';
-export * from './utils/load-custom-presets';
export * from './utils/load-main-config';
export * from './utils/load-manager-or-addons-file';
export * from './utils/load-preview-or-config-file';
@@ -37,7 +36,6 @@ export * from './utils/template';
export * from './utils/validate-config';
export * from './utils/validate-configuration-files';
export * from './utils/satisfies';
-export * from './utils/strip-abs-node-modules-path';
export * from './utils/formatter';
export * from './utils/get-story-id';
export * from './utils/posix';
@@ -46,6 +44,7 @@ export * from './utils/sync-main-preview-addons';
export * from './js-package-manager';
export * from './utils/scan-and-transform-files';
export * from './utils/transform-imports';
+export * from '../shared/utils/module';
export { versions };
diff --git a/code/core/src/common/js-package-manager/BUNProxy.ts b/code/core/src/common/js-package-manager/BUNProxy.ts
index 73facb20dbe6..af9026eb85ae 100644
--- a/code/core/src/common/js-package-manager/BUNProxy.ts
+++ b/code/core/src/common/js-package-manager/BUNProxy.ts
@@ -84,7 +84,7 @@ export class BUNProxy extends JsPackageManager {
return `bunx ${pkg}${specifier ? `@${specifier}` : ''} ${args.join(' ')}`;
}
- public getModulePackageJSON(packageName: string): PackageJson | null {
+ public async getModulePackageJSON(packageName: string): Promise {
const packageJsonPath = findUpSync(
(dir) => {
const possiblePath = join(dir, 'node_modules', packageName, 'package.json');
diff --git a/code/core/src/common/js-package-manager/JsPackageManager.ts b/code/core/src/common/js-package-manager/JsPackageManager.ts
index 1103edb1220f..0bf7834ae79c 100644
--- a/code/core/src/common/js-package-manager/JsPackageManager.ts
+++ b/code/core/src/common/js-package-manager/JsPackageManager.ts
@@ -106,7 +106,7 @@ export abstract class JsPackageManager {
abstract getRemoteRunCommand(pkg: string, args: string[], specifier?: string): string;
/** Get the package.json file for a given module. */
- abstract getModulePackageJSON(packageName: string): PackageJson | null;
+ abstract getModulePackageJSON(packageName: string): Promise;
isStorybookInMonorepo() {
const turboJsonPath = findUpSync(`turbo.json`, { stopAt: getProjectRoot() });
diff --git a/code/core/src/common/js-package-manager/NPMProxy.ts b/code/core/src/common/js-package-manager/NPMProxy.ts
index bf89129993ff..28b27c46d38b 100644
--- a/code/core/src/common/js-package-manager/NPMProxy.ts
+++ b/code/core/src/common/js-package-manager/NPMProxy.ts
@@ -76,7 +76,7 @@ export class NPMProxy extends JsPackageManager {
return `npx ${pkg}${specifier ? `@${specifier}` : ''} ${args.join(' ')}`;
}
- getModulePackageJSON(packageName: string): PackageJson | null {
+ async getModulePackageJSON(packageName: string): Promise {
const packageJsonPath = findUpSync(
(dir) => {
const possiblePath = join(dir, 'node_modules', packageName, 'package.json');
diff --git a/code/core/src/common/js-package-manager/PNPMProxy.ts b/code/core/src/common/js-package-manager/PNPMProxy.ts
index 519c5743b56e..c5fddbdf6e50 100644
--- a/code/core/src/common/js-package-manager/PNPMProxy.ts
+++ b/code/core/src/common/js-package-manager/PNPMProxy.ts
@@ -1,5 +1,6 @@
import { existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
+import { pathToFileURL } from 'node:url';
import { prompt } from 'storybook/internal/node-logger';
import { FindPackageVersionsError } from 'storybook/internal/server-errors';
@@ -141,7 +142,7 @@ export class PNPMProxy extends JsPackageManager {
}
}
- public getModulePackageJSON(packageName: string): PackageJson | null {
+ public async getModulePackageJSON(packageName: string): Promise {
const pnpapiPath = findUpSync(['.pnp.js', '.pnp.cjs'], {
cwd: this.primaryPackageJson.operationDir,
stopAt: getProjectRoot(),
@@ -149,7 +150,7 @@ export class PNPMProxy extends JsPackageManager {
if (pnpapiPath) {
try {
- const pnpApi = require(pnpapiPath);
+ const pnpApi = await import(pathToFileURL(pnpapiPath).href);
const resolvedPath = pnpApi.resolveToUnqualified(packageName, this.cwd, {
considerBuiltins: false,
diff --git a/code/core/src/common/js-package-manager/Yarn1Proxy.ts b/code/core/src/common/js-package-manager/Yarn1Proxy.ts
index 4988887a77f4..e9b4506ca87e 100644
--- a/code/core/src/common/js-package-manager/Yarn1Proxy.ts
+++ b/code/core/src/common/js-package-manager/Yarn1Proxy.ts
@@ -81,7 +81,7 @@ export class Yarn1Proxy extends JsPackageManager {
return this.executeCommand({ command: `yarn`, args: [command, ...args], cwd, stdio });
}
- public getModulePackageJSON(packageName: string): PackageJson | null {
+ public async getModulePackageJSON(packageName: string): Promise {
const packageJsonPath = findUpSync(
(dir) => {
const possiblePath = join(dir, 'node_modules', packageName, 'package.json');
diff --git a/code/core/src/common/js-package-manager/Yarn2Proxy.ts b/code/core/src/common/js-package-manager/Yarn2Proxy.ts
index 44305b233605..9ea3c474b885 100644
--- a/code/core/src/common/js-package-manager/Yarn2Proxy.ts
+++ b/code/core/src/common/js-package-manager/Yarn2Proxy.ts
@@ -1,5 +1,6 @@
import { existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
+import { pathToFileURL } from 'node:url';
import { prompt } from 'storybook/internal/node-logger';
import { FindPackageVersionsError } from 'storybook/internal/server-errors';
@@ -148,7 +149,7 @@ export class Yarn2Proxy extends JsPackageManager {
}
}
- getModulePackageJSON(packageName: string): PackageJson | null {
+ async getModulePackageJSON(packageName: string): Promise {
const pnpapiPath = findUpSync(['.pnp.js', '.pnp.cjs'], {
cwd: this.cwd,
stopAt: getProjectRoot(),
@@ -156,7 +157,16 @@ export class Yarn2Proxy extends JsPackageManager {
if (pnpapiPath) {
try {
- const pnpApi = require(pnpapiPath);
+ /*
+ This is a rather fragile way to access Yarn's PnP API, essentially manually loading it.
+ The proper way to do this would be to just do await import('pnpapi'),
+ as documented at https://yarnpkg.com/advanced/pnpapi#requirepnpapi
+
+ However the 'pnpapi' module is only injected when the Node process is started via Yarn,
+ which is not always the case for us, because we spawn child processes directly with Node,
+ eg. when running automigrations.
+ */
+ const { default: pnpApi } = await import(pathToFileURL(pnpapiPath).href);
const resolvedPath = pnpApi.resolveToUnqualified(
packageName,
@@ -180,7 +190,7 @@ export class Yarn2Proxy extends JsPackageManager {
return crossFs.readJsonSync(virtualPath);
} catch (error: any) {
- if (error.code !== 'MODULE_NOT_FOUND') {
+ if (error.code !== 'ERR_MODULE_NOT_FOUND') {
console.error('Error while fetching package version in Yarn PnP mode:', error);
}
return null;
diff --git a/code/core/src/common/presets.test.ts b/code/core/src/common/presets.test.ts
index d55ee58596fd..3233734e1516 100644
--- a/code/core/src/common/presets.test.ts
+++ b/code/core/src/common/presets.test.ts
@@ -1,11 +1,11 @@
-import { normalize } from 'node:path';
+import path, { join, normalize, relative } from 'node:path';
+import { fileURLToPath, pathToFileURL, resolve } from 'node:url';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { logger } from 'storybook/internal/node-logger';
-import mockRequire from 'mock-require';
-
+import * as resolveUtils from '../shared/utils/module';
import { getPresets, loadPreset, resolveAddonName } from './presets';
function wrapPreset(basePresets: any): { babel: Function; webpack: Function } {
@@ -15,10 +15,6 @@ function wrapPreset(basePresets: any): { babel: Function; webpack: Function } {
};
}
-function mockPreset(name: string, mockPresetObject: any) {
- mockRequire(name, mockPresetObject);
-}
-
vi.mock('storybook/internal/node-logger', () => ({
logger: {
info: vi.fn(),
@@ -27,44 +23,38 @@ vi.mock('storybook/internal/node-logger', () => ({
},
}));
-vi.mock('./utils/safeResolve', () => {
- const KNOWN_FILES = [
- '@storybook/react',
- 'storybook/actions/manager',
- './local/preset',
- './local/addons',
- '/absolute/preset',
- '/absolute/addons',
- '@storybook/addon-docs',
- '@storybook/addon-cool',
- '@storybook/addon-docs/preset',
- '@storybook/addon-essentials',
- '@storybook/addon-knobs/manager',
- '@storybook/addon-knobs/register',
- '@storybook/addon-notes/register-panel',
- '@storybook/preset-create-react-app',
- '@storybook/preset-typescript',
- 'addon-bar/preset.js',
- 'addon-bar',
- 'addon-baz/register.js',
- 'addon-foo/register.js',
- ];
+vi.mock('../shared/utils/module', () => ({
+ importModule: vi.fn(),
+ safeResolveModule: vi.fn(({ specifier }) => {
+ const KNOWN_FILES = [
+ '@storybook/react',
+ 'storybook/actions/manager',
+ './local/preset',
+ './local/addons',
+ '/absolute/preset',
+ '/absolute/addons',
+ '@storybook/addon-docs',
+ '@storybook/addon-cool',
+ '@storybook/addon-docs/preset',
+ '@storybook/addon-essentials',
+ '@storybook/addon-knobs/manager',
+ '@storybook/addon-knobs/register',
+ '@storybook/addon-notes/register-panel',
+ '@storybook/preset-create-react-app',
+ '@storybook/preset-typescript',
+ 'addon-bar/preset.js',
+ 'addon-bar',
+ 'addon-baz/register.js',
+ 'addon-foo/register.js',
+ ];
+ if (KNOWN_FILES.includes(specifier)) {
+ return specifier;
+ }
+ return undefined;
+ }),
+}));
- return {
- safeResolveFrom: vi.fn((l: any, name: string) => {
- if (KNOWN_FILES.includes(name)) {
- return name;
- }
- return undefined;
- }),
- safeResolve: vi.fn((name: string) => {
- if (KNOWN_FILES.includes(name)) {
- return name;
- }
- return undefined;
- }),
- };
-});
+const mockedResolveUtils = vi.mocked(resolveUtils);
describe('presets', () => {
it('does not throw when there is no preset file', async () => {
@@ -111,44 +101,62 @@ describe('presets', () => {
});
it('loads and applies presets when they are combined in another preset', async () => {
- mockPreset('preset-foo', {
- foo: (exec: string[]) => exec.concat('foo'),
- });
-
- mockPreset('preset-bar', {
- foo: (exec: string[]) => exec.concat('bar'),
- });
-
- mockPreset('preset-got', [
- 'preset-dracarys',
- { name: 'preset-valar', options: { custom: 'morghulis' } },
- ]);
-
- mockPreset('preset-dracarys', {
- foo: (exec: string[]) => exec.concat('dracarys'),
- });
-
- mockPreset('preset-valar', {
- foo: (exec: string[], options: any) => exec.concat(`valar ${options.custom}`),
+ mockedResolveUtils.importModule.mockImplementation(async (path: string) => {
+ if (path === 'preset-first') {
+ return {
+ aProperty: (existing: string[]) => existing.concat('first'),
+ };
+ }
+ if (path === 'preset-second') {
+ return {
+ aProperty: (existing: string[]) => existing.concat('second'),
+ };
+ }
+ if (path === 'preset-third') {
+ return {
+ presets: ['sub-preset'],
+ aProperty: (existing: string[]) => existing.concat('third'),
+ };
+ }
+ if (path === 'sub-preset') {
+ return {
+ aProperty: (existing: string[]) => existing.concat('sub-preset-fourth'),
+ };
+ }
+ if (path === 'preset-fifth') {
+ return {
+ aProperty: (existing: string[]) => existing.concat('fifth'),
+ };
+ }
+ throw new Error(`Could not resolve ${path}`);
});
- const presets = await getPresets(['preset-foo', 'preset-got', 'preset-bar'], {} as any);
+ const presets = await getPresets(
+ ['preset-first', 'preset-second', 'preset-third', 'preset-fifth'],
+ {} as any
+ );
- const result = await presets.apply('foo', []);
+ const result = await presets.apply('aProperty', []);
- expect(result).toEqual(['foo', 'dracarys', 'valar morghulis', 'bar']);
+ expect(result).toEqual(['first', 'second', 'sub-preset-fourth', 'third', 'fifth']);
});
it('loads and applies presets when they are declared as a string', async () => {
const mockPresetFooExtendWebpack = vi.fn();
const mockPresetBarExtendBabel = vi.fn();
- mockPreset('preset-foo', {
- webpack: mockPresetFooExtendWebpack,
- });
-
- mockPreset('preset-bar', {
- babel: mockPresetBarExtendBabel,
+ mockedResolveUtils.importModule.mockImplementation(async (path: string) => {
+ if (path === 'preset-foo') {
+ return {
+ webpack: mockPresetFooExtendWebpack,
+ };
+ }
+ if (path === 'preset-bar') {
+ return {
+ babel: mockPresetBarExtendBabel,
+ };
+ }
+ throw new Error(`Could not resolve ${path}`);
});
const presets = wrapPreset(await getPresets(['preset-foo', 'preset-bar'], {} as any));
@@ -168,12 +176,18 @@ describe('presets', () => {
const mockPresetFooExtendWebpack = vi.fn();
const mockPresetBarExtendBabel = vi.fn();
- mockPreset('preset-foo', {
- webpack: mockPresetFooExtendWebpack,
- });
-
- mockPreset('preset-bar', {
- babel: mockPresetBarExtendBabel,
+ mockedResolveUtils.importModule.mockImplementation(async (path: string) => {
+ if (path === 'preset-foo') {
+ return {
+ webpack: mockPresetFooExtendWebpack,
+ };
+ }
+ if (path === 'preset-bar') {
+ return {
+ babel: mockPresetBarExtendBabel,
+ };
+ }
+ throw new Error(`Could not resolve ${path}`);
});
const presets = wrapPreset(
@@ -195,12 +209,18 @@ describe('presets', () => {
const mockPresetFooExtendWebpack = vi.fn();
const mockPresetBarExtendBabel = vi.fn();
- mockPreset('preset-foo', {
- webpack: mockPresetFooExtendWebpack,
- });
-
- mockPreset('preset-bar', {
- babel: mockPresetBarExtendBabel,
+ mockedResolveUtils.importModule.mockImplementation(async (path: string) => {
+ if (path === 'preset-foo') {
+ return {
+ webpack: mockPresetFooExtendWebpack,
+ };
+ }
+ if (path === 'preset-bar') {
+ return {
+ babel: mockPresetBarExtendBabel,
+ };
+ }
+ throw new Error(`Could not resolve ${path}`);
});
const presets = wrapPreset(
@@ -236,12 +256,18 @@ describe('presets', () => {
const mockPresetFooExtendWebpack = vi.fn();
const mockPresetBarExtendBabel = vi.fn();
- mockPreset('preset-foo', {
- webpack: mockPresetFooExtendWebpack,
- });
-
- mockPreset('preset-bar', {
- babel: mockPresetBarExtendBabel,
+ mockedResolveUtils.importModule.mockImplementation(async (path: string) => {
+ if (path === 'preset-foo') {
+ return {
+ webpack: mockPresetFooExtendWebpack,
+ };
+ }
+ if (path === 'preset-bar') {
+ return {
+ babel: mockPresetBarExtendBabel,
+ };
+ }
+ throw new Error(`Could not resolve ${path}`);
});
const presets = wrapPreset(
@@ -281,12 +307,18 @@ describe('presets', () => {
const mockPresetFooExtendWebpack = vi.fn((...args: any[]) => ({}));
const mockPresetBarExtendWebpack = vi.fn((...args: any[]) => ({}));
- mockPreset('preset-foo', {
- webpack: mockPresetFooExtendWebpack,
- });
-
- mockPreset('preset-bar', {
- webpack: mockPresetBarExtendWebpack,
+ mockedResolveUtils.importModule.mockImplementation(async (path: string) => {
+ if (path === 'preset-foo') {
+ return {
+ webpack: mockPresetFooExtendWebpack,
+ };
+ }
+ if (path === 'preset-bar') {
+ return {
+ webpack: mockPresetBarExtendWebpack,
+ };
+ }
+ throw new Error(`Could not resolve ${path}`);
});
const presets = wrapPreset(
@@ -331,12 +363,18 @@ describe('presets', () => {
const input = {};
const mockPresetBar = vi.fn((...args: any[]) => input);
- mockPreset('preset-foo', {
- presets: ['preset-bar'],
- });
-
- mockPreset('preset-bar', {
- bar: mockPresetBar,
+ mockedResolveUtils.importModule.mockImplementation(async (path: string) => {
+ if (path === 'preset-foo') {
+ return {
+ presets: ['preset-bar'],
+ };
+ }
+ if (path === 'preset-bar') {
+ return {
+ bar: mockPresetBar,
+ };
+ }
+ throw new Error(`Could not resolve ${path}`);
});
const presets = await getPresets(['preset-foo'], {} as any);
@@ -355,12 +393,18 @@ describe('presets', () => {
const mockPresetBar = vi.fn((...args: any[]) => input);
const mockPresetFoo = vi.fn((...args: any[]) => ['preset-bar']);
- mockPreset('preset-foo', {
- presets: mockPresetFoo,
- });
-
- mockPreset('preset-bar', {
- bar: mockPresetBar,
+ mockedResolveUtils.importModule.mockImplementation(async (path: string) => {
+ if (path === 'preset-foo') {
+ return {
+ presets: mockPresetFoo,
+ };
+ }
+ if (path === 'preset-bar') {
+ return {
+ bar: mockPresetBar,
+ };
+ }
+ throw new Error(`Could not resolve ${path}`);
});
const presets = await getPresets(
@@ -378,13 +422,17 @@ describe('presets', () => {
afterEach(() => {
vi.resetModules();
- mockRequire.stopAll();
});
});
describe('resolveAddonName', () => {
it('should resolve packages with metadata (relative path)', () => {
- mockPreset('./local/preset', {
- presets: [],
+ mockedResolveUtils.importModule.mockImplementation(async (path: string) => {
+ if (path === './local/preset') {
+ return {
+ presets: [],
+ };
+ }
+ throw new Error(`Could not resolve ${path}`);
});
expect(resolveAddonName({} as any, './local/preset', {})).toEqual({
name: './local/preset',
@@ -393,8 +441,13 @@ describe('resolveAddonName', () => {
});
it('should resolve packages with metadata (absolute path)', () => {
- mockPreset('/absolute/preset', {
- presets: [],
+ mockedResolveUtils.importModule.mockImplementation(async (path: string) => {
+ if (path === '/absolute/preset') {
+ return {
+ presets: [],
+ };
+ }
+ throw new Error(`Could not resolve ${path}`);
});
expect(resolveAddonName({} as any, '/absolute/preset', {})).toEqual({
name: '/absolute/preset',
@@ -409,14 +462,6 @@ describe('resolveAddonName', () => {
});
});
- it('should resolve managerEntries from new /manager path', () => {
- expect(resolveAddonName({} as any, 'storybook/actions/manager', {})).toEqual({
- name: 'storybook/actions/manager',
- managerEntries: [normalize('storybook/actions/manager')],
- type: 'virtual',
- });
- });
-
it('should resolve presets', () => {
expect(resolveAddonName({} as any, '@storybook/addon-docs/preset', {})).toEqual({
name: '@storybook/addon-docs/preset',
@@ -430,31 +475,29 @@ describe('resolveAddonName', () => {
type: 'presets',
});
});
-
- it('should error on invalid inputs', () => {
- // @ts-expect-error (invalid use)
- expect(() => resolveAddonName({} as any, null, {})).toThrow();
- });
});
describe('loadPreset', () => {
beforeEach(() => {
vi.spyOn(logger, 'warn');
- mockPreset('@storybook/react', {});
- mockPreset('@storybook/preset-typescript', {});
- mockPreset('@storybook/addon-docs/preset', {});
- mockPreset('addon-foo/register.js', {});
- mockPreset('@storybook/addon-cool', {});
- mockPreset('addon-bar', {
- addons: ['@storybook/addon-cool'],
- presets: [],
+ mockedResolveUtils.importModule.mockImplementation(async (path: string) => {
+ switch (path) {
+ case '@storybook/react':
+ case '@storybook/preset-typescript':
+ case '@storybook/addon-docs/preset':
+ case 'addon-foo/register.js':
+ case '@storybook/addon-cool':
+ case 'addon-baz/register.js':
+ case '@storybook/addon-notes/register-panel':
+ return {};
+ case 'addon-bar':
+ return {
+ addons: ['@storybook/addon-cool'],
+ presets: [],
+ };
+ }
+ throw new Error(`Could not resolve ${path}`);
});
- mockPreset('addon-baz/register.js', {});
- mockPreset('@storybook/addon-notes/register-panel', {});
- });
-
- afterEach(() => {
- mockRequire.stopAll();
});
it('should prepend framework field to list of presets', async () => {
@@ -503,7 +546,7 @@ describe('loadPreset', () => {
`);
});
- it('should resolve all addons & presets in correct order', async () => {
+ it.skip('should resolve all addons & presets in correct order', async () => {
const loaded = await loadPreset(
{
name: '',
diff --git a/code/core/src/common/presets.ts b/code/core/src/common/presets.ts
index ba7b352928ae..b8e8a32ad34a 100644
--- a/code/core/src/common/presets.ts
+++ b/code/core/src/common/presets.ts
@@ -1,5 +1,3 @@
-import { join, parse } from 'node:path';
-
import { logger } from 'storybook/internal/node-logger';
import { CriticalPresetLoadError } from 'storybook/internal/server-errors';
import type {
@@ -14,12 +12,13 @@ import type {
StorybookConfigRaw,
} from 'storybook/internal/types';
+import { parseNodeModulePath } from 'mlly';
+import { join, parse, resolve } from 'pathe';
import { dedent } from 'ts-dedent';
-import { interopRequireDefault } from './utils/interpret-require';
-import { loadCustomPresets } from './utils/load-custom-presets';
-import { safeResolve, safeResolveFrom } from './utils/safeResolve';
-import { stripAbsNodeModulesPath } from './utils/strip-abs-node-modules-path';
+import { importModule, safeResolveModule } from '../shared/utils/module';
+import { getInterpretedFile } from './utils/interpret-files';
+import { validateConfigurationFiles } from './utils/validate-configuration-files';
type InterPresetOptions = Omit<
CLIOptions &
@@ -39,31 +38,6 @@ export function filterPresetsConfig(presetsConfig: PresetConfig[]): PresetConfig
});
}
-function resolvePathToESM(filePath: string): string {
- const { dir, name, ext } = parse(filePath);
- if (ext === '.mjs') {
- return filePath;
- }
- const mjsPath = join(dir, `${name}.mjs`);
- if (safeResolve(mjsPath)) {
- return mjsPath;
- }
- if (ext === '.cjs') {
- /*
- If the file is a CJS file, try to resolve the ESM version instead.
- We must assume that in the case that NO .mjs file exists, but a .cjs file does, the package is type="module"
- This is the case for addon-kit, which distributes both preview.cjs and preview.js for Jest compatibility
- and in that situation we want to prefer the .js version.
- */
- const jsPath = join(dir, `${name}.js`);
- if (safeResolve(jsPath)) {
- return jsPath;
- }
- }
-
- return filePath;
-}
-
function resolvePresetFunction(
input: T[] | Function,
presetOptions: any,
@@ -95,98 +69,38 @@ export const resolveAddonName = (
name: string,
options: any
): CoreCommon_ResolvedAddonPreset | CoreCommon_ResolvedAddonVirtual | undefined => {
- const resolve = name.startsWith('/') ? safeResolve : safeResolveFrom.bind(null, configDir);
- const resolved = resolve(name);
+ const resolved = safeResolveModule({ specifier: name, parent: configDir });
- if (resolved) {
- const { dir: fdir, name: fname } = parse(resolved);
-
- if (name.match(/\/(manager|register(-panel)?)(\.(js|mjs|ts|tsx|jsx))?$/)) {
- return {
- type: 'virtual',
- name,
- // we remove the extension
- // this is a bit of a hack to try to find .mjs files
- // node can only ever resolve .js files; it does not look at the exports field in package.json
- managerEntries: [resolvePathToESM(join(fdir, fname))],
- };
- }
- if (name.match(/\/(preset)(\.(js|mjs|ts|tsx|jsx))?$/)) {
- return {
- type: 'presets',
- name: resolved,
- };
- }
- }
-
- const checkExists = (exportName: string) => {
- if (resolve(`${name}${exportName}`)) {
- return `${name}${exportName}`;
- }
- return undefined;
- };
-
- /**
- * This is used to maintain back-compat with community addons that do not re-export their
- * sub-addons but reference the sub-addon name directly. We need to turn it into an absolute path
- * so that webpack can serve it up correctly when yarn pnp or pnpm is being used. Vite will be
- * broken in such cases, because it does not process absolute paths, and it will try to import
- * from the bare import, breaking in pnp/pnpm.
- */
- const absolutizeExport = (exportName: string, preferESM: boolean) => {
- const found = resolve(`${name}${exportName}`);
-
- if (found) {
- return preferESM ? resolvePathToESM(found) : found;
- }
- return undefined;
- };
-
- const managerFile = absolutizeExport(`/manager`, true);
- const registerFile =
- absolutizeExport(`/register`, true) || absolutizeExport(`/register-panel`, true);
- const previewFile = checkExists(`/preview`);
- const previewFileAbsolute = absolutizeExport('/preview', true);
- const presetFile = absolutizeExport(`/preset`, false);
-
- if (!(managerFile || previewFile) && presetFile) {
+ if (resolved && parse(name).name === 'preset') {
return {
type: 'presets',
- name: presetFile,
+ name: resolved,
};
}
- if (managerFile || registerFile || previewFile || presetFile) {
- const managerEntries = [];
-
- if (managerFile) {
- managerEntries.push(managerFile);
- }
- // register file is the old way of registering addons
- if (!managerFile && registerFile && !presetFile) {
- managerEntries.push(registerFile);
+ const presetFile = safeResolveModule({ specifier: join(name, 'preset'), parent: configDir });
+ const managerFile = safeResolveModule({ specifier: join(name, 'manager'), parent: configDir });
+ const previewFile = safeResolveModule({ specifier: join(name, 'preview'), parent: configDir });
+
+ if (managerFile || previewFile || presetFile) {
+ const previewAnnotations = [];
+ if (previewFile) {
+ const parsedPreviewFile = parseNodeModulePath(previewFile);
+ if (parsedPreviewFile.name) {
+ previewAnnotations.push({
+ bare: join(parsedPreviewFile.name, parsedPreviewFile.subpath || ''),
+ absolute: previewFile,
+ });
+ } else {
+ previewAnnotations.push(previewFile);
+ }
}
-
return {
type: 'virtual',
name,
- ...(managerEntries.length ? { managerEntries } : {}),
- ...(previewFile
- ? {
- previewAnnotations: [
- previewFileAbsolute
- ? {
- // TODO: Evaluate if searching for node_modules in a yarn pnp environment is correct
- bare: previewFile.includes('node_modules')
- ? stripAbsNodeModulesPath(previewFile)
- : previewFile,
- absolute: previewFileAbsolute,
- }
- : previewFile,
- ],
- }
- : {}),
- ...(presetFile ? { presets: [{ name: presetFile, options }] } : {}),
+ presets: presetFile ? [{ name: presetFile, options }] : [],
+ managerEntries: managerFile ? [managerFile] : [],
+ previewAnnotations,
};
}
@@ -235,7 +149,7 @@ async function getContent(input: any) {
}
const name = input.name ? input.name : input;
- return interopRequireDefault(name);
+ return importModule(name);
}
export async function loadPreset(
@@ -424,12 +338,10 @@ export async function loadAllPresets(
}
) {
const { corePresets = [], overridePresets = [], ...restOptions } = options;
+ validateConfigurationFiles(options.configDir);
- const presetsConfig: PresetConfig[] = [
- ...corePresets,
- ...loadCustomPresets(options),
- ...overridePresets,
- ];
+ const mainUrl = getInterpretedFile(resolve(options.configDir, 'main')) as string;
+ const presetsConfig: PresetConfig[] = [...corePresets, mainUrl, ...overridePresets];
// Remove `@storybook/preset-typescript` and add a warning if in use.
const filteredPresetConfig = filterPresetsConfig(presetsConfig);
diff --git a/code/core/src/common/utils/__tests__/template.test.ts b/code/core/src/common/utils/__tests__/template.test.ts
index 0c6dfa4968b4..f91e8857617e 100644
--- a/code/core/src/common/utils/__tests__/template.test.ts
+++ b/code/core/src/common/utils/__tests__/template.test.ts
@@ -1,6 +1,6 @@
-import { dirname } from 'node:path';
+import { join } from 'node:path';
-import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
+import { afterEach, describe, expect, it, vi } from 'vitest';
import { vol } from 'memfs';
@@ -18,71 +18,60 @@ const BASE_HTML_CONTENTS = '';
const BASE_BODY_HTML_CONTENTS = '
story contents
';
const BODY_HTML_CONTENTS = '
custom body contents
';
-const base = dirname(require.resolve('storybook/internal/package.json'));
+const BASE_CORE_PKG_DIR = join(import.meta.url, '..', '..', '..', '..', '..');
+
+vi.mock('../../../shared/utils/module', () => {
+ return {
+ resolvePackageDir: () => BASE_CORE_PKG_DIR,
+ };
+});
+
+afterEach(() => {
+ vol.reset();
+});
describe('server.getPreviewHeadHtml', () => {
- afterEach(() => {
- vol.reset();
- });
- describe('when .storybook/preview-head.html does not exist', () => {
- beforeEach(() => {
- vol.fromNestedJSON({
- [`${base}/assets/server/base-preview-head.html`]: BASE_HTML_CONTENTS,
- config: {},
- });
+ it('return an empty string when .storybook/preview-head.html does NOT exist', () => {
+ vol.fromNestedJSON({
+ [`${BASE_CORE_PKG_DIR}/assets/server/base-preview-head.html`]: BASE_HTML_CONTENTS,
+ config: {},
});
- it('return an empty string', () => {
- const result = getPreviewHeadTemplate('./config');
- expect(result).toEqual(BASE_HTML_CONTENTS);
- });
+ expect(getPreviewHeadTemplate('./config')).toEqual(BASE_HTML_CONTENTS);
});
- describe('when .storybook/preview-head.html exists', () => {
- beforeEach(() => {
- vol.fromNestedJSON({
- [`${base}/assets/server/base-preview-head.html`]: BASE_HTML_CONTENTS,
- config: {
- 'preview-head.html': HEAD_HTML_CONTENTS,
- },
- });
+ it('return the contents of the file when .storybook/preview-head.html exists', () => {
+ vol.fromNestedJSON({
+ [`${BASE_CORE_PKG_DIR}/assets/server/base-preview-head.html`]: BASE_HTML_CONTENTS,
+ config: {
+ 'preview-head.html': HEAD_HTML_CONTENTS,
+ },
});
- it('return the contents of the file', () => {
- const result = getPreviewHeadTemplate('./config');
- expect(result).toEqual(BASE_HTML_CONTENTS + HEAD_HTML_CONTENTS);
- });
+ expect(getPreviewHeadTemplate('./config')).toEqual(BASE_HTML_CONTENTS + HEAD_HTML_CONTENTS);
});
});
describe('server.getPreviewBodyHtml', () => {
- describe('when .storybook/preview-body.html does not exist', () => {
- beforeEach(() => {
- vol.fromNestedJSON({
- [`${base}/assets/server/base-preview-body.html`]: BASE_BODY_HTML_CONTENTS,
- config: {},
- });
+ it('return an empty string when .storybook/preview-body.html does NOT exist', () => {
+ vol.fromNestedJSON({
+ [`${BASE_CORE_PKG_DIR}/assets/server/base-preview-body.html`]: BASE_BODY_HTML_CONTENTS,
+ config: {},
});
- it('return an empty string', () => {
- const result = getPreviewBodyTemplate('./config');
- expect(result).toEqual(BASE_BODY_HTML_CONTENTS);
- });
+ expect(getPreviewBodyTemplate('./config')).toEqual(BASE_BODY_HTML_CONTENTS);
});
- describe('when .storybook/preview-body.html exists', () => {
- beforeEach(() => {
- vol.fromNestedJSON({
- [`${base}/assets/server/base-preview-body.html`]: BASE_BODY_HTML_CONTENTS,
- config: {
- 'preview-body.html': BODY_HTML_CONTENTS,
- },
- });
+ it('return the contents of the file when .storybook/preview-body.html exists', () => {
+ vol.fromNestedJSON({
+ [`${BASE_CORE_PKG_DIR}/assets/server/base-preview-body.html`]: BASE_BODY_HTML_CONTENTS,
+ config: {
+ 'preview-body.html': BODY_HTML_CONTENTS,
+ },
});
- it('return the contents of the file', () => {
- const result = getPreviewBodyTemplate('./config');
- expect(result).toEqual(BODY_HTML_CONTENTS + BASE_BODY_HTML_CONTENTS);
- });
+ expect(getPreviewBodyTemplate('./config')).toEqual(
+ BODY_HTML_CONTENTS + BASE_BODY_HTML_CONTENTS
+ );
});
});
diff --git a/code/core/src/common/utils/cli.ts b/code/core/src/common/utils/cli.ts
index 9907ebfbbb8b..d36c99d78d97 100644
--- a/code/core/src/common/utils/cli.ts
+++ b/code/core/src/common/utils/cli.ts
@@ -78,7 +78,7 @@ export async function getCoercedStorybookVersion(packageManager: JsPackageManage
await Promise.all(
Object.keys(rendererPackages).map(async (pkg) => ({
name: pkg,
- version: packageManager.getModulePackageJSON(pkg)?.version ?? null,
+ version: (await packageManager.getModulePackageJSON(pkg))?.version ?? null,
}))
)
).filter(({ version }) => !!version);
diff --git a/code/core/src/common/utils/get-addon-annotations.ts b/code/core/src/common/utils/get-addon-annotations.ts
index f82a13bdd437..6e5bd996b8eb 100644
--- a/code/core/src/common/utils/get-addon-annotations.ts
+++ b/code/core/src/common/utils/get-addon-annotations.ts
@@ -1,5 +1,3 @@
-import path from 'node:path';
-
import { isCorePackage } from './cli';
/**
@@ -23,6 +21,7 @@ export function getAnnotationsName(addonName: string): string {
return cleanedUpName;
}
+// TODO: test this
export async function getAddonAnnotations(addon: string, configDir: string) {
const data = {
// core addons will have a function as default export in index entrypoint
@@ -38,7 +37,7 @@ export async function getAddonAnnotations(addon: string, configDir: string) {
// If the preview endpoint doesn't exist, we don't need to add the addon to definePreview
try {
- require.resolve(path.join(addon, 'preview'), { paths: [configDir] });
+ import.meta.resolve(`${addon}/preview`, configDir);
} catch (err) {
return null;
}
diff --git a/code/core/src/common/utils/get-storybook-refs.ts b/code/core/src/common/utils/get-storybook-refs.ts
index 2b7033239119..7772aa653fb7 100644
--- a/code/core/src/common/utils/get-storybook-refs.ts
+++ b/code/core/src/common/utils/get-storybook-refs.ts
@@ -118,7 +118,7 @@ export async function getRefs(options: Options) {
};
});
- // verify the refs are publicly reachable, if they are not we'll require stories.json at runtime, otherwise the ref won't work
+ // verify the refs are publicly reachable, if they are not we'll fetch stories.json at runtime, otherwise the ref won't work
await Promise.all(
Object.entries(refs).map(async ([k, value]) => {
const ok = await checkRef(value.url);
diff --git a/code/core/src/common/utils/glob-to-regexp.ts b/code/core/src/common/utils/glob-to-regexp.ts
index 5a52a8b656ed..d96caad1adab 100644
--- a/code/core/src/common/utils/glob-to-regexp.ts
+++ b/code/core/src/common/utils/glob-to-regexp.ts
@@ -19,7 +19,7 @@ export function globToRegexp(glob: string) {
// creates a matcher that expects files with no prefix (e.g. `src/file.js`)
// but if you pass it a directory that starts with `../` it expects files that
// start with `../`. Let's make it consistent.
- // Globs starting `**` require special treatment due to the regex they
+ // Globs starting `**` need special treatment due to the regex they
// produce, specifically a negative look-ahead
return new RegExp(
['^\\.', glob.startsWith('./**') ? '' : '[\\\\/]', regex.source.substring(1)].join('')
diff --git a/code/core/src/common/utils/interpret-files.ts b/code/core/src/common/utils/interpret-files.ts
index 0bbc70589823..6f792473e8de 100644
--- a/code/core/src/common/utils/interpret-files.ts
+++ b/code/core/src/common/utils/interpret-files.ts
@@ -1,21 +1,18 @@
import { existsSync } from 'node:fs';
-export const boost = new Set(['.js', '.jsx', '.ts', '.tsx', '.cts', '.mts', '.cjs', '.mjs']);
-
-function sortExtensions() {
- return [...Array.from(boost)];
-}
-
-const possibleExtensions = sortExtensions();
+export const supportedExtensions = [
+ '.js',
+ '.mjs',
+ '.cjs',
+ '.jsx',
+ '.ts',
+ '.mts',
+ '.cts',
+ '.tsx',
+] as const;
export function getInterpretedFile(pathToFile: string) {
- return possibleExtensions
+ return supportedExtensions
.map((ext) => (pathToFile.endsWith(ext) ? pathToFile : `${pathToFile}${ext}`))
.find((candidate) => existsSync(candidate));
}
-
-export function getInterpretedFileWithExt(pathToFile: string) {
- return possibleExtensions
- .map((ext) => ({ path: pathToFile.endsWith(ext) ? pathToFile : `${pathToFile}${ext}`, ext }))
- .find((candidate) => existsSync(candidate.path));
-}
diff --git a/code/core/src/common/utils/interpret-require.ts b/code/core/src/common/utils/interpret-require.ts
index bc5349408a95..939486cdbb54 100644
--- a/code/core/src/common/utils/interpret-require.ts
+++ b/code/core/src/common/utils/interpret-require.ts
@@ -1,40 +1,9 @@
-import { getInterpretedFileWithExt } from './interpret-files';
-
-let registered = false;
-
-export function interopRequireDefault(filePath: string) {
- const hasEsbuildBeenRegistered = !!require('module')._extensions['.ts'];
-
- if (registered === false && !hasEsbuildBeenRegistered) {
- const { register } = require('esbuild-register/dist/node');
- registered = true;
- register({
- target: `node${process.version.slice(1)}`,
- format: 'cjs',
- hookIgnoreNodeModules: true,
- // Some frameworks, like Stylus, rely on the 'name' property of classes or functions
- // https://github.com/storybookjs/storybook/issues/19049
- keepNames: true,
- tsconfigRaw: `{
- "compilerOptions": {
- "strict": false,
- "skipLibCheck": true,
- },
- }`,
- });
- }
-
- const result = require(filePath);
-
- const isES6DefaultExported =
- typeof result === 'object' && result !== null && typeof result.default !== 'undefined';
-
- return isES6DefaultExported ? result.default : result;
-}
+import { importModule } from '../../shared/utils/module';
+import { getInterpretedFile } from './interpret-files';
function getCandidate(paths: string[]) {
for (let i = 0; i < paths.length; i += 1) {
- const candidate = getInterpretedFileWithExt(paths[i]);
+ const candidate = getInterpretedFile(paths[i]);
if (candidate) {
return candidate;
@@ -44,23 +13,14 @@ function getCandidate(paths: string[]) {
return undefined;
}
+// TODO: remove this when it is no longer used by @storybook/core-webpack
export function serverRequire(filePath: string | string[]) {
- const candidatePath = serverResolve(filePath);
-
- if (!candidatePath) {
- return null;
- }
-
- return interopRequireDefault(candidatePath);
-}
-
-export function serverResolve(filePath: string | string[]): string | null {
const paths = Array.isArray(filePath) ? filePath : [filePath];
- const existingCandidate = getCandidate(paths);
+ const candidatePath = getCandidate(paths);
- if (!existingCandidate) {
+ if (!candidatePath) {
return null;
}
- return existingCandidate.path;
+ return importModule(candidatePath);
}
diff --git a/code/core/src/common/utils/load-custom-presets.ts b/code/core/src/common/utils/load-custom-presets.ts
deleted file mode 100644
index c5e2b272daef..000000000000
--- a/code/core/src/common/utils/load-custom-presets.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { resolve } from 'node:path';
-
-import type { PresetConfig } from 'storybook/internal/types';
-
-import { serverRequire, serverResolve } from './interpret-require';
-import { validateConfigurationFiles } from './validate-configuration-files';
-
-export function loadCustomPresets({ configDir }: { configDir: string }): PresetConfig[] {
- validateConfigurationFiles(configDir);
-
- const presets = serverRequire(resolve(configDir, 'presets'));
- const main = serverRequire(resolve(configDir, 'main'));
-
- if (main) {
- const resolved = serverResolve(resolve(configDir, 'main'));
- if (resolved) {
- return [resolved];
- }
- }
-
- return presets || [];
-}
diff --git a/code/core/src/common/utils/load-main-config.ts b/code/core/src/common/utils/load-main-config.ts
index d1b9127f05d2..4bc08250e602 100644
--- a/code/core/src/common/utils/load-main-config.ts
+++ b/code/core/src/common/utils/load-main-config.ts
@@ -1,65 +1,33 @@
-import { readFile } from 'node:fs/promises';
import { relative, resolve } from 'node:path';
-import {
- MainFileESMOnlyImportError,
- MainFileEvaluationError,
-} from 'storybook/internal/server-errors';
+import { MainFileEvaluationError } from 'storybook/internal/server-errors';
import type { StorybookConfig } from 'storybook/internal/types';
-import { serverRequire, serverResolve } from './interpret-require';
+import { importModule } from '../../shared/utils/module';
+import { getInterpretedFile } from './interpret-files';
import { validateConfigurationFiles } from './validate-configuration-files';
export async function loadMainConfig({
configDir = '.storybook',
- noCache = false,
cwd,
}: {
configDir: string;
- noCache?: boolean;
cwd?: string;
}): Promise {
await validateConfigurationFiles(configDir, cwd);
- const mainJsPath = serverResolve(resolve(configDir, 'main')) as string;
-
- if (noCache && mainJsPath && require.cache[mainJsPath]) {
- delete require.cache[mainJsPath];
- }
+ const mainPath = getInterpretedFile(resolve(configDir, 'main')) as string;
try {
- const out = await serverRequire(mainJsPath);
+ const out = await importModule(mainPath);
return out;
} catch (e) {
if (!(e instanceof Error)) {
throw e;
}
- if (e.message.match(/Cannot use import statement outside a module/)) {
- const location = relative(process.cwd(), mainJsPath);
- const numFromStack = e.stack?.match(new RegExp(`${location}:(\\d+):(\\d+)`))?.[1];
- let num;
- let line;
-
- if (numFromStack) {
- const contents = await readFile(mainJsPath, 'utf-8');
- const lines = contents.split('\n');
- num = parseInt(numFromStack, 10) - 1;
- line = lines[num];
- }
-
- const out = new MainFileESMOnlyImportError({
- line,
- location,
- num,
- });
-
- delete out.stack;
-
- throw out;
- }
throw new MainFileEvaluationError({
- location: relative(process.cwd(), mainJsPath),
+ location: relative(process.cwd(), mainPath),
error: e,
});
}
diff --git a/code/core/src/common/utils/safeResolve.ts b/code/core/src/common/utils/safeResolve.ts
deleted file mode 100644
index a1d5684e45b5..000000000000
--- a/code/core/src/common/utils/safeResolve.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import resolveFrom from 'resolve-from';
-
-export const safeResolveFrom = (path: string, file: string) => {
- try {
- return resolveFrom(path, file);
- } catch (e) {
- return undefined;
- }
-};
-
-export const safeResolve = (file: string) => {
- try {
- return require.resolve(file);
- } catch (e) {
- return undefined;
- }
-};
diff --git a/code/core/src/common/utils/strip-abs-node-modules-path.ts b/code/core/src/common/utils/strip-abs-node-modules-path.ts
deleted file mode 100644
index abbb55608a64..000000000000
--- a/code/core/src/common/utils/strip-abs-node-modules-path.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { posix, sep } from 'node:path';
-
-import slash from 'slash';
-
-function normalizePath(id: string) {
- return posix.normalize(slash(id));
-}
-
-// We need to convert from an absolute path, to a traditional node module import path,
-// so that vite can correctly pre-bundle/optimize
-export function stripAbsNodeModulesPath(absPath: string) {
- // TODO: Evaluate if searching for node_modules in a yarn pnp environment is correct
- const splits = absPath.split(`node_modules${sep}`);
- // Return everything after the final "node_modules/"
- const module = normalizePath(splits[splits.length - 1]);
- return module;
-}
diff --git a/code/core/src/common/utils/template.ts b/code/core/src/common/utils/template.ts
index f4cf014787c2..ecd5f23027b2 100644
--- a/code/core/src/common/utils/template.ts
+++ b/code/core/src/common/utils/template.ts
@@ -1,5 +1,8 @@
import { existsSync, readFileSync } from 'node:fs';
-import { dirname, resolve } from 'node:path';
+
+import { join, resolve } from 'pathe';
+
+import { resolvePackageDir } from '../../shared/utils/module';
const interpolate = (string: string, data: Record = {}) =>
Object.entries(data).reduce((acc, [k, v]) => acc.replace(new RegExp(`%${k}%`, 'g'), v), string);
@@ -8,8 +11,10 @@ export function getPreviewBodyTemplate(
configDirPath: string,
interpolations?: Record
) {
- const packageDir = dirname(require.resolve('storybook/internal/package.json'));
- const base = readFileSync(`${packageDir}/assets/server/base-preview-body.html`, 'utf8');
+ const base = readFileSync(
+ join(resolvePackageDir('storybook'), 'assets/server/base-preview-body.html'),
+ 'utf8'
+ );
const bodyHtmlPath = resolve(configDirPath, 'preview-body.html');
let result = base;
@@ -25,8 +30,10 @@ export function getPreviewHeadTemplate(
configDirPath: string,
interpolations?: Record
) {
- const packageDir = dirname(require.resolve('storybook/internal/package.json'));
- const base = readFileSync(`${packageDir}/assets/server/base-preview-head.html`, 'utf8');
+ const base = readFileSync(
+ join(resolvePackageDir('storybook'), 'assets/server/base-preview-head.html'),
+ 'utf8'
+ );
const headHtmlPath = resolve(configDirPath, 'preview-head.html');
let result = base;
diff --git a/code/core/src/common/utils/transform-imports.test.ts b/code/core/src/common/utils/transform-imports.test.ts
index dc6d82325395..e57aab7ea0a4 100644
--- a/code/core/src/common/utils/transform-imports.test.ts
+++ b/code/core/src/common/utils/transform-imports.test.ts
@@ -3,7 +3,7 @@ import { join } from 'node:path';
import { describe, expect, it, vi } from 'vitest';
-import dedent from 'ts-dedent';
+import { dedent } from 'ts-dedent';
import { transformImportFiles } from './transform-imports';
diff --git a/code/core/src/common/utils/validate-config.ts b/code/core/src/common/utils/validate-config.ts
index 388d915de105..e58cdb15a62e 100644
--- a/code/core/src/common/utils/validate-config.ts
+++ b/code/core/src/common/utils/validate-config.ts
@@ -6,6 +6,8 @@ import {
MissingFrameworkFieldError,
} from 'storybook/internal/server-errors';
+import { resolvePathSync } from 'mlly';
+
import { frameworkPackages } from './get-storybook-info';
const renderers = ['html', 'preact', 'react', 'server', 'svelte', 'vue', 'vue3', 'web-components'];
@@ -33,7 +35,10 @@ export function validateFrameworkName(
// If it's not a known framework, we need to validate that it's a valid package at least
try {
- require.resolve(join(frameworkName, 'preset'));
+ resolvePathSync(join(frameworkName, 'preset'), {
+ extensions: ['.mjs', '.js', '.cjs'],
+ conditions: ['node', 'import', 'require'],
+ });
} catch (err) {
throw new CouldNotEvaluateFrameworkError({ frameworkName });
}
diff --git a/code/core/src/common/utils/validate-configuration-files.ts b/code/core/src/common/utils/validate-configuration-files.ts
index 62a27c238dc6..820ccd9f308b 100644
--- a/code/core/src/common/utils/validate-configuration-files.ts
+++ b/code/core/src/common/utils/validate-configuration-files.ts
@@ -8,10 +8,10 @@ import { glob } from 'glob';
import slash from 'slash';
import { dedent } from 'ts-dedent';
-import { boost } from './interpret-files';
+import { supportedExtensions } from './interpret-files';
export async function validateConfigurationFiles(configDir: string, cwd?: string) {
- const extensionsPattern = `{${Array.from(boost).join(',')}}`;
+ const extensionsPattern = `{${Array.from(supportedExtensions).join(',')}}`;
const mainConfigMatches = await glob(slash(resolve(configDir, `main${extensionsPattern}`)), {
cwd: cwd ?? process.cwd(),
});
diff --git a/code/core/src/controls/constants.ts b/code/core/src/controls/constants.ts
index a2360dfff87c..48f50d360d9a 100644
--- a/code/core/src/controls/constants.ts
+++ b/code/core/src/controls/constants.ts
@@ -1,2 +1,3 @@
+// ! please keep this in sync with addons/onboarding/src/constants.ts
export const ADDON_ID = 'addon-controls' as const;
export const PARAM_KEY = 'controls' as const;
diff --git a/code/core/src/controls/preset/checkDocsLoaded.ts b/code/core/src/controls/preset/checkDocsLoaded.ts
deleted file mode 100644
index 7f97c3267c5d..000000000000
--- a/code/core/src/controls/preset/checkDocsLoaded.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { isAbsolute, join } from 'node:path';
-
-import { checkAddonOrder, serverRequire } from 'storybook/internal/common';
-
-export const checkDocsLoaded = (configDir: string) => {
- checkAddonOrder({
- before: {
- name: '@storybook/addon-docs',
- inEssentials: true,
- },
- after: {
- name: '@storybook/addon-controls',
- inEssentials: true,
- },
- configFile: isAbsolute(configDir)
- ? join(configDir, 'main')
- : join(process.cwd(), configDir, 'main'),
- getConfig: (configFile) => serverRequire(configFile),
- });
-};
diff --git a/code/core/src/core-server/build-dev.ts b/code/core/src/core-server/build-dev.ts
index ec8427898fcb..9c1b7a3c635a 100644
--- a/code/core/src/core-server/build-dev.ts
+++ b/code/core/src/core-server/build-dev.ts
@@ -4,12 +4,12 @@ import { join, relative, resolve } from 'node:path';
import {
JsPackageManagerFactory,
getConfigInfo,
+ getInterpretedFile,
getProjectRoot,
loadAllPresets,
loadMainConfig,
resolveAddonName,
resolvePathInStorybookCache,
- serverResolve,
validateFrameworkName,
versions,
} from 'storybook/internal/common';
@@ -24,6 +24,7 @@ import prompts from 'prompts';
import invariant from 'tiny-invariant';
import { dedent } from 'ts-dedent';
+import { resolvePackageDir } from '../shared/utils/module';
import { storybookDevServer } from './dev-server';
import { buildOrThrow } from './utils/build-or-throw';
import { getManagerBuilder, getPreviewBuilder } from './utils/get-builders';
@@ -134,7 +135,7 @@ export async function buildDevStandalone(
let presets = await loadAllPresets({
corePresets,
overridePresets: [
- require.resolve('storybook/internal/core-server/presets/common-override-preset'),
+ import.meta.resolve('storybook/internal/core-server/presets/common-override-preset'),
],
...options,
isCritical: true,
@@ -152,18 +153,20 @@ export async function buildDevStandalone(
}
}
- const builderName = typeof builder === 'string' ? builder : builder.name;
+ const resolvedPreviewBuilder = typeof builder === 'string' ? builder : builder.name;
const [previewBuilder, managerBuilder] = await Promise.all([
- getPreviewBuilder(builderName, options.configDir),
+ getPreviewBuilder(resolvedPreviewBuilder),
getManagerBuilder(),
]);
- if (builderName.includes('builder-vite')) {
+ if (resolvedPreviewBuilder.includes('builder-vite')) {
const deprecationMessage =
dedent(`Using CommonJS in your main configuration file is deprecated with Vite.
- Refer to the migration guide at https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#commonjs-with-vite-is-deprecated`);
- const mainJsPath = serverResolve(resolve(options.configDir || '.storybook', 'main')) as string;
+ const mainJsPath = getInterpretedFile(
+ resolve(options.configDir || '.storybook', 'main')
+ ) as string;
if (/\.c[jt]s$/.test(mainJsPath)) {
deprecate(deprecationMessage);
}
@@ -181,7 +184,7 @@ export async function buildDevStandalone(
// Load second pass: all presets are applied in order
presets = await loadAllPresets({
corePresets: [
- require.resolve('storybook/internal/core-server/presets/common-preset'),
+ join(resolvePackageDir('storybook'), 'dist/core-server/presets/common-preset.js'),
...(managerBuilder.corePresets || []),
...(previewBuilder.corePresets || []),
...(resolvedRenderer ? [resolvedRenderer] : []),
@@ -189,7 +192,7 @@ export async function buildDevStandalone(
],
overridePresets: [
...(previewBuilder.overridePresets || []),
- require.resolve('storybook/internal/core-server/presets/common-override-preset'),
+ import.meta.resolve('storybook/internal/core-server/presets/common-override-preset'),
],
...options,
});
diff --git a/code/core/src/core-server/build-index.test.ts b/code/core/src/core-server/build-index.test.ts
index e467a4f86a85..1bd464c538a5 100644
--- a/code/core/src/core-server/build-index.test.ts
+++ b/code/core/src/core-server/build-index.test.ts
@@ -3,7 +3,7 @@ import { describe, expect, it } from 'vitest';
import { buildIndex } from './build-index';
describe('buildIndex', () => {
- it('should build index', async () => {
+ it.skip('should build index', async () => {
const index = await buildIndex({
configDir: `${__dirname}/utils/__mockdata__`,
});
diff --git a/code/core/src/core-server/build-static.ts b/code/core/src/core-server/build-static.ts
index 3aa90a955664..96679b114bd4 100644
--- a/code/core/src/core-server/build-static.ts
+++ b/code/core/src/core-server/build-static.ts
@@ -1,6 +1,6 @@
import { cp, mkdir } from 'node:fs/promises';
import { rm } from 'node:fs/promises';
-import { dirname, join, relative, resolve } from 'node:path';
+import { join, relative, resolve } from 'node:path';
import {
loadAllPresets,
@@ -17,6 +17,7 @@ import { global } from '@storybook/global';
import picocolors from 'picocolors';
+import { resolvePackageDir } from '../shared/utils/module';
import { StoryIndexGenerator } from './utils/StoryIndexGenerator';
import { buildOrThrow } from './utils/build-or-throw';
import { copyAllStaticFilesRelativeToMain } from './utils/copy-all-static-files';
@@ -60,15 +61,18 @@ export async function buildStaticStandalone(options: BuildStaticStandaloneOption
logger.warn(`you have not specified a framework in your ${options.configDir}/main.js`);
}
+ const commonPreset = join(
+ resolvePackageDir('storybook'),
+ 'dist/core-server/presets/common-preset.js'
+ );
+ const commonOverridePreset = import.meta.resolve(
+ 'storybook/internal/core-server/presets/common-override-preset'
+ );
+
logger.info('=> Loading presets');
let presets = await loadAllPresets({
- corePresets: [
- require.resolve('storybook/internal/core-server/presets/common-preset'),
- ...corePresets,
- ],
- overridePresets: [
- require.resolve('storybook/internal/core-server/presets/common-override-preset'),
- ],
+ corePresets: [commonPreset, ...corePresets],
+ overridePresets: [commonOverridePreset],
isCritical: true,
...options,
});
@@ -82,16 +86,13 @@ export async function buildStaticStandalone(options: BuildStaticStandaloneOption
: undefined;
presets = await loadAllPresets({
corePresets: [
- require.resolve('storybook/internal/core-server/presets/common-preset'),
+ commonPreset,
...(managerBuilder.corePresets || []),
...(previewBuilder.corePresets || []),
...(resolvedRenderer ? [resolvedRenderer] : []),
...corePresets,
],
- overridePresets: [
- ...(previewBuilder.overridePresets || []),
- require.resolve('storybook/internal/core-server/presets/common-override-preset'),
- ],
+ overridePresets: [...(previewBuilder.overridePresets || []), commonOverridePreset],
...options,
build,
});
@@ -135,10 +136,7 @@ export async function buildStaticStandalone(options: BuildStaticStandaloneOption
);
}
- const coreServerPublicDir = join(
- dirname(require.resolve('storybook/internal/package.json')),
- 'assets/browser'
- );
+ const coreServerPublicDir = join(resolvePackageDir('storybook'), 'assets/browser');
effects.push(cp(coreServerPublicDir, options.outputDir, { recursive: true }));
let initializedStoryIndexGenerator: Promise =
diff --git a/code/core/src/core-server/dev-server.ts b/code/core/src/core-server/dev-server.ts
index 7039977cee05..f79251d221a3 100644
--- a/code/core/src/core-server/dev-server.ts
+++ b/code/core/src/core-server/dev-server.ts
@@ -48,7 +48,7 @@ export async function storybookDevServer(options: Options) {
app.use(getAccessControlMiddleware(core?.crossOriginIsolated ?? false));
app.use(getCachingMiddleware());
- getMiddleware(options.configDir)(app);
+ (await getMiddleware(options.configDir))(app);
const { port, host, initialPath } = options;
invariant(port, 'expected options to have a port');
@@ -59,10 +59,11 @@ export async function storybookDevServer(options: Options) {
throw new MissingBuilderError();
}
- const builderName = typeof core?.builder === 'string' ? core.builder : core?.builder?.name;
+ const resolvedPreviewBuilder =
+ typeof core?.builder === 'string' ? core.builder : core?.builder?.name;
const [previewBuilder, managerBuilder] = await Promise.all([
- getPreviewBuilder(builderName, options.configDir),
+ getPreviewBuilder(resolvedPreviewBuilder),
getManagerBuilder(),
useStatics(app, options),
]);
diff --git a/code/core/src/core-server/load.ts b/code/core/src/core-server/load.ts
index b3f7e6287df7..baa453682605 100644
--- a/code/core/src/core-server/load.ts
+++ b/code/core/src/core-server/load.ts
@@ -12,6 +12,8 @@ import type { BuilderOptions, CLIOptions, LoadOptions, Options } from 'storybook
import { global } from '@storybook/global';
+import { resolvePackageDir } from '../shared/utils/module';
+
export async function loadStorybook(
options: CLIOptions &
LoadOptions &
@@ -49,7 +51,7 @@ export async function loadStorybook(
let presets = await loadAllPresets({
corePresets,
overridePresets: [
- require.resolve('storybook/internal/core-server/presets/common-override-preset'),
+ import.meta.resolve('storybook/internal/core-server/presets/common-override-preset'),
],
...options,
isCritical: true,
@@ -62,12 +64,12 @@ export async function loadStorybook(
presets = await loadAllPresets({
corePresets: [
- require.resolve('storybook/internal/core-server/presets/common-preset'),
+ join(resolvePackageDir('storybook'), 'dist/core-server/presets/common-preset.js'),
...(resolvedRenderer ? [resolvedRenderer] : []),
...corePresets,
],
overridePresets: [
- require.resolve('storybook/internal/core-server/presets/common-override-preset'),
+ import.meta.resolve('storybook/internal/core-server/presets/common-override-preset'),
],
...options,
});
diff --git a/code/core/src/core-server/mocking-utils/resolve.ts b/code/core/src/core-server/mocking-utils/resolve.ts
index 246e4a26586e..3c52b03eaec5 100644
--- a/code/core/src/core-server/mocking-utils/resolve.ts
+++ b/code/core/src/core-server/mocking-utils/resolve.ts
@@ -1,4 +1,5 @@
-import { readFileSync } from 'node:fs';
+import { readFileSync, realpathSync } from 'node:fs';
+import { createRequire } from 'node:module';
import { findMockRedirect } from '@vitest/mocker/redirect';
import { dirname, isAbsolute, join, resolve } from 'pathe';
@@ -6,6 +7,8 @@ import { exports as resolveExports } from 'resolve.exports';
import { isModuleDirectory } from './extract';
+const require = createRequire(import.meta.url);
+
/**
* Finds the package.json for a given module specifier.
*
@@ -86,3 +89,56 @@ export function resolveMock(path: string, root: string, importer: string) {
redirectPath, // will be null if no __mocks__ file is found
};
}
+
+/**
+ * External mean not absolute, and not relative
+ *
+ * We use `require.resolve` here, because import.meta.resolve needs a experimental node flag
+ * (`--experimental-import-meta-resolve`) to be enabled to respect the context option.
+ *
+ * @param path - The path to the mock file
+ * @param from - The root of the project, this should be an absolute path
+ * @returns True if the mock path is external, false otherwise
+ * @link https://nodejs.org/api/cli.html#--experimental-import-meta-resolve
+ */
+export function isExternal(path: string, from: string) {
+ try {
+ return !isAbsolute(path) && isModuleDirectory(require.resolve(path, { paths: [from] }));
+ } catch (e) {
+ return false;
+ }
+}
+
+/**
+ * Normalizes a file path for comparison, resolving symlinks if possible. Falls back to the original
+ * path if resolution fails.
+ */
+export function getRealPath(path: string, preserveSymlinks: boolean): string {
+ try {
+ return preserveSymlinks ? realpathSync(path) : path;
+ } catch {
+ return path;
+ }
+}
+
+/**
+ * This is a wrapper around `require.resolve` that tries to resolve the path with different file
+ * extensions.
+ *
+ * @param path - The path to the mock file
+ * @param from - The root of the project, this should be an absolute path
+ * @returns The resolved path
+ */
+export function resolveWithExtensions(path: string, from: string) {
+ const extensions = ['.js', '.ts', '.tsx', '.mjs', '.cjs', '.svelte', '.vue'];
+
+ for (const extension of extensions) {
+ try {
+ return require.resolve(path + extension, { paths: [from] });
+ } catch (e) {
+ continue;
+ }
+ }
+
+ return require.resolve(path, { paths: [from] });
+}
diff --git a/code/core/src/core-server/presets/common-preset.ts b/code/core/src/core-server/presets/common-preset.ts
index d3a402e3fc64..7539f77c4378 100644
--- a/code/core/src/core-server/presets/common-preset.ts
+++ b/code/core/src/core-server/presets/common-preset.ts
@@ -1,6 +1,7 @@
import { existsSync } from 'node:fs';
import { readFile } from 'node:fs/promises';
-import { dirname, isAbsolute, join } from 'node:path';
+import { isAbsolute, join } from 'node:path';
+import { fileURLToPath } from 'node:url';
import type { Channel } from 'storybook/internal/channels';
import { optionalEnvToBoolean } from 'storybook/internal/common';
@@ -25,11 +26,13 @@ import type {
PresetPropertyFn,
} from 'storybook/internal/types';
+import * as pathe from 'pathe';
import { dedent } from 'ts-dedent';
+import { resolvePackageDir } from '../../shared/utils/module';
import { initCreateNewStoryChannel } from '../server-channel/create-new-story-channel';
import { initFileSearchChannel } from '../server-channel/file-search-channel';
-import { defaultStaticDirs } from '../utils/constants';
+import { defaultFavicon, defaultStaticDirs } from '../utils/constants';
import { initializeSaveStory } from '../utils/save-story/save-story';
import { parseStaticDir } from '../utils/server-statics';
import { type OptionsWithRequiredCache, initializeWhatsNew } from '../utils/whats-new';
@@ -37,11 +40,6 @@ import { type OptionsWithRequiredCache, initializeWhatsNew } from '../utils/what
const interpolate = (string: string, data: Record = {}) =>
Object.entries(data).reduce((acc, [k, v]) => acc.replace(new RegExp(`%${k}%`, 'g'), v), string);
-const defaultFavicon = join(
- dirname(require.resolve('storybook/internal/package.json')),
- '/assets/browser/favicon.svg'
-);
-
export const staticDirs: PresetPropertyFn<'staticDirs'> = async (values = []) => [
...defaultStaticDirs,
...values,
@@ -54,6 +52,7 @@ export const favicon = async (
if (value) {
return value;
}
+
const staticDirsValue = await options.presets.apply('staticDirs');
const statics = staticDirsValue
@@ -271,8 +270,8 @@ export const resolvedReact = async (existing: any) => {
try {
return {
...existing,
- react: dirname(require.resolve('react/package.json')),
- reactDom: dirname(require.resolve('react-dom/package.json')),
+ react: resolvePackageDir('react'),
+ reactDom: resolvePackageDir('react-dom'),
};
} catch (e) {
return existing;
@@ -291,10 +290,7 @@ export const tags = async (existing: any) => {
export const managerEntries = async (existing: any) => {
return [
- join(
- dirname(require.resolve('storybook/internal/package.json')),
- 'dist/core-server/presets/common-manager.js'
- ),
+ pathe.join(resolvePackageDir('storybook'), 'dist/core-server/presets/common-manager.js'),
...(existing || []),
];
};
@@ -351,8 +347,8 @@ export const webpackFinal = async (
test: /preview\.(t|j)sx?$/,
use: [
{
- loader: require.resolve(
- 'storybook/internal/core-server/presets/webpack/loaders/storybook-mock-transform-loader'
+ loader: fileURLToPath(
+ import.meta.resolve('storybook/webpack/loaders/storybook-mock-transform-loader')
),
},
],
diff --git a/code/core/src/core-server/presets/favicon.test.ts b/code/core/src/core-server/presets/favicon.test.ts
index 3203b86387c0..dada54a547ca 100644
--- a/code/core/src/core-server/presets/favicon.test.ts
+++ b/code/core/src/core-server/presets/favicon.test.ts
@@ -7,8 +7,23 @@ import { logger } from 'storybook/internal/node-logger';
import * as m from './common-preset';
+// mock src/core-server/utils/constants.ts:8:27
+vi.mock('../utils/constants', () => {
+ return {
+ defaultStaticDirs: [{ from: './from', to: './to' }],
+ defaultFavicon: join(
+ dirname(require.resolve('storybook/package.json')),
+ '/assets/browser/favicon.svg'
+ ),
+ };
+});
+
+vi.mock('../../shared/utils/module', () => ({
+ resolvePackageDir: vi.fn().mockReturnValue('mocked-path'),
+}));
+
const defaultFavicon = join(
- dirname(require.resolve('storybook/internal/package.json')),
+ dirname(require.resolve('storybook/package.json')),
'/assets/browser/favicon.svg'
);
diff --git a/code/core/src/core-server/presets/vitePlugins/vite-inject-mocker/plugin.ts b/code/core/src/core-server/presets/vitePlugins/vite-inject-mocker/plugin.ts
index 57a177c51abc..eec9b0490405 100644
--- a/code/core/src/core-server/presets/vitePlugins/vite-inject-mocker/plugin.ts
+++ b/code/core/src/core-server/presets/vitePlugins/vite-inject-mocker/plugin.ts
@@ -1,10 +1,13 @@
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
+import { fileURLToPath } from 'node:url';
import { exactRegex } from '@rolldown/pluginutils';
import { dedent } from 'ts-dedent';
import type { ResolvedConfig, ViteDevServer } from 'vite';
+import { resolvePackageDir } from '../../../../shared/utils/module';
+
const entryPath = '/vite-inject-mocker-entry.js';
const entryCode = dedent`
@@ -24,8 +27,11 @@ export const viteInjectMockerRuntime = (options: {
if (viteConfig.command === 'build') {
this.emitFile({
type: 'chunk',
- id: require.resolve(
- join(__dirname, '..', '..', '..', 'templates', 'mocker-runtime.template.js')
+ id: join(
+ resolvePackageDir('storybook'),
+ 'assets',
+ 'server',
+ 'mocker-runtime.template.js'
),
fileName: entryPath.slice(1),
});
@@ -41,8 +47,8 @@ export const viteInjectMockerRuntime = (options: {
// leads to errors, if the imported module is not a dependency of the project.
// By resolving the module to the real path, we can avoid this issue.
alias: {
- '@vitest/mocker/browser': require.resolve('@vitest/mocker/browser'),
- '@vitest/mocker': require.resolve('@vitest/mocker'),
+ '@vitest/mocker/browser': fileURLToPath(import.meta.resolve('@vitest/mocker/browser')),
+ '@vitest/mocker': fileURLToPath(import.meta.resolve('@vitest/mocker')),
},
},
};
@@ -77,9 +83,7 @@ export const viteInjectMockerRuntime = (options: {
async load(id) {
if (exactRegex(id).test(entryPath)) {
return readFileSync(
- require.resolve(
- join(__dirname, '..', '..', '..', 'templates', 'mocker-runtime.template.js')
- ),
+ join(resolvePackageDir('storybook'), 'assets', 'server', 'mocker-runtime.template.js'),
'utf-8'
);
}
diff --git a/code/core/src/core-server/presets/vitePlugins/vite-mock/plugin.ts b/code/core/src/core-server/presets/vitePlugins/vite-mock/plugin.ts
index f5b18d7f1f10..57d06762d2a9 100644
--- a/code/core/src/core-server/presets/vitePlugins/vite-mock/plugin.ts
+++ b/code/core/src/core-server/presets/vitePlugins/vite-mock/plugin.ts
@@ -12,12 +12,8 @@ import {
extractMockCalls,
rewriteSbMockImportCalls,
} from '../../../mocking-utils/extract';
-import {
- type MockCall,
- getCleanId,
- invalidateAllRelatedModules,
- normalizePathForComparison,
-} from './utils';
+import { getRealPath } from '../../../mocking-utils/resolve';
+import { type MockCall, getCleanId, invalidateAllRelatedModules } from './utils';
export interface MockPluginOptions {
/** The absolute path to the preview.tsx file where mocks are defined. */
@@ -106,11 +102,11 @@ export function viteMockPlugin(options: MockPluginOptions): Plugin[] {
handler(id) {
const preserveSymlinks = viteConfig.resolve.preserveSymlinks;
- const idNorm = normalizePathForComparison(id, preserveSymlinks);
+ const idNorm = getRealPath(id, preserveSymlinks);
const cleanId = getCleanId(idNorm);
for (const call of mockCalls) {
- const callNorm = normalizePathForComparison(call.absolutePath, preserveSymlinks);
+ const callNorm = getRealPath(call.absolutePath, preserveSymlinks);
if (callNorm !== idNorm && call.path !== cleanId) {
continue;
@@ -130,8 +126,8 @@ export function viteMockPlugin(options: MockPluginOptions): Plugin[] {
for (const call of mockCalls) {
const preserveSymlinks = viteConfig.resolve.preserveSymlinks;
- const idNorm = normalizePathForComparison(id, preserveSymlinks);
- const callNorm = normalizePathForComparison(call.absolutePath, preserveSymlinks);
+ const idNorm = getRealPath(id, preserveSymlinks);
+ const callNorm = getRealPath(call.absolutePath, preserveSymlinks);
if (viteConfig.command !== 'serve') {
if (callNorm !== idNorm) {
diff --git a/code/core/src/core-server/presets/vitePlugins/vite-mock/utils.ts b/code/core/src/core-server/presets/vitePlugins/vite-mock/utils.ts
index d113ce4d0ca9..d3d1de0d4bcf 100644
--- a/code/core/src/core-server/presets/vitePlugins/vite-mock/utils.ts
+++ b/code/core/src/core-server/presets/vitePlugins/vite-mock/utils.ts
@@ -30,18 +30,6 @@ export function invalidateAllRelatedModules(
}
}
-/**
- * Normalizes a file path for comparison, resolving symlinks if possible. Falls back to the original
- * path if resolution fails.
- */
-export function normalizePathForComparison(path: string, preserveSymlinks: boolean): string {
- try {
- return preserveSymlinks ? realpathSync(path) : path;
- } catch {
- return path;
- }
-}
-
export type MockCall = {
path: string;
absolutePath: string;
diff --git a/code/core/src/core-server/presets/webpack/plugins/webpack-inject-mocker-runtime-plugin.ts b/code/core/src/core-server/presets/webpack/plugins/webpack-inject-mocker-runtime-plugin.ts
index 13b5b2a797b2..efcccf2f4f95 100644
--- a/code/core/src/core-server/presets/webpack/plugins/webpack-inject-mocker-runtime-plugin.ts
+++ b/code/core/src/core-server/presets/webpack/plugins/webpack-inject-mocker-runtime-plugin.ts
@@ -6,6 +6,8 @@ import { buildSync } from 'esbuild';
import type HtmlWebpackPlugin from 'html-webpack-plugin';
import type { Compiler } from 'webpack';
+import { resolvePackageDir } from '../../../../shared/utils/module';
+
const PLUGIN_NAME = 'WebpackInjectMockerRuntimePlugin';
/**
@@ -55,8 +57,11 @@ export class WebpackInjectMockerRuntimePlugin {
try {
// The runtime template is the same for both dev and build in the final implementation,
// as all mocking logic is handled at build time or by the dev server's transform.
- const runtimeTemplatePath = require.resolve(
- join(__dirname, '..', '..', '..', 'templates', 'mocker-runtime.template.js')
+ const runtimeTemplatePath = join(
+ resolvePackageDir('storybook'),
+ 'assets',
+ 'server',
+ 'mocker-runtime.template.js'
);
// Use esbuild to bundle the runtime script and its dependencies (`@vitest/mocker`, etc.)
// into a single, self-contained string of code.
diff --git a/code/core/src/core-server/presets/webpack/plugins/webpack-mock-plugin.ts b/code/core/src/core-server/presets/webpack/plugins/webpack-mock-plugin.ts
index 976691815b37..6bb87d340fc1 100644
--- a/code/core/src/core-server/presets/webpack/plugins/webpack-mock-plugin.ts
+++ b/code/core/src/core-server/presets/webpack/plugins/webpack-mock-plugin.ts
@@ -1,9 +1,17 @@
+import { createRequire } from 'node:module';
import { dirname, isAbsolute } from 'node:path';
+import { fileURLToPath } from 'node:url';
import type { Compiler } from 'webpack';
import { babelParser, extractMockCalls } from '../../../mocking-utils/extract';
-import { getIsExternal, resolveExternalModule, resolveMock } from '../../../mocking-utils/resolve';
+import {
+ getIsExternal,
+ resolveExternalModule,
+ resolveWithExtensions,
+} from '../../../mocking-utils/resolve';
+
+const require = createRequire(import.meta.url);
// --- Type Definitions ---
@@ -61,14 +69,19 @@ export class WebpackMockPlugin {
// This function will be called to update the mock map before each compilation.
const updateMocks = () => {
this.mockMap = new Map(
- this.extractAndResolveMocks(compiler).map((mock) => [mock.absolutePath, mock])
+ this.extractAndResolveMocks(compiler).flatMap((mock) => [
+ // first one, full path
+ [mock.absolutePath, mock],
+ // second one, without the extension
+ [mock.absolutePath.replace(/\.[^.]+$/, ''), mock],
+ ])
);
- logger.info(`Mock map updated with ${this.mockMap.size} mocks.`);
+ // divide by 2 because we add both the full path and the path without the extension
+ logger.info(`Mock map updated with ${this.mockMap.size / 2} mocks.`);
};
- // Hook into `beforeRun` for single builds and `watchRun` for development mode.
- compiler.hooks.beforeRun.tap(PLUGIN_NAME, updateMocks);
- compiler.hooks.watchRun.tap(PLUGIN_NAME, updateMocks);
+ compiler.hooks.beforeRun.tap(PLUGIN_NAME, updateMocks); // for build
+ compiler.hooks.watchRun.tap(PLUGIN_NAME, updateMocks); // for dev
// Apply the replacement plugin. Its callback will now use the dynamically updated mockMap.
new compiler.webpack.NormalModuleReplacementPlugin(/.*/, (resource) => {
@@ -79,14 +92,13 @@ export class WebpackMockPlugin {
const isExternal = getIsExternal(path, importer);
const absolutePath = isExternal
? resolveExternalModule(path, importer)
- : require.resolve(path, { paths: [importer] });
+ : resolveWithExtensions(path, importer);
if (this.mockMap.has(absolutePath)) {
- const mock = this.mockMap.get(absolutePath)!;
- resource.request = mock.replacementResource;
+ resource.request = this.mockMap.get(absolutePath)!.replacementResource;
}
} catch (e) {
- // Ignore errors for virtual modules, built-ins, etc.
+ logger.debug(`Could not resolve mock for "${resource.request}".`);
}
}).apply(compiler);
@@ -126,11 +138,7 @@ export class WebpackMockPlugin {
const resolvedMocks: ResolvedMock[] = [];
for (const mock of mocks) {
try {
- const { absolutePath, redirectPath } = resolveMock(
- mock.path,
- compiler.context,
- previewConfigPath
- );
+ const { absolutePath, redirectPath } = mock;
let replacementResource: string;
@@ -139,15 +147,14 @@ export class WebpackMockPlugin {
replacementResource = redirectPath;
} else {
// No `__mocks__` file found. Use our custom loader to automock the module.
- const loaderPath = require.resolve(
- 'storybook/internal/core-server/presets/webpack/loaders/webpack-automock-loader'
+ const loaderPath = fileURLToPath(
+ import.meta.resolve('storybook/webpack/loaders/webpack-automock-loader')
);
replacementResource = `${loaderPath}?spy=${mock.spy}!${absolutePath}`;
}
resolvedMocks.push({
...mock,
- absolutePath,
replacementResource,
});
} catch (e) {
diff --git a/code/core/src/core-server/standalone.ts b/code/core/src/core-server/standalone.ts
index 581a6dcc6166..00a058434e54 100644
--- a/code/core/src/core-server/standalone.ts
+++ b/code/core/src/core-server/standalone.ts
@@ -1,13 +1,13 @@
-import { dirname } from 'node:path';
-
import { buildDevStandalone } from './build-dev';
import { buildIndexStandalone } from './build-index';
import { buildStaticStandalone } from './build-static';
async function build(options: any = {}, frameworkOptions: any = {}) {
const { mode = 'dev' } = options;
- const packageJsonDir = dirname(require.resolve('storybook/internal/package.json'));
- const packageJson = JSON.parse(require('fs').readFileSync(`${packageJsonDir}/package.json`));
+
+ const { default: packageJson } = await import('storybook/package.json', {
+ with: { type: 'json' },
+ });
const commonOptions = {
...options,
diff --git a/code/core/src/core-server/utils/StoryIndexGenerator.test.ts b/code/core/src/core-server/utils/StoryIndexGenerator.test.ts
index 777dc6982bb3..f366f1e8ac74 100644
--- a/code/core/src/core-server/utils/StoryIndexGenerator.test.ts
+++ b/code/core/src/core-server/utils/StoryIndexGenerator.test.ts
@@ -12,6 +12,13 @@ import { csfIndexer } from '../presets/common-preset';
import type { StoryIndexGeneratorOptions } from './StoryIndexGenerator';
import { StoryIndexGenerator } from './StoryIndexGenerator';
+vi.mock('../utils/constants', () => {
+ return {
+ defaultStaticDirs: [{ from: './from', to: './to' }],
+ defaultFavicon: './favicon.svg',
+ };
+});
+
vi.mock('storybook/internal/csf', async (importOriginal) => {
const csf = await importOriginal();
return {
diff --git a/code/core/src/core-server/utils/StoryIndexGenerator.ts b/code/core/src/core-server/utils/StoryIndexGenerator.ts
index ccf1221f4853..64841b4727a4 100644
--- a/code/core/src/core-server/utils/StoryIndexGenerator.ts
+++ b/code/core/src/core-server/utils/StoryIndexGenerator.ts
@@ -593,7 +593,7 @@ export class StoryIndexGenerator {
if (err && (err as { source: any }).source?.match(/mdast-util-mdx-jsx/g)) {
logger.warn(
`💡 This seems to be an MDX2 syntax error. Please refer to the MDX section in the following resource for assistance on how to fix this: ${picocolors.yellow(
- 'https://storybook.js.org/migration-guides/7.0'
+ 'https://storybook.js.org/docs/7/migration-guide?ref=error'
)}`
);
}
diff --git a/code/core/src/core-server/utils/__tests__/remove-mdx-stories.test.ts b/code/core/src/core-server/utils/__tests__/remove-mdx-stories.test.ts
index 3f8258f012f3..53fde3953412 100644
--- a/code/core/src/core-server/utils/__tests__/remove-mdx-stories.test.ts
+++ b/code/core/src/core-server/utils/__tests__/remove-mdx-stories.test.ts
@@ -56,7 +56,7 @@ Your main configuration somehow does not contain a 'stories' field, or it resolv
Please check your main configuration file and make sure it exports a 'stories' field that is not an empty array.
-More info: https://storybook.js.org/docs/faq#can-i-have-a-storybook-with-no-local-stories
+More info: https://storybook.js.org/docs/faq?ref=error#can-i-have-a-storybook-with-no-local-stories
]
`);
});
diff --git a/code/core/src/core-server/utils/constants.ts b/code/core/src/core-server/utils/constants.ts
index bd38053cef0a..a1586b8004e7 100644
--- a/code/core/src/core-server/utils/constants.ts
+++ b/code/core/src/core-server/utils/constants.ts
@@ -1,10 +1,14 @@
-import { dirname, join } from 'node:path';
+import { join } from 'pathe';
+
+import { resolvePackageDir } from '../../shared/utils/module';
export const DEBOUNCE = 100;
export const defaultStaticDirs = [
{
- from: join(dirname(require.resolve('storybook/internal/package.json')), 'assets', 'browser'),
+ from: join(resolvePackageDir('storybook'), 'assets/browser'),
to: '/sb-common-assets',
},
];
+
+export const defaultFavicon = join(resolvePackageDir('storybook'), 'assets/browser/favicon.svg');
diff --git a/code/core/src/core-server/utils/copy-all-static-files.ts b/code/core/src/core-server/utils/copy-all-static-files.ts
index e00c08b8c166..e75ae0af0a8e 100644
--- a/code/core/src/core-server/utils/copy-all-static-files.ts
+++ b/code/core/src/core-server/utils/copy-all-static-files.ts
@@ -24,7 +24,7 @@ export async function copyAllStaticFiles(staticDirs: any[] | undefined, outputDi
}
// Storybook's own files should not be overwritten, so we skip such files if we find them
- const skipPaths = ['index.html', 'iframe.html'].map((f) => join(targetPath, f));
+ const skipPaths = ['index.html', 'iframe.html'].map((f) => join(outputDir, f));
await cp(staticPath, targetPath, {
dereference: true,
preserveTimestamps: true,
@@ -62,7 +62,7 @@ export async function copyAllStaticFilesRelativeToMain(
);
const targetPath = join(outputDir, to);
- const skipPaths = ['index.html', 'iframe.html'].map((f) => join(targetPath, f));
+ const skipPaths = ['index.html', 'iframe.html'].map((f) => join(outputDir, f));
if (!from.includes('node_modules')) {
logger.info(
`=> Copying static files: ${picocolors.cyan(print(from))} at ${picocolors.cyan(print(targetPath))}`
diff --git a/code/core/src/core-server/utils/get-builders.ts b/code/core/src/core-server/utils/get-builders.ts
index 6ffe6250e72b..2c764f77b4e7 100644
--- a/code/core/src/core-server/utils/get-builders.ts
+++ b/code/core/src/core-server/utils/get-builders.ts
@@ -1,32 +1,23 @@
-import { pathToFileURL } from 'node:url';
-
import { MissingBuilderError } from 'storybook/internal/server-errors';
import type { Builder, Options } from 'storybook/internal/types';
+import { importModule } from '../../shared/utils/module';
+
export async function getManagerBuilder(): Promise> {
- return import('storybook/internal/builder-manager');
+ return await import('../../builder-manager/index');
}
-export async function getPreviewBuilder(
- builderName: string,
- configDir: string
-): Promise> {
- const builderPackage = require.resolve(
- ['webpack5'].includes(builderName) ? `@storybook/builder-${builderName}` : builderName,
- { paths: [configDir] }
- );
- const previewBuilder = await import(pathToFileURL(builderPackage).href);
- return previewBuilder;
+export async function getPreviewBuilder(resolvedPreviewBuilder: string): Promise> {
+ return await importModule(resolvedPreviewBuilder);
}
-export async function getBuilders({ presets, configDir }: Options): Promise[]> {
+export async function getBuilders({ presets }: Options): Promise[]> {
const { builder } = await presets.apply('core', {});
-
if (!builder) {
throw new MissingBuilderError();
}
- const builderName = typeof builder === 'string' ? builder : builder.name;
+ const resolvedPreviewBuilder = typeof builder === 'string' ? builder : builder.name;
- return Promise.all([getPreviewBuilder(builderName, configDir), getManagerBuilder()]);
+ return Promise.all([getPreviewBuilder(resolvedPreviewBuilder), getManagerBuilder()]);
}
diff --git a/code/core/src/core-server/utils/middleware.ts b/code/core/src/core-server/utils/middleware.ts
index ffcf5e42dc64..702d862472b1 100644
--- a/code/core/src/core-server/utils/middleware.ts
+++ b/code/core/src/core-server/utils/middleware.ts
@@ -1,20 +1,18 @@
import { existsSync } from 'node:fs';
-import { resolve } from 'node:path';
+
+import { resolve } from 'pathe';
const fileExists = (basename: string) =>
- ['.js', '.cjs'].reduce((found: string, ext: string) => {
+ ['.js', '.mjs', '.cjs'].reduce((found: string, ext: string) => {
const filename = `${basename}${ext}`;
return !found && existsSync(filename) ? filename : found;
}, '');
-export function getMiddleware(configDir: string) {
+export async function getMiddleware(configDir: string) {
const middlewarePath = fileExists(resolve(configDir, 'middleware'));
if (middlewarePath) {
- let middlewareModule = require(middlewarePath);
- if (middlewareModule.__esModule) {
- middlewareModule = middlewareModule.default;
- }
- return middlewareModule;
+ const middlewareModule = await import(middlewarePath);
+ return middlewareModule.default ?? middlewareModule;
}
return () => {};
}
diff --git a/code/core/src/core-server/utils/server-statics.ts b/code/core/src/core-server/utils/server-statics.ts
index c4cdc34ce04b..c7b567bc0593 100644
--- a/code/core/src/core-server/utils/server-statics.ts
+++ b/code/core/src/core-server/utils/server-statics.ts
@@ -11,6 +11,8 @@ import type { Polka } from 'polka';
import sirv from 'sirv';
import { dedent } from 'ts-dedent';
+import { resolvePackageDir } from '../../shared/utils/module';
+
const cacheDir = resolvePathInStorybookCache('', 'ignored-sub').split('ignored-sub')[0];
const files = new Map();
@@ -26,7 +28,7 @@ const readFileOnce = async (path: string) => {
};
const faviconWrapperPath = join(
- dirname(require.resolve('storybook/internal/package.json')),
+ resolvePackageDir('storybook'),
'/assets/browser/favicon-wrapper.svg'
);
diff --git a/code/core/src/core-server/utils/stories-json.test.ts b/code/core/src/core-server/utils/stories-json.test.ts
index 5920551c8455..6f5907f2c38c 100644
--- a/code/core/src/core-server/utils/stories-json.test.ts
+++ b/code/core/src/core-server/utils/stories-json.test.ts
@@ -19,6 +19,13 @@ vi.mock('watchpack');
vi.mock('es-toolkit/compat');
vi.mock('storybook/internal/node-logger');
+vi.mock('../utils/constants', () => {
+ return {
+ defaultStaticDirs: [{ from: './from', to: './to' }],
+ defaultFavicon: './favicon.svg',
+ };
+});
+
const workingDir = join(__dirname, '__mockdata__');
const normalizedStories = [
normalizeStoriesEntry(
diff --git a/code/core/src/core-server/utils/whats-new.ts b/code/core/src/core-server/utils/whats-new.ts
index f3acbd3f2bec..c63bf60086cc 100644
--- a/code/core/src/core-server/utils/whats-new.ts
+++ b/code/core/src/core-server/utils/whats-new.ts
@@ -56,7 +56,7 @@ export function initializeWhatsNew(
throw response;
})) as WhatsNewResponse;
- const main = await loadMainConfig({ configDir: options.configDir, noCache: true });
+ const main = await loadMainConfig({ configDir: options.configDir });
const disableWhatsNewNotifications =
(main.core as CoreConfig)?.disableWhatsNewNotifications === true;
diff --git a/code/core/src/csf-tools/CsfFile.test.ts b/code/core/src/csf-tools/CsfFile.test.ts
index e269c804f73a..4299b8d586ff 100644
--- a/code/core/src/csf-tools/CsfFile.test.ts
+++ b/code/core/src/csf-tools/CsfFile.test.ts
@@ -2513,7 +2513,7 @@ describe('CsfFile', () => {
).toThrowErrorMatchingInlineSnapshot(`
[MultipleMetaError: CSF: multiple meta objects (line 4, col 24)
- More info: https://storybook.js.org/docs/writing-stories#default-export]
+ More info: https://storybook.js.org/docs/writing-stories#default-export?ref=error]
`);
});
@@ -2531,7 +2531,7 @@ describe('CsfFile', () => {
).toThrowErrorMatchingInlineSnapshot(`
[MultipleMetaError: CSF: multiple meta objects (line 3, col 25)
- More info: https://storybook.js.org/docs/writing-stories#default-export]
+ More info: https://storybook.js.org/docs/writing-stories#default-export?ref=error]
`);
});
@@ -2549,7 +2549,7 @@ describe('CsfFile', () => {
).toThrowErrorMatchingInlineSnapshot(`
[MultipleMetaError: CSF: multiple meta objects
- More info: https://storybook.js.org/docs/writing-stories#default-export]
+ More info: https://storybook.js.org/docs/writing-stories#default-export?ref=error]
`);
});
@@ -2565,7 +2565,7 @@ describe('CsfFile', () => {
).toThrowErrorMatchingInlineSnapshot(`
[BadMetaError: CSF: meta() factory must be imported from .storybook/preview configuration (line 1, col 0)
- More info: https://storybook.js.org/docs/writing-stories#default-export]
+ More info: https://storybook.js.org/docs/writing-stories#default-export?ref=error]
`);
});
@@ -2582,7 +2582,7 @@ describe('CsfFile', () => {
).toThrowErrorMatchingInlineSnapshot(`
[BadMetaError: CSF: meta() factory must be imported from .storybook/preview configuration (line 4, col 28)
- More info: https://storybook.js.org/docs/writing-stories#default-export]
+ More info: https://storybook.js.org/docs/writing-stories#default-export?ref=error]
`);
});
@@ -2599,7 +2599,7 @@ describe('CsfFile', () => {
).toThrowErrorMatchingInlineSnapshot(`
[MixedFactoryError: CSF: expected factory story (line 4, col 17)
- More info: https://storybook.js.org/docs/writing-stories#default-export]
+ More info: https://storybook.js.org/docs/writing-stories#default-export?ref=error]
`);
});
@@ -2616,7 +2616,7 @@ describe('CsfFile', () => {
).toThrowErrorMatchingInlineSnapshot(`
[MixedFactoryError: CSF: expected non-factory story (line 4, col 28)
- More info: https://storybook.js.org/docs/writing-stories#default-export]
+ More info: https://storybook.js.org/docs/writing-stories#default-export?ref=error]
`);
});
});
diff --git a/code/core/src/csf-tools/CsfFile.ts b/code/core/src/csf-tools/CsfFile.ts
index 50e5e5fd3c8a..7e88d731e6e9 100644
--- a/code/core/src/csf-tools/CsfFile.ts
+++ b/code/core/src/csf-tools/CsfFile.ts
@@ -181,7 +181,7 @@ export class NoMetaError extends Error {
super(dedent`
CSF: ${msg} ${formatLocation(ast, fileName)}
- More info: https://storybook.js.org/docs/writing-stories#default-export
+ More info: https://storybook.js.org/docs/writing-stories#default-export?ref=error
`);
this.name = this.constructor.name;
}
@@ -193,7 +193,7 @@ export class MultipleMetaError extends Error {
super(dedent`
CSF: ${message} ${formatLocation(ast, fileName)}
- More info: https://storybook.js.org/docs/writing-stories#default-export
+ More info: https://storybook.js.org/docs/writing-stories#default-export?ref=error
`);
this.name = this.constructor.name;
}
@@ -205,7 +205,7 @@ export class MixedFactoryError extends Error {
super(dedent`
CSF: ${message} ${formatLocation(ast, fileName)}
- More info: https://storybook.js.org/docs/writing-stories#default-export
+ More info: https://storybook.js.org/docs/writing-stories#default-export?ref=error
`);
this.name = this.constructor.name;
}
@@ -217,7 +217,7 @@ export class BadMetaError extends Error {
super(dedent`
CSF: ${message} ${formatLocation(ast, fileName)}
- More info: https://storybook.js.org/docs/writing-stories#default-export
+ More info: https://storybook.js.org/docs/writing-stories#default-export?ref=error
`);
this.name = this.constructor.name;
}
diff --git a/code/core/src/docs-tools/argTypes/convert/convert.test.ts b/code/core/src/docs-tools/argTypes/convert/convert.test.ts
index dc7e68390983..354a4779324b 100644
--- a/code/core/src/docs-tools/argTypes/convert/convert.test.ts
+++ b/code/core/src/docs-tools/argTypes/convert/convert.test.ts
@@ -4,7 +4,7 @@ import { describe, expect, it } from 'vitest';
import { transformSync } from 'storybook/internal/babel';
-import { mapValues } from 'es-toolkit';
+import { mapValues } from 'es-toolkit/object';
import requireFromString from 'require-from-string';
import { normalizeNewlines } from '../utils';
diff --git a/code/core/src/docs-tools/argTypes/convert/proptypes/convert.ts b/code/core/src/docs-tools/argTypes/convert/proptypes/convert.ts
index 33df39b9cb48..feaac8cdbd7c 100644
--- a/code/core/src/docs-tools/argTypes/convert/proptypes/convert.ts
+++ b/code/core/src/docs-tools/argTypes/convert/proptypes/convert.ts
@@ -1,6 +1,6 @@
import type { SBType } from 'storybook/internal/types';
-import { mapValues } from 'es-toolkit';
+import { mapValues } from 'es-toolkit/object';
import { parseLiteral } from '../utils';
import type { PTType } from './types';
diff --git a/code/core/src/index.ts b/code/core/src/index.ts
deleted file mode 100644
index 15e1677a3050..000000000000
--- a/code/core/src/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export {};
-
-console.log('This file should remain unused until we have figured out the best API');
diff --git a/code/core/src/manager-api/lib/merge.ts b/code/core/src/manager-api/lib/merge.ts
index 9490e82891a1..4663af3abef0 100644
--- a/code/core/src/manager-api/lib/merge.ts
+++ b/code/core/src/manager-api/lib/merge.ts
@@ -1,6 +1,7 @@
import { logger } from 'storybook/internal/client-logger';
-import { isEqual, mergeWith, omitBy, pick } from 'es-toolkit';
+import { mergeWith } from 'es-toolkit/object';
+import { isEqual } from 'es-toolkit/predicate';
export default (a: TObj, ...b: Partial[]): TObj => {
// start with empty object
diff --git a/code/core/src/manager-api/lib/stories.ts b/code/core/src/manager-api/lib/stories.ts
index 5b21256dc0ec..3eed9e500890 100644
--- a/code/core/src/manager-api/lib/stories.ts
+++ b/code/core/src/manager-api/lib/stories.ts
@@ -21,7 +21,8 @@ import type {
Tag,
} from 'storybook/internal/types';
-import { countBy, mapValues } from 'es-toolkit';
+import { countBy } from 'es-toolkit/array';
+import { mapValues } from 'es-toolkit/object';
import memoize from 'memoizerific';
import { dedent } from 'ts-dedent';
diff --git a/code/core/src/manager-api/modules/layout.ts b/code/core/src/manager-api/modules/layout.ts
index 1af2f6a84e0f..25ace974951c 100644
--- a/code/core/src/manager-api/modules/layout.ts
+++ b/code/core/src/manager-api/modules/layout.ts
@@ -8,7 +8,8 @@ import type {
import { global } from '@storybook/global';
-import { isEqual as deepEqual, pick, toMerged } from 'es-toolkit';
+import { pick, toMerged } from 'es-toolkit/object';
+import { isEqual as deepEqual } from 'es-toolkit/predicate';
import type { ThemeVars } from 'storybook/theming';
import { create } from 'storybook/theming/create';
diff --git a/code/core/src/manager-api/modules/notifications.ts b/code/core/src/manager-api/modules/notifications.ts
index 31431b684dd2..14d876274bdd 100644
--- a/code/core/src/manager-api/modules/notifications.ts
+++ b/code/core/src/manager-api/modules/notifications.ts
@@ -1,6 +1,6 @@
import type { API_Notification } from 'storybook/internal/types';
-import { partition } from 'es-toolkit';
+import { partition } from 'es-toolkit/array';
import type { ModuleFn } from '../lib/types';
diff --git a/code/core/src/manager-api/root.tsx b/code/core/src/manager-api/root.tsx
index 625e58b66ea4..f5fa4eea2532 100644
--- a/code/core/src/manager-api/root.tsx
+++ b/code/core/src/manager-api/root.tsx
@@ -42,7 +42,7 @@ import type {
StoryId,
} from 'storybook/internal/types';
-import { isEqual } from 'es-toolkit';
+import { isEqual } from 'es-toolkit/predicate';
import { createContext } from './context';
import getInitialState from './initial-state';
diff --git a/code/core/src/manager/components/mobile/about/MobileAbout.tsx b/code/core/src/manager/components/mobile/about/MobileAbout.tsx
index 03edc8beae1c..c117a5410350 100644
--- a/code/core/src/manager/components/mobile/about/MobileAbout.tsx
+++ b/code/core/src/manager/components/mobile/about/MobileAbout.tsx
@@ -40,7 +40,7 @@ export const MobileAbout: FC = () => {
diff --git a/code/core/src/manager/components/mobile/navigation/MobileNavigation.stories.tsx b/code/core/src/manager/components/mobile/navigation/MobileNavigation.stories.tsx
index f5f18761fb95..74bfdc9bfb6f 100644
--- a/code/core/src/manager/components/mobile/navigation/MobileNavigation.stories.tsx
+++ b/code/core/src/manager/components/mobile/navigation/MobileNavigation.stories.tsx
@@ -2,7 +2,7 @@ import React from 'react';
import type { Meta, StoryObj } from '@storybook/react-vite';
-import { startCase } from 'es-toolkit';
+import { startCase } from 'es-toolkit/string';
import { ManagerContext } from 'storybook/manager-api';
import { within } from 'storybook/test';
diff --git a/code/core/src/manager/components/panel/Panel.tsx b/code/core/src/manager/components/panel/Panel.tsx
index 4af4608a69df..5bc1993603ae 100644
--- a/code/core/src/manager/components/panel/Panel.tsx
+++ b/code/core/src/manager/components/panel/Panel.tsx
@@ -75,7 +75,7 @@ export const AddonPanel = React.memo<{
>
}
footer={
-
+
Explore integrations catalog
}
diff --git a/code/core/src/manager/components/sidebar/RefBlocks.tsx b/code/core/src/manager/components/sidebar/RefBlocks.tsx
index 64f45b091b99..96a8eb4e468e 100644
--- a/code/core/src/manager/components/sidebar/RefBlocks.tsx
+++ b/code/core/src/manager/components/sidebar/RefBlocks.tsx
@@ -120,7 +120,7 @@ export const ErrorBlock: FC<{ error: Error }> = ({ error }) => (
View error
{' '}
-
+
View docs
diff --git a/code/core/src/manager/components/sidebar/RefIndicator.tsx b/code/core/src/manager/components/sidebar/RefIndicator.tsx
index ae8c6d010220..c7259181b29d 100644
--- a/code/core/src/manager/components/sidebar/RefIndicator.tsx
+++ b/code/core/src/manager/components/sidebar/RefIndicator.tsx
@@ -308,7 +308,10 @@ const ReadDocsMessage: FC = () => {
const theme = useTheme();
return (
-
+
Reduce lag
diff --git a/code/core/src/manager/components/sidebar/SidebarBottom.stories.tsx b/code/core/src/manager/components/sidebar/SidebarBottom.stories.tsx
index 60c80943ca79..b5aeff174191 100644
--- a/code/core/src/manager/components/sidebar/SidebarBottom.stories.tsx
+++ b/code/core/src/manager/components/sidebar/SidebarBottom.stories.tsx
@@ -132,6 +132,12 @@ export const Both: Story = {
};
export const DynamicHeight: Story = {
+ // do not test in chromatic
+ parameters: {
+ chromatic: {
+ disable: true,
+ },
+ },
args: {
registeredTestProviders: {
'dynamic-height': {
diff --git a/code/core/src/manager/components/sidebar/useExpanded.ts b/code/core/src/manager/components/sidebar/useExpanded.ts
index c9f5282b2eaf..bc6b3ebd630f 100644
--- a/code/core/src/manager/components/sidebar/useExpanded.ts
+++ b/code/core/src/manager/components/sidebar/useExpanded.ts
@@ -5,7 +5,7 @@ import { STORIES_COLLAPSE_ALL, STORIES_EXPAND_ALL } from 'storybook/internal/cor
import { global } from '@storybook/global';
-import { throttle } from 'es-toolkit';
+import { throttle } from 'es-toolkit/function';
import type { StoriesHash } from 'storybook/manager-api';
import { useStorybookApi } from 'storybook/manager-api';
diff --git a/code/core/src/manager/globals-module-info.ts b/code/core/src/manager/globals-module-info.ts
deleted file mode 100644
index 4bcbf259af79..000000000000
--- a/code/core/src/manager/globals-module-info.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './globals/globals-module-info';
diff --git a/code/core/src/manager/settings/About.tsx b/code/core/src/manager/settings/About.tsx
index e2507eebdfb8..7985a8d4f146 100644
--- a/code/core/src/manager/settings/About.tsx
+++ b/code/core/src/manager/settings/About.tsx
@@ -73,7 +73,7 @@ const AboutScreen: FC<{ onNavigateToWhatsNew?: () => void }> = ({ onNavigateToWh
@@ -67,7 +67,7 @@ export const RightArrow = () =>
@@ -85,7 +85,7 @@ export const RightArrow = () =>
Learn more
@@ -128,7 +128,7 @@ export const RightArrow = () => Publish to Chromatic
Publish your Storybook to review and collaborate with your entire team.
Learn more
@@ -144,7 +144,7 @@ export const RightArrow = () => Embed your stories into Figma to cross-reference the design and live
implementation in one place.
Learn more
@@ -160,7 +160,7 @@ export const RightArrow = () => Use stories to test a component in all its variations, no matter how
complex.
Learn more
@@ -175,7 +175,7 @@ export const RightArrow = () => Accessibility
Automatically test your components for a11y issues as you develop.
Integrate your tools with Storybook to connect workflows.
Discover all addons
@@ -281,7 +281,7 @@ export const RightArrow = () => Follow guided walkthroughs on for key workflows.
Discover tutorials
diff --git a/code/frameworks/nextjs-vite/template/cli/ts/Configure.mdx b/code/frameworks/nextjs-vite/template/cli/ts/Configure.mdx
index 8734a26fe4bc..70fcc2a9777c 100644
--- a/code/frameworks/nextjs-vite/template/cli/ts/Configure.mdx
+++ b/code/frameworks/nextjs-vite/template/cli/ts/Configure.mdx
@@ -52,7 +52,7 @@ export const RightArrow = () => Add styling and CSS
Like with web applications, there are many ways to include CSS within Storybook. Learn more about setting up styling within Storybook.
Learn more
@@ -67,7 +67,7 @@ export const RightArrow = () => Provide context and mocking
Often when a story doesn't render, it's because your component is expecting a specific environment or context (like a theme provider) to be available.
Learn more
@@ -85,7 +85,7 @@ export const RightArrow = () => Learn more
@@ -113,7 +113,7 @@ export const RightArrow = () => Auto-generate living,
interactive reference documentation from your components and stories.
Learn more
@@ -128,7 +128,7 @@ export const RightArrow = () => Publish to Chromatic
Publish your Storybook to review and collaborate with your entire team.
Learn more
@@ -144,7 +144,7 @@ export const RightArrow = () => Embed your stories into Figma to cross-reference the design and live
implementation in one place.
Learn more
@@ -160,7 +160,7 @@ export const RightArrow = () => Use stories to test a component in all its variations, no matter how
complex.
Learn more
@@ -175,7 +175,7 @@ export const RightArrow = () => Accessibility
Automatically test your components for a11y issues as you develop.
Like with web applications, there are many ways to include CSS within Storybook. Learn more about setting up styling within Storybook.
Learn more
@@ -67,7 +67,7 @@ export const RightArrow = () => Provide context and mocking
Often when a story doesn't render, it's because your component is expecting a specific environment or context (like a theme provider) to be available.
Learn more
@@ -85,7 +85,7 @@ export const RightArrow = () => Learn more
@@ -113,7 +113,7 @@ export const RightArrow = () => Auto-generate living,
interactive reference documentation from your components and stories.
Learn more
@@ -128,7 +128,7 @@ export const RightArrow = () => Publish to Chromatic
Publish your Storybook to review and collaborate with your entire team.
Learn more
@@ -144,7 +144,7 @@ export const RightArrow = () => Embed your stories into Figma to cross-reference the design and live
implementation in one place.
Learn more
@@ -160,7 +160,7 @@ export const RightArrow = () => Use stories to test a component in all its variations, no matter how
complex.
Learn more
@@ -175,7 +175,7 @@ export const RightArrow = () => Accessibility
Automatically test your components for a11y issues as you develop.
Integrate your tools with Storybook to connect workflows.
Discover all addons
@@ -281,7 +281,7 @@ export const RightArrow = () => Follow guided walkthroughs on for key workflows.
Discover tutorials
diff --git a/code/frameworks/nextjs/template/cli/ts/Configure.mdx b/code/frameworks/nextjs/template/cli/ts/Configure.mdx
index 8734a26fe4bc..70fcc2a9777c 100644
--- a/code/frameworks/nextjs/template/cli/ts/Configure.mdx
+++ b/code/frameworks/nextjs/template/cli/ts/Configure.mdx
@@ -52,7 +52,7 @@ export const RightArrow = () => Add styling and CSS
Like with web applications, there are many ways to include CSS within Storybook. Learn more about setting up styling within Storybook.
Learn more
@@ -67,7 +67,7 @@ export const RightArrow = () => Provide context and mocking
Often when a story doesn't render, it's because your component is expecting a specific environment or context (like a theme provider) to be available.
Learn more
@@ -85,7 +85,7 @@ export const RightArrow = () => Learn more
@@ -113,7 +113,7 @@ export const RightArrow = () => Auto-generate living,
interactive reference documentation from your components and stories.
Learn more
@@ -128,7 +128,7 @@ export const RightArrow = () => Publish to Chromatic
Publish your Storybook to review and collaborate with your entire team.
Learn more
@@ -144,7 +144,7 @@ export const RightArrow = () => Embed your stories into Figma to cross-reference the design and live
implementation in one place.
Learn more
@@ -160,7 +160,7 @@ export const RightArrow = () => Use stories to test a component in all its variations, no matter how
complex.
Learn more
@@ -175,7 +175,7 @@ export const RightArrow = () => Accessibility
Automatically test your components for a11y issues as you develop.
Integrate your tools with Storybook to connect workflows.
Discover all addons
@@ -281,7 +281,7 @@ export const RightArrow = () => Follow guided walkthroughs on for key workflows.
Discover tutorials
diff --git a/code/frameworks/preact-vite/README.md b/code/frameworks/preact-vite/README.md
index ce6ad2a786e8..90ef8eae35d6 100644
--- a/code/frameworks/preact-vite/README.md
+++ b/code/frameworks/preact-vite/README.md
@@ -1,4 +1,7 @@
# Storybook for Preact & Vite
-See [documentation](https://storybook.js.org/docs/get-started/frameworks/preact-vite?renderer=preact) for installation instructions, usage examples, APIs, and more.
-`;
+Develop, document, and test UI components in isolation.
+
+See [documentation](https://storybook.js.org/docs/get-started/frameworks/preact-vite?renderer=preact&ref=readme) for installation instructions, usage examples, APIs, and more.
+
+Learn more about Storybook at [storybook.js.org](https://storybook.js.org/?ref=readme).
diff --git a/code/frameworks/preact-vite/build-config.ts b/code/frameworks/preact-vite/build-config.ts
new file mode 100644
index 000000000000..167eeec054b8
--- /dev/null
+++ b/code/frameworks/preact-vite/build-config.ts
@@ -0,0 +1,25 @@
+import type { BuildEntries } from '../../../scripts/build/utils/entry-utils';
+
+const config: BuildEntries = {
+ entries: {
+ browser: [
+ {
+ exportEntries: ['.'],
+ entryPoint: './src/index.ts',
+ },
+ ],
+ node: [
+ {
+ exportEntries: ['./preset'],
+ entryPoint: './src/preset.ts',
+ dts: false,
+ },
+ {
+ exportEntries: ['./node'],
+ entryPoint: './src/node/index.ts',
+ },
+ ],
+ },
+};
+
+export default config;
diff --git a/code/frameworks/preact-vite/package.json b/code/frameworks/preact-vite/package.json
index 25571fb892ff..90688291e6c1 100644
--- a/code/frameworks/preact-vite/package.json
+++ b/code/frameworks/preact-vite/package.json
@@ -1,9 +1,14 @@
{
"name": "@storybook/preact-vite",
"version": "9.2.0-alpha.3",
- "description": "Storybook for Preact and Vite: Develop Preact components in isolation with Hot Reloading.",
+ "description": "Storybook for Preact and Vite: Develop, document, and test UI components in isolation",
"keywords": [
- "storybook"
+ "storybook",
+ "storybook-framework",
+ "preact",
+ "vite",
+ "component",
+ "components"
],
"homepage": "https://github.com/storybookjs/storybook/tree/next/code/frameworks/preact-vite",
"bugs": {
@@ -19,27 +24,19 @@
"url": "https://opencollective.com/storybook"
},
"license": "MIT",
+ "type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
- "import": "./dist/index.mjs",
- "require": "./dist/index.js"
- },
- "./preset": {
- "types": "./dist/preset.d.ts",
- "require": "./dist/preset.js"
+ "default": "./dist/index.js"
},
"./node": {
"types": "./dist/node/index.d.ts",
- "node": "./dist/node/index.js",
- "import": "./dist/node/index.mjs",
- "require": "./dist/node/index.js"
+ "default": "./dist/node/index.js"
},
- "./package.json": "./package.json"
+ "./package.json": "./package.json",
+ "./preset": "./dist/preset.js"
},
- "main": "dist/index.js",
- "module": "dist/index.mjs",
- "types": "dist/index.d.ts",
"files": [
"dist/**/*",
"template/cli/**/*",
@@ -50,8 +47,8 @@
"!src/**/*"
],
"scripts": {
- "check": "jiti ../../../scripts/prepare/check.ts",
- "prep": "jiti ../../../scripts/prepare/bundle.ts"
+ "check": "jiti ../../../scripts/check/check-package.ts",
+ "prep": "jiti ../../../scripts/build/build-package.ts"
},
"dependencies": {
"@storybook/builder-vite": "workspace:*",
@@ -60,24 +57,14 @@
"devDependencies": {
"@types/node": "^22.0.0",
"typescript": "^5.8.3",
- "vite": "^6.2.5"
+ "vite": "^7.0.4"
},
"peerDependencies": {
"preact": ">=10",
"storybook": "workspace:^"
},
- "engines": {
- "node": ">=20.0.0"
- },
"publishConfig": {
"access": "public"
},
- "bundler": {
- "entries": [
- "./src/index.ts",
- "./src/preset.ts"
- ],
- "platform": "node"
- },
- "gitHead": "ce6a1e4a8d5ad69c699021a0b183df89cfc7b684"
+ "gitHead": "a8e7fd8a655c69780bc20b9749d2699e45beae16"
}
diff --git a/code/frameworks/preact-vite/preset.js b/code/frameworks/preact-vite/preset.js
index a83f95279e7f..4bd63d324002 100644
--- a/code/frameworks/preact-vite/preset.js
+++ b/code/frameworks/preact-vite/preset.js
@@ -1 +1 @@
-module.exports = require('./dist/preset');
+export * from './dist/preset.js';
diff --git a/code/frameworks/preact-vite/src/preset.ts b/code/frameworks/preact-vite/src/preset.ts
index 18d7c604cf87..f02d5b10c3f9 100644
--- a/code/frameworks/preact-vite/src/preset.ts
+++ b/code/frameworks/preact-vite/src/preset.ts
@@ -1,15 +1,8 @@
-import { dirname, join } from 'node:path';
-
-import type { PresetProperty } from 'storybook/internal/types';
-
import type { StorybookConfig } from './types';
-const getAbsolutePath = (input: I): I =>
- dirname(require.resolve(join(input, 'package.json'))) as any;
-
-export const core: PresetProperty<'core'> = {
- builder: getAbsolutePath('@storybook/builder-vite'),
- renderer: getAbsolutePath('@storybook/preact'),
+export const core: StorybookConfig['core'] = {
+ builder: import.meta.resolve('@storybook/builder-vite'),
+ renderer: import.meta.resolve('@storybook/preact/preset'),
};
export const viteFinal: StorybookConfig['viteFinal'] = async (config) => {
diff --git a/code/frameworks/preact-vite/tsconfig.json b/code/frameworks/preact-vite/tsconfig.json
index e342f997283b..c749496d9a6e 100644
--- a/code/frameworks/preact-vite/tsconfig.json
+++ b/code/frameworks/preact-vite/tsconfig.json
@@ -1,8 +1,6 @@
{
"compilerOptions": {
"baseUrl": ".",
- "jsx": "react-jsx",
- "jsxImportSource": "preact",
"paths": {
"storybook/internal/*": ["../../lib/cli/core/*"]
},
diff --git a/code/frameworks/react-native-web-vite/README.md b/code/frameworks/react-native-web-vite/README.md
index 5bdae8f819c0..7ff0ba99ecc3 100644
--- a/code/frameworks/react-native-web-vite/README.md
+++ b/code/frameworks/react-native-web-vite/README.md
@@ -1,3 +1,5 @@
# Storybook for React Native Web & Vite
-See [documentation](https://storybook.js.org/docs/get-started/frameworks/react-native-web-vite?renderer=react-native-web) for installation instructions, usage examples, APIs, and more.
+See [documentation](https://storybook.js.org/docs/get-started/frameworks/react-native-web-vite?renderer=react-native-web&ref=readme) for installation instructions, usage examples, APIs, and more.
+
+Learn more about Storybook at [storybook.js.org](https://storybook.js.org/?ref=readme).
diff --git a/code/frameworks/react-native-web-vite/build-config.ts b/code/frameworks/react-native-web-vite/build-config.ts
new file mode 100644
index 000000000000..ff507cf8efcb
--- /dev/null
+++ b/code/frameworks/react-native-web-vite/build-config.ts
@@ -0,0 +1,30 @@
+import type { BuildEntries } from '../../../scripts/build/utils/entry-utils';
+
+const config: BuildEntries = {
+ entries: {
+ browser: [
+ {
+ exportEntries: ['.'],
+ entryPoint: './src/index.ts',
+ },
+ ],
+ node: [
+ {
+ exportEntries: ['./node'],
+ entryPoint: './src/node/index.ts',
+ },
+ {
+ exportEntries: ['./preset'],
+ entryPoint: './src/preset.ts',
+ dts: false,
+ },
+ {
+ exportEntries: ['./vite-plugin'],
+ entryPoint: './src/vite-plugin.ts',
+ dts: false,
+ },
+ ],
+ },
+};
+
+export default config;
diff --git a/code/frameworks/react-native-web-vite/package.json b/code/frameworks/react-native-web-vite/package.json
index bedf130edb29..923d54ab872c 100644
--- a/code/frameworks/react-native-web-vite/package.json
+++ b/code/frameworks/react-native-web-vite/package.json
@@ -1,9 +1,16 @@
{
"name": "@storybook/react-native-web-vite",
"version": "9.2.0-alpha.3",
- "description": "Develop react-native components an isolated web environment with hot reloading.",
+ "description": "Storybook for React Native Web and Vite: Develop, document, and test UI components in isolation",
"keywords": [
- "storybook"
+ "storybook",
+ "react-native",
+ "react-native-web",
+ "expo",
+ "expo-web",
+ "vite",
+ "component",
+ "components"
],
"homepage": "https://github.com/storybookjs/storybook/tree/next/code/frameworks/react-native-web-vite",
"bugs": {
@@ -19,33 +26,20 @@
"url": "https://opencollective.com/storybook"
},
"license": "MIT",
+ "type": "module",
"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"
- },
- "./vite-plugin": {
- "types": "./dist/vite-plugin.d.ts",
- "import": "./dist/vite-plugin.mjs",
- "require": "./dist/vite-plugin.js"
+ "default": "./dist/index.js"
},
"./node": {
"types": "./dist/node/index.d.ts",
- "node": "./dist/node/index.js",
- "import": "./dist/node/index.mjs",
- "require": "./dist/node/index.js"
+ "default": "./dist/node/index.js"
},
- "./package.json": "./package.json"
+ "./package.json": "./package.json",
+ "./preset": "./dist/preset.js",
+ "./vite-plugin": "./dist/vite-plugin.js"
},
- "main": "dist/index.js",
- "module": "dist/index.mjs",
- "types": "dist/index.d.ts",
"files": [
"dist/**/*",
"template/cli/**/*",
@@ -55,8 +49,8 @@
"!src/**/*"
],
"scripts": {
- "check": "jiti ../../../scripts/prepare/check.ts",
- "prep": "jiti ../../../scripts/prepare/bundle.ts"
+ "check": "jiti ../../../scripts/check/check-package.ts",
+ "prep": "jiti ../../../scripts/build/build-package.ts"
},
"dependencies": {
"@storybook/builder-vite": "workspace:*",
@@ -70,27 +64,15 @@
"typescript": "^5.8.3"
},
"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": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-native": ">=0.74.5",
"react-native-web": "^0.19.12 || ^0.20.0",
"storybook": "workspace:^",
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0"
},
- "engines": {
- "node": ">=20.0.0"
- },
"publishConfig": {
"access": "public"
},
- "bundler": {
- "entries": [
- "./src/index.ts",
- "./src/preset.ts",
- "./src/vite-plugin.ts",
- "./src/node/index.ts"
- ],
- "platform": "node"
- },
- "gitHead": "ce6a1e4a8d5ad69c699021a0b183df89cfc7b684"
+ "gitHead": "a8e7fd8a655c69780bc20b9749d2699e45beae16"
}
diff --git a/code/frameworks/react-native-web-vite/preset.js b/code/frameworks/react-native-web-vite/preset.js
index a83f95279e7f..4bd63d324002 100644
--- a/code/frameworks/react-native-web-vite/preset.js
+++ b/code/frameworks/react-native-web-vite/preset.js
@@ -1 +1 @@
-module.exports = require('./dist/preset');
+export * from './dist/preset.js';
diff --git a/code/frameworks/react-native-web-vite/src/preset.ts b/code/frameworks/react-native-web-vite/src/preset.ts
index 46256bf01278..84edddc887a8 100644
--- a/code/frameworks/react-native-web-vite/src/preset.ts
+++ b/code/frameworks/react-native-web-vite/src/preset.ts
@@ -49,6 +49,6 @@ export const viteFinal: StorybookConfig['viteFinal'] = async (config, options) =
};
export const core = {
- builder: '@storybook/builder-vite',
- renderer: '@storybook/react',
+ builder: import.meta.resolve('@storybook/builder-vite'),
+ renderer: import.meta.resolve('@storybook/react/preset'),
};
diff --git a/code/frameworks/react-vite/README.md b/code/frameworks/react-vite/README.md
index c6b6e6abf0a4..c8e34f8aa0be 100644
--- a/code/frameworks/react-vite/README.md
+++ b/code/frameworks/react-vite/README.md
@@ -1,3 +1,7 @@
# 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.
+Develop, document, and test UI components in isolation.
+
+See [documentation](https://storybook.js.org/docs/get-started/frameworks/react-vite?renderer=react&ref=readme) for installation instructions, usage examples, APIs, and more.
+
+Learn more about Storybook at [storybook.js.org](https://storybook.js.org/?ref=readme).
diff --git a/code/frameworks/react-vite/build-config.ts b/code/frameworks/react-vite/build-config.ts
new file mode 100644
index 000000000000..7bf960e4db09
--- /dev/null
+++ b/code/frameworks/react-vite/build-config.ts
@@ -0,0 +1,24 @@
+import type { BuildEntries } from '../../../scripts/build/utils/entry-utils';
+
+const config: BuildEntries = {
+ entries: {
+ browser: [
+ {
+ exportEntries: ['.'],
+ entryPoint: './src/index.ts',
+ },
+ ],
+ node: [
+ {
+ exportEntries: ['./preset'],
+ entryPoint: './src/preset.ts',
+ },
+ {
+ exportEntries: ['./node'],
+ entryPoint: './src/node/index.ts',
+ },
+ ],
+ },
+};
+
+export default config;
diff --git a/code/frameworks/react-vite/package.json b/code/frameworks/react-vite/package.json
index d382c1d72bb6..7423dcc3bc0c 100644
--- a/code/frameworks/react-vite/package.json
+++ b/code/frameworks/react-vite/package.json
@@ -1,9 +1,14 @@
{
"name": "@storybook/react-vite",
"version": "9.2.0-alpha.3",
- "description": "Storybook for React and Vite: Develop React components in isolation with Hot Reloading.",
+ "description": "Storybook for React and Vite: Develop, document, and test UI components in isolation",
"keywords": [
- "storybook"
+ "storybook",
+ "storybook-framework",
+ "react",
+ "vite",
+ "component",
+ "components"
],
"homepage": "https://github.com/storybookjs/storybook/tree/next/code/frameworks/react-vite",
"bugs": {
@@ -19,39 +24,20 @@
"url": "https://opencollective.com/storybook"
},
"license": "MIT",
+ "type": "module",
"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"
+ "default": "./dist/index.js"
},
"./node": {
"types": "./dist/node/index.d.ts",
- "node": "./dist/node/index.js",
- "import": "./dist/node/index.mjs",
- "require": "./dist/node/index.js"
+ "default": "./dist/node/index.js"
},
- "./package.json": "./package.json"
- },
- "main": "dist/index.js",
- "module": "dist/index.mjs",
- "types": "dist/index.d.ts",
- "typesVersions": {
- "*": {
- "*": [
- "dist/index.d.ts"
- ],
- "preset": [
- "dist/preset.d.ts"
- ],
- "node": [
- "dist/node/index.d.ts"
- ]
+ "./package.json": "./package.json",
+ "./preset": {
+ "types": "./dist/preset.d.ts",
+ "default": "./dist/preset.js"
}
},
"files": [
@@ -63,8 +49,8 @@
"!src/**/*"
],
"scripts": {
- "check": "jiti ../../../scripts/prepare/check.ts",
- "prep": "jiti ../../../scripts/prepare/bundle.ts"
+ "check": "jiti ../../../scripts/check/check-package.ts",
+ "prep": "jiti ../../../scripts/build/build-package.ts"
},
"dependencies": {
"@joshwooding/vite-plugin-react-docgen-typescript": "0.6.1",
@@ -80,27 +66,16 @@
"devDependencies": {
"@types/node": "^22.0.0",
"typescript": "^5.8.3",
- "vite": "^6.2.5"
+ "vite": "^7.0.4"
},
"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": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"storybook": "workspace:^",
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0"
},
- "engines": {
- "node": ">=20.0.0"
- },
"publishConfig": {
"access": "public"
},
- "bundler": {
- "entries": [
- "./src/index.ts",
- "./src/preset.ts",
- "./src/node/index.ts"
- ],
- "platform": "node"
- },
- "gitHead": "ce6a1e4a8d5ad69c699021a0b183df89cfc7b684"
+ "gitHead": "a8e7fd8a655c69780bc20b9749d2699e45beae16"
}
diff --git a/code/frameworks/react-vite/preset.js b/code/frameworks/react-vite/preset.js
index a83f95279e7f..4bd63d324002 100644
--- a/code/frameworks/react-vite/preset.js
+++ b/code/frameworks/react-vite/preset.js
@@ -1 +1 @@
-module.exports = require('./dist/preset');
+export * from './dist/preset.js';
diff --git a/code/frameworks/react-vite/src/preset.ts b/code/frameworks/react-vite/src/preset.ts
index 593fac66a5db..8046ab82c197 100644
--- a/code/frameworks/react-vite/src/preset.ts
+++ b/code/frameworks/react-vite/src/preset.ts
@@ -1,15 +1,10 @@
-import { dirname, join } from 'node:path';
-
import type { PresetProperty } from 'storybook/internal/types';
import type { StorybookConfig } from './types';
-const getAbsolutePath = (input: I): I =>
- dirname(require.resolve(join(input, 'package.json'))) as any;
-
export const core: PresetProperty<'core'> = {
- builder: getAbsolutePath('@storybook/builder-vite'),
- renderer: getAbsolutePath('@storybook/react'),
+ builder: import.meta.resolve('@storybook/builder-vite'),
+ renderer: import.meta.resolve('@storybook/react/preset'),
};
export const viteFinal: NonNullable = async (config, { presets }) => {
@@ -23,7 +18,7 @@ export const viteFinal: NonNullable = async (confi
let typescriptPresent;
try {
- require.resolve('typescript');
+ import.meta.resolve('typescript');
typescriptPresent = true;
} catch (e) {
typescriptPresent = false;
@@ -31,7 +26,7 @@ export const viteFinal: NonNullable = async (confi
if (reactDocgenOption === 'react-docgen-typescript' && typescriptPresent) {
plugins.push(
- require('@joshwooding/vite-plugin-react-docgen-typescript')({
+ (await import('@joshwooding/vite-plugin-react-docgen-typescript')).default({
...reactDocgenTypescriptOptions,
// We *need* this set so that RDT returns default values in the same format as react-docgen
savePropValueAsString: true,
diff --git a/code/frameworks/react-webpack5/README.md b/code/frameworks/react-webpack5/README.md
index 2ff4ff2ebd79..efe63d0d6e1f 100644
--- a/code/frameworks/react-webpack5/README.md
+++ b/code/frameworks/react-webpack5/README.md
@@ -1,3 +1,7 @@
# Storybook for React & Webpack
-See [documentation](https://storybook.js.org/docs/get-started/frameworks/react-webpack5?renderer=react) for installation instructions, usage examples, APIs, and more.
+Develop, document, and test UI components in isolation.
+
+See [documentation](https://storybook.js.org/docs/get-started/frameworks/react-webpack5?renderer=react&ref=readme) for installation instructions, usage examples, APIs, and more.
+
+Learn more about Storybook at [storybook.js.org](https://storybook.js.org/?ref=readme).
diff --git a/code/frameworks/react-webpack5/build-config.ts b/code/frameworks/react-webpack5/build-config.ts
new file mode 100644
index 000000000000..167eeec054b8
--- /dev/null
+++ b/code/frameworks/react-webpack5/build-config.ts
@@ -0,0 +1,25 @@
+import type { BuildEntries } from '../../../scripts/build/utils/entry-utils';
+
+const config: BuildEntries = {
+ entries: {
+ browser: [
+ {
+ exportEntries: ['.'],
+ entryPoint: './src/index.ts',
+ },
+ ],
+ node: [
+ {
+ exportEntries: ['./preset'],
+ entryPoint: './src/preset.ts',
+ dts: false,
+ },
+ {
+ exportEntries: ['./node'],
+ entryPoint: './src/node/index.ts',
+ },
+ ],
+ },
+};
+
+export default config;
diff --git a/code/frameworks/react-webpack5/package.json b/code/frameworks/react-webpack5/package.json
index 1bda0e4f6efd..25c79453cf38 100644
--- a/code/frameworks/react-webpack5/package.json
+++ b/code/frameworks/react-webpack5/package.json
@@ -1,9 +1,14 @@
{
"name": "@storybook/react-webpack5",
"version": "9.2.0-alpha.3",
- "description": "Storybook for React: Develop React Component in isolation with Hot Reloading.",
+ "description": "Storybook for React and Webpack: Develop, document, and test UI components in isolation",
"keywords": [
- "storybook"
+ "storybook",
+ "storybook-framework",
+ "react",
+ "webpack",
+ "component",
+ "components"
],
"homepage": "https://github.com/storybookjs/storybook/tree/next/code/frameworks/react-webpack5",
"bugs": {
@@ -19,28 +24,19 @@
"url": "https://opencollective.com/storybook"
},
"license": "MIT",
+ "type": "module",
"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"
+ "default": "./dist/index.js"
},
"./node": {
"types": "./dist/node/index.d.ts",
- "node": "./dist/node/index.js",
- "import": "./dist/node/index.mjs",
- "require": "./dist/node/index.js"
+ "default": "./dist/node/index.js"
},
- "./package.json": "./package.json"
+ "./package.json": "./package.json",
+ "./preset": "./dist/preset.js"
},
- "main": "dist/index.js",
- "module": "dist/index.mjs",
- "types": "dist/index.d.ts",
"files": [
"dist/**/*",
"template/cli/**/*",
@@ -50,8 +46,8 @@
"!src/**/*"
],
"scripts": {
- "check": "jiti ../../../scripts/prepare/check.ts",
- "prep": "jiti ../../../scripts/prepare/bundle.ts"
+ "check": "jiti ../../../scripts/check/check-package.ts",
+ "prep": "jiti ../../../scripts/build/build-package.ts"
},
"dependencies": {
"@storybook/builder-webpack5": "workspace:*",
@@ -62,8 +58,8 @@
"@types/node": "^22.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": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"storybook": "workspace:^",
"typescript": ">= 4.9.x"
},
@@ -72,19 +68,8 @@
"optional": true
}
},
- "engines": {
- "node": ">=20.0.0"
- },
"publishConfig": {
"access": "public"
},
- "bundler": {
- "entries": [
- "./src/index.ts",
- "./src/preset.ts",
- "./src/node/index.ts"
- ],
- "platform": "node"
- },
- "gitHead": "ce6a1e4a8d5ad69c699021a0b183df89cfc7b684"
+ "gitHead": "a8e7fd8a655c69780bc20b9749d2699e45beae16"
}
diff --git a/code/frameworks/react-webpack5/preset.js b/code/frameworks/react-webpack5/preset.js
index a83f95279e7f..4bd63d324002 100644
--- a/code/frameworks/react-webpack5/preset.js
+++ b/code/frameworks/react-webpack5/preset.js
@@ -1 +1 @@
-module.exports = require('./dist/preset');
+export * from './dist/preset.js';
diff --git a/code/frameworks/react-webpack5/src/preset.ts b/code/frameworks/react-webpack5/src/preset.ts
index 08f870a23054..7bf994fd0c4e 100644
--- a/code/frameworks/react-webpack5/src/preset.ts
+++ b/code/frameworks/react-webpack5/src/preset.ts
@@ -1,4 +1,4 @@
-import { dirname, join } from 'node:path';
+import { fileURLToPath } from 'node:url';
import type { PresetProperty } from 'storybook/internal/types';
@@ -6,11 +6,8 @@ import { WebpackDefinePlugin } from '@storybook/builder-webpack5';
import type { StorybookConfig } from './types';
-const getAbsolutePath = (input: I): I =>
- dirname(require.resolve(join(input, 'package.json'))) as any;
-
export const addons: PresetProperty<'addons'> = [
- getAbsolutePath('@storybook/preset-react-webpack'),
+ fileURLToPath(import.meta.resolve('@storybook/preset-react-webpack')),
];
export const core: PresetProperty<'core'> = async (config, options) => {
@@ -19,10 +16,10 @@ export const core: PresetProperty<'core'> = async (config, options) => {
return {
...config,
builder: {
- name: getAbsolutePath('@storybook/builder-webpack5'),
+ name: fileURLToPath(import.meta.resolve('@storybook/builder-webpack5')),
options: typeof framework === 'string' ? {} : framework.options.builder || {},
},
- renderer: getAbsolutePath('@storybook/react'),
+ renderer: fileURLToPath(import.meta.resolve('@storybook/react/preset')),
};
};
@@ -31,7 +28,7 @@ export const webpack: StorybookConfig['webpack'] = async (config, options) => {
config.resolve.alias = {
...config.resolve?.alias,
- '@storybook/react': getAbsolutePath('@storybook/react'),
+ '@storybook/react': fileURLToPath(import.meta.resolve('@storybook/react')),
};
if (options.features?.developmentModeForBuild) {
diff --git a/code/frameworks/server-webpack5/README.md b/code/frameworks/server-webpack5/README.md
index fdb20cbe4c57..8b261f52eeac 100644
--- a/code/frameworks/server-webpack5/README.md
+++ b/code/frameworks/server-webpack5/README.md
@@ -29,7 +29,7 @@ export const parameters = {
The URL you connect to should have the ability to render a story, see [server rendering](#server-rendering) below.
-For more information visit: [storybook.js.org](https://storybook.js.org)
+For more information visit: [storybook.js.org](https://storybook.js.org?ref=readme)
## Writing Stories
@@ -230,7 +230,7 @@ Just like CSF stories we can define `argTypes` to specify the controls used in t
## Addon compatibility
-Storybook also comes with a lot of [addons](https://storybook.js.org/addons) and a great API to customize as you wish. As some addons assume the story is rendered in JS, they may not work with `@storybook/server` (yet!).
+Storybook also comes with a lot of [addons](https://storybook.js.org/addons?ref=readme) and a great API to customize as you wish. As some addons assume the story is rendered in JS, they may not work with `@storybook/server` (yet!).
Many addons that act on the manager side (such as `backgrounds` and `viewport`) will work out of the box with `@storybook/server` -- you can configure them with parameters written on the server as usual.
@@ -315,3 +315,5 @@ type FetchStoryHtmlType = (
- id: Id of the story being rendered given by `parameters.server.id`
- params: Merged story params `parameters.server.params`and story args
- context: The context of the story
+
+Learn more about Storybook at [storybook.js.org](https://storybook.js.org/?ref=readme).
diff --git a/code/frameworks/server-webpack5/build-config.ts b/code/frameworks/server-webpack5/build-config.ts
new file mode 100644
index 000000000000..167eeec054b8
--- /dev/null
+++ b/code/frameworks/server-webpack5/build-config.ts
@@ -0,0 +1,25 @@
+import type { BuildEntries } from '../../../scripts/build/utils/entry-utils';
+
+const config: BuildEntries = {
+ entries: {
+ browser: [
+ {
+ exportEntries: ['.'],
+ entryPoint: './src/index.ts',
+ },
+ ],
+ node: [
+ {
+ exportEntries: ['./preset'],
+ entryPoint: './src/preset.ts',
+ dts: false,
+ },
+ {
+ exportEntries: ['./node'],
+ entryPoint: './src/node/index.ts',
+ },
+ ],
+ },
+};
+
+export default config;
diff --git a/code/frameworks/server-webpack5/package.json b/code/frameworks/server-webpack5/package.json
index 223681546d4a..4f568fc546b8 100644
--- a/code/frameworks/server-webpack5/package.json
+++ b/code/frameworks/server-webpack5/package.json
@@ -3,7 +3,10 @@
"version": "9.2.0-alpha.3",
"description": "Storybook for Server: View HTML snippets from a server in isolation with Hot Reloading.",
"keywords": [
- "storybook"
+ "storybook",
+ "server",
+ "component",
+ "components"
],
"homepage": "https://github.com/storybookjs/storybook/tree/next/code/frameworks/server-webpack5",
"bugs": {
@@ -19,28 +22,19 @@
"url": "https://opencollective.com/storybook"
},
"license": "MIT",
+ "type": "module",
"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"
+ "default": "./dist/index.js"
},
"./node": {
"types": "./dist/node/index.d.ts",
- "node": "./dist/node/index.js",
- "import": "./dist/node/index.mjs",
- "require": "./dist/node/index.js"
+ "default": "./dist/node/index.js"
},
- "./package.json": "./package.json"
+ "./package.json": "./package.json",
+ "./preset": "./dist/preset.js"
},
- "main": "dist/index.js",
- "module": "dist/index.mjs",
- "types": "dist/index.d.ts",
"files": [
"dist/**/*",
"template/cli/**/*",
@@ -50,8 +44,8 @@
"!src/**/*"
],
"scripts": {
- "check": "jiti ../../../scripts/prepare/check.ts",
- "prep": "jiti ../../../scripts/prepare/bundle.ts"
+ "check": "jiti ../../../scripts/check/check-package.ts",
+ "prep": "jiti ../../../scripts/build/build-package.ts"
},
"dependencies": {
"@storybook/builder-webpack5": "workspace:*",
@@ -65,19 +59,8 @@
"peerDependencies": {
"storybook": "workspace:^"
},
- "engines": {
- "node": ">=20.0.0"
- },
"publishConfig": {
"access": "public"
},
- "bundler": {
- "entries": [
- "./src/index.ts",
- "./src/preset.ts",
- "./src/node/index.ts"
- ],
- "platform": "node"
- },
- "gitHead": "ce6a1e4a8d5ad69c699021a0b183df89cfc7b684"
+ "gitHead": "a8e7fd8a655c69780bc20b9749d2699e45beae16"
}
diff --git a/code/frameworks/server-webpack5/preset.js b/code/frameworks/server-webpack5/preset.js
index a83f95279e7f..4bd63d324002 100644
--- a/code/frameworks/server-webpack5/preset.js
+++ b/code/frameworks/server-webpack5/preset.js
@@ -1 +1 @@
-module.exports = require('./dist/preset');
+export * from './dist/preset.js';
diff --git a/code/frameworks/server-webpack5/src/preset.ts b/code/frameworks/server-webpack5/src/preset.ts
index 689a984336e1..137a2da3b700 100644
--- a/code/frameworks/server-webpack5/src/preset.ts
+++ b/code/frameworks/server-webpack5/src/preset.ts
@@ -1,12 +1,7 @@
-import { dirname, join } from 'node:path';
-
import type { PresetProperty } from 'storybook/internal/types';
-const getAbsolutePath = (input: I): I =>
- dirname(require.resolve(join(input, 'package.json'))) as any;
-
export const addons: PresetProperty<'addons'> = [
- getAbsolutePath('@storybook/preset-server-webpack'),
+ import.meta.resolve('@storybook/preset-server-webpack'),
];
export const core: PresetProperty<'core'> = async (config, options) => {
@@ -15,9 +10,9 @@ export const core: PresetProperty<'core'> = async (config, options) => {
return {
...config,
builder: {
- name: getAbsolutePath('@storybook/builder-webpack5'),
+ name: import.meta.resolve('@storybook/builder-webpack5'),
options: typeof framework === 'string' ? {} : framework.options.builder || {},
},
- renderer: getAbsolutePath('@storybook/server'),
+ renderer: import.meta.resolve('@storybook/server/preset'),
};
};
diff --git a/code/frameworks/svelte-vite/README.md b/code/frameworks/svelte-vite/README.md
index 6aa6211fd44a..65b56c497146 100644
--- a/code/frameworks/svelte-vite/README.md
+++ b/code/frameworks/svelte-vite/README.md
@@ -1,3 +1,5 @@
# Storybook for Svelte & Vite
-See [documentation](https://storybook.js.org/docs/get-started/frameworks/svelte-vite?renderer=svelte) for installation instructions, usage examples, APIs, and more.
+See [documentation](https://storybook.js.org/docs/get-started/frameworks/svelte-vite?renderer=svelte&ref=readme) for installation instructions, usage examples, APIs, and more.
+
+Learn more about Storybook at [storybook.js.org](https://storybook.js.org/?ref=readme).
diff --git a/code/frameworks/svelte-vite/build-config.ts b/code/frameworks/svelte-vite/build-config.ts
new file mode 100644
index 000000000000..7bf960e4db09
--- /dev/null
+++ b/code/frameworks/svelte-vite/build-config.ts
@@ -0,0 +1,24 @@
+import type { BuildEntries } from '../../../scripts/build/utils/entry-utils';
+
+const config: BuildEntries = {
+ entries: {
+ browser: [
+ {
+ exportEntries: ['.'],
+ entryPoint: './src/index.ts',
+ },
+ ],
+ node: [
+ {
+ exportEntries: ['./preset'],
+ entryPoint: './src/preset.ts',
+ },
+ {
+ exportEntries: ['./node'],
+ entryPoint: './src/node/index.ts',
+ },
+ ],
+ },
+};
+
+export default config;
diff --git a/code/frameworks/svelte-vite/package.json b/code/frameworks/svelte-vite/package.json
index 280936be3028..6285f8323b37 100644
--- a/code/frameworks/svelte-vite/package.json
+++ b/code/frameworks/svelte-vite/package.json
@@ -1,9 +1,14 @@
{
"name": "@storybook/svelte-vite",
"version": "9.2.0-alpha.3",
- "description": "Storybook for Svelte and Vite: Develop Svelte components in isolation with Hot Reloading.",
+ "description": "Storybook for Svelte and Vite: Develop, document, and test UI components in isolation",
"keywords": [
- "storybook"
+ "storybook",
+ "storybook-framework",
+ "svelte",
+ "vite",
+ "component",
+ "components"
],
"homepage": "https://github.com/storybookjs/storybook/tree/next/code/frameworks/svelte-vite",
"bugs": {
@@ -19,28 +24,22 @@
"url": "https://opencollective.com/storybook"
},
"license": "MIT",
+ "type": "module",
"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"
+ "default": "./dist/index.js"
},
"./node": {
"types": "./dist/node/index.d.ts",
- "node": "./dist/node/index.js",
- "import": "./dist/node/index.mjs",
- "require": "./dist/node/index.js"
+ "default": "./dist/node/index.js"
},
- "./package.json": "./package.json"
+ "./package.json": "./package.json",
+ "./preset": {
+ "types": "./dist/preset.d.ts",
+ "default": "./dist/preset.js"
+ }
},
- "main": "dist/index.js",
- "module": "dist/index.mjs",
- "types": "dist/index.d.ts",
"files": [
"dist/**/*",
"template/**/*",
@@ -50,8 +49,8 @@
"!src/**/*"
],
"scripts": {
- "check": "jiti ../../../scripts/prepare/check.ts",
- "prep": "jiti ../../../scripts/prepare/bundle.ts"
+ "check": "jiti ../../../scripts/check/check-package.ts",
+ "prep": "jiti ../../../scripts/build/build-package.ts"
},
"dependencies": {
"@storybook/builder-vite": "workspace:*",
@@ -66,7 +65,7 @@
"svelte": "^5.0.5",
"sveltedoc-parser": "^4.2.1",
"typescript": "^5.8.3",
- "vite": "^6.2.5"
+ "vite": "^7.0.4"
},
"peerDependencies": {
"@sveltejs/vite-plugin-svelte": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0",
@@ -74,19 +73,8 @@
"svelte": "^5.0.0",
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0"
},
- "engines": {
- "node": ">=20.0.0"
- },
"publishConfig": {
"access": "public"
},
- "bundler": {
- "entries": [
- "./src/index.ts",
- "./src/preset.ts",
- "./src/node/index.ts"
- ],
- "platform": "node"
- },
- "gitHead": "ce6a1e4a8d5ad69c699021a0b183df89cfc7b684"
+ "gitHead": "a8e7fd8a655c69780bc20b9749d2699e45beae16"
}
diff --git a/code/frameworks/svelte-vite/preset.js b/code/frameworks/svelte-vite/preset.js
index a83f95279e7f..4bd63d324002 100644
--- a/code/frameworks/svelte-vite/preset.js
+++ b/code/frameworks/svelte-vite/preset.js
@@ -1 +1 @@
-module.exports = require('./dist/preset');
+export * from './dist/preset.js';
diff --git a/code/frameworks/svelte-vite/src/plugins/generateDocgen.ts b/code/frameworks/svelte-vite/src/plugins/generateDocgen.ts
index cd3ac2f4ea09..81fdf105c8f3 100644
--- a/code/frameworks/svelte-vite/src/plugins/generateDocgen.ts
+++ b/code/frameworks/svelte-vite/src/plugins/generateDocgen.ts
@@ -264,7 +264,7 @@ export function generateDocgen(targetFileName: string, cache: DocgenCache): Docg
if (cache.options === undefined || !cache.rootNames?.has(targetFileName)) {
[cache.options, cache.rootNames] = loadConfig(targetFileName);
- const shimFilename = require.resolve('svelte2tsx/svelte-shims-v4.d.ts');
+ const shimFilename = import.meta.resolve('svelte2tsx/svelte-shims-v4.d.ts');
cache.rootNames.add(shimFilename);
cache.rootNames.add(targetFileName);
}
diff --git a/code/frameworks/svelte-vite/src/preset.ts b/code/frameworks/svelte-vite/src/preset.ts
index 276f53f56cee..7bba394714b8 100644
--- a/code/frameworks/svelte-vite/src/preset.ts
+++ b/code/frameworks/svelte-vite/src/preset.ts
@@ -1,17 +1,12 @@
-import { dirname, join } from 'node:path';
-
import type { PresetProperty } from 'storybook/internal/types';
import { svelteDocgen } from './plugins/svelte-docgen';
import type { StorybookConfig } from './types';
import { handleSvelteKit } from './utils';
-const getAbsolutePath = (input: I): I =>
- dirname(require.resolve(join(input, 'package.json'))) as any;
-
export const core: PresetProperty<'core'> = {
- builder: getAbsolutePath('@storybook/builder-vite'),
- renderer: getAbsolutePath('@storybook/svelte'),
+ builder: import.meta.resolve('@storybook/builder-vite'),
+ renderer: import.meta.resolve('@storybook/svelte/preset'),
};
export const viteFinal: NonNullable = async (config, options) => {
diff --git a/code/frameworks/sveltekit/README.md b/code/frameworks/sveltekit/README.md
index 6b68bdff7320..d4a36d768f34 100644
--- a/code/frameworks/sveltekit/README.md
+++ b/code/frameworks/sveltekit/README.md
@@ -1,8 +1,10 @@
# Storybook for SvelteKit
-See [documentation](https://storybook.js.org/docs/get-started/frameworks/sveltekit?renderer=svelte) for installation instructions, usage examples, APIs, and more.
+See [documentation](https://storybook.js.org/docs/get-started/frameworks/sveltekit?renderer=svelte&ref=readme) for installation instructions, usage examples, APIs, and more.
## Acknowledgements
Integrating with SvelteKit would not have been possible if it weren't for the fantastic efforts by the Svelte core team - especially [Ben McCann](https://twitter.com/benjaminmccann) - to make integrations with the wider ecosystem possible.
A big thank you also goes out to [Paolo Ricciuti](https://twitter.com/PaoloRicciuti) for improving the mocking capabilities.
+
+Learn more about Storybook at [storybook.js.org](https://storybook.js.org/?ref=readme).
diff --git a/code/frameworks/sveltekit/build-config.ts b/code/frameworks/sveltekit/build-config.ts
new file mode 100644
index 000000000000..79b871767e32
--- /dev/null
+++ b/code/frameworks/sveltekit/build-config.ts
@@ -0,0 +1,50 @@
+import type { BuildEntries } from '../../../scripts/build/utils/entry-utils';
+
+const config: BuildEntries = {
+ entries: {
+ browser: [
+ {
+ exportEntries: ['.'],
+ entryPoint: './src/index.ts',
+ },
+ {
+ exportEntries: ['./preview'],
+ entryPoint: './src/preview.ts',
+ dts: false,
+ },
+ {
+ exportEntries: ['./internal/mocks/app/forms'],
+ entryPoint: './src/mocks/app/forms.ts',
+ dts: false,
+ },
+ {
+ exportEntries: ['./internal/mocks/app/navigation'],
+ entryPoint: './src/mocks/app/navigation.ts',
+ dts: false,
+ },
+ {
+ exportEntries: ['./internal/mocks/app/stores'],
+ entryPoint: './src/mocks/app/stores.ts',
+ dts: false,
+ },
+ ],
+ node: [
+ {
+ exportEntries: ['./node'],
+ entryPoint: './src/node/index.ts',
+ },
+ {
+ exportEntries: ['./vite-plugin'],
+ entryPoint: './src/vite-plugin.ts',
+ dts: false,
+ },
+ {
+ exportEntries: ['./preset'],
+ entryPoint: './src/preset.ts',
+ dts: false,
+ },
+ ],
+ },
+};
+
+export default config;
diff --git a/code/frameworks/sveltekit/package.json b/code/frameworks/sveltekit/package.json
index ebbc9c70080d..50e0c87e9c7e 100644
--- a/code/frameworks/sveltekit/package.json
+++ b/code/frameworks/sveltekit/package.json
@@ -1,12 +1,16 @@
{
"name": "@storybook/sveltekit",
"version": "9.2.0-alpha.3",
- "description": "Storybook for SvelteKit",
+ "description": "Storybook for SvelteKit: Develop, document, and test UI components in isolation",
"keywords": [
"storybook",
+ "storybook-framework",
"svelte",
"sveltekit",
- "svelte-kit"
+ "svelte-kit",
+ "vite",
+ "component",
+ "components"
],
"homepage": "https://github.com/storybookjs/storybook/tree/next/code/frameworks/sveltekit",
"bugs": {
@@ -22,36 +26,24 @@
"url": "https://opencollective.com/storybook"
},
"license": "MIT",
+ "type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
- "import": "./dist/index.mjs",
- "node": "./dist/index.js",
- "require": "./dist/index.js"
- },
- "./dist/preview.mjs": {
- "import": "./dist/preview.mjs"
- },
- "./preset": {
- "types": "./dist/preset.d.ts",
- "require": "./dist/preset.js"
- },
- "./vite-plugin": {
- "types": "./dist/vite-plugin.d.ts",
- "require": "./dist/vite-plugin.js",
- "import": "./dist/vite-plugin.mjs"
+ "default": "./dist/index.js"
},
+ "./internal/mocks/app/forms": "./dist/mocks/app/forms.js",
+ "./internal/mocks/app/navigation": "./dist/mocks/app/navigation.js",
+ "./internal/mocks/app/stores": "./dist/mocks/app/stores.js",
"./node": {
"types": "./dist/node/index.d.ts",
- "node": "./dist/node/index.js",
- "import": "./dist/node/index.mjs",
- "require": "./dist/node/index.js"
+ "default": "./dist/node/index.js"
},
- "./package.json": "./package.json"
+ "./package.json": "./package.json",
+ "./preset": "./dist/preset.js",
+ "./preview": "./dist/preview.js",
+ "./vite-plugin": "./dist/vite-plugin.js"
},
- "main": "dist/index.js",
- "module": "dist/index.mjs",
- "types": "dist/index.d.ts",
"files": [
"dist/**/*",
"template/**/*",
@@ -61,8 +53,8 @@
"src/mocks/**/*"
],
"scripts": {
- "check": "jiti ../../../scripts/prepare/check.ts",
- "prep": "jiti ../../../scripts/prepare/bundle.ts"
+ "check": "jiti ../../../scripts/check/check-package.ts",
+ "prep": "jiti ../../../scripts/build/build-package.ts"
},
"dependencies": {
"@storybook/builder-vite": "workspace:*",
@@ -72,28 +64,15 @@
"devDependencies": {
"@types/node": "^22.0.0",
"typescript": "^5.8.3",
- "vite": "^6.2.5"
+ "vite": "^7.0.4"
},
"peerDependencies": {
"storybook": "workspace:^",
"svelte": "^5.0.0",
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0"
},
- "engines": {
- "node": ">=20.0.0"
- },
"publishConfig": {
"access": "public"
},
- "bundler": {
- "entries": [
- "./src/index.ts",
- "./src/preview.ts",
- "./src/preset.ts",
- "./src/vite-plugin.ts",
- "./src/node/index.ts"
- ],
- "platform": "node"
- },
- "gitHead": "ce6a1e4a8d5ad69c699021a0b183df89cfc7b684"
+ "gitHead": "a8e7fd8a655c69780bc20b9749d2699e45beae16"
}
diff --git a/code/frameworks/sveltekit/preset.js b/code/frameworks/sveltekit/preset.js
index a83f95279e7f..4bd63d324002 100644
--- a/code/frameworks/sveltekit/preset.js
+++ b/code/frameworks/sveltekit/preset.js
@@ -1 +1 @@
-module.exports = require('./dist/preset');
+export * from './dist/preset.js';
diff --git a/code/frameworks/sveltekit/preview.js b/code/frameworks/sveltekit/preview.js
new file mode 100644
index 000000000000..542d45f8cf26
--- /dev/null
+++ b/code/frameworks/sveltekit/preview.js
@@ -0,0 +1 @@
+export * from './dist/preview.js';
diff --git a/code/frameworks/sveltekit/src/plugins/mock-sveltekit-stores.ts b/code/frameworks/sveltekit/src/plugins/mock-sveltekit-stores.ts
index 4b2414cec59f..44b49f9b1f8f 100644
--- a/code/frameworks/sveltekit/src/plugins/mock-sveltekit-stores.ts
+++ b/code/frameworks/sveltekit/src/plugins/mock-sveltekit-stores.ts
@@ -1,20 +1,14 @@
-import { dirname, resolve } from 'node:path';
-import { fileURLToPath } from 'node:url';
-
import type { Plugin } from 'vite';
-const filename = __filename ?? fileURLToPath(import.meta.url);
-const dir = dirname(filename);
-
export function mockSveltekitStores() {
return {
name: 'storybook:sveltekit-mock-stores',
config: () => ({
resolve: {
alias: {
- '$app/forms': resolve(dir, '../src/mocks/app/forms.ts'),
- '$app/navigation': resolve(dir, '../src/mocks/app/navigation.ts'),
- '$app/stores': resolve(dir, '../src/mocks/app/stores.ts'),
+ '$app/forms': '@storybook/sveltekit/internal/mocks/app/forms',
+ '$app/navigation': '@storybook/sveltekit/internal/mocks/app/navigation',
+ '$app/stores': '@storybook/sveltekit/internal/mocks/app/stores',
},
},
}),
diff --git a/code/frameworks/sveltekit/src/preset.ts b/code/frameworks/sveltekit/src/preset.ts
index 0f16cc7cd8e2..6ecb9c89fd12 100644
--- a/code/frameworks/sveltekit/src/preset.ts
+++ b/code/frameworks/sveltekit/src/preset.ts
@@ -1,4 +1,4 @@
-import { dirname, join } from 'node:path';
+import { fileURLToPath } from 'node:url';
import type { PresetProperty } from 'storybook/internal/types';
@@ -9,16 +9,13 @@ import { configOverrides } from './plugins/config-overrides';
import { mockSveltekitStores } from './plugins/mock-sveltekit-stores';
import { type StorybookConfig } from './types';
-const getAbsolutePath = (input: I): I =>
- dirname(require.resolve(join(input, 'package.json'))) as any;
-
export const core: PresetProperty<'core'> = {
- builder: getAbsolutePath('@storybook/builder-vite'),
- renderer: getAbsolutePath('@storybook/svelte'),
+ builder: import.meta.resolve('@storybook/builder-vite'),
+ renderer: import.meta.resolve('@storybook/svelte/preset'),
};
export const previewAnnotations: PresetProperty<'previewAnnotations'> = (entry = []) => [
...entry,
- join(dirname(require.resolve('@storybook/sveltekit/package.json')), 'dist/preview.mjs'),
+ fileURLToPath(import.meta.resolve('@storybook/sveltekit/preview')),
];
export const viteFinal: NonNullable = async (config, options) => {
@@ -37,3 +34,9 @@ export const viteFinal: NonNullable = async (confi
],
};
};
+
+export const optimizeViteDeps = [
+ '@storybook/sveltekit/internal/mocks/app/forms',
+ '@storybook/sveltekit/internal/mocks/app/navigation',
+ '@storybook/sveltekit/internal/mocks/app/stores',
+];
diff --git a/code/frameworks/vue3-vite/README.md b/code/frameworks/vue3-vite/README.md
index 409d28b424c0..70c2b44fdf3f 100644
--- a/code/frameworks/vue3-vite/README.md
+++ b/code/frameworks/vue3-vite/README.md
@@ -1,3 +1,5 @@
# Storybook for Vue and Vite
-See [documentation](https://storybook.js.org/docs/get-started/frameworks/vue3-vite?renderer=vue) for installation instructions, usage examples, APIs, and more.
+See [documentation](https://storybook.js.org/docs/get-started/frameworks/vue3-vite?renderer=vue&ref=readme) for installation instructions, usage examples, APIs, and more.
+
+Learn more about Storybook at [storybook.js.org](https://storybook.js.org/?ref=readme).
diff --git a/code/frameworks/vue3-vite/build-config.ts b/code/frameworks/vue3-vite/build-config.ts
new file mode 100644
index 000000000000..c2d2ecf7f9ee
--- /dev/null
+++ b/code/frameworks/vue3-vite/build-config.ts
@@ -0,0 +1,30 @@
+import type { BuildEntries } from '../../../scripts/build/utils/entry-utils';
+
+const config: BuildEntries = {
+ entries: {
+ browser: [
+ {
+ exportEntries: ['.'],
+ entryPoint: './src/index.ts',
+ },
+ ],
+ node: [
+ {
+ exportEntries: ['./preset'],
+ entryPoint: './src/preset.ts',
+ dts: false,
+ },
+ {
+ exportEntries: ['./node'],
+ entryPoint: './src/node/index.ts',
+ },
+ {
+ exportEntries: ['./vite-plugin'],
+ entryPoint: './src/vite-plugin.ts',
+ dts: false,
+ },
+ ],
+ },
+};
+
+export default config;
diff --git a/code/frameworks/vue3-vite/package.json b/code/frameworks/vue3-vite/package.json
index e7a0e163638e..bf5e2403bfc1 100644
--- a/code/frameworks/vue3-vite/package.json
+++ b/code/frameworks/vue3-vite/package.json
@@ -1,9 +1,14 @@
{
"name": "@storybook/vue3-vite",
"version": "9.2.0-alpha.3",
- "description": "Storybook for Vue3 and Vite: Develop Vue3 components in isolation with Hot Reloading.",
+ "description": "Storybook for Vue3 and Vite: Develop, document, and test UI components in isolation",
"keywords": [
- "storybook"
+ "storybook",
+ "storybook-framework",
+ "vue3",
+ "vite",
+ "component",
+ "components"
],
"homepage": "https://github.com/storybookjs/storybook/tree/next/code/frameworks/vue3-vite",
"bugs": {
@@ -19,33 +24,20 @@
"url": "https://opencollective.com/storybook"
},
"license": "MIT",
+ "type": "module",
"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"
- },
- "./vite-plugin": {
- "types": "./dist/vite-plugin.d.ts",
- "require": "./dist/vite-plugin.js",
- "import": "./dist/vite-plugin.mjs"
+ "default": "./dist/index.js"
},
"./node": {
"types": "./dist/node/index.d.ts",
- "node": "./dist/node/index.js",
- "import": "./dist/node/index.mjs",
- "require": "./dist/node/index.js"
+ "default": "./dist/node/index.js"
},
- "./package.json": "./package.json"
+ "./package.json": "./package.json",
+ "./preset": "./dist/preset.js",
+ "./vite-plugin": "./dist/vite-plugin.js"
},
- "main": "dist/index.js",
- "module": "dist/index.mjs",
- "types": "dist/index.d.ts",
"files": [
"dist/**/*",
"template/**/*",
@@ -55,8 +47,8 @@
"!src/**/*"
],
"scripts": {
- "check": "jiti ../../../scripts/prepare/check.ts",
- "prep": "jiti ../../../scripts/prepare/bundle.ts"
+ "check": "jiti ../../../scripts/check/check-package.ts",
+ "prep": "jiti ../../../scripts/build/build-package.ts"
},
"dependencies": {
"@storybook/builder-vite": "workspace:*",
@@ -71,26 +63,14 @@
"@types/find-package-json": "^1.2.6",
"@types/node": "^22.0.0",
"typescript": "^5.8.3",
- "vite": "^6.2.5"
+ "vite": "^7.0.4"
},
"peerDependencies": {
"storybook": "workspace:^",
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0"
},
- "engines": {
- "node": ">=20.0.0"
- },
"publishConfig": {
"access": "public"
},
- "bundler": {
- "entries": [
- "./src/index.ts",
- "./src/preset.ts",
- "./src/vite-plugin.ts",
- "./src/node/index.ts"
- ],
- "platform": "node"
- },
- "gitHead": "ce6a1e4a8d5ad69c699021a0b183df89cfc7b684"
+ "gitHead": "a8e7fd8a655c69780bc20b9749d2699e45beae16"
}
diff --git a/code/frameworks/vue3-vite/preset.js b/code/frameworks/vue3-vite/preset.js
index a83f95279e7f..4bd63d324002 100644
--- a/code/frameworks/vue3-vite/preset.js
+++ b/code/frameworks/vue3-vite/preset.js
@@ -1 +1 @@
-module.exports = require('./dist/preset');
+export * from './dist/preset.js';
diff --git a/code/frameworks/vue3-vite/src/preset.ts b/code/frameworks/vue3-vite/src/preset.ts
index d565b947587c..e5719df191aa 100644
--- a/code/frameworks/vue3-vite/src/preset.ts
+++ b/code/frameworks/vue3-vite/src/preset.ts
@@ -1,5 +1,3 @@
-import { dirname, join } from 'node:path';
-
import type { PresetProperty } from 'storybook/internal/types';
import type { Plugin } from 'vite';
@@ -9,12 +7,9 @@ import { vueDocgen } from './plugins/vue-docgen';
import { templateCompilation } from './plugins/vue-template';
import type { FrameworkOptions, StorybookConfig, VueDocgenPlugin } from './types';
-const getAbsolutePath = (input: I): I =>
- dirname(require.resolve(join(input, 'package.json'))) as any;
-
export const core: PresetProperty<'core'> = {
- builder: getAbsolutePath('@storybook/builder-vite'),
- renderer: getAbsolutePath('@storybook/vue3'),
+ builder: import.meta.resolve('@storybook/builder-vite'),
+ renderer: import.meta.resolve('@storybook/vue3/preset'),
};
export const viteFinal: StorybookConfig['viteFinal'] = async (config, options) => {
diff --git a/code/frameworks/web-components-vite/README.md b/code/frameworks/web-components-vite/README.md
index 516a8c1257c3..f30baaf76b54 100644
--- a/code/frameworks/web-components-vite/README.md
+++ b/code/frameworks/web-components-vite/README.md
@@ -1,3 +1,5 @@
# Storybook for Web components & Vite
-See [documentation](https://storybook.js.org/docs/get-started/frameworks/web-components-vite?renderer=web-components) for installation instructions, usage examples, APIs, and more.
+See [documentation](https://storybook.js.org/docs/get-started/frameworks/web-components-vite?renderer=web-components&ref=readme) for installation instructions, usage examples, APIs, and more.
+
+Learn more about Storybook at [storybook.js.org](https://storybook.js.org/?ref=readme).
diff --git a/code/frameworks/web-components-vite/build-config.ts b/code/frameworks/web-components-vite/build-config.ts
new file mode 100644
index 000000000000..167eeec054b8
--- /dev/null
+++ b/code/frameworks/web-components-vite/build-config.ts
@@ -0,0 +1,25 @@
+import type { BuildEntries } from '../../../scripts/build/utils/entry-utils';
+
+const config: BuildEntries = {
+ entries: {
+ browser: [
+ {
+ exportEntries: ['.'],
+ entryPoint: './src/index.ts',
+ },
+ ],
+ node: [
+ {
+ exportEntries: ['./preset'],
+ entryPoint: './src/preset.ts',
+ dts: false,
+ },
+ {
+ exportEntries: ['./node'],
+ entryPoint: './src/node/index.ts',
+ },
+ ],
+ },
+};
+
+export default config;
diff --git a/code/frameworks/web-components-vite/package.json b/code/frameworks/web-components-vite/package.json
index 0a396e05d0a5..e3b58876bcf4 100644
--- a/code/frameworks/web-components-vite/package.json
+++ b/code/frameworks/web-components-vite/package.json
@@ -1,9 +1,16 @@
{
"name": "@storybook/web-components-vite",
"version": "9.2.0-alpha.3",
- "description": "Storybook for web-components and Vite: Develop Web Components in isolation with Hot Reloading.",
+ "description": "Storybook for Web Components and Vite: Develop, document, and test UI components in isolation",
"keywords": [
- "storybook"
+ "storybook",
+ "storybook-framework",
+ "lit",
+ "lit-html",
+ "web-components",
+ "vite",
+ "component",
+ "components"
],
"homepage": "https://github.com/storybookjs/storybook/tree/next/code/frameworks/web-components-vite",
"bugs": {
@@ -19,28 +26,19 @@
"url": "https://opencollective.com/storybook"
},
"license": "MIT",
+ "type": "module",
"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"
+ "default": "./dist/index.js"
},
"./node": {
"types": "./dist/node/index.d.ts",
- "node": "./dist/node/index.js",
- "import": "./dist/node/index.mjs",
- "require": "./dist/node/index.js"
+ "default": "./dist/node/index.js"
},
- "./package.json": "./package.json"
+ "./package.json": "./package.json",
+ "./preset": "./dist/preset.js"
},
- "main": "dist/index.js",
- "module": "dist/index.mjs",
- "types": "dist/index.d.ts",
"files": [
"dist/**/*",
"template/**/*",
@@ -50,8 +48,8 @@
"!src/**/*"
],
"scripts": {
- "check": "jiti ../../../scripts/prepare/check.ts",
- "prep": "jiti ../../../scripts/prepare/bundle.ts"
+ "check": "jiti ../../../scripts/check/check-package.ts",
+ "prep": "jiti ../../../scripts/build/build-package.ts"
},
"dependencies": {
"@storybook/builder-vite": "workspace:*",
@@ -64,19 +62,8 @@
"peerDependencies": {
"storybook": "workspace:^"
},
- "engines": {
- "node": ">=20.0.0"
- },
"publishConfig": {
"access": "public"
},
- "bundler": {
- "entries": [
- "./src/node/index.ts",
- "./src/index.ts",
- "./src/preset.ts"
- ],
- "platform": "node"
- },
- "gitHead": "ce6a1e4a8d5ad69c699021a0b183df89cfc7b684"
+ "gitHead": "a8e7fd8a655c69780bc20b9749d2699e45beae16"
}
diff --git a/code/frameworks/web-components-vite/preset.js b/code/frameworks/web-components-vite/preset.js
index a83f95279e7f..4bd63d324002 100644
--- a/code/frameworks/web-components-vite/preset.js
+++ b/code/frameworks/web-components-vite/preset.js
@@ -1 +1 @@
-module.exports = require('./dist/preset');
+export * from './dist/preset.js';
diff --git a/code/frameworks/web-components-vite/src/preset.ts b/code/frameworks/web-components-vite/src/preset.ts
index 397deddb6e4b..15326524c821 100644
--- a/code/frameworks/web-components-vite/src/preset.ts
+++ b/code/frameworks/web-components-vite/src/preset.ts
@@ -1,11 +1,6 @@
-import { dirname, join } from 'node:path';
-
import type { PresetProperty } from 'storybook/internal/types';
-const getAbsolutePath = (input: I): I =>
- dirname(require.resolve(join(input, 'package.json'))) as any;
-
export const core: PresetProperty<'core'> = {
- builder: getAbsolutePath('@storybook/builder-vite'),
- renderer: getAbsolutePath('@storybook/web-components'),
+ builder: import.meta.resolve('@storybook/builder-vite'),
+ renderer: import.meta.resolve('@storybook/web-components/preset'),
};
diff --git a/code/lib/cli-sb/README.md b/code/lib/cli-sb/README.md
index f31715566811..840f706d8bfd 100644
--- a/code/lib/cli-sb/README.md
+++ b/code/lib/cli-sb/README.md
@@ -1,3 +1,5 @@
# Storybook CLI
This is a wrapper for
+
+Learn more about Storybook at [storybook.js.org](https://storybook.js.org/?ref=readme).
diff --git a/code/lib/cli-sb/index.js b/code/lib/cli-sb/index.js
index 7cb4855dc32f..f1fc05fa45da 100755
--- a/code/lib/cli-sb/index.js
+++ b/code/lib/cli-sb/index.js
@@ -1,3 +1,3 @@
#!/usr/bin/env node
-require('storybook/bin/index.cjs');
+import('storybook/internal/bin/dispatcher');
diff --git a/code/lib/cli-sb/package.json b/code/lib/cli-sb/package.json
index b392ca522c71..ab04fa65b198 100644
--- a/code/lib/cli-sb/package.json
+++ b/code/lib/cli-sb/package.json
@@ -1,9 +1,14 @@
{
"name": "sb",
"version": "9.2.0-alpha.3",
- "description": "Storybook CLI",
+ "description": "Storybook CLI: Develop, document, and test UI components in isolation",
"keywords": [
- "storybook"
+ "storybook",
+ "cli",
+ "dev",
+ "build",
+ "upgrade",
+ "init"
],
"homepage": "https://github.com/storybookjs/storybook/tree/next/code/lib/cli-sb",
"bugs": {
@@ -26,5 +31,5 @@
"publishConfig": {
"access": "public"
},
- "gitHead": "ce6a1e4a8d5ad69c699021a0b183df89cfc7b684"
+ "gitHead": "a8e7fd8a655c69780bc20b9749d2699e45beae16"
}
diff --git a/code/lib/cli-storybook/README.md b/code/lib/cli-storybook/README.md
index 6e3183109f1a..4a32ac10a7e4 100644
--- a/code/lib/cli-storybook/README.md
+++ b/code/lib/cli-storybook/README.md
@@ -47,3 +47,5 @@ This export contains the API that is available in the manager iframe.
### `storybook/types`
This export exposes a lot of TypeScript interfaces used throughout storybook, including for storybook configuration, addons etc.
+
+Learn more about Storybook at [storybook.js.org](https://storybook.js.org/?ref=readme).
diff --git a/code/lib/cli-storybook/bin/index.cjs b/code/lib/cli-storybook/bin/index.cjs
deleted file mode 100755
index d0395f4f3f9e..000000000000
--- a/code/lib/cli-storybook/bin/index.cjs
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/usr/bin/env node
-
-const majorNodeVersion = parseInt(process.versions.node, 10);
-if (majorNodeVersion < 20) {
- console.error('To run Storybook you need to have Node.js 20 or higher');
- process.exit(1);
-}
-
-// The Storybook CLI has a catch block for all of its commands, but if an error
-// occurs before the command even runs, for instance, if an import fails, then
-// such error will fall under the uncaughtException handler.
-// This is the earliest moment we can catch such errors.
-process.once('uncaughtException', (error) => {
- if (error.message.includes('string-width')) {
- console.error(
- [
- '🔴 Error: It looks like you are having a known issue with package hoisting.',
- 'Please check the following issue for details and solutions: https://github.com/storybookjs/storybook/issues/22431#issuecomment-1630086092\n\n',
- ].join('\n')
- );
- }
-
- throw error;
-});
-
-require('../dist/bin/index.cjs');
diff --git a/code/lib/cli-storybook/build-config.ts b/code/lib/cli-storybook/build-config.ts
new file mode 100644
index 000000000000..403def7e5b32
--- /dev/null
+++ b/code/lib/cli-storybook/build-config.ts
@@ -0,0 +1,16 @@
+import { join } from 'node:path';
+
+import type { BuildEntries } from '../../../scripts/build/utils/entry-utils';
+
+const config: BuildEntries = {
+ entries: {
+ node: [
+ {
+ entryPoint: './src/bin/index.ts',
+ dts: false,
+ },
+ ],
+ },
+};
+
+export default config;
diff --git a/code/lib/cli-storybook/package.json b/code/lib/cli-storybook/package.json
index 884424312c88..24a7af6cc633 100644
--- a/code/lib/cli-storybook/package.json
+++ b/code/lib/cli-storybook/package.json
@@ -1,9 +1,14 @@
{
"name": "@storybook/cli",
"version": "9.2.0-alpha.3",
- "description": "Storybook CLI",
+ "description": "Storybook CLI: Develop, document, and test UI components in isolation",
"keywords": [
- "storybook"
+ "storybook",
+ "cli",
+ "dev",
+ "build",
+ "upgrade",
+ "init"
],
"homepage": "https://github.com/storybookjs/storybook/tree/next/code/lib/cli-storybook",
"bugs": {
@@ -22,14 +27,9 @@
"author": "Storybook Team",
"type": "module",
"exports": {
- ".": {
- "types": "./dist/index.d.ts",
- "import": "./dist/index.js"
- },
- "./bin/index.cjs": "./bin/index.cjs",
"./package.json": "./package.json"
},
- "bin": "./bin/index.cjs",
+ "bin": "./dist/bin/index.js",
"files": [
"bin/**/*",
"dist/**/*",
@@ -37,8 +37,8 @@
"!src/**/*"
],
"scripts": {
- "check": "jiti ../../../scripts/prepare/check.ts",
- "prep": "jiti ../../../scripts/prepare/bundle.ts"
+ "check": "jiti ../../../scripts/check/check-package.ts",
+ "prep": "jiti ../../../scripts/build/build-package.ts"
},
"dependencies": {
"@storybook/codemod": "workspace:*",
@@ -72,15 +72,5 @@
"publishConfig": {
"access": "public"
},
- "bundler": {
- "entries": [
- "./src/index.ts",
- "./src/bin/index.ts"
- ],
- "formats": [
- "cjs"
- ],
- "platform": "node"
- },
- "gitHead": "ce6a1e4a8d5ad69c699021a0b183df89cfc7b684"
+ "gitHead": "a8e7fd8a655c69780bc20b9749d2699e45beae16"
}
diff --git a/code/lib/cli-storybook/src/add.test.ts b/code/lib/cli-storybook/src/add.test.ts
index 82625573ece2..1bd09a624feb 100644
--- a/code/lib/cli-storybook/src/add.test.ts
+++ b/code/lib/cli-storybook/src/add.test.ts
@@ -31,10 +31,10 @@ const MockedPostInstall = vi.hoisted(() => {
postinstallAddon: vi.fn(),
};
});
-const MockWrapRequireUtils = vi.hoisted(() => {
+const MockWrapGetAbsolutePathUtils = vi.hoisted(() => {
return {
- getRequireWrapperName: vi.fn(),
- wrapValueWithRequireWrapper: vi.fn(),
+ getAbsolutePathWrapperName: vi.fn(),
+ wrapValueWithGetAbsolutePathWrapper: vi.fn(),
};
});
const MockedConsole = {
@@ -66,8 +66,8 @@ vi.mock('storybook/internal/csf-tools', () => {
vi.mock('./postinstallAddon', () => {
return MockedPostInstall;
});
-vi.mock('./automigrate/fixes/wrap-require-utils', () => {
- return MockWrapRequireUtils;
+vi.mock('./automigrate/fixes/wrap-getAbsolutePath-utils', () => {
+ return MockWrapGetAbsolutePathUtils;
});
vi.mock('./automigrate/helpers/mainConfigFile', () => {
return MockedMainConfigFileHelper;
@@ -148,29 +148,29 @@ describe('add (extra)', () => {
beforeEach(() => {
vi.clearAllMocks();
});
- test('should not add a "wrap require" to the addon when not needed', async () => {
+ test('should not add a "wrap getAbsolutePath" to the addon when not needed', async () => {
MockedConfig.getFieldNode.mockReturnValue({});
- MockWrapRequireUtils.getRequireWrapperName.mockReturnValue(null);
+ MockWrapGetAbsolutePathUtils.getAbsolutePathWrapperName.mockReturnValue(null);
await add(
'@storybook/addon-docs',
{ packageManager: 'npm', skipPostinstall: true },
MockedConsole
);
- expect(MockWrapRequireUtils.wrapValueWithRequireWrapper).not.toHaveBeenCalled();
+ expect(MockWrapGetAbsolutePathUtils.wrapValueWithGetAbsolutePathWrapper).not.toHaveBeenCalled();
expect(MockedConfig.appendValueToArray).toHaveBeenCalled();
expect(MockedConfig.appendNodeToArray).not.toHaveBeenCalled();
});
- test('should add a "wrap require" to the addon when applicable', async () => {
+ test('should add a "wrap getAbsolutePath" to the addon when applicable', async () => {
MockedConfig.getFieldNode.mockReturnValue({});
- MockWrapRequireUtils.getRequireWrapperName.mockReturnValue('require');
+ MockWrapGetAbsolutePathUtils.getAbsolutePathWrapperName.mockReturnValue('getAbsolutePath');
await add(
'@storybook/addon-docs',
{ packageManager: 'npm', skipPostinstall: true },
MockedConsole
);
- expect(MockWrapRequireUtils.wrapValueWithRequireWrapper).toHaveBeenCalled();
+ expect(MockWrapGetAbsolutePathUtils.wrapValueWithGetAbsolutePathWrapper).toHaveBeenCalled();
expect(MockedConfig.appendValueToArray).not.toHaveBeenCalled();
expect(MockedConfig.appendNodeToArray).toHaveBeenCalled();
});
diff --git a/code/lib/cli-storybook/src/add.ts b/code/lib/cli-storybook/src/add.ts
index 0418cccc6fc8..3d6fb548a7f2 100644
--- a/code/lib/cli-storybook/src/add.ts
+++ b/code/lib/cli-storybook/src/add.ts
@@ -14,9 +14,9 @@ import SemVer from 'semver';
import { dedent } from 'ts-dedent';
import {
- getRequireWrapperName,
- wrapValueWithRequireWrapper,
-} from './automigrate/fixes/wrap-require-utils';
+ getAbsolutePathWrapperName,
+ wrapValueWithGetAbsolutePathWrapper,
+} from './automigrate/fixes/wrap-getAbsolutePath-utils';
import { getStorybookData } from './automigrate/helpers/mainConfigFile';
import { postinstallAddon } from './postinstallAddon';
@@ -47,13 +47,6 @@ export const getVersionSpecifier = (addon: string) => {
return [addon, undefined] as const;
};
-const requireMain = (configDir: string) => {
- const absoluteConfigDir = isAbsolute(configDir) ? configDir : join(process.cwd(), configDir);
- const mainFile = join(absoluteConfigDir, 'main');
-
- return serverRequire(mainFile) ?? {};
-};
-
const checkInstalled = (addonName: string, main: StorybookConfigRaw) => {
const existingAddon = main.addons?.find((entry: string | { name: string }) => {
const name = typeof entry === 'string' ? entry : entry.name;
@@ -176,10 +169,10 @@ export async function add(
logger.log(`Adding '${addon}' to the "addons" field in ${mainConfigPath}`);
const mainConfigAddons = main.getFieldNode(['addons']);
- if (mainConfigAddons && getRequireWrapperName(main) !== null) {
+ if (mainConfigAddons && getAbsolutePathWrapperName(main) !== null) {
const addonNode = main.valueToNode(addonName);
main.appendNodeToArray(['addons'], addonNode as any);
- wrapValueWithRequireWrapper(main, addonNode as any);
+ wrapValueWithGetAbsolutePathWrapper(main, addonNode as any);
} else {
main.appendValueToArray(['addons'], addonName);
}
diff --git a/code/lib/cli-storybook/src/autoblock/block-experimental-addon-test.test.ts b/code/lib/cli-storybook/src/autoblock/block-experimental-addon-test.test.ts
index b4e471f6a060..bfbcd5d71efa 100644
--- a/code/lib/cli-storybook/src/autoblock/block-experimental-addon-test.test.ts
+++ b/code/lib/cli-storybook/src/autoblock/block-experimental-addon-test.test.ts
@@ -1,7 +1,7 @@
import { beforeEach, describe, expect, test, vi } from 'vitest';
import semver from 'semver';
-import dedent from 'ts-dedent';
+import { dedent } from 'ts-dedent';
import { blocker } from './block-experimental-addon-test';
diff --git a/code/lib/cli-storybook/src/autoblock/block-major-version.ts b/code/lib/cli-storybook/src/autoblock/block-major-version.ts
index 00b5a93b81d3..03c390321fa3 100644
--- a/code/lib/cli-storybook/src/autoblock/block-major-version.ts
+++ b/code/lib/cli-storybook/src/autoblock/block-major-version.ts
@@ -79,7 +79,7 @@ export const blocker = createBlocker({
Your Storybook version (v${data.currentVersion}) is newer than the target release (v${versions.storybook}).Downgrading is not supported.
Please follow the 8.0 migration guide to upgrade to v8.0 first.
`,
- link: 'https://storybook.js.org/docs/8/migration-guide',
+ link: 'https://storybook.js.org/docs/8/migration-guide?ref=upgrade',
};
}
@@ -94,7 +94,7 @@ export const blocker = createBlocker({
You can upgrade to version ${nextMajor} by running:
${CLI_COLORS.info(`npx storybook@${nextMajor} upgrade`)}
`,
- link: `https://storybook.js.org/docs/${nextMajor}/migration-guide`,
+ link: `https://storybook.js.org/docs/${nextMajor}/migration-guide?ref=upgrade`,
};
}
diff --git a/code/lib/cli-storybook/src/autoblock/utils.ts b/code/lib/cli-storybook/src/autoblock/utils.ts
index 0b5259baf920..60e33b253017 100644
--- a/code/lib/cli-storybook/src/autoblock/utils.ts
+++ b/code/lib/cli-storybook/src/autoblock/utils.ts
@@ -31,7 +31,8 @@ export async function findOutdatedPackage>(
const list = await Promise.all(
typedKeys(minimalVersionsMap).map(async (packageName) => ({
packageName,
- installedVersion: options.packageManager.getModulePackageJSON(packageName)?.version ?? null,
+ installedVersion:
+ (await options.packageManager.getModulePackageJSON(packageName))?.version ?? null,
minimumVersion: minimalVersionsMap[packageName],
}))
);
diff --git a/code/lib/cli-storybook/src/automigrate/fixes/__test__/main-config-with-wrappers.js b/code/lib/cli-storybook/src/automigrate/fixes/__test__/main-config-with-wrappers.js
index e736d9d23c31..654d9f84b491 100644
--- a/code/lib/cli-storybook/src/automigrate/fixes/__test__/main-config-with-wrappers.js
+++ b/code/lib/cli-storybook/src/automigrate/fixes/__test__/main-config-with-wrappers.js
@@ -1,6 +1,8 @@
import { dirname, join } from 'node:path';
+import { fileURLToPath } from 'node:url';
-const wrapForPnp = (packageName) => dirname(require.resolve(join(packageName, 'package.json')));
+const wrapForPnp = (packageName) =>
+ dirname(fileURLToPath(import.meta.resolve(join(packageName, 'package.json'))));
const config = {
stories: ['../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
diff --git a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts
index 2a6ce52f47d7..d02b89184685 100644
--- a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts
+++ b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts
@@ -6,7 +6,7 @@ import { logger } from 'storybook/internal/node-logger';
import { existsSync, readFileSync, writeFileSync } from 'fs';
import * as jscodeshift from 'jscodeshift';
import path from 'path';
-import dedent from 'ts-dedent';
+import { dedent } from 'ts-dedent';
import {
addonA11yAddonTest,
diff --git a/code/lib/cli-storybook/src/automigrate/fixes/addon-experimental-test.test.ts b/code/lib/cli-storybook/src/automigrate/fixes/addon-experimental-test.test.ts
index 823a0fd0eefc..dc13e570a5de 100644
--- a/code/lib/cli-storybook/src/automigrate/fixes/addon-experimental-test.test.ts
+++ b/code/lib/cli-storybook/src/automigrate/fixes/addon-experimental-test.test.ts
@@ -152,7 +152,7 @@ describe('addon-experimental-test fix', () => {
describe('run function', () => {
it('should replace @storybook/experimental-addon-test in files', async () => {
- packageManager.getModulePackageJSON.mockImplementation((packageName: string) => {
+ packageManager.getModulePackageJSON.mockImplementation(async (packageName: string) => {
if (packageName === '@storybook/experimental-addon-test') {
return {
version: '8.6.0',
@@ -229,7 +229,7 @@ describe('addon-experimental-test fix', () => {
});
it('should replace @storybook/experimental-addon-test in files (dependency)', async () => {
- packageManager.getModulePackageJSON.mockImplementation((packageName: string) => {
+ packageManager.getModulePackageJSON.mockImplementation(async (packageName: string) => {
if (packageName === '@storybook/experimental-addon-test') {
return {
version: '8.6.0',
diff --git a/code/lib/cli-storybook/src/automigrate/fixes/addon-storysource-code-panel.test.ts b/code/lib/cli-storybook/src/automigrate/fixes/addon-storysource-code-panel.test.ts
index 338e2b845382..be8f6ffb6be8 100644
--- a/code/lib/cli-storybook/src/automigrate/fixes/addon-storysource-code-panel.test.ts
+++ b/code/lib/cli-storybook/src/automigrate/fixes/addon-storysource-code-panel.test.ts
@@ -3,7 +3,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { JsPackageManager } from 'storybook/internal/common';
import type { StorybookConfigRaw } from 'storybook/internal/types';
-import dedent from 'ts-dedent';
+import { dedent } from 'ts-dedent';
import { add } from '../../add';
import type { CheckOptions, RunOptions } from '../types';
diff --git a/code/lib/cli-storybook/src/automigrate/fixes/index.ts b/code/lib/cli-storybook/src/automigrate/fixes/index.ts
index b45c6c88a503..744bb66f33e1 100644
--- a/code/lib/cli-storybook/src/automigrate/fixes/index.ts
+++ b/code/lib/cli-storybook/src/automigrate/fixes/index.ts
@@ -14,7 +14,7 @@ import { removeEssentials } from './remove-essentials';
import { rendererToFramework } from './renderer-to-framework';
import { rnstorybookConfig } from './rnstorybook-config';
import { upgradeStorybookRelatedDependencies } from './upgrade-storybook-related-dependencies';
-import { wrapRequire } from './wrap-require';
+import { wrapGetAbsolutePath } from './wrap-getAbsolutePath';
export * from '../types';
@@ -33,7 +33,7 @@ export const allFixes: Fix[] = [
removeEssentials,
addonA11yParameters,
removeDocsAutodocs,
- wrapRequire,
+ wrapGetAbsolutePath,
];
export const initFixes: Fix[] = [eslintPlugin];
diff --git a/code/lib/cli-storybook/src/automigrate/fixes/wrap-require-utils.ts b/code/lib/cli-storybook/src/automigrate/fixes/wrap-getAbsolutePath-utils.ts
similarity index 58%
rename from code/lib/cli-storybook/src/automigrate/fixes/wrap-require-utils.ts
rename to code/lib/cli-storybook/src/automigrate/fixes/wrap-getAbsolutePath-utils.ts
index f96aed08b9b6..af5751811d09 100644
--- a/code/lib/cli-storybook/src/automigrate/fixes/wrap-require-utils.ts
+++ b/code/lib/cli-storybook/src/automigrate/fixes/wrap-getAbsolutePath-utils.ts
@@ -1,7 +1,8 @@
import { types as t } from 'storybook/internal/babel';
import type { ConfigFile } from 'storybook/internal/csf-tools';
-const defaultRequireWrapperName = 'getAbsolutePath';
+const PREFERRED_GET_ABSOLUTE_PATH_WRAPPER_NAME = 'getAbsolutePath';
+const ALTERNATIVE_GET_ABSOLUTE_PATH_WRAPPER_NAME = 'wrapForPnp';
/**
* Checks if the following node declarations exists in the main config file.
@@ -25,42 +26,42 @@ export function doesVariableOrFunctionDeclarationExist(node: t.Node, name: strin
}
/**
- * Wrap a value with require wrapper.
+ * Wrap a value with getAbsolutePath wrapper.
*
* @example
*
* ```ts
* // Before
* {
- * framework: 'react';
+ * framework: '@storybook/react-vite';
* }
*
* // After
* {
- * framework: wrapForPnp('react');
+ * framework: getAbsolutePath('@storybook/react-vite');
* }
* ```
*/
-function getReferenceToRequireWrapper(config: ConfigFile, value: string) {
+function getReferenceToGetAbsolutePathWrapper(config: ConfigFile, value: string) {
return t.callExpression(
- t.identifier(getRequireWrapperName(config) ?? defaultRequireWrapperName),
+ t.identifier(getAbsolutePathWrapperName(config) ?? PREFERRED_GET_ABSOLUTE_PATH_WRAPPER_NAME),
[t.stringLiteral(value)]
);
}
/**
- * Returns the name of the require wrapper function if it exists in the main config file.
+ * Returns the name of the getAbsolutePath wrapper function if it exists in the main config file.
*
- * @returns Name of the require wrapper function.
+ * @returns Name of the getAbsolutePath wrapper function (e.g. `getAbsolutePath`).
*/
-export function getRequireWrapperName(config: ConfigFile) {
+export function getAbsolutePathWrapperName(config: ConfigFile) {
const declarationName = config
.getBodyDeclarations()
.flatMap((node) =>
- doesVariableOrFunctionDeclarationExist(node, 'wrapForPnp')
- ? ['wrapForPnp']
- : doesVariableOrFunctionDeclarationExist(node, defaultRequireWrapperName)
- ? [defaultRequireWrapperName]
+ doesVariableOrFunctionDeclarationExist(node, ALTERNATIVE_GET_ABSOLUTE_PATH_WRAPPER_NAME)
+ ? [ALTERNATIVE_GET_ABSOLUTE_PATH_WRAPPER_NAME]
+ : doesVariableOrFunctionDeclarationExist(node, PREFERRED_GET_ABSOLUTE_PATH_WRAPPER_NAME)
+ ? [PREFERRED_GET_ABSOLUTE_PATH_WRAPPER_NAME]
: []
);
@@ -71,8 +72,8 @@ export function getRequireWrapperName(config: ConfigFile) {
return null;
}
-/** Check if the node needs to be wrapped with require wrapper. */
-export function isRequireWrapperNecessary(
+/** Check if the node needs to be wrapped with getAbsolutePath wrapper. */
+export function isGetAbsolutePathWrapperNecessary(
node: t.Node,
cb: (node: t.StringLiteral | t.ObjectProperty | t.ArrayExpression) => void = () => {}
) {
@@ -96,7 +97,7 @@ export function isRequireWrapperNecessary(
if (
t.isArrayExpression(node) &&
- node.elements.some((element) => element && isRequireWrapperNecessary(element))
+ node.elements.some((element) => element && isGetAbsolutePathWrapperNecessary(element))
) {
cb(node);
return true;
@@ -106,11 +107,11 @@ export function isRequireWrapperNecessary(
}
/**
- * Get all fields that need to be wrapped with require wrapper.
+ * Get all fields that need to be wrapped with getAbsolutePath wrapper.
*
- * @returns Array of fields that need to be wrapped with require wrapper.
+ * @returns Array of fields that need to be wrapped with getAbsolutePath wrapper.
*/
-export function getFieldsForRequireWrapper(config: ConfigFile): t.Node[] {
+export function getFieldsForGetAbsolutePathWrapper(config: ConfigFile): t.Node[] {
const frameworkNode = config.getFieldNode(['framework']);
const builderNode = config.getFieldNode(['core', 'builder']);
const rendererNode = config.getFieldNode(['core', 'renderer']);
@@ -133,16 +134,16 @@ export function getFieldsForRequireWrapper(config: ConfigFile): t.Node[] {
*
* ```ts
* function getAbsolutePath(value) {
- * return dirname(require.resolve(join(value, 'package.json')));
+ * return dirname(fileURLToPath(import.meta.resolve(join(value, 'package.json'))));
* }
* ```
*/
-export function getRequireWrapperAsCallExpression(
+export function getAbsolutePathWrapperAsCallExpression(
isConfigTypescript: boolean
): t.FunctionDeclaration {
const functionDeclaration = {
...t.functionDeclaration(
- t.identifier(defaultRequireWrapperName),
+ t.identifier(PREFERRED_GET_ABSOLUTE_PATH_WRAPPER_NAME),
[
{
...t.identifier('value'),
@@ -154,11 +155,19 @@ export function getRequireWrapperAsCallExpression(
t.blockStatement([
t.returnStatement(
t.callExpression(t.identifier('dirname'), [
- t.callExpression(t.memberExpression(t.identifier('require'), t.identifier('resolve')), [
- t.callExpression(t.identifier('join'), [
- t.identifier('value'),
- t.stringLiteral('package.json'),
- ]),
+ t.callExpression(t.identifier('fileURLToPath'), [
+ t.callExpression(
+ t.memberExpression(
+ t.metaProperty(t.identifier('import'), t.identifier('meta')),
+ t.identifier('resolve')
+ ),
+ [
+ t.callExpression(t.identifier('join'), [
+ t.identifier('value'),
+ t.stringLiteral('package.json'),
+ ]),
+ ]
+ ),
]),
])
),
@@ -176,10 +185,10 @@ export function getRequireWrapperAsCallExpression(
return functionDeclaration;
}
-export function wrapValueWithRequireWrapper(config: ConfigFile, node: t.Node) {
- isRequireWrapperNecessary(node, (n) => {
+export function wrapValueWithGetAbsolutePathWrapper(config: ConfigFile, node: t.Node) {
+ isGetAbsolutePathWrapperNecessary(node, (n) => {
if (t.isStringLiteral(n)) {
- const wrapperNode = getReferenceToRequireWrapper(config, n.value);
+ const wrapperNode = getReferenceToGetAbsolutePathWrapper(config, n.value);
Object.keys(n).forEach((k) => {
delete n[k as keyof typeof n];
});
@@ -189,13 +198,13 @@ export function wrapValueWithRequireWrapper(config: ConfigFile, node: t.Node) {
}
if (t.isObjectProperty(n) && t.isStringLiteral(n.value)) {
- n.value = getReferenceToRequireWrapper(config, n.value.value) as any;
+ n.value = getReferenceToGetAbsolutePathWrapper(config, n.value.value) as any;
}
if (t.isArrayExpression(n)) {
n.elements.forEach((element) => {
- if (element && isRequireWrapperNecessary(element)) {
- wrapValueWithRequireWrapper(config, element);
+ if (element && isGetAbsolutePathWrapperNecessary(element)) {
+ wrapValueWithGetAbsolutePathWrapper(config, element);
}
});
}
diff --git a/code/lib/cli-storybook/src/automigrate/fixes/wrap-require.test.ts b/code/lib/cli-storybook/src/automigrate/fixes/wrap-getAbsolutePath.test.ts
similarity index 67%
rename from code/lib/cli-storybook/src/automigrate/fixes/wrap-require.test.ts
rename to code/lib/cli-storybook/src/automigrate/fixes/wrap-getAbsolutePath.test.ts
index 730f1bb6d307..b5c102222350 100644
--- a/code/lib/cli-storybook/src/automigrate/fixes/wrap-require.test.ts
+++ b/code/lib/cli-storybook/src/automigrate/fixes/wrap-getAbsolutePath.test.ts
@@ -4,7 +4,7 @@ import { describe, expect, it, vi } from 'vitest';
import * as detect from 'storybook/internal/cli';
import type { RunOptions } from '../types';
-import { type WrapRequireRunOptions, wrapRequire } from './wrap-require';
+import { type WrapGetAbsolutePathRunOptions, wrapGetAbsolutePath } from './wrap-getAbsolutePath';
vi.mock('storybook/internal/cli', async (importOriginal) => ({
...(await importOriginal()),
@@ -16,18 +16,18 @@ vi.mock('node:fs/promises', async (importOriginal) => ({
writeFile: vi.fn(),
}));
-describe('wrapRequire', () => {
+describe('wrapGetAbsolutePath', () => {
describe('check', () => {
it('should return null if not in a monorepo and pnp is not enabled', async () => {
(detect.detectPnp as any as MockInstance).mockResolvedValue(false);
- const check = wrapRequire.check({
+ const check = wrapGetAbsolutePath.check({
packageManager: {
isStorybookInMonorepo: () => false,
},
storybookVersion: '7.0.0',
mainConfigPath: require.resolve('./__test__/main-config-without-wrappers.js'),
- } as RunOptions);
+ } as RunOptions);
await expect(check).resolves.toBeNull();
});
@@ -35,13 +35,13 @@ describe('wrapRequire', () => {
it('should return the configuration object if in a pnp environment', async () => {
(detect.detectPnp as any as MockInstance).mockResolvedValue(true);
- const check = wrapRequire.check({
+ const check = wrapGetAbsolutePath.check({
packageManager: {
isStorybookInMonorepo: () => false,
},
storybookVersion: '7.0.0',
mainConfigPath: require.resolve('./__test__/main-config-without-wrappers.js'),
- } as RunOptions);
+ } as RunOptions);
await expect(check).resolves.toEqual({
isConfigTypescript: false,
@@ -54,13 +54,13 @@ describe('wrapRequire', () => {
it('should return the configuration object if in a monorepo environment', async () => {
(detect.detectPnp as any as MockInstance).mockResolvedValue(false);
- const check = wrapRequire.check({
+ const check = wrapGetAbsolutePath.check({
packageManager: {
isStorybookInMonorepo: () => true,
},
storybookVersion: '7.0.0',
mainConfigPath: require.resolve('./__test__/main-config-without-wrappers.js'),
- } as RunOptions);
+ } as RunOptions);
await expect(check).resolves.toEqual({
isConfigTypescript: false,
@@ -73,13 +73,13 @@ describe('wrapRequire', () => {
it('should return null, if all fields have the require wrapper', async () => {
(detect.detectPnp as any as MockInstance).mockResolvedValue(true);
- const check = wrapRequire.check({
+ const check = wrapGetAbsolutePath.check({
packageManager: {
isStorybookInMonorepo: () => true,
},
storybookVersion: '7.0.0',
mainConfigPath: require.resolve('./__test__/main-config-with-wrappers.js'),
- } as RunOptions);
+ } as RunOptions);
await expect(check).resolves.toBeNull();
});
@@ -87,45 +87,44 @@ describe('wrapRequire', () => {
describe('run', () => {
it('should wrap the require wrapper', async () => {
- await wrapRequire.run?.({
+ await wrapGetAbsolutePath.run?.({
mainConfigPath: require.resolve('./__test__/main-config-without-wrappers.js'),
result: {
isConfigTypescript: false,
},
- } as RunOptions);
+ } as RunOptions);
const writeFile = vi.mocked((await import('node:fs/promises')).writeFile);
const call = writeFile.mock.calls[0];
expect(call[1]).toMatchInlineSnapshot(`
- "import { createRequire } from "node:module";
- import { dirname, join } from "node:path";
- const require = createRequire(import.meta.url);
- const config = {
- stories: ['../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
- addons: [
- {
- name: getAbsolutePath("@chromatic-com/storybook"),
- options: {},
- },
- getAbsolutePath("@storybook/addon-vitest"),
- ],
- framework: {
- name: getAbsolutePath("@storybook/angular"),
- options: {},
- },
- docs: {
- autodocs: 'tag',
- },
- };
- export default config;
-
- function getAbsolutePath(value) {
- return dirname(require.resolve(join(value, "package.json")));
- }
- "
-`);
+ "import { fileURLToPath } from "node:url";
+ import { dirname, join } from "node:path";
+ const config = {
+ stories: ['../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
+ addons: [
+ {
+ name: getAbsolutePath("@chromatic-com/storybook"),
+ options: {},
+ },
+ getAbsolutePath("@storybook/addon-vitest"),
+ ],
+ framework: {
+ name: getAbsolutePath("@storybook/angular"),
+ options: {},
+ },
+ docs: {
+ autodocs: 'tag',
+ },
+ };
+ export default config;
+
+ function getAbsolutePath(value) {
+ return dirname(fileURLToPath(import.meta.resolve(join(value, "package.json"))));
+ }
+ "
+ `);
});
});
});
diff --git a/code/lib/cli-storybook/src/automigrate/fixes/wrap-require.ts b/code/lib/cli-storybook/src/automigrate/fixes/wrap-getAbsolutePath.ts
similarity index 51%
rename from code/lib/cli-storybook/src/automigrate/fixes/wrap-require.ts
rename to code/lib/cli-storybook/src/automigrate/fixes/wrap-getAbsolutePath.ts
index d14ee015acbf..2f085ea1de38 100644
--- a/code/lib/cli-storybook/src/automigrate/fixes/wrap-require.ts
+++ b/code/lib/cli-storybook/src/automigrate/fixes/wrap-getAbsolutePath.ts
@@ -1,29 +1,28 @@
-import { types as t } from 'storybook/internal/babel';
import { detectPnp } from 'storybook/internal/cli';
import { readConfig } from 'storybook/internal/csf-tools';
+import { CommonJsConfigNotSupportedError } from 'storybook/internal/server-errors';
import { dedent } from 'ts-dedent';
import { updateMainConfig } from '../helpers/mainConfigFile';
import type { Fix } from '../types';
import {
- doesVariableOrFunctionDeclarationExist,
- getFieldsForRequireWrapper,
- getRequireWrapperAsCallExpression,
- getRequireWrapperName,
- isRequireWrapperNecessary,
- wrapValueWithRequireWrapper,
-} from './wrap-require-utils';
-
-export interface WrapRequireRunOptions {
+ getAbsolutePathWrapperAsCallExpression,
+ getAbsolutePathWrapperName,
+ getFieldsForGetAbsolutePathWrapper,
+ isGetAbsolutePathWrapperNecessary,
+ wrapValueWithGetAbsolutePathWrapper,
+} from './wrap-getAbsolutePath-utils';
+
+export interface WrapGetAbsolutePathRunOptions {
storybookVersion: string;
isStorybookInMonorepo: boolean;
isPnp: boolean;
isConfigTypescript: boolean;
}
-export const wrapRequire: Fix = {
- id: 'wrap-require',
+export const wrapGetAbsolutePath: Fix = {
+ id: 'wrap-getAbsolutePath',
link: 'https://storybook.js.org/docs/faq#how-do-i-fix-module-resolution-in-special-environments',
async check({ packageManager, storybookVersion, mainConfigPath }) {
@@ -40,7 +39,11 @@ export const wrapRequire: Fix = {
return null;
}
- if (!getFieldsForRequireWrapper(config).some((node) => isRequireWrapperNecessary(node))) {
+ if (
+ !getFieldsForGetAbsolutePathWrapper(config).some((node) =>
+ isGetAbsolutePathWrapperNecessary(node)
+ )
+ ) {
return null;
}
@@ -55,11 +58,11 @@ export const wrapRequire: Fix = {
async run({ dryRun, mainConfigPath, result }) {
await updateMainConfig({ dryRun: !!dryRun, mainConfigPath }, (mainConfig) => {
- getFieldsForRequireWrapper(mainConfig).forEach((node) => {
- wrapValueWithRequireWrapper(mainConfig, node);
+ getFieldsForGetAbsolutePathWrapper(mainConfig).forEach((node) => {
+ wrapValueWithGetAbsolutePathWrapper(mainConfig, node);
});
- if (getRequireWrapperName(mainConfig) === null) {
+ if (getAbsolutePathWrapperName(mainConfig) === null) {
if (
mainConfig?.fileName?.endsWith('.cjs') ||
mainConfig?.fileName?.endsWith('.cts') ||
@@ -67,29 +70,14 @@ export const wrapRequire: Fix = {
mainConfig?.fileName?.endsWith('.ctsx') ||
mainConfig._code.includes('module.exports')
) {
- mainConfig.setRequireImport(['dirname', 'join'], 'node:path');
+ throw new CommonJsConfigNotSupportedError();
} else {
mainConfig.setImport(['dirname', 'join'], 'node:path');
- mainConfig.setImport(['createRequire'], 'node:module');
-
- // Continue here
- const hasRequire = mainConfig
- .getBodyDeclarations()
- .some((node) => doesVariableOrFunctionDeclarationExist(node, 'require'));
-
- if (!hasRequire) {
- const body = mainConfig._ast.program.body;
- const lastImportIndex = body.findLastIndex((node) => t.isImportDeclaration(node));
- const requireDeclaration = t.variableDeclaration('const', [
- t.variableDeclarator(
- t.identifier('require'),
- t.callExpression(t.identifier('createRequire'), [t.identifier('import.meta.url')])
- ),
- ]);
- body.splice(lastImportIndex + 1, 0, requireDeclaration);
- }
+ mainConfig.setImport(['fileURLToPath'], 'node:url');
}
- mainConfig.setBodyDeclaration(getRequireWrapperAsCallExpression(result.isConfigTypescript));
+ mainConfig.setBodyDeclaration(
+ getAbsolutePathWrapperAsCallExpression(result.isConfigTypescript)
+ );
}
});
},
diff --git a/code/lib/cli-storybook/src/automigrate/helpers/addon-a11y-parameters.test.ts b/code/lib/cli-storybook/src/automigrate/helpers/addon-a11y-parameters.test.ts
index 5d7dbfb5b951..059330a1f0a6 100644
--- a/code/lib/cli-storybook/src/automigrate/helpers/addon-a11y-parameters.test.ts
+++ b/code/lib/cli-storybook/src/automigrate/helpers/addon-a11y-parameters.test.ts
@@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest';
import { printConfig, printCsf } from 'storybook/internal/csf-tools';
-import dedent from 'ts-dedent';
+import { dedent } from 'ts-dedent';
import {
transformPreviewA11yParameters,
diff --git a/code/lib/cli-storybook/src/automigrate/helpers/logMigrationSummary.test.ts b/code/lib/cli-storybook/src/automigrate/helpers/logMigrationSummary.test.ts
index 1dcdce0c2526..1c4f22ff5bc7 100644
--- a/code/lib/cli-storybook/src/automigrate/helpers/logMigrationSummary.test.ts
+++ b/code/lib/cli-storybook/src/automigrate/helpers/logMigrationSummary.test.ts
@@ -111,7 +111,7 @@ describe('logMigrationSummary', () => {
The automigrations try to migrate common patterns in your project, but might not contain everything needed to migrate to the latest version of Storybook.
- Please check the changelog and migration guide for manual migrations and more information: https://storybook.js.org/docs/releases/migration-guide
+ Please check the changelog and migration guide for manual migrations and more information: https://storybook.js.org/docs/releases/migration-guide?ref=upgrade
And reach out on Discord if you need help: https://discord.gg/storybook"
`);
});
@@ -132,7 +132,7 @@ describe('logMigrationSummary', () => {
The automigrations try to migrate common patterns in your project, but might not contain everything needed to migrate to the latest version of Storybook.
- Please check the changelog and migration guide for manual migrations and more information: https://storybook.js.org/docs/releases/migration-guide
+ Please check the changelog and migration guide for manual migrations and more information: https://storybook.js.org/docs/releases/migration-guide?ref=upgrade
And reach out on Discord if you need help: https://discord.gg/storybook"
`);
});
@@ -153,7 +153,7 @@ describe('logMigrationSummary', () => {
The automigrations try to migrate common patterns in your project, but might not contain everything needed to migrate to the latest version of Storybook.
- Please check the changelog and migration guide for manual migrations and more information: https://storybook.js.org/docs/releases/migration-guide
+ Please check the changelog and migration guide for manual migrations and more information: https://storybook.js.org/docs/releases/migration-guide?ref=upgrade
And reach out on Discord if you need help: https://discord.gg/storybook"
`);
});
diff --git a/code/lib/cli-storybook/src/automigrate/helpers/logMigrationSummary.ts b/code/lib/cli-storybook/src/automigrate/helpers/logMigrationSummary.ts
index 6e676dcd53e4..527b15db41ad 100644
--- a/code/lib/cli-storybook/src/automigrate/helpers/logMigrationSummary.ts
+++ b/code/lib/cli-storybook/src/automigrate/helpers/logMigrationSummary.ts
@@ -63,7 +63,7 @@ export function logMigrationSummary({
The automigrations try to migrate common patterns in your project, but might not contain everything needed to migrate to the latest version of Storybook.
Please check the changelog and migration guide for manual migrations and more information: ${picocolors.yellow(
- 'https://storybook.js.org/docs/releases/migration-guide'
+ 'https://storybook.js.org/docs/releases/migration-guide?ref=upgrade'
)}
And reach out on Discord if you need help: ${picocolors.yellow('https://discord.gg/storybook')}
`);
diff --git a/code/lib/cli-storybook/src/automigrate/helpers/mainConfigFile.ts b/code/lib/cli-storybook/src/automigrate/helpers/mainConfigFile.ts
index 02c41ea64bd8..33830078ec7a 100644
--- a/code/lib/cli-storybook/src/automigrate/helpers/mainConfigFile.ts
+++ b/code/lib/cli-storybook/src/automigrate/helpers/mainConfigFile.ts
@@ -128,7 +128,6 @@ export const getStorybookData = async ({
configDir: userDefinedConfigDir,
cwd,
packageManagerName,
- cache = false,
}: {
configDir?: string;
cwd?: string;
@@ -141,14 +140,14 @@ export const getStorybookData = async ({
version: storybookVersionSpecifier,
configDir: configDirFromScript,
previewConfigPath,
- } = getStorybookInfo(userDefinedConfigDir);
+ } = await getStorybookInfo(userDefinedConfigDir);
const configDir = userDefinedConfigDir || configDirFromScript || '.storybook';
logger.debug('Loading main config...');
let mainConfig: StorybookConfigRaw;
try {
- mainConfig = (await loadMainConfig({ configDir, noCache: !cache, cwd })) as StorybookConfigRaw;
+ mainConfig = (await loadMainConfig({ configDir, cwd })) as StorybookConfigRaw;
} catch (err) {
throw new Error(
dedent`Unable to find or evaluate ${picocolors.blue(mainConfigPath)}: ${String(err)}`
diff --git a/code/lib/cli-storybook/src/automigrate/index.test.ts b/code/lib/cli-storybook/src/automigrate/index.test.ts
index 0c8fa5b32da7..4bbf7b180ffc 100644
--- a/code/lib/cli-storybook/src/automigrate/index.test.ts
+++ b/code/lib/cli-storybook/src/automigrate/index.test.ts
@@ -57,7 +57,10 @@ vi.mock('prompts', () => {
});
class PackageManager implements Partial {
- getModulePackageJSON(packageName: string, basePath?: string | undefined): PackageJson | null {
+ async getModulePackageJSON(
+ packageName: string,
+ basePath?: string | undefined
+ ): Promise {
return getModulePackageJSON(packageName, basePath);
}
}
diff --git a/code/lib/cli-storybook/src/bin/index.ts b/code/lib/cli-storybook/src/bin/index.ts
index 04232bb00865..01e202cd8487 100644
--- a/code/lib/cli-storybook/src/bin/index.ts
+++ b/code/lib/cli-storybook/src/bin/index.ts
@@ -1,255 +1,20 @@
-import { globalSettings } from 'storybook/internal/cli';
-import {
- HandledError,
- JsPackageManagerFactory,
- isCI,
- optionalEnvToBoolean,
- removeAddon as remove,
- versions,
-} from 'storybook/internal/common';
-import { withTelemetry } from 'storybook/internal/core-server';
-import { CLI_COLORS, logTracker, logger, prompt } from 'storybook/internal/node-logger';
-import { addToGlobalContext, telemetry } from 'storybook/internal/telemetry';
+#!/usr/bin/env node
+import { logger } from 'storybook/internal/node-logger';
-import { program } from 'commander';
-import envinfo from 'envinfo';
-import leven from 'leven';
-import picocolors from 'picocolors';
+import { dedent } from 'ts-dedent';
-import { version } from '../../package.json';
-import { add } from '../add';
-import { doAutomigrate } from '../automigrate';
-import { doctor } from '../doctor';
-import { link } from '../link';
-import { migrate } from '../migrate';
-import { sandbox } from '../sandbox';
-import { type UpgradeOptions, upgrade } from '../upgrade';
+const [majorNodeVersion, minorNodeVersion] = process.versions.node.split('.').map(Number);
-addToGlobalContext('cliVersion', versions.storybook);
-
-// Return a failed exit code but write the logs to a file first
-const handleCommandFailure = async (error: unknown): Promise => {
- if (!(error instanceof HandledError)) {
- logger.error(String(error));
- }
-
- const logFile = await logTracker.writeToFile();
- logger.log(`Storybook debug logs can be found at: ${logFile}`);
- logger.outro('');
- process.exit(1);
-};
-
-const command = (name: string) =>
- program
- .command(name)
- .option(
- '--disable-telemetry',
- 'Disable sending telemetry data',
- optionalEnvToBoolean(process.env.STORYBOOK_DISABLE_TELEMETRY)
- )
- .option('--debug', 'Get more logs in debug mode', false)
- .option('--enable-crash-reports', 'Enable sending crash reports to telemetry data')
- .option('--write-logs', 'Write all debug logs to a file at the end of the run')
- .option('--loglevel ', 'Define log level', 'info')
- .hook('preAction', async (self) => {
- try {
- const options = self.opts();
- if (options.loglevel) {
- logger.setLogLevel(options.loglevel);
- }
-
- if (options.writeLogs) {
- logTracker.enableLogWriting();
- }
-
- await globalSettings();
- } catch (e) {
- logger.error('Error loading global settings:\n' + String(e));
- }
- })
- .hook('postAction', async () => {
- if (logTracker.shouldWriteLogsToFile) {
- const logFile = await logTracker.writeToFile();
- logger.log(`Storybook debug logs can be found at: ${logFile}`);
- logger.outro(CLI_COLORS.success('Done!'));
- }
- });
-
-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)',
- !isCI() && !optionalEnvToBoolean(process.env.IN_STORYBOOK_SANDBOX)
- )
- .option(
- '--no-dev',
- 'Complete the initialization of Storybook without launching the Storybook development server'
- );
-
-command('add ')
- .description('Add an addon to your Storybook')
- .option(
- '--package-manager ',
- 'Force package manager for installing dependencies'
- )
- .option('-c, --config-dir ', 'Directory where to load Storybook configurations from')
- .option('--skip-install', 'Skip installing deps')
- .option('-s --skip-postinstall', 'Skip package specific postinstall config modifications')
- .option('-y --yes', 'Skip prompting the user')
- .option('--skip-doctor', 'Skip doctor check')
- .action((addonName: string, options: any) => add(addonName, options));
-
-command('remove ')
- .description('Remove an addon from your Storybook')
- .option(
- '--package-manager ',
- 'Force package manager for installing dependencies'
- )
- .option('-c, --config-dir ', 'Directory where to load Storybook configurations from')
- .option('-s --skip-install', 'Skip installing deps')
- .action((addonName: string, options: any) =>
- withTelemetry('remove', { cliOptions: options }, async () => {
- const packageManager = JsPackageManagerFactory.getPackageManager({
- configDir: options.configDir,
- force: options.packageManager,
- });
- await remove(addonName, {
- configDir: options.configDir,
- packageManager,
- skipInstall: options.skipInstall,
- });
- if (!options.disableTelemetry) {
- await telemetry('remove', { addon: addonName, source: 'cli' });
- }
- })
+if (
+ majorNodeVersion < 20 ||
+ (majorNodeVersion === 20 && minorNodeVersion < 19) ||
+ (majorNodeVersion === 22 && minorNodeVersion < 12)
+) {
+ logger.error(
+ dedent`To run Storybook, you need Node.js version 20.19+ or 22.12+.
+ You are currently running Node.js ${process.version}. Please upgrade your Node.js installation.`
);
-
-command('upgrade')
- .description(`Upgrade your Storybook packages to v${versions.storybook}`)
- .option(
- '--package-manager ',
- 'Force package manager for installing dependencies'
- )
- .option('-y --yes', 'Skip prompting the user')
- .option('-f --force', 'force the upgrade, skipping autoblockers')
- .option('-n --dry-run', 'Only check for upgrades, do not install')
- .option('-s --skip-check', 'Skip postinstall version and automigration checks')
- .option(
- '-c, --config-dir ',
- 'Directory(ies) where to load Storybook configurations from'
- )
- .action(async (options: UpgradeOptions) => {
- prompt.setPromptLibrary('clack');
- await upgrade(options).catch(handleCommandFailure);
- });
-
-command('info')
- .description('Prints debugging information about the local environment')
- .action(async () => {
- logger.log(picocolors.bold('\nStorybook Environment Info:'));
- const pkgManager = JsPackageManagerFactory.getPackageManager();
- const activePackageManager = pkgManager.type.replace(/\d/, ''); // 'yarn1' -> 'yarn'
- const output = await envinfo.run({
- System: ['OS', 'CPU', 'Shell'],
- Binaries: ['Node', 'Yarn', 'npm', 'pnpm'],
- Browsers: ['Chrome', 'Edge', 'Firefox', 'Safari'],
- npmPackages: '{@storybook/*,*storybook*,sb,chromatic}',
- npmGlobalPackages: '{@storybook/*,*storybook*,sb,chromatic}',
- });
- const activePackageManagerLine = output.match(new RegExp(`${activePackageManager}:.*`, 'i'));
- logger.log(
- output.replace(
- activePackageManagerLine,
- picocolors.bold(`${activePackageManagerLine} <----- active`)
- )
- );
- });
-
-command('migrate [migration]')
- .description('Run a Storybook codemod migration on your source files')
- .option('-l --list', 'List available migrations')
- .option('-g --glob ', 'Glob for files upon which to apply the migration', '**/*.js')
- .option('-p --parser ', 'jscodeshift parser')
- .option('-c, --config-dir ', 'Directory where to load Storybook configurations from')
- .option(
- '-n --dry-run',
- 'Dry run: verify the migration exists and show the files to which it will be applied'
- )
- .option(
- '-r --rename ',
- 'Rename suffix of matching files after codemod has been applied, e.g. ".js:.ts"'
- )
- .action((migration, { configDir, glob, dryRun, list, rename, parser }) => {
- migrate(migration, {
- configDir,
- glob,
- dryRun,
- list,
- rename,
- parser,
- }).catch(handleCommandFailure);
- });
-
-command('sandbox [filterValue]')
- .alias('repro') // for backwards compatibility
- .description('Create a sandbox from a set of possible templates')
- .option('-o --output ', 'Define an output directory')
- .option('--no-init', 'Whether to download a template without an initialized Storybook', false)
- .action((filterValue, options) =>
- sandbox({ filterValue, ...options }).catch(handleCommandFailure)
- );
-
-command('link ')
- .description('Pull down a repro from a URL (or a local directory), link it, and run storybook')
- .option('--local', 'Link a local directory already in your file system')
- .option('--no-start', 'Start the storybook', true)
- .action((target, { local, start }) => link({ target, local, start }).catch(handleCommandFailure));
-
-command('automigrate [fixId]')
- .description('Check storybook for incompatibilities or migrations and apply fixes')
- .option('-y --yes', 'Skip prompting the user')
- .option('-n --dry-run', 'Only check for fixes, do not actually run them')
- .option('--package-manager ', 'Force package manager')
- .option('-l --list', 'List available migrations')
- .option('-c, --config-dir ', 'Directory of Storybook configurations to migrate')
- .option('-s --skip-install', 'Skip installing deps')
- .option(
- '--renderer ',
- 'The renderer package for the framework Storybook is using.'
- )
- .option('--skip-doctor', 'Skip doctor check')
- .action(async (fixId, options) => {
- prompt.setPromptLibrary('clack');
- await doAutomigrate({ fixId, ...options }).catch(handleCommandFailure);
- });
-
-command('doctor')
- .description('Check Storybook for known problems and provide suggestions or fixes')
- .option('--package-manager ', 'Force package manager')
- .option('-c, --config-dir ', 'Directory of Storybook configuration')
- .action(async (options) => {
- await doctor(options).catch(handleCommandFailure);
- });
-
-program.on('command:*', ([invalidCmd]) => {
- let errorMessage = ` Invalid command: ${picocolors.bold(invalidCmd)}.\n See --help for a list of available commands.`;
- const availableCommands = program.commands.map((cmd) => cmd.name());
- const suggestion = availableCommands.find((cmd) => leven(cmd, invalidCmd) < 3);
- if (suggestion) {
- errorMessage += `\n Did you mean ${picocolors.yellow(suggestion)}?`;
- }
- logger.error(errorMessage);
process.exit(1);
-});
+}
-program.usage(' [options]').version(String(version)).parse(process.argv);
+import('./run');
diff --git a/code/lib/cli-storybook/src/bin/run.ts b/code/lib/cli-storybook/src/bin/run.ts
new file mode 100644
index 000000000000..04232bb00865
--- /dev/null
+++ b/code/lib/cli-storybook/src/bin/run.ts
@@ -0,0 +1,255 @@
+import { globalSettings } from 'storybook/internal/cli';
+import {
+ HandledError,
+ JsPackageManagerFactory,
+ isCI,
+ optionalEnvToBoolean,
+ removeAddon as remove,
+ versions,
+} from 'storybook/internal/common';
+import { withTelemetry } from 'storybook/internal/core-server';
+import { CLI_COLORS, logTracker, logger, prompt } from 'storybook/internal/node-logger';
+import { addToGlobalContext, telemetry } from 'storybook/internal/telemetry';
+
+import { program } from 'commander';
+import envinfo from 'envinfo';
+import leven from 'leven';
+import picocolors from 'picocolors';
+
+import { version } from '../../package.json';
+import { add } from '../add';
+import { doAutomigrate } from '../automigrate';
+import { doctor } from '../doctor';
+import { link } from '../link';
+import { migrate } from '../migrate';
+import { sandbox } from '../sandbox';
+import { type UpgradeOptions, upgrade } from '../upgrade';
+
+addToGlobalContext('cliVersion', versions.storybook);
+
+// Return a failed exit code but write the logs to a file first
+const handleCommandFailure = async (error: unknown): Promise => {
+ if (!(error instanceof HandledError)) {
+ logger.error(String(error));
+ }
+
+ const logFile = await logTracker.writeToFile();
+ logger.log(`Storybook debug logs can be found at: ${logFile}`);
+ logger.outro('');
+ process.exit(1);
+};
+
+const command = (name: string) =>
+ program
+ .command(name)
+ .option(
+ '--disable-telemetry',
+ 'Disable sending telemetry data',
+ optionalEnvToBoolean(process.env.STORYBOOK_DISABLE_TELEMETRY)
+ )
+ .option('--debug', 'Get more logs in debug mode', false)
+ .option('--enable-crash-reports', 'Enable sending crash reports to telemetry data')
+ .option('--write-logs', 'Write all debug logs to a file at the end of the run')
+ .option('--loglevel ', 'Define log level', 'info')
+ .hook('preAction', async (self) => {
+ try {
+ const options = self.opts();
+ if (options.loglevel) {
+ logger.setLogLevel(options.loglevel);
+ }
+
+ if (options.writeLogs) {
+ logTracker.enableLogWriting();
+ }
+
+ await globalSettings();
+ } catch (e) {
+ logger.error('Error loading global settings:\n' + String(e));
+ }
+ })
+ .hook('postAction', async () => {
+ if (logTracker.shouldWriteLogsToFile) {
+ const logFile = await logTracker.writeToFile();
+ logger.log(`Storybook debug logs can be found at: ${logFile}`);
+ logger.outro(CLI_COLORS.success('Done!'));
+ }
+ });
+
+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)',
+ !isCI() && !optionalEnvToBoolean(process.env.IN_STORYBOOK_SANDBOX)
+ )
+ .option(
+ '--no-dev',
+ 'Complete the initialization of Storybook without launching the Storybook development server'
+ );
+
+command('add ')
+ .description('Add an addon to your Storybook')
+ .option(
+ '--package-manager ',
+ 'Force package manager for installing dependencies'
+ )
+ .option('-c, --config-dir ', 'Directory where to load Storybook configurations from')
+ .option('--skip-install', 'Skip installing deps')
+ .option('-s --skip-postinstall', 'Skip package specific postinstall config modifications')
+ .option('-y --yes', 'Skip prompting the user')
+ .option('--skip-doctor', 'Skip doctor check')
+ .action((addonName: string, options: any) => add(addonName, options));
+
+command('remove ')
+ .description('Remove an addon from your Storybook')
+ .option(
+ '--package-manager ',
+ 'Force package manager for installing dependencies'
+ )
+ .option('-c, --config-dir ', 'Directory where to load Storybook configurations from')
+ .option('-s --skip-install', 'Skip installing deps')
+ .action((addonName: string, options: any) =>
+ withTelemetry('remove', { cliOptions: options }, async () => {
+ const packageManager = JsPackageManagerFactory.getPackageManager({
+ configDir: options.configDir,
+ force: options.packageManager,
+ });
+ await remove(addonName, {
+ configDir: options.configDir,
+ packageManager,
+ skipInstall: options.skipInstall,
+ });
+ if (!options.disableTelemetry) {
+ await telemetry('remove', { addon: addonName, source: 'cli' });
+ }
+ })
+ );
+
+command('upgrade')
+ .description(`Upgrade your Storybook packages to v${versions.storybook}`)
+ .option(
+ '--package-manager ',
+ 'Force package manager for installing dependencies'
+ )
+ .option('-y --yes', 'Skip prompting the user')
+ .option('-f --force', 'force the upgrade, skipping autoblockers')
+ .option('-n --dry-run', 'Only check for upgrades, do not install')
+ .option('-s --skip-check', 'Skip postinstall version and automigration checks')
+ .option(
+ '-c, --config-dir ',
+ 'Directory(ies) where to load Storybook configurations from'
+ )
+ .action(async (options: UpgradeOptions) => {
+ prompt.setPromptLibrary('clack');
+ await upgrade(options).catch(handleCommandFailure);
+ });
+
+command('info')
+ .description('Prints debugging information about the local environment')
+ .action(async () => {
+ logger.log(picocolors.bold('\nStorybook Environment Info:'));
+ const pkgManager = JsPackageManagerFactory.getPackageManager();
+ const activePackageManager = pkgManager.type.replace(/\d/, ''); // 'yarn1' -> 'yarn'
+ const output = await envinfo.run({
+ System: ['OS', 'CPU', 'Shell'],
+ Binaries: ['Node', 'Yarn', 'npm', 'pnpm'],
+ Browsers: ['Chrome', 'Edge', 'Firefox', 'Safari'],
+ npmPackages: '{@storybook/*,*storybook*,sb,chromatic}',
+ npmGlobalPackages: '{@storybook/*,*storybook*,sb,chromatic}',
+ });
+ const activePackageManagerLine = output.match(new RegExp(`${activePackageManager}:.*`, 'i'));
+ logger.log(
+ output.replace(
+ activePackageManagerLine,
+ picocolors.bold(`${activePackageManagerLine} <----- active`)
+ )
+ );
+ });
+
+command('migrate [migration]')
+ .description('Run a Storybook codemod migration on your source files')
+ .option('-l --list', 'List available migrations')
+ .option('-g --glob ', 'Glob for files upon which to apply the migration', '**/*.js')
+ .option('-p --parser ', 'jscodeshift parser')
+ .option('-c, --config-dir ', 'Directory where to load Storybook configurations from')
+ .option(
+ '-n --dry-run',
+ 'Dry run: verify the migration exists and show the files to which it will be applied'
+ )
+ .option(
+ '-r --rename ',
+ 'Rename suffix of matching files after codemod has been applied, e.g. ".js:.ts"'
+ )
+ .action((migration, { configDir, glob, dryRun, list, rename, parser }) => {
+ migrate(migration, {
+ configDir,
+ glob,
+ dryRun,
+ list,
+ rename,
+ parser,
+ }).catch(handleCommandFailure);
+ });
+
+command('sandbox [filterValue]')
+ .alias('repro') // for backwards compatibility
+ .description('Create a sandbox from a set of possible templates')
+ .option('-o --output ', 'Define an output directory')
+ .option('--no-init', 'Whether to download a template without an initialized Storybook', false)
+ .action((filterValue, options) =>
+ sandbox({ filterValue, ...options }).catch(handleCommandFailure)
+ );
+
+command('link ')
+ .description('Pull down a repro from a URL (or a local directory), link it, and run storybook')
+ .option('--local', 'Link a local directory already in your file system')
+ .option('--no-start', 'Start the storybook', true)
+ .action((target, { local, start }) => link({ target, local, start }).catch(handleCommandFailure));
+
+command('automigrate [fixId]')
+ .description('Check storybook for incompatibilities or migrations and apply fixes')
+ .option('-y --yes', 'Skip prompting the user')
+ .option('-n --dry-run', 'Only check for fixes, do not actually run them')
+ .option('--package-manager ', 'Force package manager')
+ .option('-l --list', 'List available migrations')
+ .option('-c, --config-dir ', 'Directory of Storybook configurations to migrate')
+ .option('-s --skip-install', 'Skip installing deps')
+ .option(
+ '--renderer ',
+ 'The renderer package for the framework Storybook is using.'
+ )
+ .option('--skip-doctor', 'Skip doctor check')
+ .action(async (fixId, options) => {
+ prompt.setPromptLibrary('clack');
+ await doAutomigrate({ fixId, ...options }).catch(handleCommandFailure);
+ });
+
+command('doctor')
+ .description('Check Storybook for known problems and provide suggestions or fixes')
+ .option('--package-manager ', 'Force package manager')
+ .option('-c, --config-dir ', 'Directory of Storybook configuration')
+ .action(async (options) => {
+ await doctor(options).catch(handleCommandFailure);
+ });
+
+program.on('command:*', ([invalidCmd]) => {
+ let errorMessage = ` Invalid command: ${picocolors.bold(invalidCmd)}.\n See --help for a list of available commands.`;
+ const availableCommands = program.commands.map((cmd) => cmd.name());
+ const suggestion = availableCommands.find((cmd) => leven(cmd, invalidCmd) < 3);
+ if (suggestion) {
+ errorMessage += `\n Did you mean ${picocolors.yellow(suggestion)}?`;
+ }
+ logger.error(errorMessage);
+ process.exit(1);
+});
+
+program.usage(' [options]').version(String(version)).parse(process.argv);
diff --git a/code/lib/cli-storybook/src/codemod/csf-factories.ts b/code/lib/cli-storybook/src/codemod/csf-factories.ts
index 52c8c24aabd4..cc7e23dc0e6c 100644
--- a/code/lib/cli-storybook/src/codemod/csf-factories.ts
+++ b/code/lib/cli-storybook/src/codemod/csf-factories.ts
@@ -70,7 +70,7 @@ export const csfFactories: CommandFix = {
However, please note that this might not work if you have an outdated tsconfig, use custom paths, or have type alias plugins configured in your project. You can always rerun this codemod and select another option to update your code later.
- More info: ${picocolors.yellow('https://storybook.js.org/docs/api/csf/csf-factories#subpath-imports')}
+ More info: ${picocolors.yellow('https://storybook.js.org/docs/api/csf/csf-factories#subpath-imports?ref=upgrade')}
As we modify your story files, we can create two types of imports:
@@ -127,7 +127,7 @@ export const csfFactories: CommandFix = {
You can now run Storybook with the new CSF factories format.
For more info, check out the docs:
- ${picocolors.yellow('https://storybook.js.org/docs/api/csf/csf-factories')}
+ ${picocolors.yellow('https://storybook.js.org/docs/api/csf/csf-factories?ref=upgrade')}
`
);
},
diff --git a/code/lib/cli-storybook/src/doctor/getIncompatibleStorybookPackages.test.ts b/code/lib/cli-storybook/src/doctor/getIncompatibleStorybookPackages.test.ts
index fbc8fa5b5484..2ec0a78ed637 100644
--- a/code/lib/cli-storybook/src/doctor/getIncompatibleStorybookPackages.test.ts
+++ b/code/lib/cli-storybook/src/doctor/getIncompatibleStorybookPackages.test.ts
@@ -29,12 +29,12 @@ describe('checkPackageCompatibility', () => {
beforeEach(() => {
vi.mocked(packageManagerMock.getAllDependencies).mockReturnValue({});
vi.mocked(packageManagerMock.latestVersion).mockResolvedValue('9.0.0');
- vi.mocked(packageManagerMock.getModulePackageJSON).mockReturnValue({ version: '9.0.0' });
+ vi.mocked(packageManagerMock.getModulePackageJSON).mockResolvedValue({ version: '9.0.0' });
});
it('returns that a package is incompatible', async () => {
const packageName = 'my-storybook-package';
- vi.mocked(packageManagerMock.getModulePackageJSON).mockReturnValueOnce({
+ vi.mocked(packageManagerMock.getModulePackageJSON).mockResolvedValueOnce({
name: packageName,
version: '1.0.0',
dependencies: {
@@ -56,7 +56,7 @@ describe('checkPackageCompatibility', () => {
it('returns that a package is compatible', async () => {
const packageName = 'my-storybook-package';
- vi.mocked(packageManagerMock.getModulePackageJSON).mockReturnValueOnce({
+ vi.mocked(packageManagerMock.getModulePackageJSON).mockResolvedValueOnce({
name: packageName,
version: '1.0.0',
dependencies: {
@@ -79,7 +79,7 @@ describe('checkPackageCompatibility', () => {
it('returns that a package is incompatible and because it is core, can be upgraded', async () => {
const packageName = '@storybook/addon-docs';
- vi.mocked(packageManagerMock.getModulePackageJSON).mockReturnValueOnce({
+ vi.mocked(packageManagerMock.getModulePackageJSON).mockResolvedValueOnce({
name: packageName,
version: '8.0.0',
dependencies: {
@@ -105,7 +105,7 @@ describe('checkPackageCompatibility', () => {
it('returns that an addon is incompatible because it uses legacy consolidated packages', async () => {
const packageName = '@storybook/addon-designs';
- vi.mocked(packageManagerMock.getModulePackageJSON).mockReturnValueOnce({
+ vi.mocked(packageManagerMock.getModulePackageJSON).mockResolvedValueOnce({
name: packageName,
version: '8.0.0',
dependencies: {
@@ -132,7 +132,7 @@ describe('getIncompatibleStorybookPackages', () => {
beforeEach(() => {
vi.mocked(packageManagerMock.getAllDependencies).mockReturnValue({});
vi.mocked(packageManagerMock.latestVersion).mockResolvedValue('9.0.0');
- vi.mocked(packageManagerMock.getModulePackageJSON).mockReturnValue({ version: '9.0.0' });
+ vi.mocked(packageManagerMock.getModulePackageJSON).mockResolvedValue({ version: '9.0.0' });
});
it('succeeds if only core storybook packages used', async () => {
@@ -154,7 +154,7 @@ describe('getIncompatibleStorybookPackages', () => {
'react-storybook-addon': '1.0.0',
});
- vi.mocked(packageManagerMock.getModulePackageJSON).mockReturnValueOnce({
+ vi.mocked(packageManagerMock.getModulePackageJSON).mockResolvedValueOnce({
name: 'react-storybook-addon',
version: '1.0.0',
dependencies: {
diff --git a/code/lib/cli-storybook/src/doctor/getIncompatibleStorybookPackages.ts b/code/lib/cli-storybook/src/doctor/getIncompatibleStorybookPackages.ts
index f783f47b3443..24fd52b35e35 100644
--- a/code/lib/cli-storybook/src/doctor/getIncompatibleStorybookPackages.ts
+++ b/code/lib/cli-storybook/src/doctor/getIncompatibleStorybookPackages.ts
@@ -31,7 +31,7 @@ export const checkPackageCompatibility = async (
): Promise => {
const { currentStorybookVersion, skipErrors, packageManager } = context;
try {
- const dependencyPackageJson = packageManager.getModulePackageJSON(dependency);
+ const dependencyPackageJson = await packageManager.getModulePackageJSON(dependency);
if (dependencyPackageJson === null) {
return { packageName: dependency };
}
diff --git a/code/lib/cli-storybook/src/link.ts b/code/lib/cli-storybook/src/link.ts
index 078c78f94115..99fbbaea0df4 100644
--- a/code/lib/cli-storybook/src/link.ts
+++ b/code/lib/cli-storybook/src/link.ts
@@ -101,7 +101,7 @@ export const link = async ({ target, local, start }: LinkOptions) => {
}
logger.info(`Linking ${reproDir}`);
- await exec(`yarn link --all --relative ${storybookDir}`, { cwd: reproDir });
+ await exec(`yarn link --all --relative "${storybookDir}"`, { cwd: reproDir });
logger.info(`Installing ${reproName}`);
diff --git a/code/lib/cli-storybook/src/postinstallAddon.ts b/code/lib/cli-storybook/src/postinstallAddon.ts
index 50719c29e29c..bdb633c8cad1 100644
--- a/code/lib/cli-storybook/src/postinstallAddon.ts
+++ b/code/lib/cli-storybook/src/postinstallAddon.ts
@@ -1,19 +1,49 @@
+import { createRequire } from 'node:module';
+import { fileURLToPath } from 'node:url';
+
+import { importModule } from '../../../core/src/shared/utils/module';
import type { PostinstallOptions } from './add';
+const DIR_CWD = process.cwd();
+const require = createRequire(DIR_CWD);
export const postinstallAddon = async (addonName: string, options: PostinstallOptions) => {
+ const hookPath = `${addonName}/postinstall`;
+ let modulePath: string;
try {
- const modulePath = require.resolve(`${addonName}/postinstall`, { paths: [process.cwd()] });
-
- const postinstall = require(modulePath);
-
+ modulePath = import.meta.resolve(hookPath, DIR_CWD);
+ } catch (e) {
try {
- console.log(`Running postinstall script for ${addonName}`);
- await postinstall(options);
+ modulePath = require.resolve(hookPath, { paths: [DIR_CWD] });
} catch (e) {
- console.error(`Error running postinstall script for ${addonName}`);
- console.error(e);
+ return;
}
+ }
+
+ let moduledLoaded: any;
+ try {
+ moduledLoaded = await import(hookPath)
+ .catch(() => importModule(hookPath))
+ .catch(() => importModule(modulePath))
+ .catch(() => importModule(fileURLToPath(modulePath)))
+ .catch(() => require(hookPath))
+ .catch(() => require(modulePath))
+ .catch(() => require(fileURLToPath(modulePath)));
+ } catch (e) {
+ return;
+ }
+
+ const postinstall = moduledLoaded?.default || moduledLoaded?.postinstall || moduledLoaded;
+
+ if (!postinstall || typeof postinstall !== 'function') {
+ console.log(`Error finding postinstall function for ${addonName}`);
+ return;
+ }
+
+ try {
+ console.log(`Running postinstall script for ${addonName}`);
+ await postinstall(options);
} catch (e) {
- // no postinstall script
+ console.error(`Error running postinstall script for ${addonName}`);
+ console.error(e);
}
};
diff --git a/code/lib/cli-storybook/src/sandbox-templates.ts b/code/lib/cli-storybook/src/sandbox-templates.ts
index 1a581489dedd..c41dba130757 100644
--- a/code/lib/cli-storybook/src/sandbox-templates.ts
+++ b/code/lib/cli-storybook/src/sandbox-templates.ts
@@ -782,7 +782,7 @@ export const normal: TemplateKey[] = [
'react-vite/default-ts',
'angular-cli/default-ts',
'vue3-vite/default-ts',
- // 'nuxt-vite/default-ts',
+ // 'nuxt-vite/default-ts', // temporarily disabled because it's broken
'lit-vite/default-ts',
'svelte-vite/default-ts',
'svelte-kit/skeleton-ts',
diff --git a/code/lib/cli-storybook/src/upgrade.ts b/code/lib/cli-storybook/src/upgrade.ts
index ce2dd03fbcd7..2d009c1fcef4 100644
--- a/code/lib/cli-storybook/src/upgrade.ts
+++ b/code/lib/cli-storybook/src/upgrade.ts
@@ -265,7 +265,7 @@ function logUpgradeResults(
}
logger.log(
- `For a full list of changes, please check our migration guide: ${CLI_COLORS.cta('https://storybook.js.org/docs/releases/migration-guide')}`
+ `For a full list of changes, please check our migration guide: ${CLI_COLORS.cta('https://storybook.js.org/docs/releases/migration-guide?ref=upgrade')}`
);
}
diff --git a/code/lib/cli-storybook/src/util.ts b/code/lib/cli-storybook/src/util.ts
index c486bc83a60d..0c6bf6416ed9 100644
--- a/code/lib/cli-storybook/src/util.ts
+++ b/code/lib/cli-storybook/src/util.ts
@@ -319,7 +319,7 @@ const processProject = async ({
previewConfigPath,
storiesPaths,
storybookVersion: beforeVersion,
- } = await getStorybookData({ configDir, cache: true });
+ } = await getStorybookData({ configDir });
// Validate version and upgrade compatibility
logger.debug(`${name} - Validating before version... ${beforeVersion}`);
diff --git a/code/lib/cli-storybook/test/helpers.cjs b/code/lib/cli-storybook/test/helpers.cjs
index f51ca61e3ebc..8593c6373451 100644
--- a/code/lib/cli-storybook/test/helpers.cjs
+++ b/code/lib/cli-storybook/test/helpers.cjs
@@ -1,7 +1,16 @@
const { sync: spawnSync } = require('cross-spawn');
const path = require('path');
-const CLI_PATH = path.join(__dirname, '..', '..', '..', 'core', 'bin', 'index.cjs');
+const CORE_CLI_PATH = path.join(
+ __dirname,
+ '..',
+ '..',
+ '..',
+ 'core',
+ 'dist',
+ 'bin',
+ 'dispatcher.js'
+);
/**
* Execute command
@@ -10,7 +19,7 @@ const CLI_PATH = path.join(__dirname, '..', '..', '..', 'core', 'bin', 'index.cj
* @param {Object} options - Customize the behavior
* @returns {Object}
*/
-const run = (args, options = {}) => spawnSync('node', [CLI_PATH].concat(args), options);
+const run = (args, options = {}) => spawnSync('node', [CORE_CLI_PATH].concat(args), options);
const cleanLog = (str) => {
const pattern = [
diff --git a/code/lib/codemod/README.md b/code/lib/codemod/README.md
index ab506e5d6edd..8143868242de 100644
--- a/code/lib/codemod/README.md
+++ b/code/lib/codemod/README.md
@@ -107,3 +107,5 @@ Basic.decorators = [ ... ];
```
The new syntax is slightly more compact, is more ergonomic, and resembles React's `displayName`/`propTypes`/`defaultProps` annotations.
+
+Learn more about Storybook at [storybook.js.org](https://storybook.js.org/?ref=readme).
diff --git a/code/lib/codemod/build-config.ts b/code/lib/codemod/build-config.ts
new file mode 100644
index 000000000000..0d9a8fe70d77
--- /dev/null
+++ b/code/lib/codemod/build-config.ts
@@ -0,0 +1,34 @@
+import type { BuildEntries } from '../../../scripts/build/utils/entry-utils';
+
+const config: BuildEntries = {
+ entries: {
+ node: [
+ {
+ exportEntries: ['.'],
+ entryPoint: './src/index.ts',
+ },
+ {
+ exportEntries: ['./transforms/csf-2-to-3'],
+ entryPoint: './src/transforms/csf-2-to-3.ts',
+ dts: false,
+ },
+ {
+ exportEntries: ['./transforms/find-implicit-spies'],
+ entryPoint: './src/transforms/find-implicit-spies.ts',
+ dts: false,
+ },
+ {
+ exportEntries: ['./transforms/upgrade-deprecated-types'],
+ entryPoint: './src/transforms/upgrade-deprecated-types.ts',
+ dts: false,
+ },
+ {
+ exportEntries: ['./transforms/upgrade-hierarchy-separators'],
+ entryPoint: './src/transforms/upgrade-hierarchy-separators.js',
+ dts: false,
+ },
+ ],
+ },
+};
+
+export default config;
diff --git a/code/lib/codemod/package.json b/code/lib/codemod/package.json
index 06e58f751c58..9bd0a135bf65 100644
--- a/code/lib/codemod/package.json
+++ b/code/lib/codemod/package.json
@@ -20,21 +20,18 @@
},
"license": "MIT",
"sideEffects": false,
+ "type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
- "node": "./dist/index.js",
- "require": "./dist/index.js"
+ "default": "./dist/index.js"
},
- "./dist/transforms/csf-2-to-3.js": "./dist/transforms/csf-2-to-3.js",
- "./dist/transforms/csf-hoist-story-annotations.js": "./dist/transforms/csf-hoist-story-annotations.js",
- "./dist/transforms/find-implicit-spies.js": "./dist/transforms/find-implicit-spies.js",
- "./dist/transforms/upgrade-deprecated-types.js": "./dist/transforms/upgrade-deprecated-types.js",
- "./dist/transforms/upgrade-hierarchy-separators.js": "./dist/transforms/upgrade-hierarchy-separators.js",
- "./package.json": "./package.json"
+ "./package.json": "./package.json",
+ "./transforms/csf-2-to-3": "./dist/transforms/csf-2-to-3.js",
+ "./transforms/find-implicit-spies": "./dist/transforms/find-implicit-spies.js",
+ "./transforms/upgrade-deprecated-types": "./dist/transforms/upgrade-deprecated-types.js",
+ "./transforms/upgrade-hierarchy-separators": "./dist/transforms/upgrade-hierarchy-separators.js"
},
- "main": "dist/index.js",
- "types": "dist/index.d.ts",
"files": [
"dist/**/*",
"README.md",
@@ -43,8 +40,8 @@
"!src/**/*"
],
"scripts": {
- "check": "jiti ../../../scripts/prepare/check.ts",
- "prep": "jiti ../../../scripts/prepare/bundle.ts"
+ "check": "jiti ../../../scripts/check/check-package.ts",
+ "prep": "jiti ../../../scripts/build/build-package.ts"
},
"dependencies": {
"@types/cross-spawn": "^6.0.6",
@@ -65,17 +62,5 @@
"publishConfig": {
"access": "public"
},
- "bundler": {
- "entries": [
- "./src/index.ts",
- "./src/transforms/csf-2-to-3.ts",
- "./src/transforms/find-implicit-spies.ts",
- "./src/transforms/upgrade-deprecated-types.ts",
- "./src/transforms/upgrade-hierarchy-separators.js"
- ],
- "formats": [
- "cjs"
- ]
- },
- "gitHead": "ce6a1e4a8d5ad69c699021a0b183df89cfc7b684"
+ "gitHead": "a8e7fd8a655c69780bc20b9749d2699e45beae16"
}
diff --git a/code/lib/codemod/src/index.ts b/code/lib/codemod/src/index.ts
index 3d1ac65c0ca6..4b9160b9931b 100644
--- a/code/lib/codemod/src/index.ts
+++ b/code/lib/codemod/src/index.ts
@@ -1,13 +1,15 @@
/* eslint import/prefer-default-export: "off" */
import { readdirSync } from 'node:fs';
import { rename as renameAsync } from 'node:fs/promises';
-import { extname } from 'node:path';
+import { extname, join } from 'node:path';
+
+import { resolvePackageDir } from 'storybook/internal/common';
import { sync as spawnSync } from 'cross-spawn';
import { jscodeshiftToPrettierParser } from './lib/utils';
-const TRANSFORM_DIR = `${__dirname}/transforms`;
+const TRANSFORM_DIR = join(resolvePackageDir('@storybook/codemod'), 'dist', 'transforms');
export function listCodemods() {
return readdirSync(TRANSFORM_DIR)
@@ -77,7 +79,7 @@ export async function runCodemod(
const result = spawnSync(
'node',
[
- require.resolve('jscodeshift/bin/jscodeshift'),
+ join(resolvePackageDir('jscodeshift'), 'bin', 'jscodeshift'),
// this makes sure codeshift doesn't transform our own source code with babel
// which is faster, and also makes sure the user won't see babel messages such as:
// [BABEL] Note: The code generator has deoptimised the styling of repo/node_modules/prettier/index.js as it exceeds the max of 500KB.
diff --git a/code/lib/core-webpack/README.md b/code/lib/core-webpack/README.md
index 3fc05c4c32c8..d4edc2758a16 100644
--- a/code/lib/core-webpack/README.md
+++ b/code/lib/core-webpack/README.md
@@ -7,3 +7,5 @@ This is a lot of code extracted for convenience, not because it made sense.
Supporting multiple version of webpack and this duplicating a large portion of code that was never meant to be generic caused this.
At some point we'll refactor this, it's likely a lot of this code is dead or barely used.
+
+Learn more about Storybook at [storybook.js.org](https://storybook.js.org/?ref=readme).
diff --git a/code/lib/core-webpack/build-config.ts b/code/lib/core-webpack/build-config.ts
new file mode 100644
index 000000000000..3399bb510955
--- /dev/null
+++ b/code/lib/core-webpack/build-config.ts
@@ -0,0 +1,14 @@
+import type { BuildEntries } from '../../../scripts/build/utils/entry-utils';
+
+const config: BuildEntries = {
+ entries: {
+ node: [
+ {
+ exportEntries: ['.'],
+ entryPoint: './src/index.ts',
+ },
+ ],
+ },
+};
+
+export default config;
diff --git a/code/lib/core-webpack/package.json b/code/lib/core-webpack/package.json
index f0bc00cf6844..ba38d6461b07 100644
--- a/code/lib/core-webpack/package.json
+++ b/code/lib/core-webpack/package.json
@@ -19,18 +19,14 @@
"url": "https://opencollective.com/storybook"
},
"license": "MIT",
+ "type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
- "node": "./dist/index.js",
- "import": "./dist/index.mjs",
- "require": "./dist/index.js"
+ "default": "./dist/index.js"
},
"./package.json": "./package.json"
},
- "main": "dist/index.js",
- "module": "dist/index.mjs",
- "types": "dist/index.d.ts",
"files": [
"dist/**/*",
"templates/**/*",
@@ -40,8 +36,8 @@
"!src/**/*"
],
"scripts": {
- "check": "jiti ../../../scripts/prepare/check.ts",
- "prep": "jiti ../../../scripts/prepare/bundle.ts"
+ "check": "jiti ../../../scripts/check/check-package.ts",
+ "prep": "jiti ../../../scripts/build/build-package.ts"
},
"dependencies": {
"ts-dedent": "^2.0.0"
@@ -57,11 +53,5 @@
"publishConfig": {
"access": "public"
},
- "bundler": {
- "entries": [
- "./src/index.ts"
- ],
- "platform": "node"
- },
- "gitHead": "ce6a1e4a8d5ad69c699021a0b183df89cfc7b684"
+ "gitHead": "a8e7fd8a655c69780bc20b9749d2699e45beae16"
}
diff --git a/code/lib/core-webpack/src/load-custom-webpack-config.ts b/code/lib/core-webpack/src/load-custom-webpack-config.ts
index 021dedff7f3d..0e0b651daebe 100644
--- a/code/lib/core-webpack/src/load-custom-webpack-config.ts
+++ b/code/lib/core-webpack/src/load-custom-webpack-config.ts
@@ -4,5 +4,5 @@ import { serverRequire } from 'storybook/internal/common';
const webpackConfigs = ['webpack.config', 'webpackfile'];
-export const loadCustomWebpackConfig = (configDir: string) =>
+export const loadCustomWebpackConfig = async (configDir: string) =>
serverRequire(webpackConfigs.map((configName) => resolve(configDir, configName)));
diff --git a/code/lib/create-storybook/README.md b/code/lib/create-storybook/README.md
index a84cddc67be2..66ba5b0929d1 100644
--- a/code/lib/create-storybook/README.md
+++ b/code/lib/create-storybook/README.md
@@ -1 +1,7 @@
-## Initialize Storybook into your project
+## Install Storybook in your project
+
+```sh
+npm create storybook@latest
+```
+
+Learn more about Storybook at [storybook.js.org](https://storybook.js.org/?ref=readme).
diff --git a/code/lib/create-storybook/bin/index.cjs b/code/lib/create-storybook/bin/index.cjs
deleted file mode 100755
index fb3d5cc22c35..000000000000
--- a/code/lib/create-storybook/bin/index.cjs
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/usr/bin/env node
-
-const majorNodeVersion = parseInt(process.versions.node, 10);
-if (majorNodeVersion < 20) {
- console.error('To run Storybook you need to have Node.js 20 or higher');
- process.exit(1);
-}
-
-// The Storybook CLI has a catch block for all of its commands, but if an error
-// occurs before the command even runs, for instance, if an import fails, then
-// such error will fall under the uncaughtException handler.
-// This is the earliest moment we can catch such errors.
-process.once('uncaughtException', (error) => {
- if (error.message.includes('string-width')) {
- console.error(
- [
- '🔴 Error: It looks like you are having a known issue with package hoisting.',
- 'Please check the following issue for details and solutions: https://github.com/storybookjs/storybook/issues/22431#issuecomment-1630086092\n\n',
- ].join('\n')
- );
- }
-
- throw error;
-});
-
-import('../dist/bin/index.js').catch((error) => {
- console.error(error);
- process.exit(1);
-});
diff --git a/code/lib/create-storybook/build-config.ts b/code/lib/create-storybook/build-config.ts
new file mode 100644
index 000000000000..ffd06463e796
--- /dev/null
+++ b/code/lib/create-storybook/build-config.ts
@@ -0,0 +1,21 @@
+import { join } from 'node:path';
+
+import type { BuildEntries } from '../../../scripts/build/utils/entry-utils';
+
+const config: BuildEntries = {
+ entries: {
+ node: [
+ {
+ exportEntries: ['.'],
+ entryPoint: './src/index.ts',
+ dts: false,
+ },
+ {
+ entryPoint: './src/bin/index.ts',
+ dts: false,
+ },
+ ],
+ },
+};
+
+export default config;
diff --git a/code/lib/create-storybook/package.json b/code/lib/create-storybook/package.json
index 47ae08ed58c0..494c1476abb4 100644
--- a/code/lib/create-storybook/package.json
+++ b/code/lib/create-storybook/package.json
@@ -1,7 +1,13 @@
{
"name": "create-storybook",
"version": "9.2.0-alpha.3",
- "description": "Initialize Storybook into your project",
+ "description": "Storybook installer: Develop, document, and test UI components in isolation",
+ "keywords": [
+ "storybook",
+ "cli",
+ "create",
+ "init"
+ ],
"homepage": "https://github.com/storybookjs/storybook/tree/next/code/lib/create-storybook",
"bugs": {
"url": "https://github.com/storybookjs/storybook/issues"
@@ -20,12 +26,9 @@
"type": "module",
"exports": {
".": "./dist/index.js",
- "./bin/index.cjs": "./bin/index.cjs",
"./package.json": "./package.json"
},
- "main": "dist/index.cjs",
- "module": "dist/index.js",
- "bin": "./bin/index.cjs",
+ "bin": "./dist/bin/index.js",
"files": [
"bin/**/*",
"dist/**/*",
@@ -35,11 +38,12 @@
"!src/**/*"
],
"scripts": {
- "check": "jiti ../../../scripts/prepare/check.ts",
- "prep": "jiti ../../../scripts/prepare/bundle.ts"
+ "check": "jiti ../../../scripts/check/check-package.ts",
+ "prep": "jiti ../../../scripts/build/build-package.ts"
},
"dependencies": {
- "semver": "^7.6.2"
+ "semver": "^7.6.2",
+ "storybook": "workspace:*"
},
"devDependencies": {
"@types/prompts": "^2.0.9",
@@ -52,7 +56,6 @@
"picocolors": "^1.1.0",
"prompts": "^2.4.0",
"react": "^18.2.0",
- "storybook": "workspace:*",
"tiny-invariant": "^1.3.1",
"ts-dedent": "^2.0.0",
"typescript": "^5.8.3"
@@ -60,15 +63,5 @@
"publishConfig": {
"access": "public"
},
- "bundler": {
- "entries": [
- "./src/index.ts",
- "./src/bin/index.ts"
- ],
- "formats": [
- "node-esm"
- ],
- "types": false
- },
- "gitHead": "ce6a1e4a8d5ad69c699021a0b183df89cfc7b684"
+ "gitHead": "a8e7fd8a655c69780bc20b9749d2699e45beae16"
}
diff --git a/code/lib/create-storybook/rendererAssets/common/Configure.mdx b/code/lib/create-storybook/rendererAssets/common/Configure.mdx
index e8bd42548d4e..948e410a08b0 100644
--- a/code/lib/create-storybook/rendererAssets/common/Configure.mdx
+++ b/code/lib/create-storybook/rendererAssets/common/Configure.mdx
@@ -48,7 +48,7 @@ export const RightArrow = () => Add styling and CSS
Like with web applications, there are many ways to include CSS within Storybook. Learn more about setting up styling within Storybook.
Learn more
@@ -60,7 +60,7 @@ export const RightArrow = () => Provide context and mocking
Often when a story doesn't render, it's because your component is expecting a specific environment or context (like a theme provider) to be available.
Learn more
@@ -72,7 +72,7 @@ export const RightArrow = () => Learn more
@@ -94,7 +94,7 @@ export const RightArrow = () => Auto-generate living,
interactive reference documentation from your components and stories.
Learn more
@@ -103,7 +103,7 @@ export const RightArrow = () => Publish to Chromatic
Publish your Storybook to review and collaborate with your entire team.
Learn more
@@ -113,7 +113,7 @@ export const RightArrow = () => Embed your stories into Figma to cross-reference the design and live
implementation in one place.
Learn more
@@ -123,7 +123,7 @@ export const RightArrow = () => Use stories to test a component in all its variations, no matter how
complex.
Learn more
@@ -132,7 +132,7 @@ export const RightArrow = () => Accessibility
Automatically test your components for a11y issues as you develop.
+
+# eslint-plugin-storybook
+
+Best practice rules for Storybook
+
+## Installation
+
+You'll first need to install [ESLint](https://eslint.org/):
+
+```sh
+npm install eslint --save-dev
+# or
+yarn add eslint --dev
+```
+
+Next, install `eslint-plugin-storybook`:
+
+```sh
+npm install eslint-plugin-storybook --save-dev
+# or
+yarn add eslint-plugin-storybook --dev
+```
+
+And finally, add this to your `.eslintignore` file:
+
+```
+// Inside your .eslintignore file
+!.storybook
+```
+
+This allows for this plugin to also lint your configuration files inside the .storybook folder, so that you always have a correct configuration and don't face any issues regarding mistyped addon names, for instance.
+
+> For more info on why this line is required in the .eslintignore file, check this [ESLint documentation](https://eslint.org/docs/latest/use/configure/ignore-deprecated#:~:text=In%20addition%20to,contents%20are%20ignored).
+
+If you are using [flat config style](https://eslint.org/docs/latest/use/configure/configuration-files-new), add this to your configuration file:
+
+```js
+export default [
+ // ...
+ {
+ // Inside your .eslintignore file
+ ignores: ['!.storybook'],
+ },
+];
+```
+
+## ESLint compatibility
+
+Use the following table to use the correct version of this package, based on the version of ESLint you're using:
+
+| ESLint version | Storybook plugin version |
+| -------------- | ------------------------ |
+| ^9.0.0 | ^0.10.0 |
+| ^8.57.0 | ^0.10.0 |
+| ^7.0.0 | ~0.9.0 |
+
+## Usage
+
+### Configuration (`.eslintrc`)
+
+Use `.eslintrc.*` file to configure rules in ESLint < v9. See also: https://eslint.org/docs/latest/use/configure/.
+
+Add `plugin:storybook/recommended` to the extends section of your `.eslintrc` configuration file. Note that we can omit the `eslint-plugin-` prefix:
+
+```js
+{
+ // extend plugin:storybook/, such as:
+ "extends": ["plugin:storybook/recommended"]
+}
+```
+
+This plugin will only be applied to files following the `*.stories.*` (we recommend this) or `*.story.*` pattern. This is an automatic configuration, so you don't have to do anything.
+
+#### Overriding/disabling rules
+
+Optionally, you can override, add or disable rules settings. You likely don't want these settings to be applied in every file, so make sure that you add a `overrides` section in your `.eslintrc.*` file that applies the overrides only to your stories files.
+
+```js
+{
+ "overrides": [
+ {
+ // or whatever matches stories specified in .storybook/main.js
+ "files": ['**/*.stories.@(ts|tsx|js|jsx|mjs|cjs)'],
+ "rules": {
+ // example of overriding a rule
+ 'storybook/hierarchy-separator': 'error',
+ // example of disabling a rule
+ 'storybook/default-exports': 'off',
+ }
+ }
+ ]
+}
+```
+
+### Configuration (`eslint.config.[c|m]?js`)
+
+Use `eslint.config.[c|m]?js` file to configure rules using the [flat config style](https://eslint.org/docs/latest/use/configure/configuration-files-new). This is the default in ESLint v9, but can be used starting from ESLint v8.57.0. See also: https://eslint.org/docs/latest/use/configure/configuration-files-new.
+
+```js
+import storybook from 'eslint-plugin-storybook';
+
+export default [
+ // add more generic rulesets here, such as:
+ // js.configs.recommended,
+ ...storybook.configs['flat/recommended'],
+
+ // something ...
+];
+```
+
+In case you are using utility functions from tools like `tseslint`, you might need to set the plugin a little differently:
+
+```ts
+import storybook from 'eslint-plugin-storybook';
+import somePlugin from 'some-plugin';
+import tseslint from 'typescript-eslint';
+
+export default tseslint.config(
+ somePlugin,
+ storybook.configs['flat/recommended'] // notice that it is not destructured
+);
+```
+
+#### Overriding/disabling rules
+
+Optionally, you can override, add or disable rules settings. You likely don't want these settings to be applied in every file, so make sure that you add a flat config section in your `eslint.config.[m|c]?js` file that applies the overrides only to your stories files.
+
+```js
+import storybook from 'eslint-plugin-storybook';
+
+export default [
+ // ...
+
+ ...storybook.configs['flat/recommended'],
+ {
+ files: ['**/*.stories.@(ts|tsx|js|jsx|mjs|cjs)'],
+ rules: {
+ // example of overriding a rule
+ 'storybook/hierarchy-separator': 'error',
+ // example of disabling a rule
+ 'storybook/default-exports': 'off',
+ },
+ },
+
+ // something ...
+];
+```
+
+### MDX Support
+
+This plugin does not support MDX files.
+
+## Supported Rules and configurations
+
+
+
+**Key**: 🔧 = fixable
+
+**Configurations**: csf, csf-strict, addon-interactions, recommended
+
+| Name | Description | 🔧 | Included in configurations |
+| ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- | --- | ------------------------------------------------------------------------------------------------------------------------------ |
+| [`storybook/await-interactions`](./docs/rules/await-interactions.md) | Interactions should be awaited | 🔧 |
addon-interactions
flat/addon-interactions
recommended
flat/recommended
|
+| [`storybook/context-in-play-function`](./docs/rules/context-in-play-function.md) | Pass a context when invoking play function of another story | |
recommended
flat/recommended
addon-interactions
flat/addon-interactions
|
+| [`storybook/csf-component`](./docs/rules/csf-component.md) | The component property should be set | |
csf
flat/csf
csf-strict
flat/csf-strict
|
+| [`storybook/default-exports`](./docs/rules/default-exports.md) | Story files should have a default export | 🔧 |
csf
flat/csf
recommended
flat/recommended
csf-strict
flat/csf-strict
|
+| [`storybook/hierarchy-separator`](./docs/rules/hierarchy-separator.md) | Deprecated hierarchy separator in title property | 🔧 |
csf
flat/csf
recommended
flat/recommended
csf-strict
flat/csf-strict
|
+| [`storybook/meta-inline-properties`](./docs/rules/meta-inline-properties.md) | Meta should only have inline properties | | N/A |
+| [`storybook/meta-satisfies-type`](./docs/rules/meta-satisfies-type.md) | Meta should use `satisfies Meta` | 🔧 | N/A |
+| [`storybook/no-redundant-story-name`](./docs/rules/no-redundant-story-name.md) | A story should not have a redundant name property | 🔧 |
csf
flat/csf
recommended
flat/recommended
csf-strict
flat/csf-strict
|
+| [`storybook/no-stories-of`](./docs/rules/no-stories-of.md) | storiesOf is deprecated and should not be used | |
csf-strict
flat/csf-strict
|
+| [`storybook/no-title-property-in-meta`](./docs/rules/no-title-property-in-meta.md) | Do not define a title in meta | 🔧 |
csf-strict
flat/csf-strict
|
+| [`storybook/no-uninstalled-addons`](./docs/rules/no-uninstalled-addons.md) | This rule identifies storybook addons that are invalid because they are either not installed or contain a typo in their name. | |
recommended
flat/recommended
|
+| [`storybook/prefer-pascal-case`](./docs/rules/prefer-pascal-case.md) | Stories should use PascalCase | 🔧 |
recommended
flat/recommended
|
+| [`storybook/story-exports`](./docs/rules/story-exports.md) | A story file must contain at least one story export | |
recommended
flat/recommended
csf
flat/csf
csf-strict
flat/csf-strict
|
+| [`storybook/use-storybook-expect`](./docs/rules/use-storybook-expect.md) | Use expect from `@storybook/test`, `storybook/test` or `@storybook/jest` | 🔧 |
addon-interactions
flat/addon-interactions
recommended
flat/recommended
|
+| [`storybook/use-storybook-testing-library`](./docs/rules/use-storybook-testing-library.md) | Do not use testing-library directly on stories | 🔧 |
+ Click on the Vite and React logos to learn more
+
+ >
+ )
+}
+
+export default App
diff --git a/test-storybooks/yarn-pnp/src/assets/react.svg b/test-storybooks/yarn-pnp/src/assets/react.svg
new file mode 100644
index 000000000000..6c87de9bb335
--- /dev/null
+++ b/test-storybooks/yarn-pnp/src/assets/react.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/test-storybooks/yarn-pnp/src/index.css b/test-storybooks/yarn-pnp/src/index.css
new file mode 100644
index 000000000000..08a3ac9e1e5c
--- /dev/null
+++ b/test-storybooks/yarn-pnp/src/index.css
@@ -0,0 +1,68 @@
+:root {
+ font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
+ line-height: 1.5;
+ font-weight: 400;
+
+ color-scheme: light dark;
+ color: rgba(255, 255, 255, 0.87);
+ background-color: #242424;
+
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+a {
+ font-weight: 500;
+ color: #646cff;
+ text-decoration: inherit;
+}
+a:hover {
+ color: #535bf2;
+}
+
+body {
+ margin: 0;
+ display: flex;
+ place-items: center;
+ min-width: 320px;
+ min-height: 100vh;
+}
+
+h1 {
+ font-size: 3.2em;
+ line-height: 1.1;
+}
+
+button {
+ border-radius: 8px;
+ border: 1px solid transparent;
+ padding: 0.6em 1.2em;
+ font-size: 1em;
+ font-weight: 500;
+ font-family: inherit;
+ background-color: #1a1a1a;
+ cursor: pointer;
+ transition: border-color 0.25s;
+}
+button:hover {
+ border-color: #646cff;
+}
+button:focus,
+button:focus-visible {
+ outline: 4px auto -webkit-focus-ring-color;
+}
+
+@media (prefers-color-scheme: light) {
+ :root {
+ color: #213547;
+ background-color: #ffffff;
+ }
+ a:hover {
+ color: #747bff;
+ }
+ button {
+ background-color: #f9f9f9;
+ }
+}
diff --git a/test-storybooks/yarn-pnp/src/main.tsx b/test-storybooks/yarn-pnp/src/main.tsx
new file mode 100644
index 000000000000..bef5202a32cb
--- /dev/null
+++ b/test-storybooks/yarn-pnp/src/main.tsx
@@ -0,0 +1,10 @@
+import { StrictMode } from 'react'
+import { createRoot } from 'react-dom/client'
+import './index.css'
+import App from './App.tsx'
+
+createRoot(document.getElementById('root')!).render(
+
+
+ ,
+)
diff --git a/test-storybooks/yarn-pnp/src/stories/Button.stories.ts b/test-storybooks/yarn-pnp/src/stories/Button.stories.ts
new file mode 100644
index 000000000000..0c3151f3089c
--- /dev/null
+++ b/test-storybooks/yarn-pnp/src/stories/Button.stories.ts
@@ -0,0 +1,54 @@
+import type { Meta, StoryObj } from '@storybook/react-vite';
+
+import { fn } from 'storybook/test';
+
+import { Button } from './Button';
+
+// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
+const meta = {
+ title: 'Example/Button',
+ component: Button,
+ parameters: {
+ // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
+ layout: 'centered',
+ },
+ // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
+ tags: ['autodocs'],
+ // More on argTypes: https://storybook.js.org/docs/api/argtypes
+ argTypes: {
+ backgroundColor: { control: 'color' },
+ },
+ // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args
+ args: { onClick: fn() },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
+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/test-storybooks/yarn-pnp/src/stories/Button.tsx b/test-storybooks/yarn-pnp/src/stories/Button.tsx
new file mode 100644
index 000000000000..f35dafdcb427
--- /dev/null
+++ b/test-storybooks/yarn-pnp/src/stories/Button.tsx
@@ -0,0 +1,37 @@
+import React from 'react';
+
+import './button.css';
+
+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 */
+ onClick?: () => void;
+}
+
+/** Primary UI component for user interaction */
+export const Button = ({
+ primary = false,
+ size = 'medium',
+ backgroundColor,
+ label,
+ ...props
+}: ButtonProps) => {
+ const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary';
+ return (
+
+ );
+};
diff --git a/test-storybooks/yarn-pnp/src/stories/Configure.mdx b/test-storybooks/yarn-pnp/src/stories/Configure.mdx
new file mode 100644
index 000000000000..dca091c16b1a
--- /dev/null
+++ b/test-storybooks/yarn-pnp/src/stories/Configure.mdx
@@ -0,0 +1,364 @@
+import { Meta } from "@storybook/addon-docs/blocks";
+
+import Github from "./assets/github.svg";
+import Discord from "./assets/discord.svg";
+import Youtube from "./assets/youtube.svg";
+import Tutorials from "./assets/tutorials.svg";
+import Styling from "./assets/styling.png";
+import Context from "./assets/context.png";
+import Assets from "./assets/assets.png";
+import Docs from "./assets/docs.png";
+import Share from "./assets/share.png";
+import FigmaPlugin from "./assets/figma-plugin.png";
+import Testing from "./assets/testing.png";
+import Accessibility from "./assets/accessibility.png";
+import Theming from "./assets/theming.png";
+import AddonLibrary from "./assets/addon-library.png";
+
+export const RightArrow = () =>
+
+
+
+
+
+
+
+ # Configure your project
+
+ Because Storybook works separately from your app, you'll need to configure it for your specific stack and setup. Below, explore guides for configuring Storybook with popular frameworks and tools. If you get stuck, learn how you can ask for help from our community.
+
+
+
+
+
Add styling and CSS
+
Like with web applications, there are many ways to include CSS within Storybook. Learn more about setting up styling within Storybook.
To link static files (like fonts) to your projects and stories, use the
+ `staticDirs` configuration option to specify folders to load when
+ starting Storybook.
+ # Do more with Storybook
+
+ Now that you know the basics, let's explore other parts of Storybook that will improve your experience. This list is just to get you started. You can customise Storybook in many ways to fit your needs.
+
+
+
+
+
+
+
Autodocs
+
Auto-generate living,
+ interactive reference documentation from your components and stories.
+ We recommend building UIs with a{' '}
+
+ component-driven
+ {' '}
+ process starting with atomic components and ending with pages.
+
+
+ Render pages with mock data. This makes it easy to build and review page states without
+ needing to navigate to them in your app. Here are some handy patterns for managing page
+ data in Storybook:
+
+
+
+ Use a higher-level connected component. Storybook helps you compose such data from the
+ "args" of child component stories
+
+
+ Assemble data in the page component from your services. You can mock these services out
+ using Storybook.
+