Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 9.0.11

- Addons: Use chromatic-com/storybook without version specifier - [#31627](https://github.com/storybookjs/storybook/pull/31627), thanks @valentinpalkovic!
- Angular: Tailwind 4 compatibility - [#31759](https://github.com/storybookjs/storybook/pull/31759), thanks @valentinpalkovic!
- Angular: fix Storybook experimentalZoneless is not compatible with Angular 20 - [#31772](https://github.com/storybookjs/storybook/pull/31772), thanks @guysenpai!
- React Native: Fix window event listeners that dont exist on rn - [#31780](https://github.com/storybookjs/storybook/pull/31780), thanks @dannyhw!

## 9.0.10

- CLI: Add RN/RNW "both" init option - [#31778](https://github.com/storybookjs/storybook/pull/31778), thanks @shilman!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -316,8 +316,8 @@ export class StoryRender<TRenderer extends Renderer> implements Render<TRenderer

// The phase should be 'rendering' but it might be set to 'aborted' by another render cycle
if (this.renderOptions.autoplay && forceRemount && playFunction && this.phase !== 'errored') {
window.addEventListener('error', onError);
window.addEventListener('unhandledrejection', onUnhandledRejection);
window?.addEventListener?.('error', onError);
window?.addEventListener?.('unhandledrejection', onUnhandledRejection);
this.disableKeyListeners = true;
try {
if (!isMountDestructured) {
Expand Down Expand Up @@ -360,8 +360,8 @@ export class StoryRender<TRenderer extends Renderer> implements Render<TRenderer
);
}
this.disableKeyListeners = false;
window.removeEventListener('unhandledrejection', onUnhandledRejection);
window.removeEventListener('error', onError);
window?.removeEventListener?.('unhandledrejection', onUnhandledRejection);
window?.removeEventListener?.('error', onError);

if (abortSignal.aborted) {
return;
Expand Down Expand Up @@ -473,7 +473,7 @@ export class StoryRender<TRenderer extends Renderer> implements Render<TRenderer
// If we still haven't completed, reload the page (iframe) to ensure we have a clean slate
// for the next render. Since the reload can take a brief moment to happen, we want to stop
// further rendering by awaiting a never-resolving promise (which is destroyed on reload).
window.location.reload();
window?.location?.reload?.();
await new Promise(() => {});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { getApplication } from './StorybookModule';
import { storyPropsProvider } from './StorybookProvider';
import { queueBootstrapping } from './utils/BootstrapQueue';
import { PropertyExtractor } from './utils/PropertyExtractor';
import { getProvideZonelessChangeDetectionFn } from './utils/Zoneless';

type StoryRenderInfo = {
storyFnAngular: StoryFnAngularReturnType;
Expand Down Expand Up @@ -126,11 +127,12 @@ export abstract class AbstractRenderer {
];

if (STORYBOOK_ANGULAR_OPTIONS?.experimentalZoneless) {
const { provideExperimentalZonelessChangeDetection } = await import('@angular/core');
if (!provideExperimentalZonelessChangeDetection) {
throw new Error('Experimental zoneless change detection requires Angular 18 or higher');
const provideZonelessChangeDetectionFn = await getProvideZonelessChangeDetectionFn();

if (!provideZonelessChangeDetectionFn) {
throw new Error('Zoneless change detection requires Angular 18 or higher');
} else {
providers.unshift(provideExperimentalZonelessChangeDetection());
providers.unshift(provideZonelessChangeDetectionFn());
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const getProvideZonelessChangeDetectionFn = async () => {
const angularCore: any = await import('@angular/core');

return 'provideExperimentalZonelessChangeDetection' in angularCore
? angularCore.provideExperimentalZonelessChangeDetection
: 'provideZonelessChangeDetection' in angularCore
? angularCore.provideZonelessChangeDetection
: null;
};
103 changes: 102 additions & 1 deletion code/frameworks/angular/src/server/angular-cli-webpack.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,107 @@ const {
*/
exports.getWebpackConfig = async (baseConfig, { builderOptions, builderContext }) => {
/** Get angular-cli Webpack config */

/**
* Custom styles config that handles Tailwind 4 compatibility issues.
*
* Problem: Angular's getStylesConfig() doesn't support Tailwind 4's new PostCSS plugin
* architecture. When Tailwind 4 is detected, Angular tries to load it using the old v3 API which
* throws errors.
*
* Solution: Detect Tailwind 4, bypass Angular's automatic Tailwind detection by hiding config
* files, then manually inject the correct Tailwind 4 PostCSS plugin into the webpack
* configuration.
*/
async function getCustomStylesConfig(wco) {
const { root } = wco;

/**
* Detect if Tailwind 4 is being used by checking for the new @tailwindcss/postcss package.
* Tailwind 4 uses @tailwindcss/postcss instead of the main tailwindcss package for PostCSS
* integration.
*/
const isTailwind4 = () => {
try {
require.resolve('@tailwindcss/postcss', { paths: [root] });
return true;
} catch {
return false;
}
};

if (isTailwind4()) {
// Monkey patch readdir to make findTailwindConfigurationFile return undefined
const fs = require('node:fs/promises');
const originalReaddir = fs.readdir;

/**
* Hide Tailwind config files from Angular's automatic detection. This prevents Angular from
* trying to load Tailwind using the incompatible v3 API. By filtering out tailwind config
* files, findTailwindConfigurationFile() returns undefined, and Angular skips its built-in
* Tailwind setup entirely.
*/
fs.readdir = async function (path, options) {
const results = await originalReaddir.call(this, path, options);
const tailwindFiles = [
'tailwind.config.js',
'tailwind.config.cjs',
'tailwind.config.mjs',
'tailwind.config.ts',
];
// Filter out tailwind config files from the results
return results.filter((file) => !tailwindFiles.includes(file));
};

// Get styles config without Tailwind interference
const styleConfig = await getStylesConfig(wco);

// Restore original readdir immediately after getting styles config
fs.readdir = originalReaddir;

/**
* Manually inject Tailwind 4 PostCSS plugin into the webpack configuration. Since we bypassed
* Angular's automatic Tailwind detection, we need to manually add the correct Tailwind 4
* plugin to all PostCSS loader configurations.
*/
const tailwindPackagePath = require.resolve('@tailwindcss/postcss', { paths: [root] });
const extraPostcssPlugins = [require(tailwindPackagePath)()];

/**
* Navigate through webpack's complex rule structure to find all postcss-loader instances and
* inject the Tailwind 4 plugin. This preserves Angular's existing PostCSS setup while adding
* Tailwind 4 support.
*/
styleConfig.module.rules
.map((rule) => rule.rules)
.forEach((rule) => {
rule.forEach((r) => {
r.oneOf?.forEach?.((oneOfRule) => {
return oneOfRule.use.forEach((use) => {
if (use.loader.includes('postcss-loader') && use.options.postcssOptions) {
const originalOptionsFn = use.options.postcssOptions;
// Wrap the original postcssOptions function to append Tailwind 4 plugin
use.options.postcssOptions = (loaderOptions) => {
const originalOptions = originalOptionsFn(loaderOptions);

return {
...originalOptions,
plugins: [...originalOptions.plugins, ...extraPostcssPlugins],
};
};
}
});
});
});
});

return styleConfig;
} else {
// Use Angular's default styles config for Tailwind v3 and other CSS frameworks
return getStylesConfig(wco);
}
}

const { config: cliConfig } = await generateI18nBrowserWebpackConfigFromContext(
{
// Default options
Expand All @@ -51,7 +152,7 @@ exports.getWebpackConfig = async (baseConfig, { builderOptions, builderContext }
builderContext,
(wco) => [
getCommonConfig(wco),
getStylesConfig(wco),
getCustomStylesConfig(wco),
getTypeScriptConfig ? getTypeScriptConfig(wco) : getDevServerConfig(wco),
]
);
Expand Down
2 changes: 1 addition & 1 deletion code/lib/cli-storybook/src/upgrade.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ describe('toUpgradedDependencies', () => {
return '8.0.0';
}
if (packageName === '@chromatic-com/storybook@next') {
return '4.0.0-0';
return '4.0.0';
}
if (packageName === '@chromatic-com/storybook') {
return '3.0.0';
Expand Down
2 changes: 1 addition & 1 deletion code/lib/create-storybook/src/generators/baseGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ export async function baseGenerator(
const compiler = webpackCompiler ? webpackCompiler({ builder }) : undefined;

if (features.includes('test')) {
extraAddons.push('@chromatic-com/storybook@^4');
extraAddons.push('@chromatic-com/storybook');
}

if (features.includes('docs')) {
Expand Down
5 changes: 3 additions & 2 deletions code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
"typescript": "^5.8.3"
},
"dependencies": {
"@chromatic-com/storybook": "^4.0.0-0",
"@chromatic-com/storybook": "^4.0.0",
"@happy-dom/global-registrator": "^17.4.4",
"@nx/vite": "20.2.2",
"@nx/workspace": "20.2.2",
Expand Down Expand Up @@ -283,5 +283,6 @@
"Dependency Upgrades"
]
]
}
},
"deferredNextVersion": "9.0.11"
}
42 changes: 12 additions & 30 deletions code/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1997,18 +1997,18 @@ __metadata:
languageName: node
linkType: hard

"@chromatic-com/storybook@npm:^4.0.0-0":
version: 4.0.0-next.5
resolution: "@chromatic-com/storybook@npm:4.0.0-next.5"
"@chromatic-com/storybook@npm:^4.0.0":
version: 4.0.0
resolution: "@chromatic-com/storybook@npm:4.0.0"
dependencies:
chromatic: "npm:^11.27.0"
"@neoconfetti/react": "npm:^1.0.0"
chromatic: "npm:^12.0.0"
filesize: "npm:^10.0.12"
jsonfile: "npm:^6.1.0"
react-confetti: "npm:^6.1.0"
strip-ansi: "npm:^7.1.0"
peerDependencies:
storybook: ^9.0.0 || ^9.0.0-alpha.12 || ^0.0.0-0
checksum: 10c0/d99fa57a5a7c0d5a63c30632f956bc4861484a627c6894a83e802356d55b1e918231af574d800fd14999cb752c7ea64fb3d092e3a4c4338eb9586858de887f3c
storybook: ^0.0.0-0 || ^9.0.0 || ^9.1.0-0
checksum: 10c0/36c3cfb3cfb25522e888206c3fe20b3cd5053c418afd5917b5a3ad7d93b839312cebd5c1f0a42e5c931e18a400a701781a2692d4c61ddda3a91fff62edd969b6
languageName: node
linkType: hard

Expand Down Expand Up @@ -6660,7 +6660,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@storybook/root@workspace:."
dependencies:
"@chromatic-com/storybook": "npm:^4.0.0-0"
"@chromatic-com/storybook": "npm:^4.0.0"
"@happy-dom/global-registrator": "npm:^17.4.4"
"@nx/vite": "npm:20.2.2"
"@nx/workspace": "npm:20.2.2"
Expand Down Expand Up @@ -10963,9 +10963,9 @@ __metadata:
languageName: node
linkType: hard

"chromatic@npm:^11.27.0":
version: 11.28.2
resolution: "chromatic@npm:11.28.2"
"chromatic@npm:^12.0.0":
version: 12.0.0
resolution: "chromatic@npm:12.0.0"
peerDependencies:
"@chromatic-com/cypress": ^0.*.* || ^1.0.0
"@chromatic-com/playwright": ^0.*.* || ^1.0.0
Expand All @@ -10978,7 +10978,7 @@ __metadata:
chroma: dist/bin.js
chromatic: dist/bin.js
chromatic-cli: dist/bin.js
checksum: 10c0/0e30c01adae08708b871c5e27462af14a8426e0fe443d0c42afb2477326703504b02fa96a02e64c858072f535d149b96f207c43b147ba1492de83bb5f505e59e
checksum: 10c0/fb6022581f9d04b29b731a0696178f027cf7b37b90d34bcb6c493f158c7b66f5047b2702d06b4c5246d5ac8ae5712ff5996959b32061726f862c0ec9396adfdb
languageName: node
linkType: hard

Expand Down Expand Up @@ -22104,17 +22104,6 @@ __metadata:
languageName: node
linkType: hard

"react-confetti@npm:^6.1.0":
version: 6.4.0
resolution: "react-confetti@npm:6.4.0"
dependencies:
tween-functions: "npm:^1.2.0"
peerDependencies:
react: ^16.3.0 || ^17.0.1 || ^18.0.0 || ^19.0.0
checksum: 10c0/a0e1bfc826cc086e13c18f2f8997e1825025be8ea80f5b1ffdd4871babe89a6876eac99f43525d4d4cbf141fab2a9372f5d51d19fa5995b64e77e6717ac4b286
languageName: node
linkType: hard

"react-docgen-typescript@npm:^2.2.2":
version: 2.2.2
resolution: "react-docgen-typescript@npm:2.2.2"
Expand Down Expand Up @@ -25721,13 +25710,6 @@ __metadata:
languageName: node
linkType: hard

"tween-functions@npm:^1.2.0":
version: 1.2.0
resolution: "tween-functions@npm:1.2.0"
checksum: 10c0/7e59295b8b0ee4132ed2fe335f56a9db5c87056dad6b6fd3011be72239fd20398003ddb4403bc98ad9f5c94468890830f64016edbbde35581faf95b32cda8305
languageName: node
linkType: hard

"type-check@npm:^0.4.0, type-check@npm:~0.4.0":
version: 0.4.0
resolution: "type-check@npm:0.4.0"
Expand Down
15 changes: 6 additions & 9 deletions docs/api/portable-stories/portable-stories-vitest.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ title: 'Portable stories in Vitest'
sidebar:
title: Vitest
order: 1
hidden: true
---

<If notRenderer={['react', 'vue', 'svelte']}>
Expand All @@ -24,21 +25,17 @@ sidebar:

</If>

<If renderer="react">
<Callout variant="info" icon="💡">
If you are using the [experimental CSF Factories format](../../api/csf/csf-factories.mdx), you don't need to use the portable stories API. Instead, you can [import and use your stories directly](../../api/csf/csf-factories.mdx#5-reusing-stories-in-test-files).
<If renderer={['react', 'vue', 'svelte']}>
<Callout variant="warning">
Storybook now recommends testing your stories in Vitest with the [Vitest addon](../../writing-tests/integrations/vitest-addon.mdx), which automatically transforms stories into real Vitest tests (using this API under the hood).

This API is still available for those who prefer to use portable stories directly, but we recommend using the Vitest addon for a more streamlined testing experience.
</Callout>
</If>

<If renderer={['react', 'vue', 'svelte']}>
Portable stories are Storybook [stories](../../writing-stories/index.mdx) which can be used in external environments, such as [Vitest](https://vitest.dev).

Normally, Storybook composes a story and its [annotations](#annotations) automatically, as part of the [story pipeline](#story-pipeline). When using stories in Vitest tests, you must handle the story pipeline yourself, which is what the [`composeStories`](#composestories) and [`composeStory`](#composestory) functions enable.

<Callout variant="info">
The API specified here is available in Storybook `8.2.7` and up. If you're using an older version of Storybook, you can upgrade to the latest version (`npx storybook@latest upgrade`) to use this API. If you're unable to upgrade, you can use previous API, which uses the `.play()` method instead of `.run()`, but is otherwise identical.
</Callout>

<If renderer="react">
<Callout variant="warning">
**Using `Next.js`?** You can test your Next.js stories with Vitest by installing and setting up the `@storybook/nextjs-vite` which re-exports [vite-plugin-storybook-nextjs](https://github.com/storybookjs/vite-plugin-storybook-nextjs) package.
Expand Down
2 changes: 1 addition & 1 deletion docs/versions/latest.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"version":"9.0.10","info":{"plain":"- CLI: Add RN/RNW \\\"both\\\" init option - [#31778](https://github.com/storybookjs/storybook/pull/31778), thanks @shilman!\n- Nextjs-Vite: Use tsconfig paths plugin - [#31764](https://github.com/storybookjs/storybook/pull/31764), thanks @kasperpeulen!"}}
{"version":"9.0.11","info":{"plain":"- Addons: Use chromatic-com/storybook without version specifier - [#31627](https://github.com/storybookjs/storybook/pull/31627), thanks @valentinpalkovic!\n- Angular: Tailwind 4 compatibility - [#31759](https://github.com/storybookjs/storybook/pull/31759), thanks @valentinpalkovic!\n- Angular: fix Storybook experimentalZoneless is not compatible with Angular 20 - [#31772](https://github.com/storybookjs/storybook/pull/31772), thanks @guysenpai!\n- React Native: Fix window event listeners that dont exist on rn - [#31780](https://github.com/storybookjs/storybook/pull/31780), thanks @dannyhw!"}}