彻底搞清楚 ECMAScript 的模块化(三)

简介: 彻底搞清楚 ECMAScript 的模块化

ES Modules in Node.js


ES Modules 作为 JavaScript 语言层面的标准,它会逐渐取代 JavaScript 领域内所有模块化的需求。

Node.js 作为 JavaScript 一个重要运行环境,它已经开始逐渐支持这种特性了。


支持情况


在 node.js8.5 版本后,内部就开始以实现特性的方式支持 ES Module 了。所以现在可以直接在 node.js 中编写 ES Module 的代码。

在 node.js 中使用 ES Module,首先需要将文件的扩展名设置为 mjs。

创建一个 module.mjs 文件。


export foo = 'foo'
export bar = 'bar'

再创建一个 index.mjs 文件。


import { foo, bar } from "./module.mjs";
console.log(foo, bar);

在命令行运行 index.mjs 时需要加上 --experimental-modules 参数,表示启用 ES Modules 实验特性。


node --experimental-modules index.mjs

虽然这两个成员会被正常打印,但同时也会有一句警告。


(node:7586) ExperimentalWarning: The ESM module loader is experimental.

意思就是告诉我们 ESM module 属于实验特性,尽量不要在生产环境去使用。

上面就是在 node.js 直接使用 ES Module 需要做的两件事情。


导入内置模块和第三方模块


node.js 内置模块可以正常导入,没有任何问题。


import fs from "fs";
fs.writeFileSync("./foo.txt", "es module working");

第三方模块也可以这样导入,比如 lodash。

首先安装一下 lodash。


npm i lodash

编写测试代码。


import _ from "lodash";
console.log(_.upperCase("es module"));

运行代码,发现它是可以正常工作的。

再尝试一下导出 upperCase。


import { upperCase } from "lodash";
console.log(upperCase("es module"));

会得到一个错误。


SyntaxError: The requested module 'lodash' does not provide an export named 'upperCase'

原因也很简单。

lodash 只提供了一个 default 的默认导出,而没有将所有的成员全部单独导出。

所以我们也只能通过 import default 的方式导入默认成员。

很多第三方模块都是这样,因为它们都只提供了 CommonJS 标准的模块。

但是 node.js 内置模块对导出做了兼容,可以使用命名成员的方式单独导出。


import { writeFileSync } from "fs";
writeFileSync("./foo.txt", "es module working");

上面的代码是可以正常工作的。


ES Module 与 CommonJS 模块交互


因为很多第三方模块提供的都是 CommonJS 标准的模块,所以就会面临 ES Module 和 CommonJS 交互的情况。

ES Module 导入 CommonJS

在 ES Module 中导入 CommonJS 和导入 ES Module 没有太大区别。

编写 commonjs.js。


module.exports = {
  foo: "hello world",
};

编写 esm.js,导入 commons.js 中导出的模块。


import mod from "./commonjs.js";
console.log(mod);

运行 esm.js,可以正常拿到 commonjs.js 中导出的对象。

在 commons.js 中,是无法实现 ES Module 命名导出的。


exports.foo = "hello world";

上面这种导出方式仍然是一个 default 导出。

在 ES Module 中仍然无法命名导入。


import { foo } from "./commonjs.js";

这会得到一个错误信息,提示我们没有导出一个名为 foo 的成员。

CommonJS 导入 ES Module

在 node.js 环境中,不允许 CommonJS 直接导入 ES Module。

修改 esm.js。


export const foo = "hello world";

修改 common.js


const mod = require("./esm.mjs");
console.log(mod);

运行 common.js,会得到一个错误。


Must use import to load ES Module

如果要在 node.js 中使用 CommonJS 导入 ES Module,需要使用 webpack 之类的打包工具。


ES Module 与 CommonJS 的差异


CommonJS 提供了几个模块内置的全局成员,类似于全局变量。

创建 cjs.js 文件,并输出这几个全局成员。


// 加载模块函数
console.log(require);
// 模块对象
console.log(module);
// 导出对象别名
console.log(exports);
// 当前文件绝对路径
console.log(__filename);
// 当前文件所在目录
console.log(__dirname);

运行之后,没有任何问题。

再创建一个 esm.mjs 文件,把 cjs.js 中的内容复制过去,通过 node --experimental-modules esm.mjs 来运行。

会得到一个错误。


ReferenceError: require is not defined

使用了 ES Module 载入模块后,这些全局成员全部都无法获取了。

require、module 和 exports 在 ES Module 中没有意义,但 __filename 和 __dirname 还是需要的。

通过 import.meta.url 拿到当前的文件绝对路径,再通过 nodejs 内置的 fileURLToPath 和 dirname 函数,就可以获取到 __filename 和 __dirname 了。


import { fileURLToPath } from "url";
import { dirname } from "path";
const __filename = fileURLToPath(import.meta.url);
console.log(__filename);
const __dirname = dirname(__filename);
console.log(__dirname);


新版本的进一步支持


在 nodejs 最新的版本(v12+)中,对 ES Module 支持度更高了。

可以通过在 package.json 中添加 type:module 属性来提高对 ES Module 的支持。


{
  "type": "module"
}

这样项目下所有的 js 文件都会按照 ES Module 的方式执行,但是 --experimental-modules 参数不可以省略。

如果开启了这个特性,那么以 Commonjs 编写的模块也会以 ES Modules 的模式来运行,这样并不会识别 require 等函数,所以会报错。


const fs = require("fs");
fs.writeFileSync("./foo.txt", "es module working");

这种情况下,就需要将 CommonJS 标准规范的模块后缀名改为.cjs。


Babel 兼容方案


在早期的 nodejs 版本中,并不支持 ES Module。这时就需要借助 babel 来让 ES Module 转换为 CommonJS 代码。

可以安装一个低版本的 node,或者使用类似 nvm 或 n 的工具来设置一个低版本的 node。

在项目中安装 babel 和 babel 相关的插件。


npm i -D @babel/node @babel/core @babel/preset-env

只是安装了 babel 是不够的,因为 babel 并不会自动帮助我们转换代码,所以还需要自己编写 babel 的配置,在项目根目录下创建.babelrc 文件。


{
  "presets": ["@babel/preset-env"]
}

再去运行 ES Module 的代码,就可以正常工作了。


npx babel-node index.js

@babel/preset-env 是一个 ES6 转 ES5 的插件集合,我们也可以通过单个的插件去转换。

比如使用@babel/plugin-transform-modules-commonjs。

安装。


npm i -D @babel/plugin-transform-modules-commonjs

修改配置。


{
  "presets": ["@babel/plugin-transform-modules-commonjs"]
}

这样的话,编译速度可能会更快一些。


写在最后


前端模块化是前端工程化的第三块内容,到此讲解结束,接下来会发布另外几篇关于前端工程化的文章,敬请期待。



相关文章
|
2月前
|
C语言 开发者
模块化程序设计
模块化程序设计
15 0
|
6月前
|
安全 程序员 C++
代码规范:函数设计
除非告诉人们“危险”是什么,否则这个警告牌难以起到积极有效的作用。难以理解的断言常常被程序员忽略,甚至被删除。 ↩︎
43 0
|
4月前
|
JavaScript 前端开发 安全
ES6的类 vs TypeScript的类:解密两种语言中的面向对象之争
ES6的类 vs TypeScript的类:解密两种语言中的面向对象之争
|
9天前
|
JavaScript 前端开发 开发者
JavaScript与TypeScript:深入解析两者之间的异同
【4月更文挑战第23天】本文对比分析了JavaScript和TypeScript的异同。作为JavaScript超集,TypeScript添加了静态类型和类等特性,有助于提升代码质量和可维护性。两者在语法上相似,都能在浏览器或Node.js环境中运行。JavaScript适合小项目和快速开发,而TypeScript适用于大型项目,因其静态类型和强大的生态系统能减少错误并便于团队协作。理解两者差异有助于开发人员根据项目需求作出合适选择。
|
5月前
|
JavaScript 前端开发 开发者
探究ECMAScript 6的新特性
ECMAScript 6(也称为ES6或ECMAScript 2015)是JavaScript的一个重大更新,它引入了许多新特性和改进,使得JavaScript的开发更加简单和高效。在本篇博客中,我们将详细介绍ECMAScript 6的新特性。
44 1
探究ECMAScript 6的新特性
|
10月前
|
JavaScript 前端开发
彻底搞清楚 ECMAScript 的模块化(二)
彻底搞清楚 ECMAScript 的模块化
115 0
|
10月前
|
JavaScript 前端开发 Go
彻底搞清楚 ECMAScript 的模块化(一)
彻底搞清楚 ECMAScript 的模块化
|
JavaScript 索引
用大白话让你理解TypeScript的要点.(三)
用大白话让你理解TypeScript的要点.(三)
用大白话让你理解TypeScript的要点.(三)
|
JavaScript Java C语言
用大白话让你理解TypeScript的要点.(一)
用大白话让你理解TypeScript的要点.(一)
用大白话让你理解TypeScript的要点.(一)
|
JavaScript
用大白话让你理解TypeScript的要点.(二)
用大白话让你理解TypeScript的要点.(二)
用大白话让你理解TypeScript的要点.(二)