Loading...

# 背景

早期 js 是没有模块化的概念的,直到 nodejs 诞生,才把模块系统引入 jsnodejs 使用的是 CJS(Commonjs) 规范。而 js 语言标准的模块规范是 ESM(Ecmascript Module)

# CJS

# CJS 导出

s
// util.cjs
function add(a, b) {
  return a + b;
}
module.exports = { add };

# CJS 导入

s
//index.cjs
const { add } = require("./util.cjs");
async function foo() {
  console.log(add(1, 2));
}
foo();

关键词:

module,exports,global,require

特点:

  • 模块加载 require 就是代码执行
  • 所有代码运行在模块作用域中,不污染全局
  • 模块可以多次加载,但是只在第一次加载运行,后面是缓存

适用场景:

Nodejs ,浏览器端需要用其他打包工具支持

注意事项:

export.xxx 不能和 module.exports 一起使用

# ESM

# ESM 导出

s
// util.mjs
export function add(a, b) {
  return a + b;
}

# ESM 导入

s
//index.mjs
import { add } from "./util.mjs";
export function foo() {
  console.log(add(1, 2));
}
foo();

关键词:

export import

特点:

  • 异步加载,利用了浏览器原生的解析能力,代码体积更小
  • 模块内自动采用严格模式
  • 模块中的 this 指向并不是 windowglobal ,而是 undefined

适用场景:

结合其他打包工具 (webpack) 使用或浏览器 <script type="module"> 标签包裹

# ESM 和 CJS 的区别

# 模块依赖关系引入时间

可以理解为引入的模块内的代码执行的时间, ESM 发生在编译阶段,模块的引入必须生命在文件头部,且路径名不能动态制定,

CJS 发生在执行阶段,且模块路径可以通过字符串拼接等运算动态指定

s
// module.js
console.log('done')
//cjs 导出
exports.name = 'cjs'
//esm 导出
export const name ='esm'
// index.js
//esm 写法
import {name} from './module'
document.querySelector('#btn').addEventListener('click', () => {
  console.log(name, '>>>>')
})
//cjs 写法
document.querySelector('#btn').addEventListener('click', () => {
  const name = require('./module.js')
  console.log(name, '>>>>')
})

如上,当我们使用 ESM 写法时候,打印的 done 会在没做任何操作的时候就输出,而 CJS 写法只有在点击按钮的时候才会输出

# 值复制与映射

CJS 中我们导入的值是一个导出值的副本,而在 ESM 中我们获取到的是一个映射

// module.js
let name = 'cjs'
module.exports = {
  name,
  changeName(value) {
    name = value;
    return name
  }
}
// index.js
let { name, changeName } = require('./module')
console.log(name) // 'cjs'
changeName('changed')
console.log(name) // 'cjs'
name = 'changed'
console.log(name) // 'changed'

而当我们用 ems 的时候情况就会有所变化

s
// module.js
let name = 'cjs'
function changeName(value) {
  name = value
}
export {
  name,
  changeName
}
// index.js
import { name, changeName } from './module'
console.log(name) // 'esm'
changeName('changed')
console.log(name) // 'changed'
name = 'esm'
console.log(name) // 'esm'

# 互相导入

  • ESM 模块导入可以直接引入 ESM 模块
  • CJS 模块导入可以直接引入 CJS 模块
  • CJS 模块导入可以直接引入 ESM 模块。
  • ESM 模块导入不可以直接引入 CJS 模块
s
// index.cjs
const { add } = require("./util.mjs");
async function foo() {
  console.log(add(1, 2));
}
foo();
// index.mjs
import { add } from "./util.cjs";
export function foo() {
  console.log(add(1, 2));
}
foo();
// util.cjs
function add(a, b) {
  return a + b;
}
module.exports = { add };
// util.mjs
export function add(a, b) {
  return a + b;
}

上面例子的结果, index.cjs 可以正常运行, index.mjs 报错:

l
node:internal/modules/cjs/loader:1072
    throw new ERR_REQUIRE_ESM(filename, true);
    ^
Error [ERR_REQUIRE_ESM]: require() of ES Module C:\Users\suyu_\Desktop\test\utils.mjs not supported.
Instead change the require of C:\Users\suyu_\Desktop\test\utils.mjs to a dynamic import() which is available in all CommonJS modules.
    at Object.<anonymous> (C:\Users\suyu_\Desktop\test\index.cjs:2:17) {
  code: 'ERR_REQUIRE_ESM'
}

一种解决方案是:

s
async function foo() {
  const { add } = await import("./util.mjs");
  console.log(add(1, 2));
}
foo();

前提条件是在 CJS 中导入 ESM 的时候,必须要有异步的执行环境,否则是无法导入