Is your feature request related to a problem? Please describe.
Yes.
Having a lot of (complex) stories can make recurring updates painful since they can take a long, long time.
Describe the solution you'd like
Most of the times you only care for a subset of stories — if not just one.
How about passing an expression when starting storybook that will be used to filter out what stories load and work with?
For example:
yarn storybookyarn storybook myappAs I noticed this, this would bring huge gains in some cases in terms of DX and sanity.
Describe alternatives you've considered
None
Are you able to assist bring the feature to reality?
Well, I guess it's not hard.
You need to pass vars from cli to webpack and then from there to config file (DefinePlugin?).
Then, use the argument passed to either narrow down context or filter out the matched files.
Additional context
Check out attachment:

if loading time is a no issue, this could be done internally after reading all stories, the storystore could just filter over all stories (by kind and story)
Yes, that's not an issue.
What is, however, is recurring updates.
Hi everyone! Seems like there hasn't been much going on in this issue lately. If there are still questions, comments, or bugs, please feel free to continue the discussion. Unfortunately, we don't have time to get to every issue. We are always open to contributions so please send us a pull request if you would like to help. Inactive issues will be closed after 30 days. Thanks!
Hey there, it's me again! I am going close this issue to help our maintainers focus on the current development roadmap instead. If the issue mentioned is still a concern, please open a new ticket and mention this old one. Cheers and thanks for using Storybook!
@phaistonian - did you ever get this sorted out?
Missed this the first time. Great idea if somebody wants to pick it up!
Automention: Hey @Keraito, you've been tagged! Can you give a hand here?
Would it be sufficient to pass all arguments from the cli to the story loader?
For example
start-storybook -p 9010 -s public --filter someExpression
....
import { configure } from '@storybook/react';
configure((command)=>{
console.log(command.p) // 9010
console.log(command.filter) // someExpression
...
}, module)
I got it to work like this . . .
const req = require.context('../src', true, /.stories.tsx$/);
const loadStories = () => {
let keys = req.keys();
if (process.env.STORYBOOK_FILTER) {
keys = keys.filter(key => key.includes(process.env.STORYBOOK_FILTER));
}
keys.forEach(req);
};
configure(loadStories, module);
...
"scripts": {
...
"storybook": "startStorybook() { STORYBOOK_FILTER=\"$1\" start-storybook -p 4002 -c .storybook; }; startStorybook",
...
},
...
$ npm storybook my-story
@joshuatvernon Thing is, this way the require.context has already done its thing, plus this is working - in terms of filtering - only for the non-CSF format.
I managed to do it in the webpack level - so it's irrelevant with the story format - in a rather ugly way that involves using ContextReplacementPlugin.
Here's the code block:
// If we have passe an extra argument to `yarn storybook`, we shall take it and use to filter context
if (process.env.npm_config_argv) {
const argv = JSON.parse(process.env.npm_config_argv);
if (argv && argv.original) {
const filters = argv.original.slice(1);
if (!filters.length) {
console.log(
'\n\nTip: You can try %s to filter for stories based on this filter\n\n',
chalk.cyan.underline('yarn storybook input input2')
);
}
const filter = filters[0];
const isLimitingToPath = filter && filter[0] === '/';
const shouldStrict = filter && filter[filter.length - 1] === '.';
if (shouldStrict) {
filter = filter.slice(0, -1);
}
if (filter && !isLimitingToPath) {
const regString =
filters.length === 1 ? filter : `(${filters.join('|')})`;
console.log('\n🔍 Filtering stories by:', chalk.cyan(regString));
// stories only
const reg = shouldStrict
? new RegExp(`${regString}.stories.js$`, 'gi')
: new RegExp(`${regString}.+?stories.js$`, 'gi');
newConfig.plugins.push(
new webpack.ContextReplacementPlugin(
/\/(js|page|another_path)/,
reg
)
);
}
}
}
This way I can do:
yarn storybook story1 story 2
yarn storybook story.*?blue
You need to make sure the regexp passed to the ContextReplacementPlugin matches the paths where your stories are.
ps: the code is ugly, but you get the idea :)
Hmm I tried to get this to work but haven't been able to. I'm using Typescript so it may be a bit different.
This is what I've got so far...
const webpack = require("webpack");
const createStyledComponentsTransformer = require('typescript-plugin-styled-components').default;
const styledComponentsTransformer = createStyledComponentsTransformer();
module.exports = ({ config }) => {
let pattern;
const argv = JSON.parse(process.env.npm_config_argv);
if (argv && argv.original) {
const filters = argv.original.slice(2, argv.original.length);
if (!filters.length) {
console.log('\n\nTip: You can try npm storybook story1 story2 to filter stories\n\n');
pattern = /\.(ts|tsx)$/;
} else {
// https://regex101.com/r/GfOQ6b/1
pattern = new RegExp(`.*(?<=(${filters.join('|')}).*stories.*|(?<!stories.*))(\.(ts|tsx)$)`);
console.log(pattern);
}
} else {
pattern = /\.(ts|tsx)$/;
}
config.plugins.push(
new webpack.ContextReplacementPlugin(
/\.(ts|tsx)$/,
pattern
)
);
config.module.rules.push({
test: /\.(ts|tsx)$/,
use: [
{
loader: require.resolve('awesome-typescript-loader'),
options: {
presets: [['react-app', { flow: false, typescript: true }]],
configFileName: './.storybook/tsconfig.json',
getCustomTransformers: () => ({ before: [styledComponentsTransformer] })
}
},
{
loader: require.resolve('react-docgen-typescript-loader')
}
]
});
config.resolve.extensions.push('.ts', '.tsx');
return config;
};
Most helpful comment
Would it be sufficient to pass all arguments from the cli to the story loader?
For example
start-storybook -p 9010 -s public --filter someExpression....