JavaScript: Module System

18th February 2020 at 10:08am
JavaScript

这是一个 ES6 的特性( ES6 Compatibility Table)。

在此之前有 AMD (Require.js 为代表)和 CommonJS (Node.js 为代表)两套规范,我认为不需要再去学他们,ES6 Module 应该是后续会发展得更好的模块系统。

基础

  1. ES6 每个模块以一个 JS 文件形式存在
  2. 这个文件是用来被导入的,在里面定义任何符号都不会污染全局空间
  3. 这个文件自动启用了 Strict Mode

语法

Basic Usage

导出单个符号:

export function detectCats() {
  // ...
}

export class Kittydar {
  // ...
}

导入单个符号:

// 注意:这里不管导入一个还是多个,都必须加上花括号
import {detectCats} from "./module.js"

批量导出:

function detectCats() {
  // ...
}

class Kittydar {
  // ...
}

// 注意:这里不管导出一个还是多个,都必须加上花括号
export {detectCats, Kittydar};

批量导入:

import {detectCats, Kittydar} from "./module.js"

导出符号重命名:

export {detectCats as dc, Kittydar as k};

导入符号重命名:

import {detectCats as dc, Kittydar as k} from "./module.js"

Default exports

导出:

// 第一种
let myObject = {
  field1: value1,
  field2: value2
};
export {myObject as default};

// 第二种
export default {
  field1: value1,
  field2: value2
};

导入:

// 下面的 moduleDefault 是个 object,有 field1, field2(对应上面的导出代码)
// 注意:这里 import 后面没有花括号
import moduleDefault from "module.js";

Module objects

import * as cows from "cows";
// 此时 cows 变量是个 object,包含 cows.js 中所有被 export 的内容

Aggregating modules

注意这里面 * 的用法,与 module objects 做对比。

// world-foods.js - good stuff from all over

// import "sri-lanka" and re-export some of its exports
export {Tea, Cinnamon} from "sri-lanka";

// import "equatorial-guinea" and re-export some of its exports
export {Coffee, Cocoa} from "equatorial-guinea";

// import "singapore" and export ALL of its exports
export * from "singapore";

如何使用?

浏览器和 Node.js 目前(2017 年 2 月)都无法直接支持 ES6 模块系统,需要转译(transpiler)和打包(bundle)工具来将其转成可被支持的代码。

最常用的 transpiler 是 Babel;最常用的打包工具是 webpack。

对于 Babel,照它的 Setup 文档 作配置即可。同时 babel-cli 库提供了 babel-node 程序,可以先转译后再调用 node 命令行程序执行 JS 代码。

webpack 相关的配置,参考它的 Getting Start 教程

Babel 默认不支持相对于固定路径的 import,参考 JavaScript: Module System: Absolute Import

设计原则

ES6 规范不关心解释器如何装载一个模块,但是它用一些要求限定了解释器的行为,使得这套模块系统非常静态化(可以与 Python 的做对比):

  • importexport 必须在一个模块的顶层作用域被使用,无法在函数块中使用,也无法实现 conditional import or export
  • 所有导出的标识符必须是显式的,即不能用编程的方式导出标识符(比如如果你想导出 a1, a2, ..., a100,那你必须在代码里输出这么多标识符的名字,而无法用一段循环来导出)
  • Module objects are frozen. 你没法 hack 进这个对象去修改它的内容,或者增加功能
  • 任何模块以及它的依赖,都必须在 JS 代码被执行之前加载完毕。没有懒加载的能力。
  • 导入出错时没有恢复的能力(webpack 之类的工具可以在编译期检测出这种错误)
  • 整个装载过程,没有提供 hook 的能力;你没法在一个模块将被装载前执行什么代码

这里面的限制都是为了保证这个系统在运行时的稳定性。但是其中有一些能力,可以借助 webpack 这类工具实现,比如 hook 能力。而且这套规范的设计也很大程度上方便了这些 compile-time 工具。

浏览器上暂时没有实现这套加载能力,需要依赖 webpack 把不同模块打包到一个大文件中。这让我觉得很奇怪,毕竟浏览器还是作为 JS 的最大运行环境,如果不能实现这套能力真是很尴尬。但是 ES6 Module 系统是否是为了解决 AMD 和 CommonJS 割裂出来的生态系统而做的整合呢?

参考文档

  1. ES6 In Depth: Modules - Mozilla Hacks
  2. Import - MDN

TOC

TOC