Is your feature request related to a problem? Please describe.
At present Storybook will not work with stories distributed amongst packages in a monorepo. Storybook's babel loader assumes that it only needs to transpile js within src, so if you use a configuration that looks for stories outside of ./src that are not transpiled, for example:
const req = require.context('../../../packages', true, /.stories.js$/)
Loading these (untranspiled) stories results in errors such as:
ERROR in ../components/src/components/nav/LinkList/LinkList.js 9:2
Module parse failed: Unexpected token (9:2)
You may need an appropriate loader to handle this file type.
This is because it is encountering JSX in files that are outside of src that are not transpiled and is choking on them.
Describe the solution you'd like
It would be great if Storybook offered a config option to specify which directories it should load using babel-loader.
Describe alternatives you've considered
There is a generic solution for create-react-apps here
Are you able to assist bring the feature to reality?
maybe
This should work. I have a storybook that loads stories from another monorepo package. Try adding this to your storybook webpack config. Find the babel rule in loaders and modify the include and exclude to include the parent directory (for a monorepo this will be packages).
module.exports = (baseConfig, env, config) => {
// Find Babel Loader
const babelRules = config.module.rules.filter(rule => {
let isBabelLoader = false;
if (rule.loader && rule.loader.includes('babel-loader')) {
isBabelLoader = true;
}
if (rule.use) {
rule.use.forEach(use => {
if (typeof use === 'string' && use.includes('babel-loader')) {
isBabelLoader = true;
} else if (
typeof use === 'object' &&
use.loader &&
use.loader.includes('babel-loader')
) {
isBabelLoader = true;
}
});
}
return isBabelLoader;
});
babelRules.forEach(rule => {
rule.include = /../;
rule.exclude = /node_modules/;
});
return config;
};
Let me know if it helps you!
@hipstersmoothie Thanks. I'm off today but I'll give this a try on Monday.
@hipstersmoothie I've spent most of the dday trying to get this working. I think I'm close. Storybook is no longer choking on jsx, but I cannot get Storybook to display stories from other packages within the monorepo. Even if I hardcode the import into my ./storybook/config.js:
function loadStories() {
// Resolves to 'packages/example/src/components/DisclosingText/DisclosingText.stories.js'
require('../../example/src/components/DisclosingText/DisclosingText.stories.js')
}
Storybook still opens without error, but with no stories loaded. I've checked and rechecked this path and Webpack doesn't complain.
Would you mind sharing your storybook config.js, or at least how you are requiring the stories in your other packages?
I've opened a separate issue as the problem seems to be related to loading stories from parent or sibling directories: #4910
This might be helpful but it has performance issues regarding to loading.
const req = require.context('..', true, /packages\/((?!node_modules).)*\/stories\/[^\/]+\.js$/);
I am not sure if this is the best solution to load stories from packages
See: https://github.com/julianburr/storybook-monorepo-concept/issues/3#issue-289557287
@jalal246 Thanks for the suggestion. I actually switched to transpiling the stories as I thought that would allow me to sidestep this issue. I thought that this would allow me to load the transpiled stories directly from within my lerna packages which are just symlinks in my package's node_modules, however even if I use an explicit import, for example:
require('../node_modules/@company/components/lib/components/branding/Logo/Logo.stories')
Storybook just will not load the story.
The following is all my setup. The main part should really just be this (I switched to babel.config.js requiring upward mode):
rule.use[0].options.rootMode = 'upward';
rule.include = /../;
rule.exclude = /node_modules/;
config.js:
const { withPropsTable } = require('storybook-addon-react-docgen');
const { addDecorator, configure } = require('@storybook/react');
const sharedConfiguration = require('../../../../.storybook/config');
const React = require('react');
require('@fuego/ttds-mint-css/dist/mint.module.css');
// @ts-ignore
React.Fragment = ({ children }) => children;
React.Fragment.propTypes = {};
React.Fragment.displayName = 'React.Fragment';
sharedConfiguration({
name: '@fuego/ttds-mint-react',
addDecorator
});
addDecorator(withPropsTable);
// automatically import all files ending in *.stories.js
const req = require.context(
'../../../base/react/Components',
true,
/.stories.tsx$/
);
const local = require.context('../Components', true, /.stories.tsx$/);
function loadStories() {
req
.keys()
.filter(
filename =>
!filename.includes('navigation') &&
!filename.includes('sidebar') &&
!filename.includes('wrapper')
)
.forEach(filename => req(filename));
local.keys().forEach(filename => local(filename));
}
configure(loadStories, module);
webpack.config.js
const fs = require('fs');
const path = require('path');
const HtmlWebpackInsertTextPlugin = require('html-webpack-insert-text-plugin')
.default;
const storybookUtils = require('../../../../.storybook/build-utils');
module.exports = (baseConfig, env, config) => {
storybookUtils.addTypescript(config);
storybookUtils.addTheme(config, env);
config.plugins.push(
new HtmlWebpackInsertTextPlugin([
{
target: 'index.html',
parent: 'head',
text: fs.readFileSync(
path.resolve(__dirname, '../../../../.storybook/mint-logo.html'),
'utf8'
)
}
])
);
return config;
};
build-utils.js
const fs = require('fs');
const path = require('path');
const HtmlWebpackInsertTextPlugin = require('html-webpack-insert-text-plugin')
.default;
function findBabelRules(config) {
return config.module.rules.filter(rule => {
let isBabelLoader = false;
if (rule.loader && rule.loader.includes('babel-loader')) {
isBabelLoader = true;
}
if (rule.use) {
rule.use.forEach(use => {
if (typeof use === 'string' && use.includes('babel-loader')) {
isBabelLoader = true;
} else if (
typeof use === 'object' &&
use.loader &&
use.loader.includes('babel-loader')
) {
isBabelLoader = true;
}
});
}
return isBabelLoader;
});
}
module.exports = {
addTypescript(config) {
// Find Babel Loader
const babelRules = findBabelRules(config);
// Add Typescript to Babel Loader Test
babelRules.forEach(rule => {
rule.test = /\.(jsx|js|ts|tsx)$/;
// Add react-docgen-typescript-loader to rule
rule.use.push({
loader: require.resolve('react-docgen-typescript-loader'),
options: {
propFilter(prop) {
if (prop.parent) {
return prop.parent.fileName.includes('react/Components');
}
return true;
}
}
});
// Remove babel docgen plugin (avoid duplicates)
rule.use[0].options.plugins = rule.use[0].options.plugins.slice(0, 3);
rule.use[0].options.rootMode = 'upward';
rule.include = /../;
rule.exclude = /node_modules/;
rule.use.push({
loader: require.resolve('@storybook/addon-storysource/loader'),
options: { parser: 'typescript' }
});
});
config.resolve.extensions.push('.ts', '.tsx', '.json');
},
addTheme(config, env) {
config.plugins.push(
new HtmlWebpackInsertTextPlugin([
{
target: 'index.html',
parent: 'head',
text: fs.readFileSync(path.resolve(__dirname, './style.html'), 'utf8')
},
{
target: 'index.html',
parent: 'body',
text: fs.readFileSync(path.resolve(__dirname, './label.html'), 'utf8')
}
])
);
if (env === 'PRODUCTION') {
config.plugins.push(
new HtmlWebpackInsertTextPlugin([
{
target: 'index.html',
parent: 'body',
text: fs.readFileSync(
path.resolve(__dirname, './back.html'),
'utf8'
)
}
])
);
}
}
};
@jalal246 I could probably just change it to:
rule.include = /..\/path\/to\/otherPackage/;
EDIT: hmmm the the source dir doesn't compile
EDIT2: The following would work
rule.include = /(..\/path\/to\/otherPackage|currentPackage)/;
Although I do not think I will add this until it becomes apparent that julianburr/storybook-monorepo-concept#3 is a problem
I've double checked and I am 99% sure that I have no other config for storybook to see the other dir
Hi again,
It sounds complicated for me right now, and I want to keep it simple. So, inside .storybook\config.js
I added the paths manually, should be a better way to do it but it works good and fast for now.
import { configure } from "@storybook/react";
const requests = [];
const req1 = require.context(
"../packages/component1/stories",
true,
/.stories.js$/
);
const req2 = require.context(
"../packages/component2/stories",
true,
/.stories.js$/
);
requests.push(req1);
requests.push(req2);
function loadStories() {
requests.forEach(req => {
req.keys().forEach(fname => req(fname));
});
}
configure(loadStories, module);
@hipstersmoothie @Undistraction
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!
this
This should work. I have a storybook that loads stories from another monorepo package. Try adding this to your storybook webpack config. Find the babel rule in loaders and modify the include and exclude to include the parent directory (for a monorepo this will be
packages).module.exports = (baseConfig, env, config) => { // Find Babel Loader const babelRules = config.module.rules.filter(rule => { let isBabelLoader = false; if (rule.loader && rule.loader.includes('babel-loader')) { isBabelLoader = true; } if (rule.use) { rule.use.forEach(use => { if (typeof use === 'string' && use.includes('babel-loader')) { isBabelLoader = true; } else if ( typeof use === 'object' && use.loader && use.loader.includes('babel-loader') ) { isBabelLoader = true; } }); } return isBabelLoader; }); babelRules.forEach(rule => { rule.include = /../; rule.exclude = /node_modules/; }); return config; };Let me know if it helps you!
This one helped with local storybooks while importing components from other packages. Thank you!!
Most helpful comment
This should work. I have a storybook that loads stories from another monorepo package. Try adding this to your storybook webpack config. Find the babel rule in loaders and modify the include and exclude to include the parent directory (for a monorepo this will be
packages).Let me know if it helps you!