Is your feature request related to a problem? Please describe.
The method of configuring Storybook is broken. We have config.js
and addons.js
which are loaded in either the preview or manager. It isn't really obvious to anyone that's what's going on, and what impact it has when you require some code in either.
config.js
main purpose is to require all story files. This is problematic since it prevents code-splitting, making the storybook bundles really big, and output warnings.
Describe the solution you'd like
We implement a storybook.config.js
file that exports a preset.
the preset can have names exports:
entries
- a glob for which files storybook should consider story fileswebpack
- a async function that allows users to modify the preview webpack settingsmanagerWebpack
- a async function that allows users to modify the manager webpack settingsserver
- an object with settings for the server (port, hostname, sll, etc)theme
- a rich object for theming (can have functions even)This file will never be directly part of the user's bundle, we'll use it in node. perhaps for some exports (like theme) we need to import that export only into a bundle.
Are you able to assist bring the feature to reality?
Yes, I'm working on this. It's on pause right now, I'm trying to make improvements to the addon APIs & documentation first.
Additional context
origin/tech/monoconfig-fork
The storybook.config.js
will be at a root level of the repo.
The file is a ES module, and has named exports.
The exports that storybook will support initially are:
entries?: Array<string> | string
logLevel?: 'info' | 'warn' | 'error' | 'debug' | 'verbose' | number
addons?: Array<string>
presets?: Array<string> | Array<object>
webpack?: (base: WebpackConfig, env: 'production' | 'development' | string) => WebpackConfig
babel?: (base: BabelConfig, env: 'production' | 'development' | string) => BabelConfig
managerWebpack?: (base: WebpackConfig, env: 'production' | 'development' | string) => WebpackConfig
managerBabel?: (base: BabelConfig, env: 'production' | 'development' | string) => BabelConfig
output?: {
location: string
compress: boolean
preview: boolean
}
server?: {
port: number
host: string
static: {
[path: string]: string,
}
ssl: {
ca: string[],
cert: string,
key: string,
}
middleware: (app: ExpressApp, server: HttpServer) => Promise<void>
}
theme: Theme
previewInit: string[]
managerInit: string[]
Custom exports will be allowed, but how to use those will have to be determined later.
Let's not call this field stories
, because semantics will maybe changed
both a single string and array of strings are valid:
export const entries = 'src/**/*.story.js'
```js
export const entries = ['src//.stories.js', 'src//.examples.js']
We'll have a sensible default, so it will work without any configuration, if the user does provide a configuration, it **replaces** the default.
### addons
An array of addons in the following structure:
```js
export const addons = ['@storybook/addon-X', '@storybook/addon-Y']
We will register the addons by ourselves (no need of addons.js
).
This will auto-add the addons to the manager, but many addons have some preview part to them as well. We want to be able to auto load those as well.
We'll have to come up with a pattern for addons to easily declare what file should go in the manager, and which part should go in the preview.
This pattern pretty much exists for the manager: @storybook/addon-X/register
, but not really for the preview. Because many addons currently exist as decorators which require the user to set it up in the story files.
Long term addon & presets will be the same thing, or at least technically have the exact same api & capability.
An addon could use the presets api for previewInit
& managerInit
for the purpose of injecting code into either bundle.
presets is an array of string or preset defining objects:
export const presets = ['@storybook/preset-scss', '@storybook/preset-typescript']
export const presets = [
{
preset: '@storybook/preset-scss',
options: {}
}
]
We only allow function composition to customise storybook's preview webpack config.
So this means no magic object merging. If the user wants to merge, they should use webpack-merge
within this function. This config has no effect on the manager.
export const webpack = async (config, env) => {
// make changes, preferable in a non-mutating fashion for debug-ability
return config;
}
Similar to webpack, no magic merging on our side.
What's really going on here, is that you're just changing the webpack config here; namely the webpack_config.module.rules[0].options
. To be determined if having this options at all is a good idea, though perhaps it's needed for things like storysource, storyshots?
export const babel = (config, env) => {
// make changes, preferable in a non-mutating fashion for debug-ability
return config;
}
Fairly self-explanatory maybe, I won't repeat the api again, but these 2 export influence the manager's webpack config and have no effect on the preview.
This export is a collection of settings related to the output; so it lets storybook know where & what to output when building.
A future version of storybook will have the option of running the preview from another location/app possibly.
export const output = {
location: './',
compress: false,
preview: true, // would enable/disable or set a custom location
}
This export is a collection of setting related to how to serve storybook. You can add proxies, additional static routes, configure SSL, change the port etc.
export const server = {
port: 1337,
host: 'localhost',
static: {
'/': 'assets',
},
ssl: {
ca: [],
cert: '',
key: '',
},
middleware: async (app, server) => {},
}
When you need certain code to run in the manager, create a setup file for this code and reference it in the array.
export const initManager = ['./src/globals']
export const initpreview = ['./src/globals']
This could be used to inject addons.
it can also be used to get compatibility with existing storybooks! Do this:
export const initManager = ['./.storybook/addons']
export const initpreview = ['./.storybook/config']
It's great for setting up global decorators, global framework things etc.
Presets can add things here.
Technically these would just be prepended as webpack entry points maybe?
tsconfig.json
to be set in .storybook
dir, only for angular app. How we will configure this ? maybe with presets
?./storybook
dir@storybook/app
api, like a configure
method, that is used in config.js
I'd be keen to know the execution lifecycle of each hook. Currently hitting against a wall where I want to use webpack aliases in a custom addon but cannot because the addon hook is executed before the manager\preview webpack hook is called to setup aliases.
This may be good for standalone presets, addons, etc. that are done in isolation but for an internal app where I'd like to reuse code (without unnecessary abstraction) and a centralized place for configuration of Storybook I cannot. Currently I am forced to use my custom preset to hook into the manager webpack config for a custom addon and the addons.js to register that addon.
@ericberens I could show you how far along monoconfig currently is at and how it works, and what's left to do.
Send me a PM on discord if interested:
https://discord.gg/sMFvFsG
This sounds great - it looks like it would cover a need I have to share config between multiple projects. What would be the replacement for manager-head.html / preview-head.html with this approach?
a simplified example of how to add stuff to the templates in monoconfig:
``js
export managerTemplate = (template, config) =>
${template}
my addition
`;
It looks like an awesome, much needed update, thanks @ndelangen! Is there an estimate when it might land in 6.0-alpha so that we could play with it? ;)
@sebastian-nowak We decided to slowly migrate to this over time instead of a big bang change.
Many things this introduced are now in a main.js
file that is in the .storybook
folder. Goal is to at some point change the many files into a single file storybook.config.js
at the project root.
We'll get there eventually.
Most helpful comment
API
The
storybook.config.js
will be at a root level of the repo.The file is a ES module, and has named exports.
The exports that storybook will support initially are:
Custom exports will be allowed, but how to use those will have to be determined later.
entries
Let's not call this field
stories
, because semantics will maybe changedboth a single string and array of strings are valid:
```js
export const entries = ['src//.stories.js', 'src//.examples.js']
We will register the addons by ourselves (no need of
addons.js
).This will auto-add the addons to the manager, but many addons have some preview part to them as well. We want to be able to auto load those as well.
We'll have to come up with a pattern for addons to easily declare what file should go in the manager, and which part should go in the preview.
This pattern pretty much exists for the manager:
@storybook/addon-X/register
, but not really for the preview. Because many addons currently exist as decorators which require the user to set it up in the story files.Long term addon & presets will be the same thing, or at least technically have the exact same api & capability.
An addon could use the presets api for
previewInit
&managerInit
for the purpose of injecting code into either bundle.presets
presets is an array of string or preset defining objects:
array of objects
webpack
We only allow function composition to customise storybook's preview webpack config.
So this means no magic object merging. If the user wants to merge, they should use
webpack-merge
within this function. This config has no effect on the manager.babel
Similar to webpack, no magic merging on our side.
What's really going on here, is that you're just changing the webpack config here; namely the
webpack_config.module.rules[0].options
. To be determined if having this options at all is a good idea, though perhaps it's needed for things like storysource, storyshots?managerWebpack & managerBabel
Fairly self-explanatory maybe, I won't repeat the api again, but these 2 export influence the manager's webpack config and have no effect on the preview.
output
This export is a collection of settings related to the output; so it lets storybook know where & what to output when building.
A future version of storybook will have the option of running the preview from another location/app possibly.
server
This export is a collection of setting related to how to serve storybook. You can add proxies, additional static routes, configure SSL, change the port etc.
initManager & initPreview
When you need certain code to run in the manager, create a setup file for this code and reference it in the array.
This could be used to inject addons.
it can also be used to get compatibility with existing storybooks! Do this:
It's great for setting up global decorators, global framework things etc.
Presets can add things here.
Technically these would just be prepended as webpack entry points maybe?
things to cover
tsconfig.json
to be set in.storybook
dir, only for angular app. How we will configure this ? maybe withpresets
?deprecations:
./storybook
dir@storybook/app
api, like aconfigure
method, that is used inconfig.js