diff --git a/docs/src/pages/configurations/custom-webpack-config/index.md b/docs/src/pages/configurations/custom-webpack-config/index.md index e2106ec7d309..ba92df931408 100644 --- a/docs/src/pages/configurations/custom-webpack-config/index.md +++ b/docs/src/pages/configurations/custom-webpack-config/index.md @@ -6,145 +6,170 @@ 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. -The webpack config [is configurable](/configurations/webpack), and the default can depend on whether you are using [Create React App](https://github.com/facebookincubator/create-react-app). +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. -Here's what the default webpack looks like 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, - }, +> We're trying to make storybook more zero-config over time, **help to hook into the config of generators is very welcome**. + +
+ 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: '', }, - DefinePlugin { - definitions: { - 'process.env': { - NODE_ENV: '"development"', - NODE_PATH: '""', - PUBLIC_URL: '"."', - '' - '' + 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, }, }, - }, - WatchMissingNodeModulesPlugin { - nodeModulesPath: './node_modules', - }, - HotModuleReplacementPlugin {}, - CaseSensitivePathsPlugin {}, - ProgressPlugin {}, - DefinePlugin { - definitions: { - '' - '' - }, - }, - ], - 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' } ], - ], - }, + DefinePlugin { + definitions: { + 'process.env': { + NODE_ENV: '"development"', + NODE_PATH: '""', + PUBLIC_URL: '"."', + '' + '' }, - ], - 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]' }, + WatchMissingNodeModulesPlugin { + nodeModulesPath: './node_modules', }, - { 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]' }, + HotModuleReplacementPlugin {}, + CaseSensitivePathsPlugin {}, + ProgressPlugin {}, + DefinePlugin { + definitions: { + '' + '' + }, }, ], - }, - 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', + 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]' }, + }, + ], }, - }, - optimization: { - splitChunks: { chunks: 'all' }, - runtimeChunk: true, - minimizer: [ [Object] ], - }, - performance: { hints: false }, -} -``` + 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', + }, + }, + optimization: { + splitChunks: { chunks: 'all' }, + runtimeChunk: true, + minimizer: [ [Object] ], + }, + performance: { hints: false }, + } + ``` +
+ +### Debug the default webpack config + +
+ To effectively customise the webpack config, you might need to get the full default config it's using. + +
+ - 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. +
-The file can export an object or a function. The former configures in [extend mode](#extend-mode), the latter in [full control mode](#full-control-mode), both described here. +## Webpack customisation modes -## Extend Mode +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**. @@ -175,11 +200,10 @@ But you won't be able to change the following config options: - entry - output -- js loader with babel For the advanced usage we strongly recommend [full control mode](#full-control-mode). -## Full Control Mode +### Full Control Mode If your file exports a **function**, it puts Storybook into **full-control-mode**. @@ -191,7 +215,7 @@ For example, here's a `webpack.config.js` to add [SASS](http://sass-lang.com/) s const path = require('path'); // Export a function. Accept the base config as the only param. -module.exports = ({ config, mode }) => { +module.exports = async ({ config, mode }) => { // `mode` has a value of 'DEVELOPMENT' or 'PRODUCTION' // You can change the configuration based on that. // 'PRODUCTION' is used when building the static version of storybook. @@ -212,8 +236,6 @@ Storybook uses the config returned from the above function. So edit `config` wit - entry - output -- first loader in the module.loaders (Babel loader for JS) -- all existing plugins > 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. @@ -223,3 +245,16 @@ If you have an existing webpack config for your project and want to reuse this a - Import your main webpack config into Storybook's `webpack.config.js` and use the loaders and plugins used in that. - 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* + +```js +const path = require('path'); +// your app's webpack.config.js +const custom = require('../webpack.config.js'); + +module.exports = async ({ config, mode }) => { + return {...config, loaders: custom.loaders }; +}; +```