You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

打包本地TypeScript包供Next.js使用时遇模块兼容问题如何解决?

共享TypeScript包配置问题:CommonJS包依赖ESM子包导致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的冲突:

  1. 更新/core/tsconfig.json:
{
  "compilerOptions": {
    // 保留原有其他配置
    "module": "ESNext",
    "moduleResolution": "NodeNext",
    "target": "es2022"
  }
}
  1. 更新/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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 10:44:54