Modern JavaScript - ES2015 Modules

  1. 1. CommonJS Module
  2. 2. ES Modules (ESM)
  3. 3. Named exports
  4. 4. Default exports (one per module)
  5. 5. Mix exports

自 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::