feng xiaohan

Node中使用ES Modules规范

在 Node.js 中一般是使用的 Common.js 规范,但是从Node.js 8.5版本之后,Node.js 就已经开始去支持 ES Modules 了(实验性),从Node.js 13.2.0版本开始,Node.js 完全支持 ES Modules(ESM),所以我们可以直接在 Node.js 中使用 ES Modules。

开启使用

更改文件后缀名使用

js文件的后缀改为.mjs即可在里面使用 ES Modules 规范。

注:如果是实验特性,在执行 node 文件时则需要添加--experimental-modules

开启 type=’module’使用

该方法需要的版本为 Node 12+。

package.json里添加"type": "module"来表明该项目使用的是 ES Modules 的语法,这时就不需要将扩展名改为.mjs了。

{
  "type": "module"
}

注意:如果在开启之后还想使用 CommonJS 模块的规范,需要将文件的后缀名改为.cjs

使用 Babel 兼容早期的 Node

对于早期的 Node.js 版本,可以使用 Babel 来实现对 ES Modules 的兼容。

Babel:目前最主流的一款 JS 的编译器,它可以将当前一些使用了新特性的代码编译成当前环境支持的代码。

  • 添加 babel 所依赖的一些包:

    yarn add @babel/node @babel/core @babel/preset-env --dev
    
  • 启动模块:

    yarn babel-node index.js --presets=@babel/preset-env
    

    preset-env:其实就是插件的集合,在这个集合里包含了最新的 JS 标准中所有的新特性。

    如果觉得每次手动配置这个参数很麻烦,可以在配置文件.babelrc(JSON 格式文件)添加presets配置:

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

    启动:

    yarn babel-node index.js
    

    由于 babel 是基于插件机制去实现的,核心模块并不会去转换我们的代码,具体转换每个代码的新特新是通过插件来实现的,一个插件来转换一个代码的特性。

    但是,如果项目中只使用了某些特性,而其他不支持的特性没有使用,也可也单独下载转换特性的插件来提高效率:

    移除插件集合:

    yarn remove @babel/preset-env
    

    添加转换的插件:

    yarn add @babel/plugin-transform-modules-commonjs --dev
    

    还有别的一些转换特性的插件,例如:

    • arrow-functions:用于转换 JS 的箭头函数;
    • classes:用于转换 JS 的类;
    • destructuring:用于转换解构;
    • module-commonjs:用于转换 ES Module;

    然后配置.babelrc里的 plugins:

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

    启动:

    yarn babel-node index.js
    

载入模块

可以通过 ES Modules 的方式载入模块。

  • 载入原生模块

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

    注意:系统内置模块的成员可以被{}提取出来,因为系统内置的模块官方都做了兼容,它会对系统内置的每一个模块的成员单独导出一次,然后再把他们作为一个对象整体做一个模块导出。

    import { writeFileSync } from "fs";
    writeFileSync("./bar.txt", "es module working");
    
  • 载入第三方模块

    前提是已经安装。

    import _ from "lodash";
    console.log(_.camelCase("ES Module"));
    

    注意:对于第三方模块,它们导出的是一个作为默认成员的对象,所以必须使用默认导入的方式去导入成员,不能使用单个命名导入。

    // import { camelCase } from 'lodash' // 不可以使用,其本身语法也和对象解构无关
    

与使用 CommonJS 规范的对比

ES Modules 里不再提供以下全局成员变量(CommonJS 里会提供)。

// commonjs.js
// CommonJS 模块内的全局成员(Node环境下)

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

对于requiremoduleexports可以通过 ES Modules 中的importimport())和export来代替它们导入模块,而对于__filename__dirname可以通过import.meta.urldirname方法代替:

import { fileURLToPath } from "url";
import { dirname } from "path";

const __filename = fileURLToPath(import.meta.url); // 将拿到的文件url转换成路径
console.log(__filename);
const __dirname = dirname(__filename); // 完整的文件路径提取出来
console.log(__dirname);
  • __filename:表示当前模块文件的绝对路径,包括文件名。
  • __dirname:表示当前模块文件所在的目录的绝对路径,不包括文件名。

注:在 Node.js 中加载 CommonJS 模块里的原代码,这几个成员其实也是把当前这几个的模块包装成函数或者是形参,它实际上也是伪全局对象。(在外侧包裹一个函数,从而实现私有模块作用域)