Yarn workspaces架构下Storybook跨包引入TS文件编译报错
Yarn Workspaces Monorepo 跨包引入TS文件转译失败修复方案
问题场景
基于Yarn workspaces搭建的monorepo项目,工作区配置如下:
"workspaces": [ "app/*", "packages/*", "utilities/*" ]
在utilities目录下用TypeScript编写Storybook通用工具函数,在app/test/index.tsx中引入该工具编写组件示例,代码如下:
import React from 'react'; import { Stack, Button } from '@mui/material'; import { storybookTemplate } from '@utilities/storybook'; const template = storybookTemplate((args) => { return ( <Stack spacing={2} direction="row"> <Button variant="text">Text</Button> <Button variant="contained">Contained</Button> <Button variant="outlined">Outlined</Button> </Stack> ); }); export const ButtonTest = template(); export default { title: 'Testing', };
执行yarn storybook启动服务时抛出编译错误:
ModuleBuildError: Module build failed (from ../../node_modules/babel-loader/lib/index.js): SyntaxError: C:\Project\javascript\utilities\storybook\setup\template\index.ts: Unexpected token, expected "," (11:47) 9 | 10 | /** To Set up a Storybook Template */ > 11 | export const storybookTemplate = (Template: Story) => { | ^ 12 | return (...data: any[]): Story<Args> => { 13 | const variant: any = Template.bind({}); 14 |
现有配置
项目相关配置如下:
app/test/tsconfig.json
{ "compilerOptions": { "module": "commonjs", "target": "ES5", "declaration": true, "esModuleInterop": true, "jsx": "react", "strict": true, "outDir": "./dist" }, "exclude": ["node_modules", "**/*.test.ts"] }
app/test/.babelrc
{ "presets": [ "@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript" ] }
app/test/webpack.config.js
const path = require('path'); const HtmlWebPackPlugin = require('html-webpack-plugin'); module.exports = { output: { path: path.resolve(__dirname, 'build'), filename: 'bundle.js', }, resolve: { modules: [path.join(__dirname, 'src'), 'node_modules'], alias: { react: path.join(__dirname, 'node_modules', 'react'), }, }, module: { rules: [ { test: /\.(ts|tsx|js|jsx|)$/, include: [/src/, /utilities/], resolve: { extensions: ['.ts', '.tsx', '.js', '.jsx'], }, use: { loader: 'babel-loader', }, }, { test: /\.css$/, use: [ { loader: 'style-loader' }, { loader: 'css-loader' }, ], }, ], }, plugins: [ new HtmlWebPackPlugin({ template: './public/index.html', }), ], };
app/test/.storybook/main.js
const custom = require('../webpack.config.js'); module.exports = { stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], addons: ['@storybook/addon-links', '@storybook/addon-essentials', '@storybook/addon-interactions'], core: { builder: 'webpack5', }, webpackFinal: async (config) => { return { ...config, module: { ...config.module, rules: custom.module.rules } }; }, };
已确认问题根因是Yarn workspace软链的utilities目录下TS文件未被正常转译,TS类型注解被当作普通JS解析触发语法错误。将文件移动到app/test目录可正常运行,但不符合monorepo跨包复用的设计目标。
修复步骤
问题来自两个配置错误,按以下修改即可:
- 修正webpack转译路径匹配逻辑
原有babel-loader的include用正则/utilities/匹配,无法命中Yarn软链后的路径,同时.babelrc默认不会处理node_modules下的依赖包。修改webpack.config.js中的babel-loader规则,直接指定utilities目录的物理绝对路径,顺便修正test正则末尾多余的分隔符:// 先声明monorepo根目录路径 const monorepoRoot = path.resolve(__dirname, '../../'); // babel-loader规则修改为 { test: /\.(ts|tsx|js|jsx)$/, include: [ path.join(__dirname, 'src'), path.join(monorepoRoot, 'utilities') ], resolve: { extensions: ['.ts', '.tsx', '.js', '.jsx'], }, use: { loader: 'babel-loader', options: { babelrcRoots: [path.join(monorepoRoot, '*')] } }, } - 修正Storybook的webpack配置合并逻辑
原有配置直接覆盖config.module.rules,会冲掉Storybook内置的必要loader规则,导致转译逻辑异常。修改.storybook/main.js的webpackFinal配置,不要覆盖原有规则,追加自定义转译规则即可:const path = require('path'); const monorepoRoot = path.resolve(__dirname, '../../'); module.exports = { stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], addons: ['@storybook/addon-links', '@storybook/addon-essentials', '@storybook/addon-interactions'], core: { builder: 'webpack5', }, webpackFinal: async (config) => { // 追加ts/tsx转译规则,不覆盖原有规则 config.module.rules.push({ test: /\.(ts|tsx)$/, include: [ path.resolve(__dirname, '../src'), path.join(monorepoRoot, 'utilities') ], use: { loader: 'babel-loader', options: { babelrcRoots: [path.join(monorepoRoot, '*')], presets: [ '@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript' ] } } }); config.resolve.extensions.push('.ts', '.tsx'); return config; }, };
如果是生产环境构建,更推荐给utilities下的每个包配置独立的TS构建流程,业务包直接引入构建后的JS产物,不需要在应用侧配置跨包转译,构建速度和稳定性都会更好。开发阶段用上面的源码转译方案即可,支持跨包代码热更新。
内容的提问来源于stack exchange,提问作者Noobit
相关产品推荐
相关产品推荐

