Merge branch 'next' into norbert/upgrades-2025-02-11

This commit is contained in:
Norbert de Langen 2025-02-24 15:30:13 +01:00
commit fa4fe8b659
168 changed files with 1626 additions and 1115 deletions

View File

@ -635,6 +635,7 @@ jobs:
command: | command: |
cd code cd code
yarn local-registry --open & yarn local-registry --open &
yarn wait-on tcp:127.0.0.1:6001
cd ../../ cd ../../
mkdir features-1 mkdir features-1
cd features-1 cd features-1

View File

@ -1,3 +1,13 @@
## 8.5.8
- Core: Support `esbuild@^0.25` - [#30574](https://github.com/storybookjs/storybook/pull/30574), thanks @JReinhold!
## 8.5.7
- Tags: Add story/meta usage telemetry - [#30555](https://github.com/storybookjs/storybook/pull/30555), thanks @shilman!
- Telemetry: Don't count example stories towards CSF feature stats - [#30561](https://github.com/storybookjs/storybook/pull/30561), thanks @shilman!
- Vite: Fix not stripping all HMR boundaries - [#30562](https://github.com/storybookjs/storybook/pull/30562), thanks @JReinhold!
## 8.5.6 ## 8.5.6
- Builder-Vite: Fix defaulting to allowing all hosts - [#30523](https://github.com/storybookjs/storybook/pull/30523), thanks @JReinhold! - Builder-Vite: Fix defaulting to allowing all hosts - [#30523](https://github.com/storybookjs/storybook/pull/30523), thanks @JReinhold!

View File

@ -1,3 +1,46 @@
## 8.6.0-beta.8
- Addon-Test: Fix console error in build mode - [#30625](https://github.com/storybookjs/storybook/pull/30625), thanks @JReinhold!
- Manager: Fix panel reactivity - [#30638](https://github.com/storybookjs/storybook/pull/30638), thanks @valentinpalkovic!
## 8.6.0-beta.7
- Angular: Fix @angular/platform-browser/animations never available - [#30618](https://github.com/storybookjs/storybook/pull/30618), thanks @valentinpalkovic!
- Angular: Fix @angular/platform-browser/animations never available - [#30619](https://github.com/storybookjs/storybook/pull/30619), thanks @valentinpalkovic!
- CLI: Fix peer dep issues for npm users during upgrade - [#30616](https://github.com/storybookjs/storybook/pull/30616), thanks @valentinpalkovic!
## 8.6.0-beta.6
- CLI: Fix printing of selected features - [#30605](https://github.com/storybookjs/storybook/pull/30605), thanks @ghengeveld!
- CLI: Remove Storybook dependencies before adding re-adding them - [#30600](https://github.com/storybookjs/storybook/pull/30600), thanks @valentinpalkovic!
## 8.6.0-beta.5
- Addon-Test: Make sure that only one global portable story config is ever loaded - [#30582](https://github.com/storybookjs/storybook/pull/30582), thanks @kasperpeulen!
- Angular: Support v19.2 when @angular/animations is not installed - [#30611](https://github.com/storybookjs/storybook/pull/30611), thanks @valentinpalkovic!
- Builder-Vite: Fix runtime and iframe 404 on first load - [#30567](https://github.com/storybookjs/storybook/pull/30567), thanks @valentinpalkovic!
- CLI: Don't initially select Documentation and Testing features - [#30599](https://github.com/storybookjs/storybook/pull/30599), thanks @ghengeveld!
- CLI: Make telemetry data an object - [#30581](https://github.com/storybookjs/storybook/pull/30581), thanks @ndelangen!
- Core: Support `esbuild@^0.25` - [#30574](https://github.com/storybookjs/storybook/pull/30574), thanks @JReinhold!
- Test addon: Only update `vitest.config.ts` with workspaces, otherwise create `vitest.workspace.ts` - [#30583](https://github.com/storybookjs/storybook/pull/30583), thanks @ghengeveld!
## 8.6.0-beta.4
- Addon-Test: Add telemetry data for Focused Tests - [#30568](https://github.com/storybookjs/storybook/pull/30568), thanks @JReinhold!
- Core: Allow empty render functions in CSF factories - [#30565](https://github.com/storybookjs/storybook/pull/30565), thanks @kasperpeulen!
- Core: Fix undeclared internal dependencies - [#30566](https://github.com/storybookjs/storybook/pull/30566), thanks @kasperpeulen!
## 8.6.0-beta.3
- Addon-A11y: Fix preset loading when loaded via getAbsolutePath - [#30563](https://github.com/storybookjs/storybook/pull/30563), thanks @valentinpalkovic!
- Essentials: Fix `addon-essentials` not working when used with `getAbsolutePath` - [#30557](https://github.com/storybookjs/storybook/pull/30557), thanks @JReinhold!
- Vite: Fix not stripping all HMR boundaries - [#30562](https://github.com/storybookjs/storybook/pull/30562), thanks @JReinhold!
## 8.6.0-beta.2
- CLI: Reimplement features prompt logic to handle `--yes` and fix `--features` - [#30534](https://github.com/storybookjs/storybook/pull/30534), thanks @ghengeveld!
- Telemetry: Don't count example stories towards CSF feature stats - [#30561](https://github.com/storybookjs/storybook/pull/30561), thanks @shilman!
## 8.6.0-beta.1 ## 8.6.0-beta.1
- Builder-Vite: Fix defaulting to allowing all hosts - [#30523](https://github.com/storybookjs/storybook/pull/30523), thanks @JReinhold! - Builder-Vite: Fix defaulting to allowing all hosts - [#30523](https://github.com/storybookjs/storybook/pull/30523), thanks @JReinhold!

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/addon-a11y", "name": "@storybook/addon-a11y",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Test component compliance with web accessibility standards", "description": "Test component compliance with web accessibility standards",
"keywords": [ "keywords": [
"a11y", "a11y",

View File

@ -1 +1 @@
import './dist/preview'; export * from './dist/preview';

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/addon-actions", "name": "@storybook/addon-actions",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Get UI feedback when an action is performed on an interactive element", "description": "Get UI feedback when an action is performed on an interactive element",
"keywords": [ "keywords": [
"storybook", "storybook",

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/addon-backgrounds", "name": "@storybook/addon-backgrounds",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Switch backgrounds to view components in different settings", "description": "Switch backgrounds to view components in different settings",
"keywords": [ "keywords": [
"addon", "addon",

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/addon-controls", "name": "@storybook/addon-controls",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Interact with component inputs dynamically in the Storybook UI", "description": "Interact with component inputs dynamically in the Storybook UI",
"keywords": [ "keywords": [
"addon", "addon",

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/addon-docs", "name": "@storybook/addon-docs",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Document component usage and properties in Markdown", "description": "Document component usage and properties in Markdown",
"keywords": [ "keywords": [
"addon", "addon",

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/addon-essentials", "name": "@storybook/addon-essentials",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Curated addons to bring out the best of Storybook", "description": "Curated addons to bring out the best of Storybook",
"keywords": [ "keywords": [
"addon", "addon",
@ -27,7 +27,7 @@
"import": "./dist/index.mjs", "import": "./dist/index.mjs",
"require": "./dist/index.js" "require": "./dist/index.js"
}, },
"./preview": { "./entry-preview": {
"types": "./dist/preview.d.ts", "types": "./dist/preview.d.ts",
"import": "./dist/preview.mjs", "import": "./dist/preview.mjs",
"require": "./dist/preview.js" "require": "./dist/preview.js"
@ -88,7 +88,7 @@
"*": [ "*": [
"dist/index.d.ts" "dist/index.d.ts"
], ],
"preview": [ "entry-preview": [
"dist/preview.d.ts" "dist/preview.d.ts"
] ]
} }

View File

@ -0,0 +1 @@
module.exports = require('./dist/preset');

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/addon-mdx-gfm", "name": "@storybook/addon-mdx-gfm",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "GitHub Flavored Markdown in Storybook", "description": "GitHub Flavored Markdown in Storybook",
"keywords": [ "keywords": [
"addon", "addon",

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/addon-highlight", "name": "@storybook/addon-highlight",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Highlight DOM nodes within your stories", "description": "Highlight DOM nodes within your stories",
"keywords": [ "keywords": [
"storybook-addons", "storybook-addons",

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/addon-interactions", "name": "@storybook/addon-interactions",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Automate, test and debug user interactions", "description": "Automate, test and debug user interactions",
"keywords": [ "keywords": [
"storybook-addons", "storybook-addons",

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/addon-jest", "name": "@storybook/addon-jest",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "React storybook addon that show component jest report", "description": "React storybook addon that show component jest report",
"keywords": [ "keywords": [
"addon", "addon",

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/addon-links", "name": "@storybook/addon-links",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Link stories together to build demos and prototypes with your UI components", "description": "Link stories together to build demos and prototypes with your UI components",
"keywords": [ "keywords": [
"storybook-addons", "storybook-addons",

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/addon-measure", "name": "@storybook/addon-measure",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Inspect layouts by visualizing the box model", "description": "Inspect layouts by visualizing the box model",
"keywords": [ "keywords": [
"storybook-addons", "storybook-addons",

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/addon-onboarding", "name": "@storybook/addon-onboarding",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Storybook Addon Onboarding - Introduces a new onboarding experience", "description": "Storybook Addon Onboarding - Introduces a new onboarding experience",
"keywords": [ "keywords": [
"storybook-addons", "storybook-addons",

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/addon-outline", "name": "@storybook/addon-outline",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Outline all elements with CSS to help with layout placement and alignment", "description": "Outline all elements with CSS to help with layout placement and alignment",
"keywords": [ "keywords": [
"storybook-addons", "storybook-addons",

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/addon-storysource", "name": "@storybook/addon-storysource",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "View a storys source code to see how it works and paste into your app", "description": "View a storys source code to see how it works and paste into your app",
"keywords": [ "keywords": [
"addon", "addon",

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/experimental-addon-test", "name": "@storybook/experimental-addon-test",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Integrate Vitest with Storybook", "description": "Integrate Vitest with Storybook",
"keywords": [ "keywords": [
"storybook-addons", "storybook-addons",

View File

@ -2,4 +2,7 @@ import { experimental_UniversalStore } from 'storybook/internal/manager-api';
import { type StoreState, storeOptions } from './constants'; import { type StoreState, storeOptions } from './constants';
export const store = experimental_UniversalStore.create<StoreState>(storeOptions); export const store = experimental_UniversalStore.create<StoreState>({
...storeOptions,
leader: (globalThis as any).CONFIG_TYPE === 'PRODUCTION',
});

View File

@ -17,6 +17,8 @@ import { VitestManager } from './vitest-manager';
export class TestManager { export class TestManager {
vitestManager: VitestManager; vitestManager: VitestManager;
selectedStoryCountForLastRun = 0;
constructor( constructor(
private channel: Channel, private channel: Channel,
public store: experimental_UniversalStore<StoreState>, public store: experimental_UniversalStore<StoreState>,
@ -96,6 +98,8 @@ export class TestManager {
await this.vitestManager.vitestRestartPromise; await this.vitestManager.vitestRestartPromise;
} }
this.selectedStoryCountForLastRun = payload.storyIds?.length ?? 0;
await this.vitestManager.runTests(payload); await this.vitestManager.runTests(payload);
if (temporarilyDisableCoverage) { if (temporarilyDisableCoverage) {
@ -120,7 +124,20 @@ export class TestManager {
} }
async sendProgressReport(payload: TestingModuleProgressReportPayload) { async sendProgressReport(payload: TestingModuleProgressReportPayload) {
this.channel.emit(TESTING_MODULE_PROGRESS_REPORT, payload); this.channel.emit(TESTING_MODULE_PROGRESS_REPORT, {
...payload,
details: { ...payload.details, selectedStoryCount: this.selectedStoryCountForLastRun },
});
const status = 'status' in payload ? payload.status : undefined;
const progress = 'progress' in payload ? payload.progress : undefined;
if (
((status === 'success' || status === 'cancelled') && progress?.finishedAt) ||
status === 'failed'
) {
// reset the count when a test run is fully finished
this.selectedStoryCountForLastRun = 0;
}
} }
async reportFatalError(message: string, error: Error | any) { async reportFatalError(message: string, error: Error | any) {

View File

@ -405,9 +405,9 @@ export default async function postInstall(options: PostinstallOptions) {
); );
} }
if (vitestWorkspaceFile) {
// If there's an existing workspace file, we update that file to include the Storybook test plugin. // If there's an existing workspace file, we update that file to include the Storybook test plugin.
// We assume the existing workspaces include the Vite(st) config, so we won't add it. // We assume the existing workspaces include the Vite(st) config, so we won't add it.
if (vitestWorkspaceFile) {
const workspaceTemplate = await loadTemplate('vitest.workspace.template.ts', { const workspaceTemplate = await loadTemplate('vitest.workspace.template.ts', {
EXTENDS_WORKSPACE: viteConfigFile EXTENDS_WORKSPACE: viteConfigFile
? relative(dirname(vitestWorkspaceFile), viteConfigFile) ? relative(dirname(vitestWorkspaceFile), viteConfigFile)
@ -445,21 +445,26 @@ export default async function postInstall(options: PostinstallOptions) {
logger.line(1); logger.line(1);
return; return;
} }
} else if (rootConfig) { }
// If there's an existing Vite/Vitest config, we update it to include the Storybook test plugin. // If there's an existing Vite/Vitest config with workspaces, we update it to include the Storybook test plugin.
else if (rootConfig) {
let target, updated; let target, updated;
// For Vitest 3+, we extend the config file, otherwise we fall back to creating a workspace file. const configFile = await fs.readFile(rootConfig, 'utf8');
if (isVitest3OrLater) { const hasWorkspaceConfig = configFile.includes('workspace:');
// For Vitest 3+ with an existing workspace option in the config file, we extend the workspace array,
// otherwise we fall back to creating a workspace file.
if (isVitest3OrLater && hasWorkspaceConfig) {
const configTemplate = await loadTemplate('vitest.config.template.ts', { const configTemplate = await loadTemplate('vitest.config.template.ts', {
CONFIG_DIR: options.configDir, CONFIG_DIR: options.configDir,
BROWSER_CONFIG: browserConfig, BROWSER_CONFIG: browserConfig,
SETUP_FILE: relative(dirname(rootConfig), vitestSetupFile), SETUP_FILE: relative(dirname(rootConfig), vitestSetupFile),
}); });
const configFile = await fs.readFile(rootConfig, 'utf8');
const source = babelParse(configTemplate); const source = babelParse(configTemplate);
target = babelParse(configFile); target = babelParse(configFile);
updated = updateConfigFile(source, target); updated = updateConfigFile(source, target);
} }
if (target && updated) { if (target && updated) {
logger.line(1); logger.line(1);
logger.plain(`${step} Updating your ${vitestConfigFile ? 'Vitest' : 'Vite'} config file:`); logger.plain(`${step} Updating your ${vitestConfigFile ? 'Vitest' : 'Vite'} config file:`);
@ -502,8 +507,9 @@ export default async function postInstall(options: PostinstallOptions) {
const formattedContent = await formatFileContent(newWorkspaceFile, workspaceTemplate); const formattedContent = await formatFileContent(newWorkspaceFile, workspaceTemplate);
await writeFile(newWorkspaceFile, formattedContent); await writeFile(newWorkspaceFile, formattedContent);
} }
} else { }
// If there's no existing Vitest/Vite config, we create a new Vitest config file. // If there's no existing Vitest/Vite config, we create a new Vitest config file.
else {
const newConfigFile = resolve(`vitest.config.${fileExtension}`); const newConfigFile = resolve(`vitest.config.${fileExtension}`);
const configTemplate = await loadTemplate('vitest.config.template.ts', { const configTemplate = await loadTemplate('vitest.config.template.ts', {
CONFIG_DIR: options.configDir, CONFIG_DIR: options.configDir,

View File

@ -146,6 +146,7 @@ export const experimental_serverChannel = async (channel: Channel, options: Opti
numTotalTests: progress?.numTotalTests, numTotalTests: progress?.numTotalTests,
numFailedTests: progress?.numFailedTests, numFailedTests: progress?.numFailedTests,
numPassedTests: progress?.numPassedTests, numPassedTests: progress?.numPassedTests,
numSelectedStories: payload.details?.selectedStoryCount ?? 0,
}); });
} }
@ -157,6 +158,7 @@ export const experimental_serverChannel = async (channel: Channel, options: Opti
...(options.enableCrashReports && { ...(options.enableCrashReports && {
error: error && sanitizeError(error), error: error && sanitizeError(error),
}), }),
numSelectedStories: payload.details?.selectedStoryCount ?? 0,
}); });
} }
} }

View File

@ -35,4 +35,11 @@ export const modifyErrorMessage = ({ task }: { task: Task }) => {
} }
}; };
// Enable this in 9.0
// beforeAll(() => {
// if (globalThis.globalProjectAnnotations) {
// return globalThis.globalProjectAnnotations.beforeAll();
// }
// });
afterEach(modifyErrorMessage); afterEach(modifyErrorMessage);

View File

@ -5,6 +5,7 @@ import { type RunnerTask, type TaskMeta, type TestContext } from 'vitest';
import { import {
type Report, type Report,
composeConfigs,
composeStory, composeStory,
getCsfFactoryAnnotations, getCsfFactoryAnnotations,
} from 'storybook/internal/preview-api'; } from 'storybook/internal/preview-api';
@ -34,7 +35,7 @@ export const testStory = (
annotations.story, annotations.story,
annotations.meta!, annotations.meta!,
{ initialGlobals: (await getInitialGlobals?.()) ?? {} }, { initialGlobals: (await getInitialGlobals?.()) ?? {} },
annotations.preview, annotations.preview ?? globalThis.globalProjectAnnotations,
exportName exportName
); );

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/addon-themes", "name": "@storybook/addon-themes",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Switch between multiple themes for you components in Storybook", "description": "Switch between multiple themes for you components in Storybook",
"keywords": [ "keywords": [
"css", "css",

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/addon-toolbars", "name": "@storybook/addon-toolbars",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Create your own toolbar items that control story rendering", "description": "Create your own toolbar items that control story rendering",
"keywords": [ "keywords": [
"addon", "addon",

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/addon-viewport", "name": "@storybook/addon-viewport",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Build responsive components by adjusting Storybooks viewport size and orientation", "description": "Build responsive components by adjusting Storybooks viewport size and orientation",
"keywords": [ "keywords": [
"addon", "addon",

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/builder-vite", "name": "@storybook/builder-vite",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "A plugin to run and build Storybooks with Vite", "description": "A plugin to run and build Storybooks with Vite",
"homepage": "https://github.com/storybookjs/storybook/tree/next/code/builders/builder-vite/#readme", "homepage": "https://github.com/storybookjs/storybook/tree/next/code/builders/builder-vite/#readme",
"bugs": { "bugs": {

View File

@ -1,6 +1,6 @@
import type { Options } from 'storybook/internal/types'; import type { Options } from 'storybook/internal/types';
import { genDynamicImport, genImport, genObjectFromRawEntries } from 'knitwork'; import { genDynamicImport, genObjectFromRawEntries } from 'knitwork';
import { normalize, relative } from 'pathe'; import { normalize, relative } from 'pathe';
import { dedent } from 'ts-dedent'; import { dedent } from 'ts-dedent';

View File

@ -1,8 +1,10 @@
import { getFrameworkName, loadPreviewOrConfigFile } from 'storybook/internal/common'; import { getFrameworkName, loadPreviewOrConfigFile } from 'storybook/internal/common';
import type { Options, PreviewAnnotation } from 'storybook/internal/types'; import type { Options, PreviewAnnotation } from 'storybook/internal/types';
import { dedent } from 'ts-dedent';
import { processPreviewAnnotation } from './utils/process-preview-annotation'; import { processPreviewAnnotation } from './utils/process-preview-annotation';
import { SB_VIRTUAL_FILES, getResolvedVirtualModuleId } from './virtual-file-names'; import { SB_VIRTUAL_FILES } from './virtual-file-names';
export async function generateModernIframeScriptCode(options: Options, projectRoot: string) { export async function generateModernIframeScriptCode(options: Options, projectRoot: string) {
const { presets, configDir } = options; const { presets, configDir } = options;
@ -21,7 +23,7 @@ export async function generateModernIframeScriptCode(options: Options, projectRo
// This is pulled out to a variable because it is reused in both the initial page load // This is pulled out to a variable because it is reused in both the initial page load
// and the HMR handler. We don't use the hot.accept callback params because only the changed // and the HMR handler. We don't use the hot.accept callback params because only the changed
// modules are provided, the rest are null. We can just re-import everything again in that case. // modules are provided, the rest are null. We can just re-import everything again in that case.
const getPreviewAnnotationsFunction = ` const getPreviewAnnotationsFunction = dedent`
const getProjectAnnotations = async (hmrPreviewAnnotationModules = []) => { const getProjectAnnotations = async (hmrPreviewAnnotationModules = []) => {
const preview = await import('${previewFileUrl}'); const preview = await import('${previewFileUrl}');
@ -43,13 +45,13 @@ export async function generateModernIframeScriptCode(options: Options, projectRo
const generateHMRHandler = (frameworkName: string): string => { const generateHMRHandler = (frameworkName: string): string => {
// Web components are not compatible with HMR, so disable HMR, reload page instead. // Web components are not compatible with HMR, so disable HMR, reload page instead.
if (frameworkName === '@storybook/web-components-vite') { if (frameworkName === '@storybook/web-components-vite') {
return ` return dedent`
if (import.meta.hot) { if (import.meta.hot) {
import.meta.hot.decline(); import.meta.hot.decline();
}`.trim(); }`.trim();
} }
return ` return dedent`
if (import.meta.hot) { if (import.meta.hot) {
import.meta.hot.accept('${SB_VIRTUAL_FILES.VIRTUAL_STORIES_FILE}', (newModule) => { import.meta.hot.accept('${SB_VIRTUAL_FILES.VIRTUAL_STORIES_FILE}', (newModule) => {
// importFn has changed so we need to patch the new one in // importFn has changed so we need to patch the new one in
@ -72,24 +74,22 @@ export async function generateModernIframeScriptCode(options: Options, projectRo
* *
* @todo Inline variable and remove `noinspection` * @todo Inline variable and remove `noinspection`
*/ */
const code = ` const code = dedent`
import { setup } from 'storybook/internal/preview/runtime'; import { setup } from 'storybook/internal/preview/runtime';
import '${SB_VIRTUAL_FILES.VIRTUAL_ADDON_SETUP_FILE}'; import '${SB_VIRTUAL_FILES.VIRTUAL_ADDON_SETUP_FILE}';
setup(); setup();
import { composeConfigs, PreviewWeb, ClientApi } from 'storybook/internal/preview-api'; import { composeConfigs, PreviewWeb } from 'storybook/internal/preview-api';
import { isPreview } from 'storybook/internal/csf'; import { isPreview } from 'storybook/internal/csf';
import { importFn } from '${SB_VIRTUAL_FILES.VIRTUAL_STORIES_FILE}'; import { importFn } from '${SB_VIRTUAL_FILES.VIRTUAL_STORIES_FILE}';
${getPreviewAnnotationsFunction} ${getPreviewAnnotationsFunction}
window.__STORYBOOK_PREVIEW__ = window.__STORYBOOK_PREVIEW__ || new PreviewWeb(importFn, getProjectAnnotations); window.__STORYBOOK_PREVIEW__ = window.__STORYBOOK_PREVIEW__ || new PreviewWeb(importFn, getProjectAnnotations);
window.__STORYBOOK_STORY_STORE__ = window.__STORYBOOK_STORY_STORE__ || window.__STORYBOOK_PREVIEW__.storyStore; window.__STORYBOOK_STORY_STORE__ = window.__STORYBOOK_STORY_STORE__ || window.__STORYBOOK_PREVIEW__.storyStore;
${generateHMRHandler(frameworkName)}; ${generateHMRHandler(frameworkName)};`.trim();
`.trim();
return code; return code;
} }

View File

@ -7,7 +7,6 @@ import type { Middleware, Options } from 'storybook/internal/types';
import type { ViteDevServer } from 'vite'; import type { ViteDevServer } from 'vite';
import { build as viteBuild } from './build'; import { build as viteBuild } from './build';
import { transformIframeHtml } from './transform-iframe-html';
import type { ViteBuilder } from './types'; import type { ViteBuilder } from './types';
import { createViteServer } from './vite-server'; import { createViteServer } from './vite-server';
@ -16,22 +15,8 @@ export { hasVitePlugins } from './utils/has-vite-plugins';
export * from './types'; export * from './types';
function iframeMiddleware(options: Options, server: ViteDevServer): Middleware { function iframeHandler(options: Options, server: ViteDevServer): Middleware {
return async (req, res, next) => { return async (req, res) => {
if (!req.url || !req.url.match(/^\/iframe\.html($|\?)/)) {
next();
return;
}
// the base isn't used for anything, but it's required by the URL constructor
const url = new URL(req.url, 'http://localhost:6006');
// We need to handle `html-proxy` params for style tag HMR https://github.com/storybookjs/builder-vite/issues/266#issuecomment-1055677865
// e.g. /iframe.html?html-proxy&index=0.css
if (url.searchParams.has('html-proxy')) {
next();
return;
}
const indexHtml = await readFile(require.resolve('@storybook/builder-vite/input/iframe.html'), { const indexHtml = await readFile(require.resolve('@storybook/builder-vite/input/iframe.html'), {
encoding: 'utf8', encoding: 'utf8',
}); });
@ -57,7 +42,7 @@ export const start: ViteBuilder['start'] = async ({
}) => { }) => {
server = await createViteServer(options as Options, devServer); server = await createViteServer(options as Options, devServer);
router.use(iframeMiddleware(options as Options, server)); router.get('/iframe.html', iframeHandler(options as Options, server));
router.use(server.middlewares); router.use(server.middlewares);
return { return {

View File

@ -17,7 +17,6 @@ const INCLUDE_CANDIDATES = [
'@storybook/addon-backgrounds/preview', '@storybook/addon-backgrounds/preview',
'@storybook/addon-designs/blocks', '@storybook/addon-designs/blocks',
'@storybook/addon-docs/preview', '@storybook/addon-docs/preview',
'@storybook/addon-essentials/preview',
'@storybook/addon-essentials/actions/preview', '@storybook/addon-essentials/actions/preview',
'@storybook/addon-essentials/actions/preview', '@storybook/addon-essentials/actions/preview',
'@storybook/addon-essentials/backgrounds/preview', '@storybook/addon-essentials/backgrounds/preview',
@ -27,6 +26,7 @@ const INCLUDE_CANDIDATES = [
'@storybook/addon-essentials/outline/preview', '@storybook/addon-essentials/outline/preview',
'@storybook/addon-essentials/viewport/preview', '@storybook/addon-essentials/viewport/preview',
'@storybook/addon-highlight/preview', '@storybook/addon-highlight/preview',
'@storybook/addon-interactions/preview',
'@storybook/addon-links/preview', '@storybook/addon-links/preview',
'@storybook/addon-measure/preview', '@storybook/addon-measure/preview',
'@storybook/addon-outline/preview', '@storybook/addon-outline/preview',
@ -132,6 +132,7 @@ const INCLUDE_CANDIDATES = [
'react-textarea-autosize', 'react-textarea-autosize',
'react', 'react',
'react/jsx-runtime', 'react/jsx-runtime',
'react/jsx-dev-runtime',
'refractor/core', 'refractor/core',
'refractor/lang/bash.js', 'refractor/lang/bash.js',
'refractor/lang/css.js', 'refractor/lang/css.js',
@ -150,6 +151,7 @@ const INCLUDE_CANDIDATES = [
'slash', 'slash',
'store2', 'store2',
'storybook/internal/preview/runtime', 'storybook/internal/preview/runtime',
'storybook/internal/csf',
'synchronous-promise', 'synchronous-promise',
'telejson', 'telejson',
'ts-dedent', 'ts-dedent',

View File

@ -9,17 +9,17 @@ import type { Plugin } from 'vite';
export async function stripStoryHMRBoundary(): Promise<Plugin> { export async function stripStoryHMRBoundary(): Promise<Plugin> {
const { createFilter } = await import('vite'); const { createFilter } = await import('vite');
const filter = createFilter(/\.stories\.([tj])sx?$/); const filter = createFilter(/\.stories\.\w+$/);
return { return {
name: 'storybook:strip-hmr-boundary-plugin', name: 'storybook:strip-hmr-boundary-plugin',
enforce: 'post', enforce: 'post',
async transform(src: string, id: string) { async transform(src, id) {
if (!filter(id)) { if (!filter(id)) {
return undefined; return undefined;
} }
const s = new MagicString(src); const s = new MagicString(src);
s.replace(/import\.meta\.hot\.accept\(\);/, ''); s.replace(/import\.meta\.hot\.accept\w*/, '(function hmrBoundaryNoop(){})');
return { return {
code: s.toString(), code: s.toString(),

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/builder-webpack5", "name": "@storybook/builder-webpack5",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Storybook framework-agnostic API", "description": "Storybook framework-agnostic API",
"keywords": [ "keywords": [
"storybook" "storybook"

View File

@ -16,7 +16,7 @@ import prettyTime from 'pretty-hrtime';
import sirv from 'sirv'; import sirv from 'sirv';
import { corePath } from 'storybook/core-path'; import { corePath } from 'storybook/core-path';
import type { Configuration, Stats, StatsOptions } from 'webpack'; import type { Configuration, Stats, StatsOptions } from 'webpack';
import webpackDep, { DefinePlugin, ProgressPlugin } from 'webpack'; import webpackDep, { DefinePlugin, IgnorePlugin, ProgressPlugin } from 'webpack';
import webpackDevMiddleware from 'webpack-dev-middleware'; import webpackDevMiddleware from 'webpack-dev-middleware';
import webpackHotMiddleware from 'webpack-hot-middleware'; import webpackHotMiddleware from 'webpack-hot-middleware';
@ -24,6 +24,7 @@ export * from './types';
export * from './preview/virtual-module-mapping'; export * from './preview/virtual-module-mapping';
export const WebpackDefinePlugin = DefinePlugin; export const WebpackDefinePlugin = DefinePlugin;
export const WebpackIgnorePlugin = IgnorePlugin;
export const printDuration = (startTime: [number, number]) => export const printDuration = (startTime: [number, number]) =>
prettyTime(process.hrtime(startTime)) prettyTime(process.hrtime(startTime))

View File

@ -83,9 +83,5 @@
import './sb-manager/runtime.js'; import './sb-manager/runtime.js';
</script> </script>
<% if (!ignorePreview) { %>
<link href="./sb-preview/runtime.js" rel="prefetch" as="script" />
<% } %>
</body> </body>
</html> </html>

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/core", "name": "@storybook/core",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Storybook framework-agnostic API", "description": "Storybook framework-agnostic API",
"keywords": [ "keywords": [
"storybook" "storybook"
@ -361,7 +361,7 @@
"downshift": "^9.0.4", "downshift": "^9.0.4",
"ejs": "^3.1.10", "ejs": "^3.1.10",
"es-toolkit": "^1.22.0", "es-toolkit": "^1.22.0",
"esbuild": "^0.25.0", "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-plugin-alias": "^0.2.1", "esbuild-plugin-alias": "^0.2.1",
"execa": "^8.0.1", "execa": "^8.0.1",
"fd-package-json": "^1.2.0", "fd-package-json": "^1.2.0",

View File

@ -34,7 +34,7 @@ export async function getAddonAnnotations(addon: string) {
// for backwards compatibility, if it's not a core addon we use /preview entrypoint // for backwards compatibility, if it's not a core addon we use /preview entrypoint
if (!data.isCoreAddon) { if (!data.isCoreAddon) {
data.importPath = `@storybook/${addon}/preview`; data.importPath = `${addon}/preview`;
} }
require.resolve(path.join(addon, 'preview')); require.resolve(path.join(addon, 'preview'));

View File

@ -13,7 +13,7 @@ import findCacheDirectory from 'find-cache-dir';
*/ */
export function resolvePathInStorybookCache(fileOrDirectoryName: string, sub = 'default'): string { export function resolvePathInStorybookCache(fileOrDirectoryName: string, sub = 'default'): string {
let cacheDirectory = findCacheDirectory({ name: 'storybook' }); let cacheDirectory = findCacheDirectory({ name: 'storybook' });
cacheDirectory ||= join(process.cwd(), '.cache', 'storybook'); cacheDirectory ||= join(process.cwd(), 'node_modules', '.cache', 'storybook');
return join(cacheDirectory, sub, fileOrDirectoryName); return join(cacheDirectory, sub, fileOrDirectoryName);
} }

View File

@ -1,88 +1,88 @@
// auto generated file, do not edit // auto generated file, do not edit
export default { export default {
'@storybook/addon-a11y': '8.6.0-beta.1', '@storybook/addon-a11y': '8.6.0-beta.8',
'@storybook/addon-actions': '8.6.0-beta.1', '@storybook/addon-actions': '8.6.0-beta.8',
'@storybook/addon-backgrounds': '8.6.0-beta.1', '@storybook/addon-backgrounds': '8.6.0-beta.8',
'@storybook/addon-controls': '8.6.0-beta.1', '@storybook/addon-controls': '8.6.0-beta.8',
'@storybook/addon-docs': '8.6.0-beta.1', '@storybook/addon-docs': '8.6.0-beta.8',
'@storybook/addon-essentials': '8.6.0-beta.1', '@storybook/addon-essentials': '8.6.0-beta.8',
'@storybook/addon-mdx-gfm': '8.6.0-beta.1', '@storybook/addon-mdx-gfm': '8.6.0-beta.8',
'@storybook/addon-highlight': '8.6.0-beta.1', '@storybook/addon-highlight': '8.6.0-beta.8',
'@storybook/addon-interactions': '8.6.0-beta.1', '@storybook/addon-interactions': '8.6.0-beta.8',
'@storybook/addon-jest': '8.6.0-beta.1', '@storybook/addon-jest': '8.6.0-beta.8',
'@storybook/addon-links': '8.6.0-beta.1', '@storybook/addon-links': '8.6.0-beta.8',
'@storybook/addon-measure': '8.6.0-beta.1', '@storybook/addon-measure': '8.6.0-beta.8',
'@storybook/addon-onboarding': '8.6.0-beta.1', '@storybook/addon-onboarding': '8.6.0-beta.8',
'@storybook/addon-outline': '8.6.0-beta.1', '@storybook/addon-outline': '8.6.0-beta.8',
'@storybook/addon-storysource': '8.6.0-beta.1', '@storybook/addon-storysource': '8.6.0-beta.8',
'@storybook/experimental-addon-test': '8.6.0-beta.1', '@storybook/experimental-addon-test': '8.6.0-beta.8',
'@storybook/addon-themes': '8.6.0-beta.1', '@storybook/addon-themes': '8.6.0-beta.8',
'@storybook/addon-toolbars': '8.6.0-beta.1', '@storybook/addon-toolbars': '8.6.0-beta.8',
'@storybook/addon-viewport': '8.6.0-beta.1', '@storybook/addon-viewport': '8.6.0-beta.8',
'@storybook/builder-vite': '8.6.0-beta.1', '@storybook/builder-vite': '8.6.0-beta.8',
'@storybook/builder-webpack5': '8.6.0-beta.1', '@storybook/builder-webpack5': '8.6.0-beta.8',
'@storybook/core': '8.6.0-beta.1', '@storybook/core': '8.6.0-beta.8',
'@storybook/builder-manager': '8.6.0-beta.1', '@storybook/builder-manager': '8.6.0-beta.8',
'@storybook/channels': '8.6.0-beta.1', '@storybook/channels': '8.6.0-beta.8',
'@storybook/client-logger': '8.6.0-beta.1', '@storybook/client-logger': '8.6.0-beta.8',
'@storybook/components': '8.6.0-beta.1', '@storybook/components': '8.6.0-beta.8',
'@storybook/core-common': '8.6.0-beta.1', '@storybook/core-common': '8.6.0-beta.8',
'@storybook/core-events': '8.6.0-beta.1', '@storybook/core-events': '8.6.0-beta.8',
'@storybook/core-server': '8.6.0-beta.1', '@storybook/core-server': '8.6.0-beta.8',
'@storybook/csf-tools': '8.6.0-beta.1', '@storybook/csf-tools': '8.6.0-beta.8',
'@storybook/docs-tools': '8.6.0-beta.1', '@storybook/docs-tools': '8.6.0-beta.8',
'@storybook/manager': '8.6.0-beta.1', '@storybook/manager': '8.6.0-beta.8',
'@storybook/manager-api': '8.6.0-beta.1', '@storybook/manager-api': '8.6.0-beta.8',
'@storybook/node-logger': '8.6.0-beta.1', '@storybook/node-logger': '8.6.0-beta.8',
'@storybook/preview': '8.6.0-beta.1', '@storybook/preview': '8.6.0-beta.8',
'@storybook/preview-api': '8.6.0-beta.1', '@storybook/preview-api': '8.6.0-beta.8',
'@storybook/router': '8.6.0-beta.1', '@storybook/router': '8.6.0-beta.8',
'@storybook/telemetry': '8.6.0-beta.1', '@storybook/telemetry': '8.6.0-beta.8',
'@storybook/theming': '8.6.0-beta.1', '@storybook/theming': '8.6.0-beta.8',
'@storybook/types': '8.6.0-beta.1', '@storybook/types': '8.6.0-beta.8',
'@storybook/angular': '8.6.0-beta.1', '@storybook/angular': '8.6.0-beta.8',
'@storybook/ember': '8.6.0-beta.1', '@storybook/ember': '8.6.0-beta.8',
'@storybook/experimental-nextjs-vite': '8.6.0-beta.1', '@storybook/experimental-nextjs-vite': '8.6.0-beta.8',
'@storybook/html-vite': '8.6.0-beta.1', '@storybook/html-vite': '8.6.0-beta.8',
'@storybook/html-webpack5': '8.6.0-beta.1', '@storybook/html-webpack5': '8.6.0-beta.8',
'@storybook/nextjs': '8.6.0-beta.1', '@storybook/nextjs': '8.6.0-beta.8',
'@storybook/preact-vite': '8.6.0-beta.1', '@storybook/preact-vite': '8.6.0-beta.8',
'@storybook/preact-webpack5': '8.6.0-beta.1', '@storybook/preact-webpack5': '8.6.0-beta.8',
'@storybook/react-native-web-vite': '8.6.0-beta.1', '@storybook/react-native-web-vite': '8.6.0-beta.8',
'@storybook/react-vite': '8.6.0-beta.1', '@storybook/react-vite': '8.6.0-beta.8',
'@storybook/react-webpack5': '8.6.0-beta.1', '@storybook/react-webpack5': '8.6.0-beta.8',
'@storybook/server-webpack5': '8.6.0-beta.1', '@storybook/server-webpack5': '8.6.0-beta.8',
'@storybook/svelte-vite': '8.6.0-beta.1', '@storybook/svelte-vite': '8.6.0-beta.8',
'@storybook/svelte-webpack5': '8.6.0-beta.1', '@storybook/svelte-webpack5': '8.6.0-beta.8',
'@storybook/sveltekit': '8.6.0-beta.1', '@storybook/sveltekit': '8.6.0-beta.8',
'@storybook/vue3-vite': '8.6.0-beta.1', '@storybook/vue3-vite': '8.6.0-beta.8',
'@storybook/vue3-webpack5': '8.6.0-beta.1', '@storybook/vue3-webpack5': '8.6.0-beta.8',
'@storybook/web-components-vite': '8.6.0-beta.1', '@storybook/web-components-vite': '8.6.0-beta.8',
'@storybook/web-components-webpack5': '8.6.0-beta.1', '@storybook/web-components-webpack5': '8.6.0-beta.8',
'@storybook/blocks': '8.6.0-beta.1', '@storybook/blocks': '8.6.0-beta.8',
storybook: '8.6.0-beta.1', storybook: '8.6.0-beta.8',
sb: '8.6.0-beta.1', sb: '8.6.0-beta.8',
'@storybook/cli': '8.6.0-beta.1', '@storybook/cli': '8.6.0-beta.8',
'@storybook/codemod': '8.6.0-beta.1', '@storybook/codemod': '8.6.0-beta.8',
'@storybook/core-webpack': '8.6.0-beta.1', '@storybook/core-webpack': '8.6.0-beta.8',
'create-storybook': '8.6.0-beta.1', 'create-storybook': '8.6.0-beta.8',
'@storybook/csf-plugin': '8.6.0-beta.1', '@storybook/csf-plugin': '8.6.0-beta.8',
'@storybook/instrumenter': '8.6.0-beta.1', '@storybook/instrumenter': '8.6.0-beta.8',
'@storybook/react-dom-shim': '8.6.0-beta.1', '@storybook/react-dom-shim': '8.6.0-beta.8',
'@storybook/source-loader': '8.6.0-beta.1', '@storybook/source-loader': '8.6.0-beta.8',
'@storybook/test': '8.6.0-beta.1', '@storybook/test': '8.6.0-beta.8',
'@storybook/preset-create-react-app': '8.6.0-beta.1', '@storybook/preset-create-react-app': '8.6.0-beta.8',
'@storybook/preset-html-webpack': '8.6.0-beta.1', '@storybook/preset-html-webpack': '8.6.0-beta.8',
'@storybook/preset-preact-webpack': '8.6.0-beta.1', '@storybook/preset-preact-webpack': '8.6.0-beta.8',
'@storybook/preset-react-webpack': '8.6.0-beta.1', '@storybook/preset-react-webpack': '8.6.0-beta.8',
'@storybook/preset-server-webpack': '8.6.0-beta.1', '@storybook/preset-server-webpack': '8.6.0-beta.8',
'@storybook/preset-svelte-webpack': '8.6.0-beta.1', '@storybook/preset-svelte-webpack': '8.6.0-beta.8',
'@storybook/preset-vue3-webpack': '8.6.0-beta.1', '@storybook/preset-vue3-webpack': '8.6.0-beta.8',
'@storybook/html': '8.6.0-beta.1', '@storybook/html': '8.6.0-beta.8',
'@storybook/preact': '8.6.0-beta.1', '@storybook/preact': '8.6.0-beta.8',
'@storybook/react': '8.6.0-beta.1', '@storybook/react': '8.6.0-beta.8',
'@storybook/server': '8.6.0-beta.1', '@storybook/server': '8.6.0-beta.8',
'@storybook/svelte': '8.6.0-beta.1', '@storybook/svelte': '8.6.0-beta.8',
'@storybook/vue3': '8.6.0-beta.1', '@storybook/vue3': '8.6.0-beta.8',
'@storybook/web-components': '8.6.0-beta.1', '@storybook/web-components': '8.6.0-beta.8',
}; };

View File

@ -78,13 +78,14 @@ export async function storybookDevServer(options: Options) {
channel: serverChannel, channel: serverChannel,
}); });
let previewStarted: Promise<any> = Promise.resolve(); let previewResult: Awaited<ReturnType<(typeof previewBuilder)['start']>> =
await Promise.resolve();
if (!options.ignorePreview) { if (!options.ignorePreview) {
if (!options.quiet) { if (!options.quiet) {
logger.info('=> Starting preview..'); logger.info('=> Starting preview..');
} }
previewStarted = previewBuilder previewResult = await previewBuilder
.start({ .start({
startTime: process.hrtime(), startTime: process.hrtime(),
options, options,
@ -108,14 +109,6 @@ export async function storybookDevServer(options: Options) {
}); });
} }
// this is a preview route, the builder has to be started before we can serve it
// this handler keeps request to that route pending until the builder is ready to serve it, preventing a 404
app.use('/iframe.html', (req, res, next) => {
// We need to catch here or node will treat any errors thrown by `previewStarted` as
// unhandled and exit (even though they are very much handled below)
previewStarted.catch(() => {}).then(() => next());
});
const listening = new Promise<void>((resolve, reject) => { const listening = new Promise<void>((resolve, reject) => {
server.once('error', reject); server.once('error', reject);
app.listen({ port, host }, resolve); app.listen({ port, host }, resolve);
@ -132,8 +125,6 @@ export async function storybookDevServer(options: Options) {
throw indexError; throw indexError;
} }
const previewResult = await previewStarted;
// Now the preview has successfully started, we can count this as a 'dev' event. // Now the preview has successfully started, we can count this as a 'dev' event.
doTelemetry(app, core, initializedStoryIndexGenerator, options); doTelemetry(app, core, initializedStoryIndexGenerator, options);

View File

@ -21,19 +21,26 @@ export interface Preview<TRenderer extends Renderer = Renderer> {
} }
export function definePreview<TRenderer extends Renderer>( export function definePreview<TRenderer extends Renderer>(
preview: Preview<TRenderer>['input'] input: Preview<TRenderer>['input']
): Preview<TRenderer> { ): Preview<TRenderer> {
return { let composed: NormalizedProjectAnnotations<TRenderer>;
const preview: Preview<TRenderer> = {
_tag: 'Preview', _tag: 'Preview',
input: preview, input,
get composed() { get composed() {
const { addons, ...rest } = preview; if (composed) {
return normalizeProjectAnnotations<TRenderer>(composeConfigs([...(addons ?? []), rest])); return composed;
}
const { addons, ...rest } = input;
composed = normalizeProjectAnnotations<TRenderer>(composeConfigs([...(addons ?? []), rest]));
return composed;
}, },
meta(meta: ComponentAnnotations<TRenderer>) { meta(meta: ComponentAnnotations<TRenderer>) {
return defineMeta(meta, this); return defineMeta(meta, this);
}, },
}; };
globalThis.globalProjectAnnotations = preview.composed;
return preview;
} }
export function isPreview(input: unknown): input is Preview<Renderer> { export function isPreview(input: unknown): input is Preview<Renderer> {
@ -46,7 +53,7 @@ export interface Meta<TRenderer extends Renderer, TArgs extends Args = Args> {
composed: NormalizedComponentAnnotations<TRenderer>; composed: NormalizedComponentAnnotations<TRenderer>;
preview: Preview<TRenderer>; preview: Preview<TRenderer>;
story(input: ComponentAnnotations<TRenderer, TArgs>): Story<TRenderer, TArgs>; story(input: StoryAnnotations<TRenderer, TArgs>): Story<TRenderer, TArgs>;
} }
export function isMeta(input: unknown): input is Meta<Renderer> { export function isMeta(input: unknown): input is Meta<Renderer> {

View File

@ -1 +1 @@
export const version = '8.6.0-beta.1'; export const version = '8.6.0-beta.8';

View File

@ -1,58 +1,70 @@
import type { FC } from 'react'; import type { FC } from 'react';
import React from 'react'; import React, { useMemo, useState } from 'react';
import { type API_LeafEntry, Addon_TypesEnum } from '@storybook/core/types'; import { Addon_TypesEnum } from '@storybook/core/types';
import { Consumer } from '@storybook/core/manager-api'; import { useChannel, useStorybookApi, useStorybookState } from '@storybook/core/manager-api';
import type { API, Combo } from '@storybook/core/manager-api';
import memoize from 'memoizerific';
import { STORY_PREPARED } from '../../core-events';
import { AddonPanel } from '../components/panel/Panel'; import { AddonPanel } from '../components/panel/Panel';
const createPanelActions = memoize(1)((api) => ({ const Panel: FC<any> = (props) => {
const api = useStorybookApi();
const state = useStorybookState();
const [story, setStory] = useState(api.getCurrentStoryData());
useChannel(
{
[STORY_PREPARED]: () => {
setStory(api.getCurrentStoryData());
},
},
[]
);
const { parameters, type } = story ?? {};
const panelActions = useMemo(
() => ({
onSelect: (panel: string) => api.setSelectedPanel(panel), onSelect: (panel: string) => api.setSelectedPanel(panel),
toggleVisibility: () => api.togglePanel(), toggleVisibility: () => api.togglePanel(),
togglePosition: () => api.togglePanelPosition(), togglePosition: () => api.togglePanelPosition(),
})); }),
[api]
);
const getPanels = memoize(1)((api: API, story: API_LeafEntry) => { const panels = useMemo(() => {
const allPanels = api.getElements(Addon_TypesEnum.PANEL); const allPanels = api.getElements(Addon_TypesEnum.PANEL);
if (!allPanels || !story || story.type !== 'story') { if (!allPanels || type !== 'story') {
return allPanels; return allPanels;
} }
const { parameters } = story;
const filteredPanels: typeof allPanels = {}; const filteredPanels: typeof allPanels = {};
Object.entries(allPanels).forEach(([id, panel]) => { Object.entries(allPanels).forEach(([id, p]) => {
const { paramKey }: any = panel; const { paramKey }: any = p;
if (paramKey && parameters && parameters[paramKey] && parameters[paramKey].disable) { if (paramKey && parameters && parameters[paramKey] && parameters[paramKey].disable) {
return; return;
} }
if ( if (p.disabled === true || (typeof p.disabled === 'function' && p.disabled(parameters))) {
panel.disabled === true ||
(typeof panel.disabled === 'function' && panel.disabled(parameters))
) {
return; return;
} }
filteredPanels[id] = panel; filteredPanels[id] = p;
}); });
return filteredPanels; return filteredPanels;
}); }, [api, type, parameters]);
const mapper = ({ state, api }: Combo) => ({ return (
panels: getPanels(api, api.getCurrentStoryData()), <AddonPanel
selectedPanel: api.getSelectedPanel(), panels={panels}
panelPosition: state.layout.panelPosition, selectedPanel={api.getSelectedPanel()}
actions: createPanelActions(api), panelPosition={state.layout.panelPosition}
shortcuts: api.getShortcutKeys(), actions={panelActions}
}); shortcuts={api.getShortcutKeys()}
{...props}
const Panel: FC<any> = (props) => ( />
<Consumer filter={mapper}>{(customProps) => <AddonPanel {...props} {...customProps} />}</Consumer>
); );
};
export default Panel; export default Panel;

View File

@ -1,7 +1,7 @@
/* eslint-disable no-underscore-dangle */ /* eslint-disable no-underscore-dangle */
/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/naming-convention */
import { type CleanupCallback, isExportStory } from '@storybook/core/csf'; import { type CleanupCallback, type Preview, isExportStory } from '@storybook/core/csf';
import type { import type {
Args, Args,
Canvas, Canvas,
@ -73,7 +73,10 @@ export function setProjectAnnotations<TRenderer extends Renderer = Renderer>(
| NamedOrDefaultProjectAnnotations<TRenderer>[] | NamedOrDefaultProjectAnnotations<TRenderer>[]
): NormalizedProjectAnnotations<TRenderer> { ): NormalizedProjectAnnotations<TRenderer> {
const annotations = Array.isArray(projectAnnotations) ? projectAnnotations : [projectAnnotations]; const annotations = Array.isArray(projectAnnotations) ? projectAnnotations : [projectAnnotations];
globalThis.globalProjectAnnotations = composeConfigs(annotations.map(extractAnnotation)); globalThis.globalProjectAnnotations = composeConfigs([
globalThis.defaultProjectAnnotations ?? {},
composeConfigs(annotations.map(extractAnnotation)),
]);
/* /*
We must return the composition of default and global annotations here We must return the composition of default and global annotations here
@ -82,10 +85,7 @@ export function setProjectAnnotations<TRenderer extends Renderer = Renderer>(
const projectAnnotations = setProjectAnnotations(...); const projectAnnotations = setProjectAnnotations(...);
beforeAll(projectAnnotations.beforeAll) beforeAll(projectAnnotations.beforeAll)
*/ */
return composeConfigs([ return globalThis.globalProjectAnnotations ?? {};
globalThis.defaultProjectAnnotations ?? {},
globalThis.globalProjectAnnotations ?? {},
]);
} }
const cleanups: CleanupCallback[] = []; const cleanups: CleanupCallback[] = [];
@ -123,10 +123,7 @@ export function composeStory<TRenderer extends Renderer = Renderer, TArgs extend
const normalizedProjectAnnotations = normalizeProjectAnnotations<TRenderer>( const normalizedProjectAnnotations = normalizeProjectAnnotations<TRenderer>(
composeConfigs([ composeConfigs([
defaultConfig && Object.keys(defaultConfig).length > 0 defaultConfig ?? globalThis.globalProjectAnnotations ?? {},
? defaultConfig
: (globalThis.defaultProjectAnnotations ?? {}),
globalThis.globalProjectAnnotations ?? {},
projectAnnotations ?? {}, projectAnnotations ?? {},
]) ])
); );
@ -165,6 +162,8 @@ export function composeStory<TRenderer extends Renderer = Renderer, TArgs extend
mount: null!, mount: null!,
}); });
context.parameters.__isPortableStory = true;
context.context = context; context.context = context;
if (story.renderToCanvas) { if (story.renderToCanvas) {

View File

@ -1,7 +1,12 @@
import type { ViewMode as ViewModeBase } from '@storybook/core/csf'; import type { ViewMode as ViewModeBase } from '@storybook/core/csf';
import type { Renderer as CSFRenderer } from '@storybook/core/csf';
import type { Addon_OptionsParameter } from './addons'; import type { Addon_OptionsParameter } from './addons';
// Fix https://github.com/storybookjs/storybook/issues/30540
// Can be removed once @storybook/core and storybook are merged in 9.0
export interface Renderer extends CSFRenderer {}
export type { export type {
AfterEach, AfterEach,
AnnotatedStoryFn, AnnotatedStoryFn,
@ -35,7 +40,6 @@ export type {
PlayFunction, PlayFunction,
PlayFunctionContext, PlayFunctionContext,
ProjectAnnotations as BaseProjectAnnotations, ProjectAnnotations as BaseProjectAnnotations,
Renderer,
SBArrayType, SBArrayType,
SBEnumType, SBEnumType,
SBIntersectionType, SBIntersectionType,

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/builder-manager", "name": "@storybook/builder-manager",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Storybook manager builder", "description": "Storybook manager builder",
"keywords": [ "keywords": [
"storybook" "storybook"

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/channels", "name": "@storybook/channels",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "", "description": "",
"keywords": [ "keywords": [
"storybook" "storybook"

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/client-logger", "name": "@storybook/client-logger",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "", "description": "",
"keywords": [ "keywords": [
"storybook" "storybook"

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/components", "name": "@storybook/components",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Core Storybook Components", "description": "Core Storybook Components",
"keywords": [ "keywords": [
"storybook" "storybook"

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/core-common", "name": "@storybook/core-common",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Storybook framework-agnostic API", "description": "Storybook framework-agnostic API",
"keywords": [ "keywords": [
"storybook" "storybook"

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/core-events", "name": "@storybook/core-events",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Event names used in storybook core", "description": "Event names used in storybook core",
"keywords": [ "keywords": [
"storybook" "storybook"

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/core-server", "name": "@storybook/core-server",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Storybook framework-agnostic API", "description": "Storybook framework-agnostic API",
"keywords": [ "keywords": [
"storybook" "storybook"

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/csf-tools", "name": "@storybook/csf-tools",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Parse and manipulate CSF and Storybook config files", "description": "Parse and manipulate CSF and Storybook config files",
"keywords": [ "keywords": [
"storybook" "storybook"

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/docs-tools", "name": "@storybook/docs-tools",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Shared utility functions for frameworks to implement docs", "description": "Shared utility functions for frameworks to implement docs",
"keywords": [ "keywords": [
"storybook" "storybook"

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/manager-api", "name": "@storybook/manager-api",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Core Storybook Manager API & Context", "description": "Core Storybook Manager API & Context",
"keywords": [ "keywords": [
"storybook" "storybook"

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/manager", "name": "@storybook/manager",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Core Storybook UI", "description": "Core Storybook UI",
"keywords": [ "keywords": [
"storybook" "storybook"

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/node-logger", "name": "@storybook/node-logger",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "", "description": "",
"keywords": [ "keywords": [
"storybook" "storybook"

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/preview-api", "name": "@storybook/preview-api",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "", "description": "",
"keywords": [ "keywords": [
"storybook" "storybook"

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/preview", "name": "@storybook/preview",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "", "description": "",
"keywords": [ "keywords": [
"storybook" "storybook"

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/router", "name": "@storybook/router",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Core Storybook Router", "description": "Core Storybook Router",
"keywords": [ "keywords": [
"storybook" "storybook"

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/telemetry", "name": "@storybook/telemetry",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Telemetry logging for crash reports and usage statistics", "description": "Telemetry logging for crash reports and usage statistics",
"keywords": [ "keywords": [
"storybook" "storybook"

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/theming", "name": "@storybook/theming",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Core Storybook Components", "description": "Core Storybook Components",
"keywords": [ "keywords": [
"storybook" "storybook"

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/types", "name": "@storybook/types",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Core Storybook TS Types", "description": "Core Storybook TS Types",
"keywords": [ "keywords": [
"storybook" "storybook"

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/angular", "name": "@storybook/angular",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Storybook for Angular: Develop Angular components in isolation with hot reloading.", "description": "Storybook for Angular: Develop Angular components in isolation with hot reloading.",
"keywords": [ "keywords": [
"storybook", "storybook",
@ -101,6 +101,7 @@
"@angular-devkit/architect": ">=0.1500.0 < 0.2000.0", "@angular-devkit/architect": ">=0.1500.0 < 0.2000.0",
"@angular-devkit/build-angular": ">=15.0.0 < 20.0.0", "@angular-devkit/build-angular": ">=15.0.0 < 20.0.0",
"@angular-devkit/core": ">=15.0.0 < 20.0.0", "@angular-devkit/core": ">=15.0.0 < 20.0.0",
"@angular/animations": ">=15.0.0 < 20.0.0",
"@angular/cli": ">=15.0.0 < 20.0.0", "@angular/cli": ">=15.0.0 < 20.0.0",
"@angular/common": ">=15.0.0 < 20.0.0", "@angular/common": ">=15.0.0 < 20.0.0",
"@angular/compiler": ">=15.0.0 < 20.0.0", "@angular/compiler": ">=15.0.0 < 20.0.0",
@ -115,6 +116,9 @@
"zone.js": ">= 0.11.1 < 1.0.0" "zone.js": ">= 0.11.1 < 1.0.0"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"@angular/animations": {
"optional": true
},
"@angular/cli": { "@angular/cli": {
"optional": true "optional": true
}, },

View File

@ -101,6 +101,7 @@ export abstract class AbstractRenderer {
this.initAngularRootElement(targetDOMNode, targetSelector); this.initAngularRootElement(targetDOMNode, targetSelector);
const analyzedMetadata = new PropertyExtractor(storyFnAngular.moduleMetadata, component); const analyzedMetadata = new PropertyExtractor(storyFnAngular.moduleMetadata, component);
await analyzedMetadata.init();
const storyUid = this.generateStoryUIdFromRawStoryUid( const storyUid = this.generateStoryUIdFromRawStoryUid(
targetDOMNode.getAttribute(STORY_UID_ATTRIBUTE) targetDOMNode.getAttribute(STORY_UID_ATTRIBUTE)

View File

@ -59,6 +59,7 @@ describe('StorybookModule', () => {
}; };
const analyzedMetadata = new PropertyExtractor({}, FooComponent); const analyzedMetadata = new PropertyExtractor({}, FooComponent);
await analyzedMetadata.init();
const application = getApplication({ const application = getApplication({
storyFnAngular: { props }, storyFnAngular: { props },
@ -98,6 +99,7 @@ describe('StorybookModule', () => {
}; };
const analyzedMetadata = new PropertyExtractor({}, FooComponent); const analyzedMetadata = new PropertyExtractor({}, FooComponent);
await analyzedMetadata.init();
const application = getApplication({ const application = getApplication({
storyFnAngular: { props }, storyFnAngular: { props },
@ -127,6 +129,7 @@ describe('StorybookModule', () => {
const storyProps$ = new BehaviorSubject<ICollection>(initialProps); const storyProps$ = new BehaviorSubject<ICollection>(initialProps);
const analyzedMetadata = new PropertyExtractor({}, FooComponent); const analyzedMetadata = new PropertyExtractor({}, FooComponent);
await analyzedMetadata.init();
const application = getApplication({ const application = getApplication({
storyFnAngular: { props: initialProps }, storyFnAngular: { props: initialProps },
@ -183,6 +186,7 @@ describe('StorybookModule', () => {
const storyProps$ = new BehaviorSubject<ICollection>(initialProps); const storyProps$ = new BehaviorSubject<ICollection>(initialProps);
const analyzedMetadata = new PropertyExtractor({}, FooComponent); const analyzedMetadata = new PropertyExtractor({}, FooComponent);
await analyzedMetadata.init();
const application = getApplication({ const application = getApplication({
storyFnAngular: { props: initialProps }, storyFnAngular: { props: initialProps },
@ -224,6 +228,7 @@ describe('StorybookModule', () => {
const storyProps$ = new BehaviorSubject<ICollection>(initialProps); const storyProps$ = new BehaviorSubject<ICollection>(initialProps);
const analyzedMetadata = new PropertyExtractor({}, FooComponent); const analyzedMetadata = new PropertyExtractor({}, FooComponent);
await analyzedMetadata.init();
const application = getApplication({ const application = getApplication({
storyFnAngular: { storyFnAngular: {
@ -262,6 +267,7 @@ describe('StorybookModule', () => {
const storyProps$ = new BehaviorSubject<ICollection>(initialProps); const storyProps$ = new BehaviorSubject<ICollection>(initialProps);
const analyzedMetadata = new PropertyExtractor({}, FooComponent); const analyzedMetadata = new PropertyExtractor({}, FooComponent);
await analyzedMetadata.init();
const application = getApplication({ const application = getApplication({
storyFnAngular: { props: initialProps }, storyFnAngular: { props: initialProps },
@ -302,6 +308,8 @@ describe('StorybookModule', () => {
WithoutSelectorComponent WithoutSelectorComponent
); );
await analyzedMetadata.init();
const application = getApplication({ const application = getApplication({
storyFnAngular: { storyFnAngular: {
props, props,
@ -330,6 +338,7 @@ describe('StorybookModule', () => {
class FooComponent {} class FooComponent {}
const analyzedMetadata = new PropertyExtractor({}, FooComponent); const analyzedMetadata = new PropertyExtractor({}, FooComponent);
await analyzedMetadata.init();
const application = getApplication({ const application = getApplication({
storyFnAngular: { template: '' }, storyFnAngular: { template: '' },

View File

@ -29,75 +29,81 @@ const TestModuleWithImportsAndProviders = NgModule({
providers: [TestTokenProvider], providers: [TestTokenProvider],
})(class {}); })(class {});
const analyzeMetadata = (metadata: NgModuleMetadata, component?: any) => { const analyzeMetadata = async (metadata: NgModuleMetadata, component?: any) => {
return new PropertyExtractor(metadata, component); const propertyExtractor = new PropertyExtractor(metadata, component);
await propertyExtractor.init();
return propertyExtractor;
}; };
const extractImports = (metadata: NgModuleMetadata, component?: any) => { const extractImports = async (metadata: NgModuleMetadata, component?: any) => {
const { imports } = new PropertyExtractor(metadata, component); const propertyExtractor = new PropertyExtractor(metadata, component);
return imports; await propertyExtractor.init();
return propertyExtractor.imports;
}; };
const extractDeclarations = (metadata: NgModuleMetadata, component?: any) => { const extractDeclarations = async (metadata: NgModuleMetadata, component?: any) => {
const { declarations } = new PropertyExtractor(metadata, component); const propertyExtractor = new PropertyExtractor(metadata, component);
return declarations; await propertyExtractor.init();
return propertyExtractor.declarations;
}; };
const extractProviders = (metadata: NgModuleMetadata, component?: any) => { const extractProviders = async (metadata: NgModuleMetadata, component?: any) => {
const { providers } = new PropertyExtractor(metadata, component); const propertyExtractor = new PropertyExtractor(metadata, component);
return providers; await propertyExtractor.init();
return propertyExtractor.providers;
}; };
const extractApplicationProviders = (metadata: NgModuleMetadata, component?: any) => { const extractApplicationProviders = async (metadata: NgModuleMetadata, component?: any) => {
const { applicationProviders } = new PropertyExtractor(metadata, component); const propertyExtractor = new PropertyExtractor(metadata, component);
return applicationProviders; await propertyExtractor.init();
return propertyExtractor.applicationProviders;
}; };
describe('PropertyExtractor', () => { describe('PropertyExtractor', () => {
vi.spyOn(console, 'warn').mockImplementation(() => {}); vi.spyOn(console, 'warn').mockImplementation(() => {});
describe('analyzeMetadata', () => { describe('analyzeMetadata', () => {
it('should remove BrowserModule', () => { it('should remove BrowserModule', async () => {
const metadata = { const metadata = {
imports: [BrowserModule], imports: [BrowserModule],
}; };
const { imports, providers, applicationProviders } = analyzeMetadata(metadata); const { imports, providers, applicationProviders } = await analyzeMetadata(metadata);
expect(imports.flat(Number.MAX_VALUE)).toEqual([CommonModule]); expect(imports.flat(Number.MAX_VALUE)).toEqual([CommonModule]);
expect(providers.flat(Number.MAX_VALUE)).toEqual([]); expect(providers.flat(Number.MAX_VALUE)).toEqual([]);
expect(applicationProviders.flat(Number.MAX_VALUE)).toEqual([]); expect(applicationProviders.flat(Number.MAX_VALUE)).toEqual([]);
}); });
it('should remove BrowserAnimationsModule and use its providers instead', () => { it('should remove BrowserAnimationsModule and use its providers instead', async () => {
const metadata = { const metadata = {
imports: [BrowserAnimationsModule], imports: [BrowserAnimationsModule],
}; };
const { imports, providers, applicationProviders } = analyzeMetadata(metadata); const { imports, providers, applicationProviders } = await analyzeMetadata(metadata);
expect(imports.flat(Number.MAX_VALUE)).toEqual([CommonModule]); expect(imports.flat(Number.MAX_VALUE)).toEqual([CommonModule]);
expect(providers.flat(Number.MAX_VALUE)).toEqual([]); expect(providers.flat(Number.MAX_VALUE)).toEqual([]);
expect(applicationProviders.flat(Number.MAX_VALUE)).toEqual(provideAnimations()); expect(applicationProviders.flat(Number.MAX_VALUE)).toEqual(provideAnimations());
}); });
it('should remove NoopAnimationsModule and use its providers instead', () => { it('should remove NoopAnimationsModule and use its providers instead', async () => {
const metadata = { const metadata = {
imports: [NoopAnimationsModule], imports: [NoopAnimationsModule],
}; };
const { imports, providers, applicationProviders } = analyzeMetadata(metadata); const { imports, providers, applicationProviders } = await analyzeMetadata(metadata);
expect(imports.flat(Number.MAX_VALUE)).toEqual([CommonModule]); expect(imports.flat(Number.MAX_VALUE)).toEqual([CommonModule]);
expect(providers.flat(Number.MAX_VALUE)).toEqual([]); expect(providers.flat(Number.MAX_VALUE)).toEqual([]);
expect(applicationProviders.flat(Number.MAX_VALUE)).toEqual(provideNoopAnimations()); expect(applicationProviders.flat(Number.MAX_VALUE)).toEqual(provideNoopAnimations());
}); });
it('should remove Browser/Animations modules recursively', () => { it('should remove Browser/Animations modules recursively', async () => {
const metadata = { const metadata = {
imports: [BrowserAnimationsModule, BrowserModule], imports: [BrowserAnimationsModule, BrowserModule],
}; };
const { imports, providers, applicationProviders } = analyzeMetadata(metadata); const { imports, providers, applicationProviders } = await analyzeMetadata(metadata);
expect(imports.flat(Number.MAX_VALUE)).toEqual([CommonModule]); expect(imports.flat(Number.MAX_VALUE)).toEqual([CommonModule]);
expect(providers.flat(Number.MAX_VALUE)).toEqual([]); expect(providers.flat(Number.MAX_VALUE)).toEqual([]);
expect(applicationProviders.flat(Number.MAX_VALUE)).toEqual(provideAnimations()); expect(applicationProviders.flat(Number.MAX_VALUE)).toEqual(provideAnimations());
}); });
it('should not destructure Angular official module', () => { it('should not destructure Angular official module', async () => {
const metadata = { const metadata = {
imports: [WithOfficialModule], imports: [WithOfficialModule],
}; };
const { imports, providers, applicationProviders } = analyzeMetadata(metadata); const { imports, providers, applicationProviders } = await analyzeMetadata(metadata);
expect(imports.flat(Number.MAX_VALUE)).toEqual([CommonModule, WithOfficialModule]); expect(imports.flat(Number.MAX_VALUE)).toEqual([CommonModule, WithOfficialModule]);
expect(providers.flat(Number.MAX_VALUE)).toEqual([]); expect(providers.flat(Number.MAX_VALUE)).toEqual([]);
expect(applicationProviders.flat(Number.MAX_VALUE)).toEqual([]); expect(applicationProviders.flat(Number.MAX_VALUE)).toEqual([]);
@ -105,13 +111,13 @@ describe('PropertyExtractor', () => {
}); });
describe('extractImports', () => { describe('extractImports', () => {
it('should return Angular official modules', () => { it('should return Angular official modules', async () => {
const imports = extractImports({ imports: [TestModuleWithImportsAndProviders] }); const imports = await extractImports({ imports: [TestModuleWithImportsAndProviders] });
expect(imports).toEqual([CommonModule, TestModuleWithImportsAndProviders]); expect(imports).toEqual([CommonModule, TestModuleWithImportsAndProviders]);
}); });
it('should return standalone components', () => { it('should return standalone components', async () => {
const imports = extractImports( const imports = await extractImports(
{ {
imports: [TestModuleWithImportsAndProviders], imports: [TestModuleWithImportsAndProviders],
}, },
@ -124,8 +130,8 @@ describe('PropertyExtractor', () => {
]); ]);
}); });
it('should return standalone directives', () => { it('should return standalone directives', async () => {
const imports = extractImports( const imports = await extractImports(
{ {
imports: [TestModuleWithImportsAndProviders], imports: [TestModuleWithImportsAndProviders],
}, },
@ -140,8 +146,11 @@ describe('PropertyExtractor', () => {
}); });
describe('extractDeclarations', () => { describe('extractDeclarations', () => {
it('should return an array of declarations that contains `storyComponent`', () => { it('should return an array of declarations that contains `storyComponent`', async () => {
const declarations = extractDeclarations({ declarations: [TestComponent1] }, TestComponent2); const declarations = await extractDeclarations(
{ declarations: [TestComponent1] },
TestComponent2
);
expect(declarations).toEqual([TestComponent1, TestComponent2]); expect(declarations).toEqual([TestComponent1, TestComponent2]);
}); });
}); });
@ -174,15 +183,15 @@ describe('PropertyExtractor', () => {
}); });
describe('extractProviders', () => { describe('extractProviders', () => {
it('should return an array of providers', () => { it('should return an array of providers', async () => {
const providers = extractProviders({ const providers = await extractProviders({
providers: [TestService], providers: [TestService],
}); });
expect(providers).toEqual([TestService]); expect(providers).toEqual([TestService]);
}); });
it('should return an array of singletons extracted', () => { it('should return an array of singletons extracted', async () => {
const singeltons = extractApplicationProviders({ const singeltons = await extractApplicationProviders({
imports: [BrowserAnimationsModule], imports: [BrowserAnimationsModule],
}); });

View File

@ -14,12 +14,6 @@ import {
VERSION, VERSION,
} from '@angular/core'; } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import {
BrowserAnimationsModule,
NoopAnimationsModule,
provideAnimations,
provideNoopAnimations,
} from '@angular/platform-browser/animations';
import { dedent } from 'ts-dedent'; import { dedent } from 'ts-dedent';
import { NgModuleMetadata } from '../../types'; import { NgModuleMetadata } from '../../types';
@ -45,9 +39,7 @@ export class PropertyExtractor implements NgModuleMetadata {
constructor( constructor(
private metadata: NgModuleMetadata, private metadata: NgModuleMetadata,
private component?: any private component?: any
) { ) {}
this.init();
}
// With the new way of mounting standalone components to the DOM via bootstrapApplication API, // With the new way of mounting standalone components to the DOM via bootstrapApplication API,
// we should now pass ModuleWithProviders to the providers array of the bootstrapApplication function. // we should now pass ModuleWithProviders to the providers array of the bootstrapApplication function.
@ -71,8 +63,8 @@ export class PropertyExtractor implements NgModuleMetadata {
} }
} }
private init() { public async init() {
const analyzed = this.analyzeMetadata(this.metadata); const analyzed = await this.analyzeMetadata(this.metadata);
this.imports = uniqueArray([CommonModule, analyzed.imports]); this.imports = uniqueArray([CommonModule, analyzed.imports]);
this.providers = uniqueArray(analyzed.providers); this.providers = uniqueArray(analyzed.providers);
this.applicationProviders = uniqueArray(analyzed.applicationProviders); this.applicationProviders = uniqueArray(analyzed.applicationProviders);
@ -101,28 +93,28 @@ export class PropertyExtractor implements NgModuleMetadata {
* - Extracts providers from ModuleWithProviders * - Extracts providers from ModuleWithProviders
* - Returns a new NgModuleMetadata object * - Returns a new NgModuleMetadata object
*/ */
private analyzeMetadata = (metadata: NgModuleMetadata) => { private analyzeMetadata = async (metadata: NgModuleMetadata) => {
const declarations = [...(metadata?.declarations || [])]; const declarations = [...(metadata?.declarations || [])];
const providers = [...(metadata?.providers || [])]; const providers = [...(metadata?.providers || [])];
const applicationProviders: Provider[] = []; const applicationProviders: Provider[] = [];
const imports = [...(metadata?.imports || [])].reduce((acc, imported) => { const imports = await Promise.all(
// remove ngModule and use only its providers if it is restricted [...(metadata?.imports || [])].map(async (imported) => {
// (e.g. BrowserModule, BrowserAnimationsModule, NoopAnimationsModule, ...etc) const [isRestricted, restrictedProviders] =
const [isRestricted, restrictedProviders] = PropertyExtractor.analyzeRestricted(imported); await PropertyExtractor.analyzeRestricted(imported);
if (isRestricted) { if (isRestricted) {
applicationProviders.unshift(restrictedProviders || []); applicationProviders.unshift(restrictedProviders || []);
return acc; return null;
} }
return imported;
acc.push(imported); })
).then((results) => results.filter(Boolean));
return acc;
}, []);
return { ...metadata, imports, providers, applicationProviders, declarations }; return { ...metadata, imports, providers, applicationProviders, declarations };
}; };
static analyzeRestricted = (ngModule: NgModule): [boolean] | [boolean, Provider] => { static analyzeRestricted = async (
ngModule: NgModule
): Promise<[boolean] | [boolean, Provider]> => {
if (ngModule === BrowserModule) { if (ngModule === BrowserModule) {
console.warn( console.warn(
dedent` dedent`
@ -136,7 +128,10 @@ export class PropertyExtractor implements NgModuleMetadata {
return [true]; return [true];
} }
if (ngModule === BrowserAnimationsModule) { try {
const animations = await import('@angular/platform-browser/animations');
if (ngModule === animations.BrowserAnimationsModule) {
console.warn( console.warn(
dedent` dedent`
Storybook Warning: Storybook Warning:
@ -147,10 +142,10 @@ export class PropertyExtractor implements NgModuleMetadata {
Please visit https://angular.io/guide/standalone-components#configuring-dependency-injection for more information. Please visit https://angular.io/guide/standalone-components#configuring-dependency-injection for more information.
` `
); );
return [true, provideAnimations()]; return [true, animations.provideAnimations()];
} }
if (ngModule === NoopAnimationsModule) { if (ngModule === animations.NoopAnimationsModule) {
console.warn( console.warn(
dedent` dedent`
Storybook Warning: Storybook Warning:
@ -161,7 +156,10 @@ export class PropertyExtractor implements NgModuleMetadata {
Please visit https://angular.io/guide/standalone-components#configuring-dependency-injection for more information. Please visit https://angular.io/guide/standalone-components#configuring-dependency-injection for more information.
` `
); );
return [true, provideNoopAnimations()]; return [true, animations.provideNoopAnimations()];
}
} catch (e) {
return [false];
} }
return [false]; return [false];

View File

@ -1,6 +1,6 @@
import { logger } from 'storybook/internal/node-logger'; import { logger } from 'storybook/internal/node-logger';
import { AngularLegacyBuildOptionsError } from 'storybook/internal/server-errors'; import { AngularLegacyBuildOptionsError } from 'storybook/internal/server-errors';
import { WebpackDefinePlugin } from '@storybook/builder-webpack5'; import { WebpackDefinePlugin, WebpackIgnorePlugin } from '@storybook/builder-webpack5';
import { BuilderContext, targetFromTargetString } from '@angular-devkit/architect'; import { BuilderContext, targetFromTargetString } from '@angular-devkit/architect';
import { JsonObject, logging } from '@angular-devkit/core'; import { JsonObject, logging } from '@angular-devkit/core';
@ -40,6 +40,16 @@ export async function webpackFinal(baseConfig: webpack.Configuration, options: P
}) })
); );
try {
require.resolve('@angular/animations');
} catch (e) {
webpackConfig.plugins.push(
new WebpackIgnorePlugin({
resourceRegExp: /@angular\/platform-browser\/animations$/,
})
);
}
return webpackConfig; return webpackConfig;
} }

View File

@ -1,41 +0,0 @@
import { Meta, StoryObj, applicationConfig } from '@storybook/angular';
import { expect, userEvent, within } from '@storybook/test';
import { importProvidersFrom } from '@angular/core';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { OpenCloseComponent } from '../moduleMetadata/angular-src/open-close-component/open-close.component';
const meta: Meta = {
component: OpenCloseComponent,
decorators: [
applicationConfig({
providers: [importProvidersFrom(BrowserAnimationsModule)],
}),
],
parameters: {
chromatic: { delay: 100 },
},
};
export default meta;
type Story = StoryObj<typeof OpenCloseComponent>;
export const WithBrowserAnimations: Story = {
render: () => ({
template: `<app-open-close></app-open-close>`,
moduleMetadata: {
declarations: [OpenCloseComponent],
},
}),
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const opened = canvas.getByText('The box is now Open!');
expect(opened).toBeDefined();
const submitButton = canvas.getByRole('button');
await userEvent.click(submitButton);
const closed = canvas.getByText('The box is now Closed!');
expect(closed).toBeDefined();
},
};

View File

@ -1,36 +0,0 @@
import { Meta, StoryObj } from '@storybook/angular';
import { expect, userEvent, within } from '@storybook/test';
import { importProvidersFrom } from '@angular/core';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { OpenCloseComponent } from '../moduleMetadata/angular-src/open-close-component/open-close.component';
const meta: Meta = {
component: OpenCloseComponent,
};
export default meta;
type Story = StoryObj<typeof OpenCloseComponent>;
export const WithNoopBrowserAnimations: Story = {
render: () => ({
template: `<app-open-close></app-open-close>`,
applicationConfig: {
providers: [importProvidersFrom(NoopAnimationsModule)],
},
moduleMetadata: {
declarations: [OpenCloseComponent],
},
}),
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const opened = canvas.getByText('The box is now Open!');
expect(opened).toBeDefined();
const submitButton = canvas.getByRole('button');
await userEvent.click(submitButton);
const closed = canvas.getByText('The box is now Closed!');
expect(closed).toBeDefined();
},
};

View File

@ -1,13 +0,0 @@
:host {
display: block;
margin-top: 1rem;
}
.open-close-container {
margin-top: 1em;
border: 1px solid #dddddd;
padding: 20px 20px 0px 20px;
color: #000000;
font-weight: bold;
font-size: 20px;
}

View File

@ -1,7 +0,0 @@
<nav>
<button type="button" (click)="toggle()">Toggle Open/Close</button>
</nav>
<div [@openClose]="isOpen ? 'open' : 'closed'" class="open-close-container">
<p>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</p>
</div>

View File

@ -1,39 +0,0 @@
import { Component } from '@angular/core';
import { trigger, state, style, transition, animate } from '@angular/animations';
@Component({
standalone: false,
selector: 'app-open-close',
animations: [
trigger('openClose', [
// ...
state(
'open',
style({
height: '200px',
opacity: 1,
backgroundColor: 'yellow',
})
),
state(
'closed',
style({
height: '100px',
opacity: 0.8,
backgroundColor: 'blue',
})
),
transition('open => closed', [animate('0.1s')]),
transition('closed => open', [animate('0.1s')]),
]),
],
templateUrl: 'open-close.component.html',
styleUrls: ['open-close.component.css'],
})
export class OpenCloseComponent {
isOpen = true;
toggle() {
this.isOpen = !this.isOpen;
}
}

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/ember", "name": "@storybook/ember",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Storybook for Ember: Develop Ember Component in isolation with Hot Reloading.", "description": "Storybook for Ember: Develop Ember Component in isolation with Hot Reloading.",
"homepage": "https://github.com/storybookjs/storybook/tree/next/code/frameworks/ember", "homepage": "https://github.com/storybookjs/storybook/tree/next/code/frameworks/ember",
"bugs": { "bugs": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/experimental-nextjs-vite", "name": "@storybook/experimental-nextjs-vite",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Storybook for Next.js and Vite", "description": "Storybook for Next.js and Vite",
"keywords": [ "keywords": [
"storybook", "storybook",

View File

@ -96,7 +96,7 @@ export function composeStory<TArgs extends Args = Args>(
story as StoryAnnotationsOrFn<ReactRenderer, Args>, story as StoryAnnotationsOrFn<ReactRenderer, Args>,
componentAnnotations, componentAnnotations,
projectAnnotations, projectAnnotations,
INTERNAL_DEFAULT_PROJECT_ANNOTATIONS, globalThis.globalProjectAnnotations ?? INTERNAL_DEFAULT_PROJECT_ANNOTATIONS,
exportsName exportsName
); );
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/html-vite", "name": "@storybook/html-vite",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Storybook for HTML and Vite: Develop HTML in isolation with Hot Reloading.", "description": "Storybook for HTML and Vite: Develop HTML in isolation with Hot Reloading.",
"keywords": [ "keywords": [
"storybook" "storybook"

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/html-webpack5", "name": "@storybook/html-webpack5",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Storybook for HTML: View HTML snippets in isolation with Hot Reloading.", "description": "Storybook for HTML: View HTML snippets in isolation with Hot Reloading.",
"keywords": [ "keywords": [
"storybook" "storybook"

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/nextjs", "name": "@storybook/nextjs",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Storybook for Next.js", "description": "Storybook for Next.js",
"keywords": [ "keywords": [
"storybook", "storybook",

View File

@ -96,7 +96,7 @@ export function composeStory<TArgs extends Args = Args>(
story as StoryAnnotationsOrFn<ReactRenderer, Args>, story as StoryAnnotationsOrFn<ReactRenderer, Args>,
componentAnnotations, componentAnnotations,
projectAnnotations, projectAnnotations,
INTERNAL_DEFAULT_PROJECT_ANNOTATIONS, globalThis.globalProjectAnnotations ?? INTERNAL_DEFAULT_PROJECT_ANNOTATIONS,
exportsName exportsName
); );
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/preact-vite", "name": "@storybook/preact-vite",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Storybook for Preact and Vite: Develop Preact components in isolation with Hot Reloading.", "description": "Storybook for Preact and Vite: Develop Preact components in isolation with Hot Reloading.",
"keywords": [ "keywords": [
"storybook" "storybook"

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/preact-webpack5", "name": "@storybook/preact-webpack5",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Storybook for Preact: Develop Preact Component in isolation.", "description": "Storybook for Preact: Develop Preact Component in isolation.",
"keywords": [ "keywords": [
"storybook" "storybook"

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/react-native-web-vite", "name": "@storybook/react-native-web-vite",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Develop react-native components an isolated web environment with hot reloading.", "description": "Develop react-native components an isolated web environment with hot reloading.",
"keywords": [ "keywords": [
"storybook" "storybook"

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/react-vite", "name": "@storybook/react-vite",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Storybook for React and Vite: Develop React components in isolation with Hot Reloading.", "description": "Storybook for React and Vite: Develop React components in isolation with Hot Reloading.",
"keywords": [ "keywords": [
"storybook" "storybook"

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/react-webpack5", "name": "@storybook/react-webpack5",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Storybook for React: Develop React Component in isolation with Hot Reloading.", "description": "Storybook for React: Develop React Component in isolation with Hot Reloading.",
"keywords": [ "keywords": [
"storybook" "storybook"

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/server-webpack5", "name": "@storybook/server-webpack5",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Storybook for Server: View HTML snippets from a server in isolation with Hot Reloading.", "description": "Storybook for Server: View HTML snippets from a server in isolation with Hot Reloading.",
"keywords": [ "keywords": [
"storybook" "storybook"

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/svelte-vite", "name": "@storybook/svelte-vite",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Storybook for Svelte and Vite: Develop Svelte components in isolation with Hot Reloading.", "description": "Storybook for Svelte and Vite: Develop Svelte components in isolation with Hot Reloading.",
"keywords": [ "keywords": [
"storybook" "storybook"

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/svelte-webpack5", "name": "@storybook/svelte-webpack5",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Storybook for Svelte: Develop Svelte Component in isolation with Hot Reloading.", "description": "Storybook for Svelte: Develop Svelte Component in isolation with Hot Reloading.",
"keywords": [ "keywords": [
"storybook" "storybook"

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/sveltekit", "name": "@storybook/sveltekit",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Storybook for SvelteKit", "description": "Storybook for SvelteKit",
"keywords": [ "keywords": [
"storybook", "storybook",

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/vue3-vite", "name": "@storybook/vue3-vite",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Storybook for Vue3 and Vite: Develop Vue3 components in isolation with Hot Reloading.", "description": "Storybook for Vue3 and Vite: Develop Vue3 components in isolation with Hot Reloading.",
"keywords": [ "keywords": [
"storybook" "storybook"

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/vue3-webpack5", "name": "@storybook/vue3-webpack5",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Storybook for Vue 3: Develop Vue 3 Components in isolation with Hot Reloading.", "description": "Storybook for Vue 3: Develop Vue 3 Components in isolation with Hot Reloading.",
"keywords": [ "keywords": [
"storybook" "storybook"

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/web-components-vite", "name": "@storybook/web-components-vite",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Storybook for web-components and Vite: Develop Web Components in isolation with Hot Reloading.", "description": "Storybook for web-components and Vite: Develop Web Components in isolation with Hot Reloading.",
"keywords": [ "keywords": [
"storybook" "storybook"

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/web-components-webpack5", "name": "@storybook/web-components-webpack5",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Storybook for web-components: View web components snippets in isolation with Hot Reloading.", "description": "Storybook for web-components: View web components snippets in isolation with Hot Reloading.",
"keywords": [ "keywords": [
"lit", "lit",

View File

@ -1,6 +1,6 @@
{ {
"name": "@storybook/blocks", "name": "@storybook/blocks",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Storybook Doc Blocks", "description": "Storybook Doc Blocks",
"keywords": [ "keywords": [
"storybook" "storybook"

View File

@ -1,6 +1,6 @@
{ {
"name": "sb", "name": "sb",
"version": "8.6.0-beta.1", "version": "8.6.0-beta.8",
"description": "Storybook CLI", "description": "Storybook CLI",
"keywords": [ "keywords": [
"storybook" "storybook"

Some files were not shown because too many files have changed in this diff Show More