Skip to content
导航

Node.js 模块

在JavaScript中,一个文件就是一个模块。

模块类型

  • 内置模块
    Node.js自带的核心模块,无效下载安装,直接导入
  • 本地模块
    相对路径或绝对路径引入的模块
  • 第三方模块
    从npm源下载的外部模块,按规律在node_modules目录下查找的模块

模块系统

早期JavaScript语法上没有原生支持任何模块,以致于社区通过多种方式实现了不同的JavaScript模块化方案。

在Node.js中,原生支持 CommonJS 模块系统。随着JavaScript的发展,JavaScript有了自己的ES模块系统,Node.js自14.0.0版本起支持ES模块系统(忽略奇数版本,属于测试版本)。

CommonJS 与 ES 模块对比

  • 文件名
    • CommonJS 文件名后缀为 .cjs.js
    • ES 模块文件名后缀为 .mjs,或在 package.json 配置 "type": "module",默认 .js 为ES模块
  • 兼容
    • CommonJS 不兼容导入 ES 模块
    • ES 模块兼容导入 CommonJS
  • 动态导入
    • CommonJS、ES 模块都支持 import() 动态导入,且都支持动态导入两种模块
  • 语法
    • CommonJS require() 可以在任何位置使用,并在导入位置执行,执行一次后缓存结果(按文件绝对路径缓存,大小写敏感)
    • ES 模块 import 在任何位置使用,都会提升到模块顶部,多次导入相当于一次导入
    • CommonJS 默认导出空对象 {}module.exports 是修改这个空对象
    • ES 模块默认导出一个空模块对象,但如果导入一个空的 CommonJS 模块,则会得到含default属性的对象{ default: {} }
    • CommonJS 导入时可以省略 .js 后缀
    • ES 模块 导入时不可省略文件名后缀

CommonJS 的模块包装器函数

在 CommonJS 模块中有5个变量是在模块的局部作用域有效,可通过 console.log(arguments) 打印出来 :

  • exports:对 module.exports 的引用
  • module:模块对象
  • require:内部使用的require函数
  • __filename:模块的绝对路径
  • __dirname:模块目录的绝对路径

在 Node.js 运行模块前,它会使用包装器函数包裹模块的所有代码,以实现模块作用域

js
(function(exports, require, module, __filename, __dirname) {
    // Module code
});

其中,modules.paths显示了Node.js模块的第三方模块依赖查找顺序:
即从进程当前目录向上查找 node_modules 目录下有没有匹配的第三方模块

js
module {
  // ...
  paths: [
    // ...
    '/Users/usernmae/repository/node_modules',
    '/Users/usernmae/node_modules',
    '/Users/node_modules',
    '/node_modules'
  ]
}

ES 模块 import.meta

在 ES 模块中没有包装器函数,可从import.meta.url获取文件路径。

import.meta是一个对象,同步JavaScript规范实现,目前只有一个url属性。

package.json 配置入口文件

在CommonJS main 字段表示入口文件的基础上,支持配置exports来区分不同模块系统的入口文件

package.json

json
{
  "name": "my-library",         
  "exports": {
    ".": {
        "browser": {
          "default": "./lib/browser-module.js"
        }
    },
    "module-a": {
        "import": "./path/to/module-a.mjs" 
        "require": "./path/to/module-a.js"
    }
  }
}

经过上述配置后,就能在其他包使用这个库时兼容两种模块系统使用

js
// For CommonJS 
const moduleA = require('my-library/module-a')

// For ES6 Module
import moduleA from 'my-library/module-a' // 是模块名,不是文件名,没有后缀

其他写法

由于 ES 模块是JavaScript后来支持的,在规范 ES 模块的过程中,模块打包工具率先做了相关支持,但自由度较高,会出现不规范的用法,例如:ES模块可以不加文件名后缀。

其他写法虽然可以通过 Babel、Typescript 等编译成标准格式,但源码不符合规范,不推荐使用。

参考资料