如何解决 Storybook 中的别名?
How to resolve aliases in Storybook?
我有一个 React/Typescript 故事书项目。 Storybook 工作得很好,但是当我开始导入带有别名的文件时,它就崩溃了。
示例:
import Foo from "@components/foo" => crash
import Foo from "../../components/foo" => ok
该应用程序可以很好地使用别名。该问题仅与 Storybook 有关。
这是我的故事书配置:
module.exports = {
stories: ["../**/stories.tsx"],
webpackFinal: (config) => {
return {
...config,
module: {
...config.module,
rules: [
{
test: /\.(ts|js)x?$/,
exclude: /node_modules/,
use: { loader: "babel-loader" },
},
{ test: /\.css$/, use: ["style-loader", "css-loader"] },
{ test: /\.(png|jpg|gif)$/, use: ["file-loader"] },
{
test: /\.svg$/,
use: [
{
loader: "babel-loader",
},
{
loader: "react-svg-loader",
options: {
jsx: true,
},
},
],
},
],
},
};
},
typescript: {
check: false,
checkOptions: {},
reactDocgen: "react-docgen-typescript",
reactDocgenTypescriptOptions: {
shouldExtractLiteralValuesFromEnum: true,
propFilter: (prop) =>
prop.parent ? !/node_modules/.test(prop.parent.fileName) : true,
},
},
};
我的 webpack 配置:
/* eslint-env node */
const path = require("path");
const TerserPlugin = require("terser-webpack-plugin");
const Dotenv = require("dotenv-webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const isProductionMode = (mode) => mode === "production";
module.exports = () => {
const env = require("dotenv").config({ path: __dirname + "/.env" });
const nodeEnv = env.parsed.NODE_ENV;
return {
mode: "development",
entry: "./src/index.tsx",
output: {
path: path.join(__dirname, "./dist"),
filename: "[name].[contenthash].bundle.js",
publicPath: "/",
},
resolve: {
extensions: [".ts", ".tsx", ".js", "jsx", ".json"],
alias: {
"@api": path.resolve(__dirname, "src/api/"),
"@assets": path.resolve(__dirname, "src/assets/"),
"@components": path.resolve(__dirname, "src/components/"),
"@containers": path.resolve(__dirname, "src/containers/"),
"@data": path.resolve(__dirname, "src/data/"),
"@i18n": path.resolve(__dirname, "src/i18n/"),
"@models": path.resolve(__dirname, "src/models/"),
"@pages": path.resolve(__dirname, "src/pages/"),
"@src": path.resolve(__dirname, "src/"),
"@stores": path.resolve(__dirname, "src/stores/"),
"@utils": path.resolve(__dirname, "src/utils/"),
},
},
module: {
rules: [
{
test: /\.(ts|js)x?$/,
exclude: /node_modules/,
use: { loader: "babel-loader" },
},
{ test: /\.css$/, use: ["style-loader", "css-loader"] },
{ test: /\.(png|jpg|jpeg|gif)$/, use: ["file-loader"] },
{
test: /\.svg$/,
use: [
{
loader: "babel-loader",
},
{
loader: "react-svg-loader",
options: {
jsx: true,
},
},
],
},
],
},
devServer: {
historyApiFallback: true,
port: 3000,
inline: true,
hot: true,
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
}),
new Dotenv(),
],
optimization: {
minimize: isProductionMode(nodeEnv),
minimizer: isProductionMode(nodeEnv) ? [new TerserPlugin()] : [],
splitChunks: { chunks: "all" },
},
};
};
如何解决这个问题?我使用的是 webpack 5.24.2 和 storybook 6.1.20,所以这些是最新版本。
当我遇到同样的问题时,这对我有用:
- 在开发部门安装一个包
yarn add -D tsconfig-paths-webpack-plugin
。
- 然后调整你的
./storybook/main.js
配置:
... // other imports
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
...
webpackFinal: (config) => {
config.resolve.plugins = config.resolve.plugins || [];
config.resolve.plugins.push(
new TsconfigPathsPlugin({
configFile: path.resolve(__dirname, "../tsconfig.json"),
})
);
return { ... }
}
...
只需将此添加到您的 .storybook/main.js
const path = require('path');
module.exports = {
"stories": [
"../components/**/*.stories.mdx",
"../components/**/*.stories.@(js|jsx|ts|tsx)"
],
"addons": [
"@storybook/addon-links",
"@storybook/addon-essentials",
'@storybook/preset-scss',
],
webpackFinal: async (config, { configType }) => {
config.resolve.alias = {
...config.resolve.alias,
'@/interfaces': path.resolve(__dirname, "../interfaces"),
};
return config;
}
}
这里interface
是我项目根目录下的文件夹
对我有用
如果您使用的是 webpack 5,则需要指定应使用 webpack5,除了之前的答案之外还添加以下内容:
core: {
builder: "webpack5",
},
最终 storybook/main.js 将类似于:
// .storybook/main.js
const path = require('path');
const appWebpack = require(path.join(process.cwd(), 'webpack.config.js'));
module.exports = {
stories: ['../src/**/*.stories.@(tsx|mdx)'],
addons: [
"@storybook/addon-links",
"@storybook/addon-essentials",
'@storybook/preset-scss'
],
core: {
builder: "webpack5",
},
webpackFinal: async (config) => {
config.resolve.modules = [
...(config.resolve.modules || []),
...[path.resolve(process.cwd(), "src")],
];
config.resolve.alias = {
...(config.resolve.alias || {}),
...appWebpack().resolve.alias,
};
return config;
},
};
这将允许绝对路径和别名(当然,只要这些别名在您的主 webpack.config.js 和 jsconfig.json/tsconfig.json 中正确设置)
已编辑
事后遇到了特别是别名的麻烦,我在 webpack 坎坷的道路上又走了一趟。
我已经为上面的 .storybook/main.js 更新了原来的 'final',在别名和模块节点中明确合并。
编辑 2
请注意,eslint 会抱怨在您创建的全局装饰器中使用别名(并添加到 .storybook/preview.js)。您可以放心地忽略这一点 - 它们仍然有效。 If/when我也想出了如何更正这个问题,我会回来添加第三次编辑。
来自文档:
// .storybook/main.js
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
module.exports = {
webpackFinal: async (config) => {
config.resolve.plugins = [
...(config.resolve.plugins || []),
new TsconfigPathsPlugin({
extensions: config.resolve.extensions,
}),
];
return config;
},
};
我的 React/TypeScript Storybook 项目使用 Vite 而不是 Webpack。
readme for storybook-builder-vite
澄清 “默认情况下构建器 不会 读取您的 vite.config.js
文件,” 所以您在其中指定的任何内容可能对 Storybook 构建没有任何影响;相反,您必须通过 .storybook/main.js
.
中的 viteFinal
选项自定义 Storybook-specific Vite 配置
以下是我如何将 vite-tsconfig-paths
引入 Storybook Vite 配置以解析 tsconfig 路径别名:
// .storybook/main.js
const path = require("path");
const tsconfigPaths = require("vite-tsconfig-paths").default;
module.exports = {
"stories": [
"../frontend/**/*.stories.mdx",
"../frontend/**/*.stories.@(js|jsx|ts|tsx)"
],
"addons": [
"@storybook/addon-links",
"@storybook/addon-essentials",
"@storybook/addon-interactions"
],
"framework": "@storybook/react",
"core": {
"builder": "storybook-builder-vite"
},
/**
* A option exposed by storybook-builder-vite for customising the Vite config.
* @see https://github.com/eirslett/storybook-builder-vite#customize-vite-config
* @param {import("vite").UserConfig} config
* @see https://vitejs.dev/config/
*/
viteFinal: async (config) => {
config.plugins.push(
/** @see https://github.com/aleclarson/vite-tsconfig-paths */
tsconfigPaths({
// My tsconfig.json isn't simply in viteConfig.root,
// so I've passed an explicit path to it:
projects: [path.resolve(path.dirname(__dirname), "frontend", "tsconfig.json")],
})
);
return config;
},
}
我有一个 React/Typescript 故事书项目。 Storybook 工作得很好,但是当我开始导入带有别名的文件时,它就崩溃了。
示例:
import Foo from "@components/foo" => crash
import Foo from "../../components/foo" => ok
该应用程序可以很好地使用别名。该问题仅与 Storybook 有关。
这是我的故事书配置:
module.exports = {
stories: ["../**/stories.tsx"],
webpackFinal: (config) => {
return {
...config,
module: {
...config.module,
rules: [
{
test: /\.(ts|js)x?$/,
exclude: /node_modules/,
use: { loader: "babel-loader" },
},
{ test: /\.css$/, use: ["style-loader", "css-loader"] },
{ test: /\.(png|jpg|gif)$/, use: ["file-loader"] },
{
test: /\.svg$/,
use: [
{
loader: "babel-loader",
},
{
loader: "react-svg-loader",
options: {
jsx: true,
},
},
],
},
],
},
};
},
typescript: {
check: false,
checkOptions: {},
reactDocgen: "react-docgen-typescript",
reactDocgenTypescriptOptions: {
shouldExtractLiteralValuesFromEnum: true,
propFilter: (prop) =>
prop.parent ? !/node_modules/.test(prop.parent.fileName) : true,
},
},
};
我的 webpack 配置:
/* eslint-env node */
const path = require("path");
const TerserPlugin = require("terser-webpack-plugin");
const Dotenv = require("dotenv-webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const isProductionMode = (mode) => mode === "production";
module.exports = () => {
const env = require("dotenv").config({ path: __dirname + "/.env" });
const nodeEnv = env.parsed.NODE_ENV;
return {
mode: "development",
entry: "./src/index.tsx",
output: {
path: path.join(__dirname, "./dist"),
filename: "[name].[contenthash].bundle.js",
publicPath: "/",
},
resolve: {
extensions: [".ts", ".tsx", ".js", "jsx", ".json"],
alias: {
"@api": path.resolve(__dirname, "src/api/"),
"@assets": path.resolve(__dirname, "src/assets/"),
"@components": path.resolve(__dirname, "src/components/"),
"@containers": path.resolve(__dirname, "src/containers/"),
"@data": path.resolve(__dirname, "src/data/"),
"@i18n": path.resolve(__dirname, "src/i18n/"),
"@models": path.resolve(__dirname, "src/models/"),
"@pages": path.resolve(__dirname, "src/pages/"),
"@src": path.resolve(__dirname, "src/"),
"@stores": path.resolve(__dirname, "src/stores/"),
"@utils": path.resolve(__dirname, "src/utils/"),
},
},
module: {
rules: [
{
test: /\.(ts|js)x?$/,
exclude: /node_modules/,
use: { loader: "babel-loader" },
},
{ test: /\.css$/, use: ["style-loader", "css-loader"] },
{ test: /\.(png|jpg|jpeg|gif)$/, use: ["file-loader"] },
{
test: /\.svg$/,
use: [
{
loader: "babel-loader",
},
{
loader: "react-svg-loader",
options: {
jsx: true,
},
},
],
},
],
},
devServer: {
historyApiFallback: true,
port: 3000,
inline: true,
hot: true,
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
}),
new Dotenv(),
],
optimization: {
minimize: isProductionMode(nodeEnv),
minimizer: isProductionMode(nodeEnv) ? [new TerserPlugin()] : [],
splitChunks: { chunks: "all" },
},
};
};
如何解决这个问题?我使用的是 webpack 5.24.2 和 storybook 6.1.20,所以这些是最新版本。
当我遇到同样的问题时,这对我有用:
- 在开发部门安装一个包
yarn add -D tsconfig-paths-webpack-plugin
。 - 然后调整你的
./storybook/main.js
配置:
... // other imports
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
...
webpackFinal: (config) => {
config.resolve.plugins = config.resolve.plugins || [];
config.resolve.plugins.push(
new TsconfigPathsPlugin({
configFile: path.resolve(__dirname, "../tsconfig.json"),
})
);
return { ... }
}
...
只需将此添加到您的 .storybook/main.js
const path = require('path');
module.exports = {
"stories": [
"../components/**/*.stories.mdx",
"../components/**/*.stories.@(js|jsx|ts|tsx)"
],
"addons": [
"@storybook/addon-links",
"@storybook/addon-essentials",
'@storybook/preset-scss',
],
webpackFinal: async (config, { configType }) => {
config.resolve.alias = {
...config.resolve.alias,
'@/interfaces': path.resolve(__dirname, "../interfaces"),
};
return config;
}
}
这里interface
是我项目根目录下的文件夹
对我有用
如果您使用的是 webpack 5,则需要指定应使用 webpack5,除了之前的答案之外还添加以下内容:
core: {
builder: "webpack5",
},
最终 storybook/main.js 将类似于:
// .storybook/main.js
const path = require('path');
const appWebpack = require(path.join(process.cwd(), 'webpack.config.js'));
module.exports = {
stories: ['../src/**/*.stories.@(tsx|mdx)'],
addons: [
"@storybook/addon-links",
"@storybook/addon-essentials",
'@storybook/preset-scss'
],
core: {
builder: "webpack5",
},
webpackFinal: async (config) => {
config.resolve.modules = [
...(config.resolve.modules || []),
...[path.resolve(process.cwd(), "src")],
];
config.resolve.alias = {
...(config.resolve.alias || {}),
...appWebpack().resolve.alias,
};
return config;
},
};
这将允许绝对路径和别名(当然,只要这些别名在您的主 webpack.config.js 和 jsconfig.json/tsconfig.json 中正确设置)
已编辑
事后遇到了特别是别名的麻烦,我在 webpack 坎坷的道路上又走了一趟。
我已经为上面的 .storybook/main.js 更新了原来的 'final',在别名和模块节点中明确合并。
编辑 2
请注意,eslint 会抱怨在您创建的全局装饰器中使用别名(并添加到 .storybook/preview.js)。您可以放心地忽略这一点 - 它们仍然有效。 If/when我也想出了如何更正这个问题,我会回来添加第三次编辑。
来自文档:
// .storybook/main.js
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
module.exports = {
webpackFinal: async (config) => {
config.resolve.plugins = [
...(config.resolve.plugins || []),
new TsconfigPathsPlugin({
extensions: config.resolve.extensions,
}),
];
return config;
},
};
我的 React/TypeScript Storybook 项目使用 Vite 而不是 Webpack。
readme for storybook-builder-vite
澄清 “默认情况下构建器 不会 读取您的 vite.config.js
文件,” 所以您在其中指定的任何内容可能对 Storybook 构建没有任何影响;相反,您必须通过 .storybook/main.js
.
viteFinal
选项自定义 Storybook-specific Vite 配置
以下是我如何将 vite-tsconfig-paths
引入 Storybook Vite 配置以解析 tsconfig 路径别名:
// .storybook/main.js
const path = require("path");
const tsconfigPaths = require("vite-tsconfig-paths").default;
module.exports = {
"stories": [
"../frontend/**/*.stories.mdx",
"../frontend/**/*.stories.@(js|jsx|ts|tsx)"
],
"addons": [
"@storybook/addon-links",
"@storybook/addon-essentials",
"@storybook/addon-interactions"
],
"framework": "@storybook/react",
"core": {
"builder": "storybook-builder-vite"
},
/**
* A option exposed by storybook-builder-vite for customising the Vite config.
* @see https://github.com/eirslett/storybook-builder-vite#customize-vite-config
* @param {import("vite").UserConfig} config
* @see https://vitejs.dev/config/
*/
viteFinal: async (config) => {
config.plugins.push(
/** @see https://github.com/aleclarson/vite-tsconfig-paths */
tsconfigPaths({
// My tsconfig.json isn't simply in viteConfig.root,
// so I've passed an explicit path to it:
projects: [path.resolve(path.dirname(__dirname), "frontend", "tsconfig.json")],
})
);
return config;
},
}