Describe the bug
When using CRA and "jsconfig.json": if I set "path" property to solve imports alias, the app works as expected running CRA, but Storybook crashes saying it can't resolve imports.
To Reproduce
Steps to reproduce the behavior:
Expected behavior
Shouldn't Storybook take the paths from jsconfig.json file - or solve it someway with full control mode, where I could solve the imports using "resolve" with Webpack?
Code snippets
This is my current jsconfig.json
file (Using it with Create React App it works perfectly and the app builds with no errors):
// jsconfig.json
{
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"@mycompany/app/*": ["./packages/app/*"],
"@mycompany/http/*": ["./packages/http/*"],
"@mycompany/icons/*": ["./packages/icons/*"],
"@mycompany/ui/*": ["./packages/ui/*"]
// etc...
}
},
"exclude": ["node_modules", "build", "coverage", "dist", "lib"]
}
This is the .storybook/config.js
file.
// .storybook/config.json
import { addDecorator, configure } from '@storybook/react';
import { withKnobs } from '@storybook/addon-knobs/react';
import JssProvider from 'react-jss/lib/JssProvider';
import MuiThemeProvider from '@material-ui/core/styles/MuiThemeProvider';
import React from 'react';
import TooltipProvider from '../src/packages/ui/src/Tooltip/TooltipProvider';
import theme from '../src/packages/app/src/theme';
addDecorator(withKnobs);
const generateClassName = (rule, styleSheet) =>
`${styleSheet.options.classNamePrefix}-${rule.key}`;
addDecorator(story => (
<JssProvider generateClassName={generateClassName}>
<MuiThemeProvider theme={theme}>
<TooltipProvider>
<div
style={{
position: 'absolute',
top: 0,
right: 0,
bottom: 0,
left: 0,
width: '100%',
height: '100%'
}}
>
{story()}
</div>
</TooltipProvider>
</MuiThemeProvider>
</JssProvider>
));
const req = require.context('../src', true, /.*.stories.js$/);
const loadStories = () => {
req.keys().forEach(filename => req(filename));
};
configure(loadStories, module);
Also, I've tried to use the full control mode, with this config:
// .storybook/webpack.config.js
const path = require('path');
module.exports = async ({ config }) => {
config.resolve = Object.assign(config.resolve, {
alias: {
"@mycompany/app": path.resolve(__dirname, "../src/packages/app/"),
"@mycompany/http": path.resolve(__dirname, "../src/packages/http"),
"@mycompany/icons": path.resolve(__dirname, "../src/packages/icons"),
// etc...
}
});
return config;
}
System:
Hey @ppalmeida, sorry you're facing this issue. Could you try updating your Storybook version to the latest stable versions - if that doesn't help, please let me know.
Also, keep in mind that Create React App doesn't support paths officially, and we've not tested that in Storybook.
Can you also share a test repository that I could have a look at? A basic reproduction only.
PS: Sorry for the slow reply, I've been away.
Hey, @mrmckeb.
Thank you for your reply.
Actually I have to say I was completely WRONG when said that CRA was assuming jsconfig.json
file and using it to resolve the paths/alias. No no. Not, at least, not YET
So, I think we can close this issue.
For those who is interested in having CRA working with paths
(or, if you prefer alias
) to your folders/packages, here it is what I did to make it work (without eject CRA)
I prefer not to eject CRA, so I went with some plugin
: I used the well known create-react-app-rewired
After install it, I created a config-overrides.js
in the root of the app and put this inside:
const path = require("path");
const resolve = dir => path.resolve(__dirname, dir);
module.exports = function(config, env) {
config.resolve.alias = Object.assign(config.resolve.alias, {
"@mycompany/app": resolve("src/packages/app"),
"@mycompany/core": resolve("src/packages/core"),
"@mycompany/http": resolve("src/packages/http"),
// etc...
});
return config;
};
Then, I created the jsconfig.json
file with these lines:
{
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"@mycompany/app/*": ["packages/app/*"],
"@mycompany/core/*": ["packages/core/*"],
"@mycompany/http/*": ["packages/http/*"],
// etc... you got the idea...
}
},
"exclude": ["node_modules", "build", "coverage", "dist", "lib"]
}
And then you would ask: "But why use this file if you already did the rewired webpack in step 2?". Because jsconfig.json
file helps your IDE to find your modules and files, etc. At least, my VSCode works better on this way.
JEST: now, you must run your tests, right? So, add these lines to your package.json
also:
// etc...
"jest": {
"moduleNameMapper": {
"^@mycompany/app(/?)(.*?)$": "packages/app$1$2",
"^@mycompany/core(/?)(.*?)$": "packages/core$1$2",
"^@mycompany/http(/?)(.*?)$": "packages/http$1$2",
}
},
Pretty nasty, ahn? The keys are RegExp that Jest uses to find your files. And "Why did you use 2 groups in your regex, if they are at the end of the string anyway?". Well, this is how Webpack (or Jest, I'm not sure) will match your files:
So, these both will work:
import MyAwesomeModule from '@mycompany/app'
import MyOtherAwesomeModule from '@mycompany/app/folder1/folder2/MyOtherAwesomeModule';
Well, let's put Storybook in full-control mode, so we can apply the same concepts to Storybook as well. To do that, you must create a webpack.config.js
file inside .storybook
folder and add there your packages "binds" too:
const path = require("path");
const resolve = dir => path.resolve(__dirname, dir);
module.exports = async ({ config }) => {
config.resolve = Object.assign(config.resolve, {
alias: {
"@mycompany/app": resolve("../src/packages/app"),
"@mycompany/core": resolve("../src/packages/core"),
"@mycompany/http": resolve("../src/packages/http"),
}
});
return config;
};
Note that, since this file is inside .storybook
folder, the path needs the ../
before all src/etc
folder.
I don't know if there is a better strategy here. Maybe there is a really better way. Keep NODE_PATH
in .env
file and ignore the CRA warning? Maybe eject CRA? Maybe use NextJS?
As I put in the link in the beginning, I think CRA will soon have it's "way" of offering path out of the box. But, for now, I don't think it's worth to do all these configurations. For me, it's too much effort. Anyway, maybe it can help someone.
cheers
@ppalmeida - what about _ESLINT_?
what about globbing paths (**
) for importing deeply nested files in those aliases?
Hey, @yairEO.
About the **
, they are not much useful I think. Since all alias just ends up with packages/app/*
(a single *
will do the trick). Maybe on Jest aliases, that uses those two matching groups like this one:
"^@mycompany/app(/?)(.*?)$": "packages/app$1$2",
But I'm not sure. didn't test it. I'll take a look.
Talking about ESLint, what's the problem you're facing? Because the ESLint works well here without any other additional configuration. Do you want to share some particular problem you're facing at?
@xppalmeida - I was facing problems where eslint complained about not being able to resolve my absolute paths but I've managed to fix them!
Using this resolver: eslint-import-resolver-node
settings: {
'import/resolver': {
'node': {
'paths': ['./', './src']
}
}
...
NODE_PATH=./
{
"compilerOptions": {
"jsx": "react",
"baseUrl": "./",
"paths": {
"stories/*": ["stories/*"],
"shared/*": ["src/shared/*"],
},
},
"include": ["src/**/*", "stories/**/*"]
}
only with 'paths': ['./', './src']
in my eslint config things the linter worked resolving the paths correctly.
Storybook seems to work well with the above configuration and nothing else.
Really good to know! Thank you!
Most helpful comment
Hey, @mrmckeb.
Thank you for your reply.
Actually I have to say I was completely WRONG when said that CRA was assuming
jsconfig.json
file and using it to resolve the paths/alias. No no. Not, at least, not YETSo, I think we can close this issue.
For those who is interested in having CRA working with
paths
(or, if you preferalias
) to your folders/packages, here it is what I did to make it work (without eject CRA)Step 1
I prefer not to eject CRA, so I went with some
plugin
: I used the well known create-react-app-rewiredStep 2
After install it, I created a
config-overrides.js
in the root of the app and put this inside:Step 3
Then, I created the
jsconfig.json
file with these lines:And then you would ask: "But why use this file if you already did the rewired webpack in step 2?". Because
jsconfig.json
file helps your IDE to find your modules and files, etc. At least, my VSCode works better on this way.Step 4: JEST
JEST: now, you must run your tests, right? So, add these lines to your
package.json
also:Pretty nasty, ahn? The keys are RegExp that Jest uses to find your files. And "Why did you use 2 groups in your regex, if they are at the end of the string anyway?". Well, this is how Webpack (or Jest, I'm not sure) will match your files:
So, these both will work:
Step 5: Storybook
Well, let's put Storybook in full-control mode, so we can apply the same concepts to Storybook as well. To do that, you must create a
webpack.config.js
file inside.storybook
folder and add there your packages "binds" too:Note that, since this file is inside
.storybook
folder, the path needs the../
before allsrc/etc
folder.Bottom Line:
I don't know if there is a better strategy here. Maybe there is a really better way. Keep
NODE_PATH
in.env
file and ignore the CRA warning? Maybe eject CRA? Maybe use NextJS?As I put in the link in the beginning, I think CRA will soon have it's "way" of offering path out of the box. But, for now, I don't think it's worth to do all these configurations. For me, it's too much effort. Anyway, maybe it can help someone.
cheers