Skip to content

Commit

Permalink
IMPROVE webpack docs
Browse files Browse the repository at this point in the history
  • Loading branch information
ndelangen committed Mar 4, 2019
1 parent 9d7c28d commit 91be9e8
Showing 1 changed file with 165 additions and 130 deletions.
295 changes: 165 additions & 130 deletions docs/src/pages/configurations/custom-webpack-config/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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',
'<your-storybook-dir>/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**.
<details>
<summary>This is what the config for storybook looks like when using CRA in dev-mode:</summary>

```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',
'<your-storybook-dir>/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: '"."',
'<storybook-environment-variables>'
'<dotenv-environment-variables>'
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: {
'<storybook-environment-variables>'
'<dotenv-environment-variables>'
},
},
],
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: '"."',
'<storybook-environment-variables>'
'<dotenv-environment-variables>'
},
],
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: {
'<storybook-environment-variables>'
'<dotenv-environment-variables>'
},
},
],
},
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 },
}
```
</details>

### Debug the default webpack config

<details>
<summary>To effectively customise the webpack config, you might need to get the full default config it's using.</summary>

<div></div>

- 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.
</details>

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**.

Expand Down Expand Up @@ -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**.

Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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 };
};
```

1 comment on commit 91be9e8

@vercel
Copy link

@vercel vercel bot commented on 91be9e8 Mar 4, 2019

Choose a reason for hiding this comment

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

Deployment failed with the following error:

Builds rate limit exceeded (0 of 100 remaining). Try again in 22h

Please sign in to comment.