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

如何在ESM模式下区分开发与生产环境配置Node子路径导入

ESM-Only模式下Node.js子路径导入的环境区分与最佳实践

问题背景

我正在开发一个Node.js服务器,采用以下技术栈:

  • 100% ESM-Only模式,配置type: module,编译文件使用*.js扩展名;
  • 带有项目引用的TypeScript;
  • Node.js的subpath imports(子路径导入)。

项目目录结构:

REPO
|- api
|   |-- index.ts
|   |-- tsconfig.json
|- server
|   |-- index.ts
|   |-- tsconfig.json 
|- tsconfig.json
|- package.json

package.json子路径配置:

{
  "name": "my-app",
  "type": "module",
  "imports": {
    "#api": "./api/index.js"
  }
}

顶层tsconfig.json(项目引用):

{
  "compilerOptions": {
    "noEmit": true,
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "verbatimModuleSyntax": true
  },
  "references": [
    { "path": "./api/tsconfig.json" },
    { "path": "./server/tsconfig.json" }
  ],
  "include": [],
  "files": []
}

开发时用npx tsx --watch ./server/index.ts能正常解析#api,但生产编译后文件输出到dist目录,Docker镜像结构如下:

WORKSPACE
|- dist
|   |-- api
|   |   |-- index.js
|   |-- server
|   |   |-- index.js
|- package.json

执行node ./dist/server/index.js报错,提示无法找到根目录的api/index.js文件。


问题解答

1. ESM-only模式下区分开发与生产处理子路径导入的正确机制

核心矛盾是开发与生产环境中,子路径指向的文件物理位置不一致:开发时指向源码目录(tsx自动解析.ts后缀),生产时指向编译后的dist目录。有两种可靠解决方式:

方式一:使用Node.js条件导入(Conditional Imports)

利用Node.js原生支持的条件导入特性,在package.json中为不同环境指定子路径的实际指向:

{
  "name": "my-app",
  "type": "module",
  "imports": {
    "#api": {
      "development": "./api/index.js",
      "production": "./dist/api/index.js"
    }
  }
}

运行时通过环境变量切换逻辑:

  • 开发环境:NODE_ENV=development npx tsx --watch ./server/index.ts
  • 生产环境:NODE_ENV=production node ./dist/server/index.js

Node.js会根据NODE_ENV自动匹配对应路径,tsx在开发阶段会自动将.js扩展名映射到.ts文件,无需额外配置。

方式二:编译后同步子路径配置

如果不想依赖条件导入,可以:

  1. 调整子项目的tsconfig.json,设置outDir将编译文件输出到dist对应目录,比如api/tsconfig.json:
    {
      "compilerOptions": {
        "outDir": "../dist/api",
        "module": "NodeNext",
        "moduleResolution": "NodeNext",
        "verbatimModuleSyntax": true,
        "target": "ES2022",
        "strict": true
      },
      "include": ["index.ts"]
    }
    
  2. 编译完成后,要么修改根目录package.json的imports字段为"./dist/api/index.js",要么将package.json复制到dist目录并调整子路径为"./api/index.js",然后进入dist目录运行node server/index.js。

2. ESM-Only模式下的最佳实践

  • 对齐Node.js模块解析逻辑:TypeScript配置始终设置module: NodeNext和moduleResolution: NodeNext,确保编译后的代码与Node.js的ESM解析规则完全一致,避免路径解析差异。
  • 优先使用子路径导入:所有内部模块引用用#xxx形式的子路径代替相对路径(如../api/index.js),提升代码可读性的同时,便于统一修改环境路径配置。
  • 分离源码与编译输出:每个子项目通过outDir将编译文件输出到dist对应子文件夹,避免源码与编译文件混杂,Docker部署时只需复制dist目录和根package.json。
  • 统一环境变量控制:用NODE_ENV作为环境区分的唯一标准,配合条件导入或动态配置,确保开发与生产环境的路径解析逻辑一致。
  • 选用ESM友好的开发工具:使用原生支持ESM的工具(如tsx、esbuild),避免使用需要将ESM转译为CommonJS的工具,减少环境差异带来的问题。
  • 部署前验证路径:编译完成后,先在本地测试生产环境运行命令(如NODE_ENV=production node ./dist/server/index.js),提前排查路径解析错误。
  • 规范Docker部署:在Dockerfile中设置工作目录为/app/dist,将根package.json复制到工作目录并调整子路径为相对路径(或直接使用条件导入),确保容器内路径解析正确。

内容的提问来源于stack exchange,提问作者Harshal Patil

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 16:13:22