diff --git a/MIGRATION.md b/MIGRATION.md index 53a10ac4519..483ed0b01fc 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -3,6 +3,7 @@ - [From version 6.5.x to 7.0.0](#from-version-65x-to-700) - [7.0 breaking changes](#70-breaking-changes) - [Dropped support for Node 15 and below](#dropped-support-for-node-15-and-below) + - [ESM format in Main.js](#esm-format-in-mainjs) - [Modern browser support](#modern-browser-support) - [React peer dependencies required](#react-peer-dependencies-required) - [start-storybook / build-storybook binaries removed](#start-storybook--build-storybook-binaries-removed) @@ -290,6 +291,48 @@ For avoiding that, this change passes the mapped args instead of raw args at `re Storybook 7.0 requires **Node 16** or above. If you are using an older version of Node, you will need to upgrade or keep using Storybook 6 in the meantime. +#### ESM format in Main.js + +Storybook 7.0 supports ESM in `.storybook/main.js`, and the configurations can be part of a default export. The default export will be the recommended way going forward. + +If your main.js file looks like this: + +```js +module.exports = { + stories: ['../stories/**/*.stories.mdx', '../stories/**/*.stories.@(js|jsx|ts|tsx)'], + framework: { name: '@storybook/react-vite' }, +}; +``` + +Or like this: + +```js +export const stories = ['../stories/**/*.stories.mdx', '../stories/**/*.stories.@(js|jsx|ts|tsx)']; +export const framework = { name: '@storybook/react-vite' }; +``` + +Please migrate them to be default exported instead: + +```js +const config = { + stories: ['../stories/**/*.stories.mdx', '../stories/**/*.stories.@(js|jsx|ts|tsx)'], + framework: { name: '@storybook/react-vite' }, +}; +export default config; +``` + +For Typescript users, we introduced types for that default export, so you can import it in your main.ts file. The `StorybookConfig` type will come from the Storybook package for the framework you are using, which relates to the package in the "framework" field you have in your main.ts file. For example, if you are using React Vite, you will import it from `@storybook/react-vite`: + +```ts +import { StorybookConfig } from '@storybook/react-vite'; + +const config: StorybookConfig = { + stories: ['../stories/**/*.stories.mdx', '../stories/**/*.stories.@(js|jsx|ts|tsx)'], + framework: { name: '@storybook/react-vite' }, +}; +export default config; +``` + #### Modern browser support Starting in Storybook 7.0, Storybook will no longer support IE11, amongst other legacy browser versions. diff --git a/code/lib/cli/src/generators/ANGULAR/index.ts b/code/lib/cli/src/generators/ANGULAR/index.ts index f23ba2c1a8f..5eeaa9f76cf 100644 --- a/code/lib/cli/src/generators/ANGULAR/index.ts +++ b/code/lib/cli/src/generators/ANGULAR/index.ts @@ -56,12 +56,15 @@ const generator: Generator<{ projectName: string }> = async ( .join(''); fs.writeFileSync( - `${storybookFolder}/main.js`, + `${storybookFolder}/main.ts`, dedent(` - const mainRoot = require('${rootReferencePathFromStorybookFolder}../.storybook/main.js'); - module.exports = { + import { StorybookConfig } from'@storybook/angular'; + import mainRoot from'${rootReferencePathFromStorybookFolder}../.storybook/main'; + + const config: StorybookConfig = { ...mainRoot }; + export default config; `) ); } diff --git a/code/lib/cli/src/generators/SVELTE/index.ts b/code/lib/cli/src/generators/SVELTE/index.ts index 9639d5b5053..136dedfb9c0 100644 --- a/code/lib/cli/src/generators/SVELTE/index.ts +++ b/code/lib/cli/src/generators/SVELTE/index.ts @@ -4,7 +4,6 @@ import type { Generator } from '../types'; const generator: Generator = async (packageManager, npmOptions, options) => { await baseGenerator(packageManager, npmOptions, options, 'svelte', { extensions: ['js', 'jsx', 'ts', 'tsx', 'svelte'], - commonJs: true, }); }; diff --git a/code/lib/cli/src/generators/baseGenerator.ts b/code/lib/cli/src/generators/baseGenerator.ts index fd57208bdb0..d5cba7d0766 100644 --- a/code/lib/cli/src/generators/baseGenerator.ts +++ b/code/lib/cli/src/generators/baseGenerator.ts @@ -25,7 +25,6 @@ const defaultOptions: FrameworkOptions = { framework: undefined, extensions: undefined, componentsDestinationPath: undefined, - commonJs: false, storybookConfigFolder: '.storybook', }; @@ -123,13 +122,7 @@ const hasFrameworkTemplates = (framework?: SupportedFrameworks) => export async function baseGenerator( packageManager: JsPackageManager, npmOptions: NpmOptions, - { - language, - builder = CoreBuilder.Webpack5, - pnp, - commonJs, - frameworkPreviewParts, - }: GeneratorOptions, + { language, builder = CoreBuilder.Webpack5, pnp, frameworkPreviewParts }: GeneratorOptions, renderer: SupportedRenderers, options: FrameworkOptions = defaultOptions, framework?: SupportedFrameworks @@ -232,7 +225,7 @@ export async function baseGenerator( docs: { autodocs: 'tag' }, addons: pnp ? addons.map(wrapForPnp) : addons, extensions, - commonJs, + language, ...(staticDir ? { staticDirs: [path.join('..', staticDir)] } : null), ...extraMain, ...(type !== 'framework' @@ -245,7 +238,7 @@ export async function baseGenerator( }); } - await configurePreview({ frameworkPreviewParts, storybookConfigFolder }); + await configurePreview({ frameworkPreviewParts, storybookConfigFolder, language }); // FIXME: temporary workaround for https://github.com/storybookjs/storybook/issues/17516 if ( diff --git a/code/lib/cli/src/generators/configure.ts b/code/lib/cli/src/generators/configure.ts index b1f26922f47..8efbf7ba059 100644 --- a/code/lib/cli/src/generators/configure.ts +++ b/code/lib/cli/src/generators/configure.ts @@ -1,12 +1,13 @@ import fse from 'fs-extra'; import { dedent } from 'ts-dedent'; +import { SupportedLanguage } from '../project_types'; interface ConfigureMainOptions { addons: string[]; extensions?: string[]; - commonJs?: boolean; staticDirs?: string[]; storybookConfigFolder: string; + language: SupportedLanguage; /** * Extra values for main.js * @@ -26,46 +27,52 @@ export interface FrameworkPreviewParts { interface ConfigurePreviewOptions { frameworkPreviewParts?: FrameworkPreviewParts; storybookConfigFolder: string; + language: SupportedLanguage; } export async function configureMain({ addons, extensions = ['js', 'jsx', 'ts', 'tsx'], - commonJs = false, storybookConfigFolder, + language, ...custom }: ConfigureMainOptions) { const prefix = (await fse.pathExists('./src')) ? '../src' : '../stories'; - const config = { stories: [`${prefix}/**/*.mdx`, `${prefix}/**/*.stories.@(${extensions.join('|')})`], addons, ...custom, }; - // replace escaped values and delimiters - const stringified = `module.exports = ${JSON.stringify(config, null, 2) - .replace(/\\"/g, '"') - .replace(/['"]%%/g, '') - .replace(/%%['"]/g, '') - .replace(/\\n/g, '\r\n')}`; - // main.js isn't actually JSON, but we used JSON.stringify to convert the runtime-object into code. - // un-stringify the value for referencing packages by string - // .replaceAll(/"(path\.dirname\(require\.resolve\(path\.join\('.*\))"/g, (_, a) => a)}`; + const isTypescript = + language === SupportedLanguage.TYPESCRIPT || language === SupportedLanguage.TYPESCRIPT_LEGACY; + + const tsTemplate = dedent`<>const config<> = <>; + export default config;`; + + const jsTemplate = dedent`export default <>;`; + + const finalTemplate = isTypescript ? tsTemplate : jsTemplate; + + const mainJsContents = finalTemplate + .replace('<>', `import { StorybookConfig } from '${custom.framework.name}';\n\n`) + .replace('<>', ': StorybookConfig') + .replace('<>', JSON.stringify(config, null, 2)); await fse.writeFile( - `./${storybookConfigFolder}/main.${commonJs ? 'cjs' : 'js'}`, - dedent` - const path = require('path'); - ${stringified} - `, + `./${storybookConfigFolder}/main.${isTypescript ? 'ts' : 'js'}`, + dedent(mainJsContents), { encoding: 'utf8' } ); } export async function configurePreview(options: ConfigurePreviewOptions) { - const { prefix = '' } = options?.frameworkPreviewParts || {}; - const previewPath = `./${options.storybookConfigFolder}/preview.js`; + const { prefix = '' } = options.frameworkPreviewParts || {}; + const isTypescript = + options.language === SupportedLanguage.TYPESCRIPT || + options.language === SupportedLanguage.TYPESCRIPT_LEGACY; + + const previewPath = `./${options.storybookConfigFolder}/preview.${isTypescript ? 'ts' : 'js'}`; // If the framework template included a preview then we have nothing to do if (await fse.pathExists(previewPath)) { diff --git a/code/lib/cli/src/generators/types.ts b/code/lib/cli/src/generators/types.ts index 198123152e1..bb62010bbd5 100644 --- a/code/lib/cli/src/generators/types.ts +++ b/code/lib/cli/src/generators/types.ts @@ -8,7 +8,6 @@ export type GeneratorOptions = { builder: Builder; linkable: boolean; pnp: boolean; - commonJs: boolean; frameworkPreviewParts?: FrameworkPreviewParts; }; @@ -24,7 +23,6 @@ export interface FrameworkOptions { extraMain?: any; extensions?: string[]; framework?: Record; - commonJs?: boolean; storybookConfigFolder?: string; componentsDestinationPath?: string; } @@ -49,7 +47,6 @@ export type CommandOptions = { yes?: boolean; builder?: Builder; linkable?: boolean; - commonJs?: boolean; disableTelemetry?: boolean; enableCrashReports?: boolean; debug?: boolean; diff --git a/code/lib/cli/src/initiate.ts b/code/lib/cli/src/initiate.ts index 6419ba9b6fb..1edf244404f 100644 --- a/code/lib/cli/src/initiate.ts +++ b/code/lib/cli/src/initiate.ts @@ -61,7 +61,6 @@ const installStorybook = ( language, builder: options.builder || detectBuilder(packageManager), linkable: !!options.linkable, - commonJs: options.commonJs, pnp: options.usePnp, }; @@ -294,7 +293,6 @@ async function doInitiate(options: CommandOptions, pkg: PackageJson): Promise { + const installResult = await installStorybook( + projectType as ProjectType, + packageManager, + options + ).catch((e) => { process.exit(); }); diff --git a/code/lib/cli/templates/angular/template-csf/.storybook/tsconfig.json b/code/lib/cli/templates/angular/template-csf/.storybook/tsconfig.json index 2243e0894a8..eb06864100d 100644 --- a/code/lib/cli/templates/angular/template-csf/.storybook/tsconfig.json +++ b/code/lib/cli/templates/angular/template-csf/.storybook/tsconfig.json @@ -2,9 +2,10 @@ "extends": "../tsconfig.app.json", "compilerOptions": { "types": ["node"], - "allowSyntheticDefaultImports": true + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true }, "exclude": ["../src/test.ts", "../src/**/*.spec.ts"], - "include": ["../src/**/*"], + "include": ["../src/**/*", "./preview.ts"], "files": ["./typings.d.ts"] }