打包本地TypeScript包供Next.js使用时遇模块兼容问题如何解决?
项目结构
/ # 仓库根目录 /core # 供projectA和projectB使用的本地未发布npm包 /projectA # Next.js应用 /projectB # 另一个Next.js应用
打包操作
执行tsc && npm pack将/core打包为core-1.0.0.tgz,过程无报错,tgz文件包含所有预期的转译后.js代码。
/core/tsconfig.json 配置
{ "compilerOptions": { "lib": ["es2022", "DOM"], "module": "commonjs", "target": "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "outDir": ".compiled", "declaration": true, "jsx": "react" }, "include": ["./index.ts"] }
/core/package.json 配置
{ "name": "core", "version": "1.0.0", "engines": { "node": "18.15.0" }, "main": "./.compiled/index.js", "types": "./.compiled/index.d.ts", "files":[ "./.compiled/**" ], "scripts": { "test": "ts-node ./tests.ts", "build": "npm install && npm run test && tsc && npm pack" }, "dependencies": { ... "@uppy/aws-s3": "3.3.1", ... } ... }
引入core到projectA
在/projectA/package.json的依赖中添加:
... "dependencies": { ... "core": "../core/core-1.0.0.tgz", ... }, ...
执行npm install后,projectA/node_modules中正常出现core文件夹及其依赖,但运行projectA时出现报错:
Error: require() of ES Module C:\dev\repo\projectA\node_modules@uppy\aws-s3\lib\index.js from C:\dev\repo\projectA\node_modules\core.compiled\library\components\file-uploader\FileUploader.js not supported.
Instead change the require of index.js in C:\dev\repo\projectA\node_modules\core.compiled\library\components\file-uploader\FileUploader.js to a dynamic import() which is available in all CommonJS modules.
相关代码
/core/.../FileUploader.ts代码:
import UppyAwsS3 from '@uppy/aws-s3' import Uppy, { SuccessResponse, UppyFile } from '@uppy/core' import UppyDashboard from '@uppy/dashboard' import cx from 'classnames' import React from 'react' interface Props { className?: string fullWidth: boolean ... } interface State {} class FileUploader extends React.Component<Props, State> { ... render = () => { return ( <div className={cx('FileUploader', this.props.className, this.props.fullWidth && 'FileUploader--fullWidth')}> ... </div> ) } } export default FileUploader
/projectA/tsconfig.json 参考配置
{ "compilerOptions": { "strict": true, "esModuleInterop": true, "jsx": "preserve", "lib": ["ES2022", "dom"], }, "exclude": [ "node_modules" ] }
补充说明
- 有人建议参考ESM/CommonJS混用相关问题,但我的场景是TypeScript编写的包,需要让
tsc编译出正确的导入语法,使Next.js能正常使用依赖ESM子包的CommonJS核心包。 - 已创建可复现错误的最小示例项目。
解决方案
方案1:将core包编译为ESM格式
修改/core的配置,让它输出ESM格式代码,从根源避免CommonJS与ESM的冲突:
- 更新
/core/tsconfig.json:
{ "compilerOptions": { // 保留原有其他配置 "module": "ESNext", "moduleResolution": "NodeNext", "target": "es2022" } }
- 更新
/core/package.json:
{ // 保留原有其他配置 "type": "module", // 若需兼容CommonJS环境,可添加exports字段做双格式支持 "exports": { ".": { "import": "./.compiled/index.js", "require": "./.compiled/index.cjs" } } }
如果需要同时支持两种模块系统,可以配置tsc编译出两套代码,分别指定不同输出目录。
方案2:在core包中动态导入ESM依赖
修改FileUploader.ts,将对@uppy的导入改为动态导入,避免编译时生成require语句:
import cx from 'classnames' import React from 'react' interface Props { className?: string fullWidth: boolean ... } interface State { Uppy?: typeof import('@uppy/core').default UppyAwsS3?: typeof import('@uppy/aws-s3').default UppyDashboard?: typeof import('@uppy/dashboard').default } class FileUploader extends React.Component<Props, State> { state = {} async componentDidMount() { const [Uppy, UppyAwsS3, UppyDashboard] = await Promise.all([ import('@uppy/core'), import('@uppy/aws-s3'), import('@uppy/dashboard') ]) this.setState({ Uppy: Uppy.default, UppyAwsS3: UppyAwsS3.default, UppyDashboard: UppyDashboard.default }) } render = () => { const { Uppy, UppyAwsS3, UppyDashboard } = this.state if (!Uppy || !UppyAwsS3 || !UppyDashboard) return null // 后续正常使用Uppy实例 return ( <div className={cx('FileUploader', this.props.className, this.props.fullWidth && 'FileUploader--fullWidth')}> ... </div> ) } } export default FileUploader
方案3:配置Next.js转译相关包
在projectA的next.config.js中添加transpilePackages,让Next.js处理core及其ESM依赖的转译:
/** @type {import('next').NextConfig} */ const nextConfig = { transpilePackages: ['core', '@uppy/aws-s3', '@uppy/core', '@uppy/dashboard'], } module.exports = nextConfig
该方案让Next.js统一处理模块格式,避免出现require不支持ESM的错误。
内容的提问来源于stack exchange,提问作者BU0

