Skip to content

package.json 中的 type: module 作用详解

问题描述

当升级 Node.js 并尝试构建现有项目时,可能会遇到以下错误:

Error [ERR_REQUIRE_ESM]: Must use import to load ES Module:
   │   ~~/nuxt.config.js
   │   require() of ES modules is not supported.
   │   require() of ~~/nuxt.config.js from
   │   ~~/config.js is an ES
   │   module file as it is a .js file whose nearest parent package.json contains "type":
   │   "module" which defines all .js files in that package scope as ES modules.
   │   Instead rename nuxt.config.js to end in .cjs, change the requiring code to use
   │   import(), or remove "type": "module" from
   │   ~~/package.json.

这个错误是因为项目中的 package.json 文件包含了 "type": "module" 声明,这改变了 Node.js 处理 JavaScript 模块的方式。

什么是 type: module?

根据 Node.js 官方文档,"type" 字段定义了 Node.js 如何处理项目中的 .js 文件:

  • 当最近的 package.json 包含 "type": "module" 时,.js 文件会被当作 ES 模块处理
  • 当最近的 package.json 缺少 "type" 字段,或包含 "type": "commonjs" 时,.js 文件会被当作 CommonJS 模块处理

简单来说

  • 使用 require()module.exports → 需要 CommonJS(默认或无 type 字段)
  • 使用 importexport → 需要 ES 模块("type": "module"

解决方案

方案一:保留 type: module 并修改代码

如果你希望使用现代 ES 模块语法,应该保留 "type": "module" 并修改代码:

javascript
// 使用 ES 模块的 import 语法
import express from 'express';
// 而不是 CommonJS 的 require()
// const express = require('express');

方案二:移除 type: module

如果你希望继续使用传统的 CommonJS 语法,可以移除 package.json 中的 "type": "module"

json
{
  // 移除或注释掉这行
  // "type": "module",
  
  "name": "your-project",
  "version": "1.0.0",
  // 其他配置...
}

注意事项

移除 type: module 会使所有 .js 文件恢复为 CommonJS 模块,确保你的代码中不使用 ES 模块的 import/export 语法。

方案三:混合使用文件扩展名

Node.js 支持通过文件扩展名区分模块类型:

  • .js - 根据 package.json 中的 type 字段决定
  • .mjs - 始终作为 ES 模块处理
  • .cjs - 始终作为 CommonJS 模块处理

你可以将特定文件重命名:

bash
# 将 ES 模块文件重命名为 .mjs
mv nuxt.config.js nuxt.config.mjs

# 或者将 CommonJS 文件重命名为 .cjs
mv config.js config.cjs

方案四:使用 Node.js 新特性(v20.10.0+)

对于 Node.js v20.10.0 或更高版本,提供了新的实验性选项:

bash
# 自动检测模块类型
node --experimental-detect-module index.js

# 或将默认类型设置为模块
node --experimental-default-type="module" index.js

你可以在 package.json 的脚本中配置:

json
{
  "scripts": {
    "start": "node --experimental-detect-module index.js",
    "dev": "node --experimental-default-type=\"module\" index.js"
  }
}

方案五:使用输入类型标志

对于 CI/CD 管道或临时执行,可以使用 --input-type 标志:

bash
# 直接将 ES 模块代码传递给 Node.js
echo "import { myFunc } from 'my-package';" | node --input-type=module

最佳实践建议

  1. 新项目:推荐使用 "type": "module" 并采用 ES 模块语法
  2. 现有项目:根据代码库现状决定是否迁移到 ES 模块
  3. 混合项目:使用文件扩展名(.mjs/.cjs)区分模块类型
  4. Node.js 版本:确保使用支持的 Node.js 版本(v16+ 为 LTS)

版本兼容性说明

  • Node.js v12+:基本支持 ES 模块(需要标志)
  • Node.js v14+:改进的 ES 模块支持
  • Node.js v16+:稳定的 ES 模块支持(LTS)
  • Node.js v20.10.0+:新增自动检测模块类型功能

常见问题解答

Q: 移除 type: module 会有问题吗? A: 如果你的代码使用的是 require() 语法,移除 type: module 是安全的。但如果代码中使用了 import/export,则需要修改代码。

Q: 如何判断我的代码使用的是哪种模块系统? A: 检查代码中的导入/导出语法:

  • const module = require('module')module.exports = {} → CommonJS
  • import module from 'module'export default module → ES 模块

Q: 可以在同一个项目中混合使用两种模块系统吗? A: 可以,但需要通过文件扩展名(.mjs 用于 ES 模块,.cjs 用于 CommonJS)或使用动态导入来实现。

通过理解 package.jsontype: module 的作用,你可以更好地管理项目的模块系统,避免常见的兼容性问题。