Is your feature request related to a problem? Please describe.
A working example with PostCSS / css-modules for Storybook v5
Describe the solution you'd like
We have a Lerna monorepo project. We're using Storybook version 4.1.14 for component development. To convert CSS for our react components, we use postcss-preset-env. This setup works without any issues in Storybook version 4.1.14. When we try to migrate to Storybook version 5, the following error message appears:
`ERROR in ./packages/brreg-frontend-card-style/src/card.css (./node_modules/css-loader/dist/cjs.js??ref--5-1!./node_modules/postcss-loader/src??postcss!./node_modules/style-loader!./node_modules/css-loader/dist/cjs.js??ref--11-1!./node_modules/postcss-load
er/src??postcss!./packages/brreg-frontend-card-style/src/card.css)
Module build failed (from ./node_modules/postcss-loader/src/index.js):
SyntaxError
(2:1) Unknown word
1 |
2 | var content = require("!!../../../node_modules/css-loader/dist/cjs.js??ref--11-1!../../../node_modules/postcss-loader/src/index.js??postcss!./card.css");
| ^
`
I guess there's something new in the webpack configuration that requires another way to override the default setup. It would be great if you could provide an example of Storybook setup with PostCSS / css modules.
Describe alternatives you've considered
The alternative is to stop using Storybook - which would not be good for us.
Are you able to assist bring the feature to reality?
no.
Additional context
Our webpack config:
// See: https://github.com/storybooks/storybook/blob/next/examples/official-storybook/webpack.config.js
const path = require("path");
const CopyWebpackPlugin = require('copy-webpack-plugin');
const StyleLintPlugin = require('stylelint-webpack-plugin');
const ErrorOverlayPlugin = require('error-overlay-webpack-plugin');
const root = path.resolve(__dirname, '..');
const modulesPath = path.join(root, 'packages');
const node_modules = path.join(root, 'node_modules');
module.exports = async ({config}) => ({
...config,
module: {
...config.module,
rules: [
// exclude 'babel-loader' so we can override it later
// Temp fix for issue "exports is not defined": https://github.com/storybooks/storybook/issues/3346
...config.module.rules.filter(rule => !(
(rule.use && rule.use.length && rule.use.find(({ loader }) => loader === 'babel-loader'))
)),
// First, run storysource.
{
enforce: 'pre',
test: /\.stories\.js?$/,
include: modulesPath,
use: [require.resolve('@storybook/addon-storysource/loader')],
},
// Second, run the linter.
{
enforce: 'pre',
test: /\.js?$/,
loader: 'eslint-loader',
include: modulesPath,
exclude: /node_modules/,
},
// Extract source maps from existing source files
{
enforce: 'pre',
test: /\.js$/,
include: modulesPath,
exclude: /node_modules/,
use: ['source-map-loader'],
},
// postcss/css-modules
{
test: /\.css$/,
include: modulesPath,
exclude: [
path.join(node_modules, '@fortawesome', 'fontawesome-pro', 'css'),
/\.min.css/
],
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
options: {
localIdentName: '[name]__[local].[hash:base64:5]',
modules: true,
importLoaders: 1, // See: https://github.com/webpack-contrib/css-loader#importloaders
sourceMap: true,
}
},
{
loader: 'postcss-loader',
options: {
config: {
path: path.join(root, 'postcss.config.js')
},
ident: 'postcss',
sourceMap: true,
}
},
],
},
// font awesome
{
test: /\.css$/,
include: path.join(node_modules, '@fortawesome', 'fontawesome-pro', 'css'),
exclude: /\.min.css/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
options: {
modules: false,
importLoaders: 1,
sourceMap: true,
}
},
{
loader: 'postcss-loader',
options: {
config: {
path: path.join(root, 'postcss.config.js')
},
ident: 'postcss',
sourceMap: true,
}
},
],
},
// Temp fix for issue: https://github.com/storybooks/storybook/issues/3346
{
test: /\.js?$/,
include: modulesPath,
exclude: /(node_modules|dist)/,
use: {
loader: 'babel-loader',
options: {
extends: path.join(root, 'babel.config.js'),
// This is a feature of `babel-loader` for webpack (not Babel itself).
// It enables caching results in ./node_modules/.cache/babel-loader/
// directory for faster rebuilds.
cacheDirectory: true,
}
},
},
{
include: /node_modules/,
test: /\.js$/,
use: {
loader: 'react-hot-loader/webpack',
},
},
{
test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
use: [
{
loader: 'file-loader',
}
]
}
],
},
plugins: [
...config.plugins,
// Copy files to build folder
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, 'server.js'),
to: path.join(root, 'build')
}
]),
// Lint CSS/SASS code using stylelint
new StyleLintPlugin({
configFile: path.join(root, '.stylelintrc'),
context: modulesPath,
files: '**/*.css',
failOnError: false,
}),
// This plugin will display an error overlay in your application. It is the same error overlay used in create-react-app.
new ErrorOverlayPlugin(),
],
resolve: {
...config.resolve,
},
});
Our postcss.config.js:
const isDev = !(/production/i).test(process.env.NODE_ENV);
const isProd = (/production/i).test(process.env.NODE_ENV);
plugins = () => {
const result = [
require('postcss-import')({ /* ...options */ }),
require('postcss-url')({ /* ...options */ }),
require('postcss-mixins')({ /* ...options */ }),
require('postcss-preset-env')({
stage: 2,
features: {
'nesting-rules': true,
'color-mod-function': { unresolved: 'warn' },
'custom-media-queries': true,
},
}),
];
if (isDev) {
result.push.apply(result, [
require('postcss-reporter')(),
require('postcss-browser-reporter')(),
]);
}
if (isProd) {
result.push.apply(result, [
require("cssnano")({ autoprefixer: false })
]);
}
return result;
};
module.exports = {
plugins: plugins()
};
Our babel.config.js:
const isHot = process.argv.slice(2).includes('--hot');
const presets = [
[
'@babel/env',
{
debug: false,
useBuiltIns: 'entry',
corejs: {
version: 3,
proposals: true
}
// corejs: 'core-js@3',
}
],
'@babel/react'
];
const plugins = [
'@babel/proposal-class-properties',
'@babel/proposal-object-rest-spread',
'@babel/syntax-dynamic-import',
'@babel/transform-react-jsx',
'@babel/transform-regenerator',
'@babel/transform-runtime',
'babel-plugin-react-docgen',
];
if (isHot) {
plugins.push.apply(plugins, ['react-hot-loader/babel']);
}
module.exports = {
presets,
plugins
};
What was your config in 4.x?
I am finding the same issue.
I believe this is caused by there being an existing rule to process css in the webpack config and it's got it's own postcss configuration. There appears to be no restrictions other than { test: /\.css$/,
so I think it's also picking up all the user code.
@kristian-puccio You can use the --debug-webpack
option to see the final config getting passed into the preview. If there are duplicate rules you can filter them out as needed?
I was able to resolve the above error with this config:
```const path = require('path');
// Export a function. Accept the base config as the only param.
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.
// Make whatever fine-grained changes you need
config.module.rules.push({
test: /.module.css$/,
exclude: /.css$/,
use: ['style-loader', 'css-loader'],
include: path.resolve(__dirname, '../'),
});
// Return the altered config
return config;
};
```
I think that the issue is that there is a conflict with the css rule that is already in the list, I'm guessing it's used to generate the css for the storybook ui.
If I change the file extension to something else, say .xcss and modify my rule so it matches that then it all works perfectly.
@shilman do you think it's possible to add more restrictions to the existing css rule?
@kristian-puccio What would you suggest? My suggestion would be that you use full-control mode and remove the existing rule and add your own on top of it.
Wouldn't that break the storybook UI though? As in do you need the existing
css rule?
My idea was that the existing css rule just have an includes clause applied
to it, so it only applies the storybook ui files.
On Fri, 29 Mar 2019 at 11:22, Michael Shilman notifications@github.com
wrote:
@kristian-puccio https://github.com/kristian-puccio What would you
suggest? My suggestion would be that you use full-control mode and remove
the existing rule and add your own on top of it.—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/storybooks/storybook/issues/6319#issuecomment-477818294,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ACA_fxiIePYH3NiCnLmjkFDWLWt-f8fcks5vbVzMgaJpZM4cOZpk
.
My suggestion would be that you use full-control mode and remove the existing rule and add your own on top of it
@shilman @kristian-puccio thats exactly what my example does. It adds a rule for css module files and excludes the .css
rule from also applying.
@kristian-puccio The storybook UI has its own separate webpack config, so the goal of the default rules are to provide sensible out-of-the-box experience for users.
If you think the defaults can be improved, we're open to it, but with the caveat that the bar's pretty high because we don't want to disrupt users that are happy with their current setup.
If you think the documentation can be improved to clarify any aspect of this customization process, definitely open to that as well, either through a PR or through feedback here. 🙏 I know this is a major pain in the neck. 😢
Ahh I understand!
Yep happy enough to filter it out my self now knowing it's not required.
Yeah I reckon documentation would help here
On Fri, 29 Mar 2019 at 13:05, Michael Shilman notifications@github.com
wrote:
@kristian-puccio https://github.com/kristian-puccio The storybook UI
has its own separate webpack config, so the goal of the default rules are
to provide sensible out-of-the-box experience for users.If you think the defaults can be improved, we're open to it, but with the
caveat that the bar's pretty high because we don't want to disrupt users
that are happy with their current setup.If you think the documentation can be improved to clarify any aspect of
this customization process, definitely open to that as well, either through
a PR or through feedback here. 🙏 I know this is a major pain in the
neck. 😢—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/storybooks/storybook/issues/6319#issuecomment-477837493,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ACA_f6rrS4VQLJHIs3D_6B8VsUied5ikks5vbXT_gaJpZM4cOZpk
.
Ok for those playing along at home this works perfectly.
const path = require('path');
// Export a function. Accept the base config as the only param.
module.exports = async ({ config, mode }) => {
// Remove the existing css rule
config.module.rules = config.module.rules.filter(
f => f.test.toString() !== '/\\.css$/'
);
// Make whatever fine-grained changes you need
config.module.rules.push({
test: /\.css$/,
loaders: ['style-loader', 'css-loader', 'postcss-loader'],
include: path.resolve(__dirname, '../'),
});
// Return the altered config
return config;
};
Hooray, glad you got it sorted! I'll update the docs to clarify this point about the default config. 👍
@kristian-puccio using that config results in the same error reported in this ticket initially 🤷🏼♂️
@iamjaredwalters I wonder if you're postcss config is working correctly?
Can you run it without issues using the postcss cli?
Also you could try changing the extension from css to something else so you can be sure that there is no interference with anything that picks up css files.
So for example I changed the extension of my css files to xcss then modified my webpack regex to also match that. That fixed the issue for me initially (though it broke css highlighting in my editor).
Maybe give those 2 things a go?
Thank you all for helpful comments. I will try the suggestions that have come in this thread and make a feedback.
I have filtered out the CSS rules, as suggested, but I'm still facing the same errors as before.
If I remove the exclusion of babel-loader, my project starts without any issues, but then I think I'm facing this issue: exports is not defined
Added:
// Exclude the existing css rule so we can override it later
...config.module.rules.filter((rule) => rule.test.toString() !== '/\\.css$/'),
Removed:
// Exclude 'babel-loader' so we can override it later
// Temp fix for issue "exports is not defined": https://github.com/storybooks/storybook/issues/3346
// ...config.module.rules.filter((rule) => !(
// (rule.use && rule.use.length && rule.use.find(({loader}) => loader === 'babel-loader'))
// )),
...
...
// Temp fix for issue: https://github.com/storybooks/storybook/issues/3346
// {
// test: /\.js?$/,
// include: modulesPath,
// exclude: /(node_modules|dist)/,
// use: {
// loader: 'babel-loader',
// options: {
// extends: path.join(root, 'babel.config.js'),
// // This is a feature of `babel-loader` for webpack (not Babel itself).
// // It enables caching results in ./node_modules/.cache/babel-loader/
// // directory for faster rebuilds.
// cacheDirectory: true,
// }
// },
// },
@kristian-puccio My postcss.config.js seems to work. This command:
npx postcss --config ./postcss.config.js ./packages/**/src/*.css --base ./packages --dir ./out --verbose
produces the expected CSS.
@leifoolsen sounds like your issue is with js code then rather than the css. You could try a couple of things first would be to get a webpack config working outside storybook to make sure your babel config is correct.
Once you have that working you could try filtering out the js rule in the storybook webpack config and replacing it with the one in your working config
@kristian-puccio Thanks. I'll try that. I already have a separate Webpack for this project, so I can investigate further.
BTW: As I menioned earlier, I use the same webpack config for Storybook-4.1.14 - the only difference is the module.exports line: module.exports = async (config) => ....
@leifoolsen Here's the difference between the two versions:
v4: module.exports = async (baseConfig, mode, defaultConfig) => ...
v5: module.exports = async ({ config, mode }) => ...
Looks similar, but actually different. v5's config
in the second line is actually equivalent to v4's defaultConfig
. In v4 you were extending baseConfig
(tho you called it config), but there is no v5 equivalent to baseConfig
. What's the difference? defaultConfig
has more loaders added.
What you need to do:
--debug-webpack
CLI flag to v5 to make this easier.Inspect the differences and figure out what you need to remove from the v5 config to get things working. Sorry for the pain here, but we removed the baseConfig
/defaultConfig
concept precisely because it's confusing and leads to problems like this.
(If it's any consolation, we'll be making this all a lot easier in v5.x with the introduction of Storybook _presets_ that handle this kind of stuff out of the box, but unfortunately it'll take us some time to get everything converted over...)
For anyone stumbling onto this as of August 2019, using v5.1.10, we were able to resolve this by using the above recommendation, and making the following modifications:
../src/
directory.include
on the new rule to be ../src/
and not ../
.Full config:
const path = require('path');
const autoprefixer = require('autoprefixer');
module.exports = async ({ config }) => {
config.module.rules = config.module.rules.map(f => {
// Needed to add this to ignore our ../src/ for the default rule, instead of purging it.
if (f.test.toString() === '/\\.css$/') {
f.exclude = path.resolve(__dirname, '../src/');
}
return f;
});
config.module.rules.push({
test: /\.css$/,
include: path.resolve(__dirname, '../src/'), // Needed to be changed from ../
loaders: ['style-loader', 'css-loader', 'postcss-loader'],
});
return config;
};
Thanks @ellisio . Your solution solved my issue. Here is my webpack config for storybook-5.1.11.
const path = require("path");
const CopyWebpackPlugin = require('copy-webpack-plugin');
const StyleLintPlugin = require('stylelint-webpack-plugin');
const root = path.resolve(__dirname, '..');
const storybookPath = __dirname;
const modulesPath = path.join(root, 'packages');
const node_modules = path.join(root, 'node_modules');
module.exports = async ({config}) => {
config.module.rules = config.module.rules.map(f => {
// Needed to add this to ignore our ../src/ for the default rule, instead of purging it.
// See: https://github.com/storybookjs/storybook/issues/6319
// See: https://gist.github.com/ademilter/5f56fe9e56c5eb8725292274c68001c5
if (f.test.toString() === '/\\.css$/') {
f.exclude = modulesPath;
}
return f;
});
return (
{
...config,
module: {
...config.module,
rules: [
// exclude 'babel-loader' so we can override it later
// Temp fix for issue "exports is not defined": https://github.com/storybooks/storybook/issues/3346
...config.module.rules.filter(rule => !(
(rule.use && rule.use.length && rule.use.find(({ loader }) => loader === 'babel-loader'))
)),
// First, run storysource.
// Virker å være veldig treg i v5
{
enforce: 'pre',
test: /\.stories\.js?$/,
include: modulesPath,
loaders: [require.resolve('@storybook/addon-storysource/loader')],
},
// Second, run the linter.
{
enforce: 'pre',
test: /\.js?$/,
loader: 'eslint-loader',
include: modulesPath,
exclude: /node_modules/,
},
// Extract source maps from existing source files
{
enforce: 'pre',
test: /\.js$/,
include: modulesPath,
exclude: /node_modules/,
use: ['source-map-loader'],
},
// postcss/css-modules
{
test: /\.css$/,
include: modulesPath,
exclude: node_modules, // Skal ikke være nædvendig
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[name]__[local].[hash:base64:5]',
},
importLoaders: 1,
sourceMap: true,
}
},
{
loader: 'postcss-loader',
options: {
config: {
path: path.join(root, 'postcss.config.js')
},
ident: 'postcss',
sourceMap: true,
}
},
],
},
// Temp fix for issue: https://github.com/storybooks/storybook/issues/3346
{
test: /\.js?$/,
include: modulesPath,
exclude: /(node_modules|dist)/,
use: {
loader: 'babel-loader',
options: {
extends: path.join(root, 'babel.config.js'),
// This is a feature of `babel-loader` for webpack (not Babel itself).
// It enables caching results in ./node_modules/.cache/babel-loader/
// directory for faster rebuilds.
cacheDirectory: true,
}
},
},
{
include: /node_modules/,
test: /\.js$/,
use: {
loader: 'react-hot-loader/webpack',
},
},
{
test: /\.(woff(2)?|ttf|eot|svg|pdf)(\?v=\d+\.\d+\.\d+)?$/,
use: [
{
loader: 'file-loader',
}
]
}
],
},
plugins: [
...config.plugins,
// Copy files to build folder
new CopyWebpackPlugin([
{
from: path.join(storybookPath, 'server.js'),
to: path.join(root, 'build')
},
{
from: path.join(storybookPath, 'public', 'logo.svg'),
to: path.join(root, 'build')
},
{
context: node_modules,
from: 'pdfjs-dist/build/pdf.worker.js'
}
]),
// Lint CSS/SASS code using stylelint
new StyleLintPlugin({
configFile: path.join(root, '.stylelintrc'),
context: modulesPath,
files: '**/*.css',
failOnError: false,
}),
],
resolve: {
...config.resolve,
},
performance: {
hints: false,
}
}
);
};
We're also using Font Awesome, but I was not able to import Font Awesome CSS from node_modules
. Using CDN for Font Awesome solved that issue.
To conclude: We can not import CSS from node_modues
if using PostCSS/CSS modules.
If someone unearths that issue as I did, it has led me to a simpler solution - instead of removing existing config for css-loader
and rebuilding it from scratch, you could tweak its options and its working fine too.
.storybook/webpack.config.js
:
module.exports = ({ config }) => {
// ...
config.module.rules.forEach(rule => {
// See: https://github.com/storybookjs/storybook/issues/6319
if (rule.test.toString() === '/\\.css$/') {
const idx = rule.use.findIndex(({ loader }) => loader && loader.includes('css-loader'));
rule.use[idx].options.modules = true;
}
});
// ...
};
In case you come across this issue while trying to integrate storybook with tailwind, update your postcss.config.js
:
module.exports = {
plugins: {
tailwindcss: {},
'postcss-preset-env': {},
},
};
That's it. The plugin tailwindcss
must be declared as an object in order for Storybook's webpack to process it. Credit for this solution goes to Andrea Cappuccio
Most helpful comment
Ok for those playing along at home this works perfectly.