Modern JavaScript - ES2015 Modules

  1. 1. Back & forward
    1. 1.1. CommonJS Module
    2. 1.2. ES Modules (ESM)
  2. 2. export 用法
    1. 2.1. Named exports
    2. 2.2. Default exports (one per module)
    3. 2.3. Mix exports
  3. 3. Namespace imports
  4. 4. Static module structure
  5. 5. Practice
  6. 6. ESM with Node (node.mjs)
  7. 7. Path Finder
  8. 8. import 所有语法
  9. 9. REF::

自 ES2015 开始,Javascript 终于有了像样的模块机制,来应对大型应用。

  • 模块的一个作用就是隔离作用域,之前的作法会污染全局变量 (Global Space)。
  • 另一作用是更好的重用。


Back & forward

CommonJS Module

1
2
3
4
const fs = require('fs')
const { networkInterfaces } = require('os')
const msg = 'Hello'
module.exports = { msg }

ES Modules (ESM)

看起来简单、清爽、好用。

1
2
3
import fs from 'fs'
import os, { networkInterfaces } from 'os'
export const msg = 'Hello'


export 用法

首先,先看下常见的两种 export 用法:

Named exports

1
2
3
4
5
6
# export 一个类
export class Logger { ... }
# export 一个值
export const ID = 123456;
# export 一个函数
export function sum() { ... }

这些类,方法,属性,在 import 时,方式如下:

1
2
3
4
5
6
# import 单个
import { Logger } from './utils';
# import 多个
import { ID, sum } from './utils';
# alias
import { ID, Logger as TheLogger } from './utils';

Default exports (one per module)

1
2
3
4
5
6
7
8
9
10
# export 一个类
export default class Logger { ... }
# export 一个值
export default const id = 123456;
# export 一个函数
export default function sum() { ... }
# Default exports support anonymous function declarations
export default function () {}
export default class {}

这些类,方法,属性,在 import 时,方式如下:

1
2
3
4
# Default 的 export 可以直接引用
import Logger from "./logger";
# 可以起别名
import TheLogger from "./logger";

Mix exports

Default 只能有一个。Named export 可以有多个,但二都可以同时使用。

1
2
3
4
5
6
7
8
9
10
# 以 React.js 为例(这里 export 不是 ES2015 的写法):
var React = {
Component: ReactComponent
};
module.exports = React;
# ES 2015 的写法:
let React = { ... }
React.Component = ReactComponent;
export default React;

然后使用时,就可以这样 import 会很方便。

1
import React, { Component } from 'react';


Namespace imports

模块对象

有时候,一个模块中有太多这样的小的方法,属性,一个个写的话比较繁琐,好在 ES2015 Modules 提供了一种优雅的方案:

1
2
import React, * as ReactUtils from 'react';
## ReactUtils used as a namespace

import * 时,导入的其实是一个模块命名空间对象,模块将它的所有属性都导出了。

然后就可以这样调用函数了: ReactUtils. Component


Static module structure

聚合模块

这是个人认为较好的一种实践,程序包中主模块的代码比较多,为了简化使用者更方便 import。可以用一种统一的方式将其它模块中的内容聚合 (Mix exports) 在一起导出。

一般会在根目录下建立一个 index.js 文件,然后在其中,整理好对应的 export。

可以参见 AntDesign

1
2
3
4
5
6
7
8
9
10
11
# 总 component 的 export 写法
import Affix from './affix';
export { Affix };
# 这是其中一个 component 的 index.js 写法
import Dropdown from './dropdown';
import DropdownButton from './dropdown-button';
Dropdown.Button = DropdownButton;
export default Dropdown;


Practice

如果模块默认输出一个函数,函数名的首字母应该小写。

1
2
3
4
function makeStyleGuide() {
}
export default makeStyleGuide;

如果模块默认输出一个对象,对象名的首字母应该大写。

1
2
3
4
5
6
const StyleGuide = {
es6: {
}
};
export default StyleGuide;

如果模块只有一个输出值,就使用export default,如果模块有多个输出值,就不使用export default,不要export default与普通的export同时使用。


ESM with Node (node.mjs)

Node 的官方讨论,如何在 node 中支持 ES2015 Modules。目前有三种方案:

  • ‘use module’
  • myModuleName.mjs
  • Automatic Detection (is import/export exist)
  • Info in package.json

看 ESP 规范,最后的方案是判断文本中是否含有 importexport,备选方案是 .mjs 后缀。


Path Finder

查找路径有三种方式,

  • 相对路径 import Menu from ./menu
  • 绝对路径 import Menu from /menu
  • npm_module import Menu from menu


import 所有语法

作为备注,这里记下 import 所有的用法:

1
2
3
4
5
6
7
8
9
import defaultMember from "module-name";
import * as name from "module-name";
import { member } from "module-name";
import { member as alias } from "module-name";
import { member1 , member2 } from "module-name";
import { member1 , member2 as alias2 , [...] } from "module-name";
import defaultMember, { member [ , [...] ] } from "module-name";
import defaultMember, * as name from "module-name";
import "module-name";


REF::