Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vitest: Add plugins from viteFinal #30105

Open
wants to merge 21 commits into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
4fdafc1
rename packageDir
JReinhold Dec 18, 2024
5110c71
don't enforce pre on the vitest plugin
JReinhold Dec 18, 2024
a079a28
selectively optimize modules to not let vite optimize everything (it …
JReinhold Dec 18, 2024
8bc928b
add plugins from viteFinal
JReinhold Dec 18, 2024
89036fe
don't exclude stories.svelte files from svelte sandboxes
JReinhold Dec 18, 2024
2259584
support undefined plugins array
JReinhold Dec 19, 2024
4bce136
remove vite plugins unnecessary for testing
JReinhold Dec 19, 2024
20f5254
also ignore @joshwooding/vite-plugin-react-docgen-typescript
JReinhold Dec 19, 2024
c38eed3
don't add plugins during postinstall
JReinhold Dec 19, 2024
470c04c
don't tell users to add framework plugins in docs
JReinhold Dec 19, 2024
21873f1
add migration note about removing duplicate vite configurations
JReinhold Dec 19, 2024
4512a5a
remove docs on manual Framework plugins
JReinhold Dec 19, 2024
a045f4e
don't add framework plugins to sandboxes
JReinhold Dec 19, 2024
5bb9a7f
fix react-vite and nextjs-vite viteFinals discarding configs from pre…
JReinhold Dec 20, 2024
e1cd01a
cleanup viteFinals
JReinhold Dec 20, 2024
2e1ad63
Merge branch 'next' into jeppe/support-svelte-csf-in-vitest
JReinhold Dec 20, 2024
5591ef4
Merge branch 'next' into jeppe/support-svelte-csf-in-vitest
JReinhold Dec 23, 2024
7c95fe0
Merge branch 'next' of github.com:storybookjs/storybook into jeppe/su…
JReinhold Jan 8, 2025
3c0886b
merge rnw preset changes
JReinhold Jan 8, 2025
a260929
Merge branch 'next' into jeppe/support-svelte-csf-in-vitest
JReinhold Jan 8, 2025
f7285f0
Merge branch 'next' of github.com:storybookjs/storybook into jeppe/su…
JReinhold Jan 10, 2025
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
11 changes: 9 additions & 2 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
- [Added source code panel to docs](#added-source-code-panel-to-docs)
- [Addon-a11y: Component test integration](#addon-a11y-component-test-integration)
- [Addon-a11y: Deprecated `parameters.a11y.manual`](#addon-a11y-deprecated-parametersa11ymanual)
- [Indexing behavior of @storybook/experimental-addon-test is changed](#indexing-behavior-of-storybookexperimental-addon-test-is-changed)
- [Addon-test: You should no longer copy the content of `viteFinal` to your configuration](#addon-test-you-should-no-longer-copy-the-content-of-vitefinal-to-your-configuration)
- [Addon-test: Indexing behavior of @storybook/experimental-addon-test is changed](#addon-test-indexing-behavior-of-storybookexperimental-addon-test-is-changed)
- [From version 8.2.x to 8.3.x](#from-version-82x-to-83x)
- [Removed `experimental_SIDEBAR_BOTTOM` and deprecated `experimental_SIDEBAR_TOP` addon types](#removed-experimental_sidebar_bottom-and-deprecated-experimental_sidebar_top-addon-types)
- [New parameters format for addon backgrounds](#new-parameters-format-for-addon-backgrounds)
Expand Down Expand Up @@ -484,7 +485,13 @@ beforeAll(annotations.beforeAll);

We have deprecated `parameters.a11y.manual` in 8.5. Please use `globals.a11y.manual` instead.

### Indexing behavior of @storybook/experimental-addon-test is changed
### Addon-test: You should no longer copy the content of `viteFinal` to your configuration

In version 8.4 of `@storybook/experimental-addon-test`, it was required to copy any custom configuration you had in `viteFinal` in `main.ts`, to the Vitest Storybook project. This is no longer necessary, as the Storybook Test plugin will automatically include your `viteFinal` configuration. You should remove any configurations you might already have in `viteFinal` to remove duplicates.

This is especially the case for any plugins you might have, as they could now end up being loaded twice, which is likely to cause errors when running tests. In 8.4 we documented and automatically added some Vite plugins from Storybook frameworks like `@storybook/experimental-nextjs-vite` and `@storybook/sveltekit` - **these needs to be removed as well**.

### Addon-test: Indexing behavior of @storybook/experimental-addon-test is changed

The Storybook test addon used to index stories based on the `test.include` field in the Vitest config file. This caused indexing issues with Storybook, because stories could have been indexed by Storybook and not Vitest, and vice versa. Starting in Storybook 8.5.0-alpha.18, we changed the indexing behavior so that it always uses the globs defined in the `stories` field in `.storybook/main.js` for a more consistent experience. It is now discouraged to use `test.include`, please remove it.

Expand Down
49 changes: 4 additions & 45 deletions code/addons/test/src/postinstall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,6 @@ export default async function postInstall(options: PostinstallOptions) {
}
}

const vitestInfo = getVitestPluginInfo(info.frameworkPackageName);

if (info.frameworkPackageName === '@storybook/nextjs') {
printInfo(
'🍿 Just so you know...',
Expand Down Expand Up @@ -418,7 +416,7 @@ export default async function postInstall(options: PostinstallOptions) {
browserWorkspaceFile,
dedent`
import { defineWorkspace } from 'vitest/config';
import { storybookTest } from '@storybook/experimental-addon-test/vitest-plugin';${vitestInfo.frameworkPluginImport}
import { storybookTest } from '@storybook/experimental-addon-test/vitest-plugin';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

Expand All @@ -434,7 +432,7 @@ export default async function postInstall(options: PostinstallOptions) {
plugins: [
// The plugin will run tests for the stories defined in your Storybook config
// See options at: https://storybook.js.org/docs/writing-tests/test-addon#storybooktest
storybookTest({ configDir: path.join(dirname, '${options.configDir}') }),${vitestInfo.frameworkPluginDocs + vitestInfo.frameworkPluginCall}
storybookTest({ configDir: path.join(dirname, '${options.configDir}') })
],
test: {
name: 'storybook',
Expand Down Expand Up @@ -464,7 +462,7 @@ export default async function postInstall(options: PostinstallOptions) {
newVitestConfigFile,
dedent`
import { defineConfig } from 'vitest/config';
import { storybookTest } from '@storybook/experimental-addon-test/vitest-plugin';${vitestInfo.frameworkPluginImport}
import { storybookTest } from '@storybook/experimental-addon-test/vitest-plugin';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

Expand All @@ -477,7 +475,7 @@ export default async function postInstall(options: PostinstallOptions) {
plugins: [
// The plugin will run tests for the stories defined in your Storybook config
// See options at: https://storybook.js.org/docs/writing-tests/test-addon#storybooktest
storybookTest({ configDir: path.join(dirname, '${options.configDir}') }),${vitestInfo.frameworkPluginDocs + vitestInfo.frameworkPluginCall}
storybookTest({ configDir: path.join(dirname, '${options.configDir}') })
],
test: {
name: 'storybook',
Expand Down Expand Up @@ -512,45 +510,6 @@ export default async function postInstall(options: PostinstallOptions) {
logger.line(1);
}

const getVitestPluginInfo = (framework: string) => {
let frameworkPluginImport = '';
let frameworkPluginCall = '';
let frameworkPluginDocs = '';

if (framework === '@storybook/nextjs' || framework === '@storybook/experimental-nextjs-vite') {
frameworkPluginImport =
"import { storybookNextJsPlugin } from '@storybook/experimental-nextjs-vite/vite-plugin';";
frameworkPluginDocs =
'// More info at: https://github.com/storybookjs/vite-plugin-storybook-nextjs';
frameworkPluginCall = 'storybookNextJsPlugin()';
}

if (framework === '@storybook/sveltekit') {
frameworkPluginImport =
"import { storybookSveltekitPlugin } from '@storybook/sveltekit/vite-plugin';";
frameworkPluginCall = 'storybookSveltekitPlugin()';
}

if (framework === '@storybook/vue3-vite') {
frameworkPluginImport =
"import { storybookVuePlugin } from '@storybook/vue3-vite/vite-plugin';";
frameworkPluginCall = 'storybookVuePlugin()';
}

if (framework === '@storybook/react-native-web-vite') {
frameworkPluginImport =
"import { storybookReactNativeWeb } from '@storybook/react-native-web-vite/vite-plugin';";
frameworkPluginCall = 'storybookReactNativeWeb()';
}

// spaces for file indentation
frameworkPluginImport = `\n${frameworkPluginImport}`;
frameworkPluginDocs = frameworkPluginDocs ? `\n ${frameworkPluginDocs}` : '';
frameworkPluginCall = frameworkPluginCall ? `\n ${frameworkPluginCall},` : '';

return { frameworkPluginImport, frameworkPluginCall, frameworkPluginDocs };
};

async function getStorybookInfo({ configDir, packageManager: pkgMgr }: PostinstallOptions) {
const packageManager = JsPackageManagerFactory.getPackageManager({ force: pkgMgr });
const packageJson = await packageManager.retrievePackageJson();
Expand Down
37 changes: 29 additions & 8 deletions code/addons/test/src/vitest-plugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ import picocolors from 'picocolors';
import sirv from 'sirv';
import { convertPathToPattern } from 'tinyglobby';
import { dedent } from 'ts-dedent';
import type { PluginOption } from 'vite';

// ! Relative import to prebundle it without needing to depend on the Vite builder
import { withoutVitePlugins } from '../../../../builders/builder-vite/src/utils/without-vite-plugins';
Copy link
Contributor

@valentinpalkovic valentinpalkovic Jan 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

without-vite-plugins uses a type import from vite. That means that the experimental-addon-test also depends on Vite; otherwise, the PluginOption cannot be resolved in package manager environments, which are strict about peer dependencies.

Generally, I like to avoid relative imports like this because of the likelihood is high that the package that imports from another package utils or functions doesn't get the dependencies right. It can easily be overseen.

If I were you, I would copy-paste the helper function and remove the PluginOptions reference. Otherwise, experimental-addon-test needs to mention vite as a peer dependency.

Copy link
Contributor Author

@JReinhold JReinhold Jan 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But type imports are stripped from the dist, so it wouldn't show up in the package, so it's not necessary. Unless the imported type made its way into a generated .d.ts file, but it doesn't because it's not part of the public API.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I am talking about the .d.ts file. Maybe you can check, whether a vite import is part of it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But it's fair to be defensive and say maybe in the future that module would import from dependencies that were in the original package but not the sibling package, without considering the sibling package.

import type { InternalOptions, UserOptions } from './types';

const WORKING_DIR = process.cwd();
Expand Down Expand Up @@ -64,9 +67,9 @@ const getStoryGlobsAndFiles = async (
};
};

const packageDir = dirname(require.resolve('@storybook/experimental-addon-test/package.json'));
const PACKAGE_DIR = dirname(require.resolve('@storybook/experimental-addon-test/package.json'));

export const storybookTest = async (options?: UserOptions): Promise<Plugin> => {
export const storybookTest = async (options?: UserOptions): Promise<Plugin[]> => {
const finalOptions = {
...defaultOptions,
...options,
Expand Down Expand Up @@ -109,14 +112,27 @@ export const storybookTest = async (options?: UserOptions): Promise<Plugin> => {
getStoryGlobsAndFiles(presets, directories),
presets.apply('framework', undefined),
presets.apply('env', {}),
presets.apply('viteFinal', {}),
presets.apply<{ plugins?: Plugin[] }>('viteFinal', {}),
JReinhold marked this conversation as resolved.
Show resolved Hide resolved
presets.apply('staticDirs', []),
extractTagsFromPreview(finalOptions.configDir),
]);

return {
// filter out plugins that we know are unnecesary for tests, eg. docgen plugins
const plugins = (await withoutVitePlugins(
(viteConfigFromStorybook.plugins as unknown as PluginOption[]) ?? [],
[
'storybook:package-deduplication', // addon-docs
'storybook:mdx-plugin', // addon-docs
'storybook:react-docgen-plugin',
'vite:react-docgen-typescript', // aka @joshwooding/vite-plugin-react-docgen-typescript
'storybook:svelte-docgen-plugin',
'storybook:vue-component-meta-plugin',
'storybook:vue-docgen-plugin',
JReinhold marked this conversation as resolved.
Show resolved Hide resolved
]
)) as unknown as Plugin[];
JReinhold marked this conversation as resolved.
Show resolved Hide resolved

const storybookTestPlugin: Plugin = {
name: 'vite-plugin-storybook-test',
enforce: 'pre',
async transformIndexHtml(html) {
const [headHtmlSnippet, bodyHtmlSnippet] = await Promise.all([
presets.apply('previewHead'),
Expand Down Expand Up @@ -153,7 +169,7 @@ export const storybookTest = async (options?: UserOptions): Promise<Plugin> => {
const baseConfig: Omit<ViteUserConfig, 'plugins'> = {
test: {
setupFiles: [
join(packageDir, 'dist/vitest-plugin/setup-file.mjs'),
join(PACKAGE_DIR, 'dist/vitest-plugin/setup-file.mjs'),
// if the existing setupFiles is a string, we have to include it otherwise we're overwriting it
typeof inputConfig_ONLY_MUTATE_WHEN_STRICTLY_NEEDED_OR_YOU_WILL_BE_FIRED.test
?.setupFiles === 'string' &&
Expand All @@ -162,7 +178,7 @@ export const storybookTest = async (options?: UserOptions): Promise<Plugin> => {

...(finalOptions.storybookScript
? {
globalSetup: [join(packageDir, 'dist/vitest-plugin/global-setup.mjs')],
globalSetup: [join(PACKAGE_DIR, 'dist/vitest-plugin/global-setup.mjs')],
}
: {}),

Expand Down Expand Up @@ -245,7 +261,9 @@ export const storybookTest = async (options?: UserOptions): Promise<Plugin> => {

optimizeDeps: {
include: [
'@storybook/experimental-addon-test/**',
'@storybook/experimental-addon-test/internal/setup-file',
'@storybook/experimental-addon-test/internal/global-setup',
'@storybook/experimental-addon-test/internal/test-utils',
...(frameworkName?.includes('react') || frameworkName?.includes('nextjs')
? ['react-dom/test-utils']
: []),
Expand Down Expand Up @@ -323,6 +341,9 @@ export const storybookTest = async (options?: UserOptions): Promise<Plugin> => {
}
},
};

plugins.push(storybookTestPlugin);
return plugins;
};

export default storybookTest;
7 changes: 4 additions & 3 deletions code/frameworks/experimental-nextjs-vite/src/preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,13 @@ export const previewAnnotations: PresetProperty<'previewAnnotations'> = (entry =

export const viteFinal: StorybookConfigVite['viteFinal'] = async (config, options) => {
const reactConfig = await reactViteFinal(config, options);
const { plugins = [] } = reactConfig;

const { nextConfigPath } = await options.presets.apply<FrameworkOptions>('frameworkOptions');

const nextDir = nextConfigPath ? path.dirname(nextConfigPath) : undefined;
plugins.push(vitePluginStorybookNextjs({ dir: nextDir }));

return reactConfig;
return {
...reactConfig,
plugins: [...(reactConfig?.plugins ?? []), vitePluginStorybookNextjs({ dir: nextDir })],
};
};
113 changes: 55 additions & 58 deletions code/frameworks/react-native-web-vite/src/preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,69 +70,66 @@ export const viteFinal: StorybookConfig['viteFinal'] = async (config, options) =

const isDevelopment = options.configType !== 'PRODUCTION';

const reactConfig = await reactViteFinal(config, options);
const { plugins = [], ...reactConfigWithoutPlugins } = await reactViteFinal(config, options);

const { plugins = [] } = reactConfig;
return mergeConfig(reactConfigWithoutPlugins, {
plugins: [
tsconfigPaths(),

plugins.unshift(
tsconfigPaths(),

// fix for react native packages shipping with flow types untranspiled
flowPlugin({
exclude: [/node_modules\/(?!react-native|@react-native)/],
}),
react({
...pluginReactOptions,
jsxRuntime: pluginReactOptions.jsxRuntime || 'automatic',
babel: {
babelrc: false,
configFile: false,
...pluginReactOptions.babel,
},
}),
// fix for react native packages shipping with flow types untranspiled
flowPlugin({
exclude: [/node_modules\/(?!react-native|@react-native)/],
}),
react({
...pluginReactOptions,
jsxRuntime: pluginReactOptions.jsxRuntime || 'automatic',
babel: {
babelrc: false,
configFile: false,
...pluginReactOptions.babel,
},
}),

// we need to add this extra babel config because the react plugin doesn't allow
// for transpiling node_modules. We need this because many react native packages are un-transpiled.
// see this pr for more context: https://github.com/vitejs/vite-plugin-react/pull/306
// However we keep the react plugin to get the fast refresh and the other stuff its doing
babel({
...pluginBabelOptions,
include: pluginBabelOptions.include || [/node_modules\/(react-native|@react-native)/],
exclude: pluginBabelOptions.exclude,
babelConfig: {
...pluginBabelOptions.babelConfig,
babelrc: false,
configFile: false,
presets: [
[
'@babel/preset-react',
{
development: isDevelopment,
runtime: 'automatic',
...(pluginBabelOptions.presetReact || {}),
},
// we need to add this extra babel config because the react plugin doesn't allow
// for transpiling node_modules. We need this because many react native packages are un-transpiled.
// see this pr for more context: https://github.com/vitejs/vite-plugin-react/pull/306
// However we keep the react plugin to get the fast refresh and the other stuff its doing
babel({
...pluginBabelOptions,
include: pluginBabelOptions.include || [/node_modules\/(react-native|@react-native)/],
exclude: pluginBabelOptions.exclude,
babelConfig: {
...pluginBabelOptions.babelConfig,
babelrc: false,
configFile: false,
presets: [
[
'@babel/preset-react',
{
development: isDevelopment,
runtime: 'automatic',
...(pluginBabelOptions.presetReact || {}),
},
],
...(pluginBabelOptions.babelConfig?.presets || []),
],
...(pluginBabelOptions.babelConfig?.presets || []),
],
plugins: [
[
// this is a fix for reanimated not working in production
'@babel/plugin-transform-modules-commonjs',
{
strict: false,
strictMode: false, // prevent "use strict" injections
allowTopLevelThis: true, // dont rewrite global `this` -> `undefined`
},
plugins: [
[
// this is a fix for reanimated not working in production
'@babel/plugin-transform-modules-commonjs',
{
strict: false,
strictMode: false, // prevent "use strict" injections
allowTopLevelThis: true, // dont rewrite global `this` -> `undefined`
},
],
...(pluginBabelOptions.babelConfig?.plugins || []),
],
...(pluginBabelOptions.babelConfig?.plugins || []),
],
},
})
);

plugins.push(reactNativeWeb());

return mergeConfig(reactConfig, {
},
}),
...plugins,
JReinhold marked this conversation as resolved.
Show resolved Hide resolved
reactNativeWeb(),
],
optimizeDeps: {
esbuildOptions: {
// fix for react native packages shipping with flow types untranspiled
Expand Down
4 changes: 2 additions & 2 deletions code/frameworks/react-vite/src/preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const core: PresetProperty<'core'> = {
};

export const viteFinal: NonNullable<StorybookConfig['viteFinal']> = async (config, { presets }) => {
const { plugins = [] } = config;
const plugins = [...(config?.plugins ?? [])];

// Add docgen plugin
const { reactDocgen: reactDocgenOption, reactDocgenTypescriptOptions } = await presets.apply<any>(
Expand Down Expand Up @@ -51,5 +51,5 @@ export const viteFinal: NonNullable<StorybookConfig['viteFinal']> = async (confi
);
}

return config;
return { ...config, plugins };
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

before, this preset would discard any config set by previous viteFinal presets. We never noticed it because this would always run first in Storybook. But in the Vitest plugin it appears that main.ts runs first, so we need to handle it properly here.

JReinhold marked this conversation as resolved.
Show resolved Hide resolved
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { Plugin } from 'vite';
const filename = __filename ?? fileURLToPath(import.meta.url);
const dir = dirname(filename);

export async function mockSveltekitStores() {
export function mockSveltekitStores() {
return {
name: 'storybook:sveltekit-mock-stores',
config: () => ({
Expand Down
Loading
Loading