如何在Storybook中动态导入自定义私有npm包?
我有一个自定义私有npm包,想通过以下方式动态导入:
export default function SelectedIcon({path, ...props}: Props) { const {data: icon} = require(`@mypackagelib/icons/${path}`) return <Icon icon={icon} {...props} /> } // 也尝试过另一种写法: export default function SelectedIcon({path, ...props}: Props) { const Component = React.lazy(() => import(`@mypackagelib/icons/${path}`)) return <Component {...props} /> }
两种写法都报错,问题出在动态导入上。
运行Storybook时,收到大量相同错误:
Module parse failed: Unexpected token (2:101) You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders | /// <reference types="react" /> | import { PropA, PropB } from '../../File'; > declare function NameOfAComponent(props: PropA): JSX.Element; | declare namespace NameOfAComponent { | var data: PropB;
奇怪的是静态导入完全正常:
import OneOfTheComponent from '@mypackagelib/icons/OneOfTheComponent'
但我的场景必须用动态导入。以下是私有包的tsconfig.json配置:
{ "compilerOptions": { "outDir": "dist", "target": "ES2020", "module": "commonjs", "jsx": "react", "lib": ["es6", "dom", "ES2020"], "moduleResolution": "node", "declaration": true, "strict": true, "skipLibCheck": true, "removeComments": true, "preserveConstEnums": true, "sourceMap": false, "esModuleInterop": true, "resolveJsonModule": true, "isolatedModules": true, "noEmitHelpers": true, "importHelpers": true, "pretty": false, }, "ts-node": { "swc": true }, "exclude": [ "node_moduls_local", "node_modules", "dist" ], "include": [ "types.d.ts", "src/icons/**/*" ] }
Storybook的./storybook/main.js配置:
module.exports = { stories: [ "../src/**/*.stories.@(ts|tsx)" ], addons: [ "@storybook/addon-links", "@storybook/addon-essentials", "@storybook/addon-interactions", "@storybook/preset-create-react-app", { name: 'storybook-addon-swc', // 以为这个能解决,但没用 options: { enable: true, enableSwcLoader: true, enableSwcMinify: true, }, }, ], framework: "@storybook/react", core: { builder: 'webpack5', }, experiments: { topLevelAwait: true, } }
想问:
- 如何解决这个问题?需要添加什么loader?
main.js该怎么配置?- 为什么动态导入需要loader而静态导入正常?
核心原因
静态导入时,Webpack在构建阶段就能精准定位到具体文件,自动应用项目中已有的TS/TSX loader处理;但动态导入使用变量路径(${path})时,Webpack会把整个@mypackagelib/icons目录作为扫描范围,会尝试加载你用tsc生成的.d.ts声明文件——而Webpack默认没有配置处理这类文件的loader,因此抛出解析错误。
你的私有包开启了declaration: true,打包后dist目录会生成对应的.d.ts文件,动态导入时这些文件被纳入扫描范围,最终导致报错。
解决方案
1. 调整私有包构建配置,隔离声明文件
修改私有包的tsconfig.json,将声明文件单独输出到子目录,并在package.json中指定类型入口,避免Webpack扫描到声明文件:
// 私有包tsconfig.json { "compilerOptions": { // ...其他原有配置 "declarationDir": "dist/types" // 将声明文件输出到dist/types子目录 } }
同时在私有包的package.json中添加:
{ "types": "dist/types/index.d.ts", "main": "dist/index.js" // 确保主入口指向编译后的JS文件 }
2. 配置Storybook的Webpack规则,处理或忽略声明文件
在./storybook/main.js中添加Webpack自定义配置,要么忽略.d.ts文件,要么用loader处理它们:
module.exports = { // ...原有配置 webpackFinal: async (config) => { // 方案一:直接忽略.d.ts文件 config.module.rules.push({ test: /\.d\.ts$/, use: [{ loader: 'ignore-loader' }] }); // 方案二:用swc-loader处理.d.ts文件(如果需要保留类型检查) // config.module.rules.push({ // test: /\.d\.ts$/, // use: [{ // loader: 'swc-loader', // options: { jsc: { parser: { syntax: 'typescript', tsx: false } } } // }] // }); // 确保TS/TSX loader能处理私有包的文件 config.module.rules.forEach(rule => { if (rule.test.toString().includes('tsx|ts')) { rule.include.push(require.resolve('@mypackagelib').replace('index.js', '')); } }); return config; }, };
如果使用ignore-loader,需要先安装依赖:npm install ignore-loader --save-dev
3. 缩小动态导入的文件范围
在动态导入时明确文件后缀,让Webpack只扫描目标类型文件,避免碰到声明文件:
// 添加.tsx后缀,限定扫描范围 const Component = React.lazy(() => import(`@mypackagelib/icons/${path}.tsx`))
静态导入正常的原因
静态导入时,Webpack能精准定位到具体的组件文件(比如@mypackagelib/icons/OneOfTheComponent),会根据文件后缀自动匹配项目中已有的loader(如SWC/TS loader)处理;而动态导入的变量路径会触发Webpack的模糊扫描,遍历目录下所有文件,包括无对应loader的声明文件,因此报错。
内容的提问来源于stack exchange,提问作者utopia

