diff --git a/MIGRATION.md b/MIGRATION.md index bb99176aa0f8..d5c439319000 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,5 +1,7 @@ # Migration +- [From version 5.0.1 to 5.0.2](#from-version-501-to-502) + - [Deprecate webpack extend mode](#deprecate-webpack-extend-mode) - [From version 4.1.x to 5.0.x](#from-version-41x-to-50x) - [Webpack config simplification](#webpack-config-simplification) - [Theming overhaul](#theming-overhaul) @@ -49,15 +51,51 @@ - [Packages renaming](#packages-renaming) - [Deprecated embedded addons](#deprecated-embedded-addons) +## From version 5.0.1 to 5.0.2 + +### Deprecate webpack extend mode + +Exporting an object from your custom webpack config puts storybook in "extend mode". + +There was a bad bug in `v5.0.0` involving webpack "extend mode" that caused webpack issues for users migrating from `4.x`. We've fixed this problem in `v5.0.2` but it means that extend-mode has a different behavior if you're migrating from `5.0.0` or `5.0.1`. In short, `4.x` extended a base config with the custom config, whereas `5.0.0-1` extended the base with a richer config object that could conflict with the custom config in different ways from `4.x`. + +We've also deprecated "extend mode" because it doesn't add a lot of value over "full control mode", but adds more code paths, documentation, user confusion etc. Starting in SB6.0 we will only support "full control mode" customization. + +To migrate from extend-mode to full-control mode, if your extend-mode webpack config looks like this: + +```js +module.exports = { + module: { + rules: [ + /* ... */ + ], + }, +}; +``` + +In full control mode, you need modify the default config to have the rules of your liking: + +```js +module.exports = ({ config }) => ({ + ...config + module: { + ...config.module + rules: [ + /* your own rules "..." here and/or some subset of config.module.rules */ + ] + } +}) +``` + +Please refer to the [current custom webpack documentation](https://github.com/storybooks/storybook/blob/next/docs/src/pages/configurations/custom-webpack-config/index.md) for more information on custom webpack config and to [Issue #6081](https://github.com/storybooks/storybook/issues/6081) for more information about the change. + ## From version 4.1.x to 5.0.x Storybook 5.0 includes sweeping UI changes as well as changes to the addon API and custom webpack configuration. We've tried to keep backwards compatibility in most cases, but there are some notable exceptions documented below. ## Webpack config simplification -The API for custom webpack configuration has been simplifed in 5.0, but it's a breaking change. - -Storybook's "full control mode" for webpack allows you to override the webpack config with a function that returns a configuration object. +The API for custom webpack configuration has been simplifed in 5.0, but it's a breaking change. Storybook's "full control mode" for webpack allows you to override the webpack config with a function that returns a configuration object. In Storybook 5 there is a single signature for full-control mode that takes a parameters object with the fields `config` and `mode`: diff --git a/addons/a11y/src/components/A11YPanel.tsx b/addons/a11y/src/components/A11YPanel.tsx index 347a50734e5b..858bee90d395 100644 --- a/addons/a11y/src/components/A11YPanel.tsx +++ b/addons/a11y/src/components/A11YPanel.tsx @@ -1,5 +1,4 @@ import React, { Component, Fragment } from 'react'; -import PropTypes from 'prop-types'; import { styled } from '@storybook/theming'; diff --git a/docs/src/pages/configurations/custom-webpack-config/index.md b/docs/src/pages/configurations/custom-webpack-config/index.md index ba92df931408..5fd0bcde5a52 100644 --- a/docs/src/pages/configurations/custom-webpack-config/index.md +++ b/docs/src/pages/configurations/custom-webpack-config/index.md @@ -5,7 +5,7 @@ title: 'Custom Webpack Config' You can customize Storybook's webpack setup by providing a `webpack.config.js` file exporting a **webpack 4** compatible config exported as a **commonjs module**. -Storybook has its own Webpack setup and a dev server. +Storybook has its own Webpack setup and a dev server. The webpack config [is configurable](/configurations/webpack), and the default can depend on which framework you're using and whether you've used a generator like [Create React App](https://github.com/facebookincubator/create-react-app) or Angular CLI etc. > We're trying to make storybook more zero-config over time, **help to hook into the config of generators is very welcome**. @@ -13,136 +13,137 @@ The webpack config [is configurable](/configurations/webpack), and the default c
This is what the config for storybook looks like when using CRA in dev-mode: - ```js - { - mode: 'development', - bail: false, - devtool: '#cheap-module-source-map', - entry: [ - '@storybook/core/dist/server/common/polyfills.js', - '@storybook/core/dist/server/preview/globals.js', - '/config.js', - 'webpack-hot-middleware/client.js?reload=true', - ], - output: { - path: './', - filename: '[name].[hash].bundle.js', - publicPath: '', - }, - plugins: [ - HtmlWebpackPlugin { - options: { - template: '@storybook/core/dist/server/templates/index.ejs', - templateContent: false, - templateParameters: [Function: templateParameters], - filename: 'iframe.html', - hash: false, - inject: false, - compile: true, - favicon: false, - minify: undefined, - cache: true, - showErrors: true, - chunks: 'all', - excludeChunks: [], - chunksSortMode: 'none', - meta: {}, - title: 'Webpack App', - xhtml: false, - alwaysWriteToDisk: true, - }, - }, - DefinePlugin { - definitions: { - 'process.env': { - NODE_ENV: '"development"', - NODE_PATH: '""', - PUBLIC_URL: '"."', - '' - '' - }, - }, - }, - WatchMissingNodeModulesPlugin { - nodeModulesPath: './node_modules', +```js +{ + mode: 'development', + bail: false, + devtool: '#cheap-module-source-map', + entry: [ + '@storybook/core/dist/server/common/polyfills.js', + '@storybook/core/dist/server/preview/globals.js', + '/config.js', + 'webpack-hot-middleware/client.js?reload=true', + ], + output: { + path: './', + filename: '[name].[hash].bundle.js', + publicPath: '', + }, + plugins: [ + HtmlWebpackPlugin { + options: { + template: '@storybook/core/dist/server/templates/index.ejs', + templateContent: false, + templateParameters: [Function: templateParameters], + filename: 'iframe.html', + hash: false, + inject: false, + compile: true, + favicon: false, + minify: undefined, + cache: true, + showErrors: true, + chunks: 'all', + excludeChunks: [], + chunksSortMode: 'none', + meta: {}, + title: 'Webpack App', + xhtml: false, + alwaysWriteToDisk: true, }, - HotModuleReplacementPlugin {}, - CaseSensitivePathsPlugin {}, - ProgressPlugin {}, - DefinePlugin { - definitions: { + }, + DefinePlugin { + definitions: { + 'process.env': { + NODE_ENV: '"development"', + NODE_PATH: '""', + PUBLIC_URL: '"."', '' '' }, }, - ], - module: { - rules: [ - { test: /\.(mjs|jsx?)$/, - use: [ - { loader: 'babel-loader', options: - { cacheDirectory: './node_modules/.cache/storybook', - presets: [ - [ './node_modules/@babel/preset-env/lib/index.js', { shippedProposals: true, useBuiltIns: 'usage' } ], - './node_modules/@babel/preset-react/lib/index.js', - './node_modules/@babel/preset-flow/lib/index.js', - ], - plugins: [ - './node_modules/@babel/plugin-proposal-object-rest-spread/lib/index.js', - './node_modules/@babel/plugin-proposal-class-properties/lib/index.js', - './node_modules/@babel/plugin-syntax-dynamic-import/lib/index.js', - [ './node_modules/babel-plugin-emotion/dist/babel-plugin-emotion.cjs.js', { sourceMap: true, autoLabel: true } ], - './node_modules/babel-plugin-macros/dist/index.js', - './node_modules/@babel/plugin-transform-react-constant-elements/lib/index.js', - './node_modules/babel-plugin-add-react-displayname/index.js', - [ './node_modules/babel-plugin-react-docgen/lib/index.js', { DOC_GEN_COLLECTION_NAME: 'STORYBOOK_REACT_CLASSES' } ], - ], - }, - }, - ], - include: [ './' ], - exclude: [ './node_modules' ], - }, - { test: /\.md$/, - use: [ - { loader: './node_modules/raw-loader/index.js' }, - ], - }, - { test: /\.css$/, - use: [ - './node_modules/style-loader/index.js', - { loader: './node_modules/css-loader/dist/cjs.js', options: { importLoaders: 1 } }, - { loader: './node_modules/postcss-loader/src/index.js', options: { ident: 'postcss', postcss: {}, plugins: [Function: plugins] } }, - ], - }, - { test: /\.(svg|ico|jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|cur|ani)(\?.*)?$/, - loader: './node_modules/file-loader/dist/cjs.js', - query: { name: 'static/media/[name].[hash:8].[ext]' }, - }, - { test: /\.(mp4|webm|wav|mp3|m4a|aac|oga)(\?.*)?$/, - loader: './node_modules/url-loader/dist/cjs.js', - query: { limit: 10000, name: 'static/media/[name].[hash:8].[ext]' }, - }, - ], }, - resolve: { - extensions: [ '.mjs', '.js', '.jsx', '.json' ], - modules: [ 'node_modules' ], - mainFields: [ 'browser', 'main', 'module' ], - alias: { - 'core-js': './node_modules/core-js', - react: './node_modules/react', - 'react-dom': './node_modules/react-dom', + WatchMissingNodeModulesPlugin { + nodeModulesPath: './node_modules', + }, + HotModuleReplacementPlugin {}, + CaseSensitivePathsPlugin {}, + ProgressPlugin {}, + DefinePlugin { + definitions: { + '' + '' }, }, - optimization: { - splitChunks: { chunks: 'all' }, - runtimeChunk: true, - minimizer: [ [Object] ], + ], + module: { + rules: [ + { test: /\.(mjs|jsx?)$/, + use: [ + { loader: 'babel-loader', options: + { cacheDirectory: './node_modules/.cache/storybook', + presets: [ + [ './node_modules/@babel/preset-env/lib/index.js', { shippedProposals: true, useBuiltIns: 'usage' } ], + './node_modules/@babel/preset-react/lib/index.js', + './node_modules/@babel/preset-flow/lib/index.js', + ], + plugins: [ + './node_modules/@babel/plugin-proposal-object-rest-spread/lib/index.js', + './node_modules/@babel/plugin-proposal-class-properties/lib/index.js', + './node_modules/@babel/plugin-syntax-dynamic-import/lib/index.js', + [ './node_modules/babel-plugin-emotion/dist/babel-plugin-emotion.cjs.js', { sourceMap: true, autoLabel: true } ], + './node_modules/babel-plugin-macros/dist/index.js', + './node_modules/@babel/plugin-transform-react-constant-elements/lib/index.js', + './node_modules/babel-plugin-add-react-displayname/index.js', + [ './node_modules/babel-plugin-react-docgen/lib/index.js', { DOC_GEN_COLLECTION_NAME: 'STORYBOOK_REACT_CLASSES' } ], + ], + }, + }, + ], + include: [ './' ], + exclude: [ './node_modules' ], + }, + { test: /\.md$/, + use: [ + { loader: './node_modules/raw-loader/index.js' }, + ], + }, + { test: /\.css$/, + use: [ + './node_modules/style-loader/index.js', + { loader: './node_modules/css-loader/dist/cjs.js', options: { importLoaders: 1 } }, + { loader: './node_modules/postcss-loader/src/index.js', options: { ident: 'postcss', postcss: {}, plugins: [Function: plugins] } }, + ], + }, + { test: /\.(svg|ico|jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|cur|ani)(\?.*)?$/, + loader: './node_modules/file-loader/dist/cjs.js', + query: { name: 'static/media/[name].[hash:8].[ext]' }, + }, + { test: /\.(mp4|webm|wav|mp3|m4a|aac|oga)(\?.*)?$/, + loader: './node_modules/url-loader/dist/cjs.js', + query: { limit: 10000, name: 'static/media/[name].[hash:8].[ext]' }, + }, + ], + }, + resolve: { + extensions: [ '.mjs', '.js', '.jsx', '.json' ], + modules: [ 'node_modules' ], + mainFields: [ 'browser', 'main', 'module' ], + alias: { + 'core-js': './node_modules/core-js', + react: './node_modules/react', + 'react-dom': './node_modules/react-dom', }, - performance: { hints: false }, - } - ``` + }, + optimization: { + splitChunks: { chunks: 'all' }, + runtimeChunk: true, + minimizer: [ [Object] ], + }, + performance: { hints: false }, +} +``` +
### Debug the default webpack config @@ -152,56 +153,23 @@ The webpack config [is configurable](/configurations/webpack), and the default c
- - Create a `.storybook/webpack.config.js` file. - - Edit it's contents: - ```js - module.exports = async ({ config }) => console.dir(config.plugins, { depth: null }) || config; - ``` - - Then run storybook: - ```sh - yarn storybook --quiet - ``` - - The console should log the entire config, for you to inspect. - - -## Webpack customisation modes - -The file can export an [object](#extend-mode) or a [function](#full-control-mode). - -### Extend Mode - -If your file exports an **object**, it puts Storybook into **extend-mode**. - -Extend-mode _merges_ the exported object with Storybook's [default webpack configuration](../default-config/) which supports a bunch of common file types. The [merge operation](https://github.com/storybooks/storybook/blob/next/lib/core/src/server/utils/merge-webpack-config.js) appends webpack arrays like `rules` and `plugins` and merges objects like `optimization`. - -For example, to add [SASS](http://sass-lang.com/) support to Storybook, install `style-loader`, `css-loader`, `sass-loader`, and `node-sass` and add the following snippet to `.storybook/webpack.config.js`: - -```js -const path = require('path'); - -module.exports = { - module: { - rules: [ - { - test: /\.scss$/, - loaders: ['style-loader', 'css-loader', 'sass-loader'], - include: path.resolve(__dirname, '../'), - }, - ], - }, -}; -``` +- Create a `.storybook/webpack.config.js` file. +- Edit it's contents: + ```js + module.exports = async ({ config }) => console.dir(config.plugins, { depth: null }) || config; + ``` +- Then run storybook: + ```sh + yarn storybook --quiet + ``` -If you're using a non-standard Storybook config directory, you should put `webpack.config.js` there instead of `.storybook` and update the `include` path to make sure that it resolves to your project root. +The console should log the entire config, for you to inspect. -You can add any kind of webpack configuration options with the above config, whether they are plugins, loaders, or aliases. -But you won't be able to change the following config options: + -- entry -- output +## Webpack customisation modes -For the advanced usage we strongly recommend [full control mode](#full-control-mode). +The file should export a [function](#full-control-mode). In older verions of Storybook you were also able to export an [object](#extend-mode), but this behavior is deprecated and will be removed in 5.x. ### Full Control Mode @@ -239,6 +207,37 @@ Storybook uses the config returned from the above function. So edit `config` wit > If your custom webpack config uses a loader that does not explicitly include specific file extensions via the `test` property, it is necessary to `exclude` the `.ejs` file extension from that loader. +### Extend Mode + +If your file exports an **object**, it puts Storybook into **extend-mode**. This mode is deprecated and will be removed in a future version. + +Extend-mode _merges_ the exported object with Storybook's [default webpack configuration](../default-config/) which supports a bunch of common file types. The [merge operation](https://github.com/storybooks/storybook/blob/next/lib/core/src/server/utils/merge-webpack-config.js) appends webpack arrays like `rules` and `plugins` and merges objects like `optimization`. + +For example, to add [SASS](http://sass-lang.com/) support to Storybook, install `style-loader`, `css-loader`, `sass-loader`, and `node-sass` and add the following snippet to `.storybook/webpack.config.js`: + +```js +const path = require('path'); + +module.exports = { + module: { + rules: [ + { + test: /\.scss$/, + loaders: ['style-loader', 'css-loader', 'sass-loader'], + include: path.resolve(__dirname, '../'), + }, + ], + }, +}; +``` + +If you're using a non-standard Storybook config directory, you should put `webpack.config.js` there instead of `.storybook` and update the `include` path to make sure that it resolves to your project root. + +You can add any kind of webpack configuration options with the above config, whether they are plugins, loaders, or aliases. But you won't be able to change the following config options: + +- entry +- output + ## Using Your Existing Config If you have an existing webpack config for your project and want to reuse this app's configuration, you can either: @@ -247,7 +246,7 @@ If you have an existing webpack config for your project and want to reuse this a - Create a new file with common webpack options and use it in both inside the main webpack config and inside Storybook's `webpack.config.js`. **Example** -*merging the loaders from your app's `webpack.config.js` with storybook's* +_merging the loaders from your app's `webpack.config.js` with storybook's_ ```js const path = require('path'); @@ -255,6 +254,6 @@ const path = require('path'); const custom = require('../webpack.config.js'); module.exports = async ({ config, mode }) => { - return {...config, loaders: custom.loaders }; + return { ...config, loaders: custom.loaders }; }; ``` diff --git a/examples/angular-cli/.storybook/webpack.config.ts b/examples/angular-cli/.storybook/webpack.config.ts index 952d18125f67..e4b756c7aa8d 100644 --- a/examples/angular-cli/.storybook/webpack.config.ts +++ b/examples/angular-cli/.storybook/webpack.config.ts @@ -1,21 +1,18 @@ const path = require('path'); -module.exports = { - module: { - rules: [ +module.exports = async ({ config }) => { + config.module.rules.push({ + test: [/\.stories\.tsx?$/, /index\.ts$/], + loaders: [ { - test: [/\.stories\.tsx?$/, /index\.ts$/], - loaders: [ - { - loader: require.resolve('@storybook/addon-storysource/loader'), - options: { - parser: 'typescript', - }, - }, - ], - include: [path.resolve(__dirname, '../src')], - enforce: 'pre', + loader: require.resolve('@storybook/addon-storysource/loader'), + options: { + parser: 'typescript', + }, }, ], - }, + include: [path.resolve(__dirname, '../src')], + enforce: 'pre', + }); + return config; }; diff --git a/examples/cra-ts-kitchen-sink/.storybook/webpack.config.js b/examples/cra-ts-kitchen-sink/.storybook/webpack.config.js index bf43c51c4edf..09a722c82e6c 100644 --- a/examples/cra-ts-kitchen-sink/.storybook/webpack.config.js +++ b/examples/cra-ts-kitchen-sink/.storybook/webpack.config.js @@ -1,13 +1,10 @@ const path = require('path'); -module.exports = { - module: { - rules: [ - { - test: /\.tsx?$/, - include: path.resolve(__dirname, '../src'), - use: [require.resolve('react-docgen-typescript-loader')], - }, - ], - }, +module.exports = async ({ config }) => { + config.module.rules.push({ + test: /\.tsx?$/, + include: path.resolve(__dirname, '../src'), + use: [require.resolve('react-docgen-typescript-loader')], + }); + return config; }; diff --git a/examples/ember-cli/.storybook/webpack.config.js b/examples/ember-cli/.storybook/webpack.config.js index 3df5f03cc6a0..915eb22693e8 100644 --- a/examples/ember-cli/.storybook/webpack.config.js +++ b/examples/ember-cli/.storybook/webpack.config.js @@ -1,14 +1,11 @@ const path = require('path'); -module.exports = { - module: { - rules: [ - { - test: [/\.stories\.js$/, /index\.js$/], - loaders: [require.resolve('@storybook/addon-storysource/loader')], - include: [path.resolve(__dirname, '../')], - enforce: 'pre', - }, - ], - }, +module.exports = async ({ config }) => { + config.module.rules.push({ + test: [/\.stories\.js$/, /index\.js$/], + loaders: [require.resolve('@storybook/addon-storysource/loader')], + include: [path.resolve(__dirname, '../')], + enforce: 'pre', + }); + return config; }; diff --git a/examples/html-kitchen-sink/.storybook/webpack.config.js b/examples/html-kitchen-sink/.storybook/webpack.config.js index 358db1cf6eed..d2d694457e04 100644 --- a/examples/html-kitchen-sink/.storybook/webpack.config.js +++ b/examples/html-kitchen-sink/.storybook/webpack.config.js @@ -1,14 +1,11 @@ const path = require('path'); -module.exports = { - module: { - rules: [ - { - test: [/\.stories\.js$/, /index\.js$/], - loaders: [require.resolve('@storybook/addon-storysource/loader')], - include: [path.resolve(__dirname, '../stories')], - enforce: 'pre', - }, - ], - }, +module.exports = async ({ config }) => { + config.module.rules.push({ + test: [/\.stories\.js$/, /index\.js$/], + loaders: [require.resolve('@storybook/addon-storysource/loader')], + include: [path.resolve(__dirname, '../stories')], + enforce: 'pre', + }); + return config; }; diff --git a/examples/marko-cli/.storybook/webpack.config.js b/examples/marko-cli/.storybook/webpack.config.js index d10037b0fd01..8c62bf31bd42 100644 --- a/examples/marko-cli/.storybook/webpack.config.js +++ b/examples/marko-cli/.storybook/webpack.config.js @@ -1,14 +1,11 @@ const path = require('path'); -module.exports = { - module: { - rules: [ - { - test: [/\.stories\.js$/], - loaders: [require.resolve('@storybook/addon-storysource/loader')], - include: [path.resolve(__dirname, '../src')], - enforce: 'pre', - }, - ], - }, +module.exports = async ({ config }) => { + config.module.rules.push({ + test: [/\.stories\.js$/], + loaders: [require.resolve('@storybook/addon-storysource/loader')], + include: [path.resolve(__dirname, '../src')], + enforce: 'pre', + }); + return config; }; diff --git a/examples/mithril-kitchen-sink/.storybook/webpack.config.js b/examples/mithril-kitchen-sink/.storybook/webpack.config.js index d10037b0fd01..8c62bf31bd42 100644 --- a/examples/mithril-kitchen-sink/.storybook/webpack.config.js +++ b/examples/mithril-kitchen-sink/.storybook/webpack.config.js @@ -1,14 +1,11 @@ const path = require('path'); -module.exports = { - module: { - rules: [ - { - test: [/\.stories\.js$/], - loaders: [require.resolve('@storybook/addon-storysource/loader')], - include: [path.resolve(__dirname, '../src')], - enforce: 'pre', - }, - ], - }, +module.exports = async ({ config }) => { + config.module.rules.push({ + test: [/\.stories\.js$/], + loaders: [require.resolve('@storybook/addon-storysource/loader')], + include: [path.resolve(__dirname, '../src')], + enforce: 'pre', + }); + return config; }; diff --git a/examples/polymer-cli/.storybook/webpack.config.js b/examples/polymer-cli/.storybook/webpack.config.js index e1ed08fcf618..3e7ae19f52be 100644 --- a/examples/polymer-cli/.storybook/webpack.config.js +++ b/examples/polymer-cli/.storybook/webpack.config.js @@ -1,16 +1,13 @@ const path = require('path'); const webpack = require('webpack'); -module.exports = { - module: { - rules: [ - { - test: [/\.stories\.js$/, /index\.js$/], - loaders: [require.resolve('@storybook/addon-storysource/loader')], - include: [path.resolve(__dirname, '../src')], - enforce: 'pre', - }, - ], - }, - plugins: [new webpack.IgnorePlugin(/vertx/)], +module.exports = async ({ config }) => { + config.module.rules.push({ + test: [/\.stories\.js$/, /index\.js$/], + loaders: [require.resolve('@storybook/addon-storysource/loader')], + include: [path.resolve(__dirname, '../src')], + enforce: 'pre', + }); + config.plugins.push(new webpack.IgnorePlugin(/vertx/)); + return config; }; diff --git a/examples/preact-kitchen-sink/.storybook/webpack.config.js b/examples/preact-kitchen-sink/.storybook/webpack.config.js index d0f476c76be4..46ba330e6665 100644 --- a/examples/preact-kitchen-sink/.storybook/webpack.config.js +++ b/examples/preact-kitchen-sink/.storybook/webpack.config.js @@ -7,6 +7,5 @@ module.exports = ({ config }) => { include: [path.resolve(__dirname, '../src')], enforce: 'pre', }); - return config; }; diff --git a/examples/riot-kitchen-sink/.storybook/webpack.config.js b/examples/riot-kitchen-sink/.storybook/webpack.config.js index f60c4be6a3d7..9eb381789988 100644 --- a/examples/riot-kitchen-sink/.storybook/webpack.config.js +++ b/examples/riot-kitchen-sink/.storybook/webpack.config.js @@ -1,18 +1,15 @@ const path = require('path'); -module.exports = { - module: { - rules: [ - { - test: [/\.stories\.js$/, /index\.js$/], - loaders: [require.resolve('@storybook/addon-storysource/loader')], - include: [path.resolve(__dirname, '../src')], - enforce: 'pre', - }, - { - test: /\.txt$/, - use: 'raw-loader', - }, - ], - }, +module.exports = async ({ config }) => { + config.module.rules.push({ + test: [/\.stories\.js$/, /index\.js$/], + loaders: [require.resolve('@storybook/addon-storysource/loader')], + include: [path.resolve(__dirname, '../src')], + enforce: 'pre', + }); + config.module.rules.push({ + test: /\.txt$/, + use: 'raw-loader', + }); + return config; }; diff --git a/examples/svelte-kitchen-sink/.storybook/webpack.config.js b/examples/svelte-kitchen-sink/.storybook/webpack.config.js index 92d286a488cc..a943831441ac 100644 --- a/examples/svelte-kitchen-sink/.storybook/webpack.config.js +++ b/examples/svelte-kitchen-sink/.storybook/webpack.config.js @@ -1,14 +1,11 @@ const path = require('path'); -module.exports = { - module: { - rules: [ - { - test: [/\.stories\.js$/, /index\.js$/], - loaders: [require.resolve('@storybook/addon-storysource/loader')], - include: [path.resolve(__dirname, '../src')], - enforce: 'pre', - }, - ], - }, +module.exports = async ({ config }) => { + config.module.rules.push({ + test: [/\.stories\.js$/, /index\.js$/], + loaders: [require.resolve('@storybook/addon-storysource/loader')], + include: [path.resolve(__dirname, '../src')], + enforce: 'pre', + }); + return config; }; diff --git a/examples/vue-kitchen-sink/.storybook/webpack.config.js b/examples/vue-kitchen-sink/.storybook/webpack.config.js index 92d286a488cc..a943831441ac 100644 --- a/examples/vue-kitchen-sink/.storybook/webpack.config.js +++ b/examples/vue-kitchen-sink/.storybook/webpack.config.js @@ -1,14 +1,11 @@ const path = require('path'); -module.exports = { - module: { - rules: [ - { - test: [/\.stories\.js$/, /index\.js$/], - loaders: [require.resolve('@storybook/addon-storysource/loader')], - include: [path.resolve(__dirname, '../src')], - enforce: 'pre', - }, - ], - }, +module.exports = async ({ config }) => { + config.module.rules.push({ + test: [/\.stories\.js$/, /index\.js$/], + loaders: [require.resolve('@storybook/addon-storysource/loader')], + include: [path.resolve(__dirname, '../src')], + enforce: 'pre', + }); + return config; }; diff --git a/lib/core/src/server/preview/custom-webpack-preset.js b/lib/core/src/server/preview/custom-webpack-preset.js index 05dadc096682..52b0a7c99b62 100644 --- a/lib/core/src/server/preview/custom-webpack-preset.js +++ b/lib/core/src/server/preview/custom-webpack-preset.js @@ -1,3 +1,5 @@ +import deprecate from 'util-deprecate'; +import { stripIndents } from 'common-tags'; import { logger } from '@storybook/node-logger'; import loadCustomWebpackConfig from '../utils/load-custom-webpack-config'; import mergeConfigs from '../utils/merge-webpack-config'; @@ -11,22 +13,31 @@ async function createFinalDefaultConfig(presets, config, options) { export async function webpack(config, options) { const { configDir, configType, presets } = options; - const finalDefaultConfig = await createFinalDefaultConfig(presets, config, options); - // Check whether user has a custom webpack config file and // return the (extended) base configuration if it's not available. const customConfig = loadCustomWebpackConfig(configDir); if (customConfig === null) { logger.info('=> Using default webpack setup.'); - return finalDefaultConfig; + return createFinalDefaultConfig(presets, config, options); } if (typeof customConfig === 'function') { logger.info('=> Loading custom webpack config (full-control mode).'); + const finalDefaultConfig = await createFinalDefaultConfig(presets, config, options); return customConfig({ config: finalDefaultConfig, mode: configType }); } logger.info('=> Loading custom webpack config (extending mode).'); - return mergeConfigs(finalDefaultConfig, customConfig); + + // Restore 4.x behavior, but deprecate this mode of extending webpack + const finalConfig = await presets.apply('webpackFinal', config, options); + return deprecate( + () => mergeConfigs(finalConfig, customConfig), + stripIndents` + Extend-mode configuration is deprecated, please use full-control mode instead. + + See https://storybook.js.org/docs/configurations/custom-webpack-config/#full-control-mode + ` + )(); }