Skip to content

Node.js中模块化方案

CommonJS 和 ESM 是 JavaScript 中两种不同的模块系统

1. CommonJS

require() 导入、module.exports 或 exports 导出

项目默认是 CommonJS 规范,指定文件后缀为 .cjs后,此文件会遵守CommonJS 规范。

2. ES Module

(也被称为ES6 Module、ECMA Modules、ESM)

import 导入、export ( export default ) 导出

在Node.js中使用ESM,需要在package.json中设置"type": "module",或者文件名后缀为.mjs。

区别

ES module 是值的引用,CommonJS 是值的拷贝。

  • ‌ES module‌:导出的是值的引用。如果模块中修改了导出变量的值,那么所有导入这个变量的模块都会看到变化。这是因为 ES module(静态导入) 在编译时就确定了模块的依赖关系,并在运行时通过引用直接访问导出的值‌。
export let a = 10
setTimeout(() => {
 a = 11
}, 1000)

// 引入模块
import { a } from './1.js'
console.log(a) // 10
setTimeout(() => {
    console.log(a) // 11
}, 1500)
  • ‌CommonJS‌:导出的是值的拷贝(浅拷贝)。如果在CommonJS模块中修改了导出变量的值,这个变化不会反映到已经require了这个模块的其他模块中。这是因为 CommonJS(动态导入) 是在运行时加载的,每个require调用都会得到一个值的拷贝,修改这个拷贝不会影响原始值‌。
exports.a = 10;
setTimeout(() => {
    a = 11
},1000)

// 引入模块
let { a } = require('./1.js')
console.log(a) // 10
setTimeout(() => {
    console.log(a) // 10
}, 1500)

执行时机

  • ES module

    模块加载和执行是异步的;

    模块的执行顺序取决于模块之间的依赖关系和浏览器的加载策略;

    import 不会阻塞代码执行,浏览器会在后台加载模块;

  • ‌CommonJS

    模块加载和执行是同步的;

    模块的执行顺序与它们在代码中出现的顺序是一致的;

    require() 会阻塞代码执行,直到模块加载完成;

兼容性

  • ES module

    是 JavaScript 的标准模块系统;

    现代浏览器都原生支持 ES 模块;Node.js 也支持

  • ‌CommonJS

    主要用于服务器端,Node.js正是它的代表;

两种模块方案互相加载

  • CommonJS 加载 ESM
const tm = await import('./index.mjs')
  • ESM 加载 CommonJS
import tm from'./index.cjs'

import('./index.cjs').then((myModule)=>{ // 动态导入,返回一个 Promise
    console.log(myModule);
})

同时支持两种模块方式

咱们开发npm包时,可能需要同时适配 CommonJS 和 ES Module:

  • 在package.json中使用 "main" 和 "module" 字段分别指定 CommonJS 和 ES Module的入口文件
{
    "main": "lib/index.js",
    "module": "src/index.mjs"
}
  • package.json中设置 exports 字段
"exports":{ 
    "require": "./index.js",
    "import": "./esm/index.js"
}

全局变量获取

ESM环境中,传统的 CommonJS 全局变量 __dirname 和 __filename 不再直接可用。 这是因为 ES Module 采用不同的模块解析策略,更加符合 ECMAScript 标准。

ESM 中获取__dirname、__filename:

import { fileURLToPath } from 'node:url'
import { dirname } from 'node:path'

const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(fileURLToPath(import.meta.url))
  • import.meta: 当前模块信息的对象
  • fileURLToPath():将 URL 字符串转换为对应的本地文件系统路径
  • dirname():接受一个文件路径作为参数,返回该路径的目录部分

规范文档:

https://nodejs.cn/api/modules.html#modules_modules_commonjs_modules