Node.js 如何处理 ES6 模块

目录

一、两种模块的差异

[二、Node.js 的区分](#二、Node.js 的区分)

[三、CommonJS 模块加载 ES6 模块](#三、CommonJS 模块加载 ES6 模块)

[四、ES6 模块加载 CommonJS 模块](#四、ES6 模块加载 CommonJS 模块)

五、同时支持两种格式的模块


学习 JavaScript 语言,你会发现它有两种格式的模块。

一种是 ES6 模块,简称 ESM;另一种是 Node.js 专用的 CommonJS 模块,简称 CJS。这两种模块不兼容。

很多人使用 Node.js,只会用require()加载模块,遇到 ES6 模块就不知道该怎么办。本文就来谈谈,ES6 模块在 Node.js 里面怎么使用。

一、两种模块的差异

ES6 模块和 CommonJS 模块有很大的差异。

语法上面,CommonJS 模块使用require()加载和module.exports输出,ES6 模块使用importexport

用法上面,require()是同步加载,后面的代码必须等待这个命令执行完,才会执行。import命令则是异步加载,或者更准确地说,ES6 模块有一个独立的静态解析阶段,依赖关系的分析是在那个阶段完成的,最底层的模块第一个执行。

二、Node.js 的区分

Node.js 要求 ES6 模块采用.mjs后缀文件名。也就是说,只要脚本文件里面使用import或者export命令,那么就必须采用.mjs后缀名。Node.js 遇到.mjs文件,就认为它是 ES6 模块,默认启用严格模式,不必在每个模块文件顶部指定"use strict"

如果不希望将后缀名改成.mjs,可以在项目的package.json文件中,指定type字段为module

复制代码
{
   "type": "module"
}

一旦设置了以后,该目录里面的 JS 脚本,就被解释用 ES6 模块。

复制代码
# 解释成 ES6 模块
$ node my-app.js

如果这时还要使用 CommonJS 模块,那么需要将 CommonJS 脚本的后缀名都改成.cjs。如果没有type字段,或者type字段为commonjs,则.js脚本会被解释成 CommonJS 模块。

总结为一句话:.mjs文件总是以 ES6 模块加载,.cjs文件总是以 CommonJS 模块加载,.js文件的加载取决于package.json里面type字段的设置。

注意,ES6 模块与 CommonJS 模块尽量不要混用。require命令不能加载.mjs文件,会报错,只有import命令才可以加载.mjs文件。反过来,.mjs文件里面也不能使用require命令,必须使用import

三、CommonJS 模块加载 ES6 模块

ES6

复制代码
export const add = function (a, b) {
  return a + b;
};
export const minus = function (x, y) {
  return x - y;
};

export default {
  age: 18,
};

CommonJS

复制代码
(async function () {
  let { add, minus, default: def } = await import("./my-app.mjs");
  console.log(add(1, 2));
  console.log(minus(1, 2));
  console.log(def.age);
})();

上面代码可以在 CommonJS 模块中运行。

require()不支持 ES6 模块的一个原因是,它是同步加载,而 ES6 模块内部可以使用顶层await命令,导致无法被同步加载。

四、ES6 模块加载 CommonJS 模块

为了更好地与 JS 生态系统中的现有用法兼容,Node.js 除了*默认导入之外,*还会尝试确定每个导入的 CommonJS 模块的 CommonJS 命名导出,并使用静态分析过程将它们作为单独的 ES 模块导出提供。

CommonJS

复制代码
exports.add = function (a, b) {
  return a + b;
};
exports.minus = function (x, y) {
  return x - y;
};

ES6

复制代码
import { add, minus } from "./commonjs-package.js";
console.log(add(1, 2));
console.log(minus(1, 2));

命名导出的检测基于常见的语法模式,但并非总能正确识别, CJS 的导出是动态计算出来的,静态分析会失败。

复制代码
const add = function (a, b) {
  return a + b;
};
const minus = function (x, y) {
  return x - y;
};

module.exports = Object.assign(
  {
    age: 18,
  },
  {
    add,
    minus,
  },
);

在这种情况下,使用下述默认导入方式可能更合适。

复制代码
import pkg from "./commonjs-package.js";
console.log(pkg.add(1, 2));
console.log(pkg.minus(1, 2));
console.log(pkg.age);

命名导出检测涵盖了许多常见的导出模式、重新导出模式以及构建工具和转译器的输出。cjs-module-lexer 有关具体实现的语义,请参阅相关文档。

  1. 默认加载(Default Import): 始终有效。import cjs from './main.js' 得到的 cjs 等同于 module.exports
  2. 解构加载(Named Import): * 支持: 如果 CJS 模块使用了简单的赋值方式(如 module.exports = { a, b }exports.a = 1),Node.js 会自动解析出命名导出,允许 import { a } from './cjs.js'
  3. 不支持: 如果 CJS 的导出是动态计算 出来的(例如 module.exports = Object.assign({}, someData)),静态分析会失败。此时只能使用整体加载,再从对象中获取属性。

五、同时支持两种格式的模块

一个模块同时要支持 CommonJS 和 ES6 两种格式,也很容易。

如果原始模块是 ES6 格式,那么CommonJS 可以用 await import()进行加载。

ES6 模块 「原始模块」

复制代码
export const add = function (a, b) {
  return a + b;
};
export const minus = function (x, y) {
  return x - y;
};

CommonJS 模块

复制代码
(async function () {
  let { add, minus } = await import("./my-app.mjs");
  console.log(add(1, 2));
  console.log(minus(1, 2));
})();

如果原始模块是 CommonJS 格式,使用默认导入方式

CommonJS 模块 「原始模块」

复制代码
exports.add = function (a, b) {
  return a + b;
};
exports.minus = function (x, y) {
  return x - y;
};

ES6 模块 「原始模块」

复制代码
import pkg from "./commonjs-package.js";
const { add, minus } = pkg;
console.log(add(1, 2));
console.log(minus(1, 2));

另一种做法是在package.json文件的exports字段,指明两种格式模块各自的加载入

复制代码
"exports":{ 
    "require": "./index.js",
    "import": "./esm/wrapper.js" 
}

上面代码指定require()import,加载该模块会自动切换到不一样的入口文件。

相关推荐
刘联其2 小时前
.net也可以用Electron开发跨平台的桌面程序了
前端·javascript·electron
韩曙亮2 小时前
【jQuery】jQuery 选择器 ④ ( jQuery 筛选方法 | 方法分类场景 - 向下找后代、向上找祖先、同级找兄弟、范围限定查找 )
前端·javascript·jquery·jquery筛选方法
pas1362 小时前
42-mini-vue 实现 transform 功能
前端·javascript·vue.js
esmap2 小时前
OpenClaw与ESMAP AOA定位系统融合技术分析
前端·人工智能·计算机视觉·3d·ai·js
周杰伦的稻香2 小时前
Hexo搭建教程
java·node.js
毕设源码-钟学长2 小时前
【开题答辩全过程】以 基于node.js vue的点餐系统的设计与实现为例,包含答辩的问题和答案
前端·vue.js·node.js
小白路过3 小时前
记录vue-cli-service serve启动本地服务卡住问题
前端·javascript·vue.js
We་ct3 小时前
LeetCode 1. 两数之和:两种高效解法(双指针 + Map)
前端·算法·leetcode·typescript·哈希算法
LYFlied3 小时前
边缘智能:下一代前端体验的技术基石
前端·人工智能·ai·大模型