js模块

AMD CMD

AMD、CMD 相对命比较短,到 2014 年基本上就摇摇欲坠了。

CommonJs

require/exports 相关的规范由于野生性质,在 2010 年前后出生。
CommonJS 作为 Node.js 的规范,一直沿用至今。由于 npm 上 CommonJS 的类库众多,以及 CommonJS 和 ES6 之间的差异,Node.js 无法直接兼容 ES6。所以现阶段 require/exports 任然是必要且实必须的。

require/exports 的用法只有以下三种简单的写法:

const fs = require('fs')
exports.fs = fs
module.exports = fs

  • 说白了,导出一个对象,导入一个对象,完成。
  • exports是module.exports的引用,修改exports就相当于修改module.exports。
  • 但是注意,直接exports = xx , 这种只是修改了引用,并没有修改导出对象,如果直接赋值导出对象的话,需要module.exports = fs
  • 这里导出是 exports , es6是export,注意区别。

ES6

出自 ES6 的 import/export 相对就晚了许多。被大家所熟知和使用也是 2015 年之后的事了。 这其实要感谢 babel(原来项目名叫做 6to5,后更名为 babel) 这个神一般的项目。由于有了 babel 将还未被宿主环境(各浏览器、Node.js)直接支持的 ES6 Module 编译为 ES5 的 CommonJS —— 也就是 require/exports 这种写法 —— Webpack 插上 babel-loader 这个翅膀才开始高飞,大家也才可以称 " 我在使用 ES6! "
import/export 的写法就多种多样:

import fs from 'fs'
import {default as fs} from 'fs'
import * as fs from 'fs'
import {readFile} from 'fs'
import {readFile as read} from 'fs'
import fs, {readFile} from 'fs'
export default fs
export const fs
export function readFile
export {readFile, read}
export * from 'fs'

  • default 是 ES6 Module 所独有的关键字,export default fs 输出默认的接口对象,import fs from 'fs' 可直接导入这个对象;
  • ES6 Module 中导入模块的属性或者方法是强绑定的,包括基础类型;而 CommonJS 则是普通的值传递或者引用传递。
    看个例子:

// counter.js
exports.count = 0
setTimeout(function () {
console.log('increase count to', ++exports.count, 'in counter.js after 500ms')
}, 500)
// commonjs.js
const {count} = require('./counter')
setTimeout(function () {
console.log('read count after 1000ms in commonjs is', count)
}, 1000)
//es6.js
import {count} from './counter'
setTimeout(function () {
console.log('read count after 1000ms in es6 is', count)
}, 1000)

运行结果:

➜ test node commonjs.js
increase count to 1 in counter.js after 500ms
read count after 1000ms in commonjs is 0
➜ test babel-node es6.js
increase count to 1 in counter.js after 500ms
read count after 1000ms in es6 is 1
可以看出,ES6的引入可以读取到最新的值的变化,是强绑定,而CommonJS 是传递的引用(基础类型是值传递)。

package.json中的type

  • type字段的产生用于定义package.json文件和该文件所在目录根目录中.js文件和无拓展名文件的处理方式。
  • 值为'module'则当作es模块处理;
  • 值为'commonjs'则被当作commonJs模块处理
  • 目前node默认的是如果pacakage.json没有定义type字段,则按照commonJs规范处理
  • node官方建议包的开发者明确指定package.json中type字段的值
  • 无论package.json中的type字段为何值,.mjs的文件都按照es模块来处理,.cjs的文件都按照commonJs模块来处理

错误处理

  • 如果package.json中没有设置type,那么默认是按CommonJs处理的。当代码中出现import的话就会报错, 解决办法是引入babel转义成CommonJs的语法。

import { createRequire } from 'module';
^^^^^^
SyntaxError: Cannot use import statement outside a module
<node_internals>/internal/modules/cjs/loader.js:1054
Process exited with code 1

  • 反之亦然,设置type=module,那么无法使用require,会得到如下报错

Uncaught ReferenceError: require is not defined
src/modulet/CommonJsimport.js:4
Process exited with code 1

如果一定要使用,那么可以引入这个函数。
import { createRequire } from 'module';
const require = createRequire(import.meta.url);

使用办法

之前说了那么多,那么实际使用的过程中,可能会出现混乱的情况,下面记录一下。
老式的CommonJS(CJS)和新型的ESM(又名MJS)。

  • CJS使用require()、module.exports;
  • ESM使用import、export。

情况一、 cjs引入cjs

正常引用就行

情况二、 esm引入esm

正常引用就行

情况三、esm引入cjs

  • ESM可以使用 import CJS脚本,但是只能使用“默认导入”语法:import _ from 'lodash',而不是“命名导入”语法:import {shuffle} from 'lodash',如果CJS使用命名导出,则很麻烦。命名导出:module.exports.sum = (x, y) => x + y; 默认导出: module.exports = 'baz'
  • 或者像之前说的,把require找回来。

import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const {foo} = require('./foo.cjs');
这个方法的问题在于它没能帮多大忙。实际上也就比做一个默认导入然后解构多了几行代码。
import cjsModule from './foo.cjs';
const {foo} = cjsModule;
另外,像 Webpack 和 Rollup 这样的打包工具并不知道如何处理 createRequire 这样的模式,所以意义何在呢?

情况四、cjs引入esm

  • CJS 无法 require() ESM,因为有顶层的 await 限制
  • 目前为止,如果你在写 CJS,你想 import 一段 ESM 代码,你得使用异步动态的 import()。
    (async () => {
    const {foo} = await import('./foo.mjs');
    })();

指导原则

  • 为你的库包提供CJS版本
  • 为您的CJS提供一个薄的ESM包装器

import cjsModule from '../index.js';
export const foo = cjsModule.foo;

  • 将exports映射添加到您的package.json

"exports": {
"require": "./index.js",
"import": "./esm/wrapper.js"
}

ESM和CJS是完全不同的

  • 在CommonJS中,require()是同步的;它不返回承诺或调用回调。require()从磁盘(或什至从网络)读取,然后立即运行脚本,脚本本身可能会产生I / O或其他副作用,然后返回在module.exports上设置的任何值。
  • 在ESM中,模块加载器以异步阶段运行。在第一阶段,它解析脚本以检测对import和export的调用,无需运行导入的脚本。在解析阶段,ESM加载程序可以立即检测到命名导入中的错字并引发异常,而无需实际运行依赖项代码。
  • 然后,ESM模块加载器异步下载并解析您导入的所有脚本,然后异步下载您导入的脚本,从而构建依赖关系的“模块图”,直到最终找到一个不导入任何内容的脚本。最后,允许该脚本执行,然后允许运行依赖该脚本的脚本,依此类推。
  • ES模块图中的所有“兄弟”脚本都是并行下载的,但是它们会按顺序执行,并由加载程序规范保证。
  • ESM更改了JavaScript中的许多内容。ESM脚本默认使用严格模式(use strict),它们this不引用全局对象,作用域的工作方式不同,等等。
  • 这就是为什么即使在浏览器中<script>标签也默认为非ESM的原因;您必须添加一个type="module"属性以选择进入ESM模式。

TS中的import

JavaScript 中有多种 export 的方式,而 TypeScript 中针对这种情况做了多种 import 语法。

// commonjs 模块
import * as xx from 'xx'

// es6 模块
import xx from 'xx'

// commonjs 模块,类型声明为 export = xx
import xx = require('xx')

// 没有类型声明,默认导入 any 类型
const xx = require('xx')

  • import * as xx from 'xx' 的语法来一般都是用来导入使用 module.exports 导出的模块。

import * as path from 'path'

  • import xx from 'xx' 默认情况下,import xx from 'xx' 的语法只适用于 ECMAScript 6 的 export default 导出

  • import xx = require('xx') import xx = require('xx') 是用来导入 commonjs 模块的库,特殊的地方在于这个库的类型声明是 export = xx 这种方式导出的

  • const xx = require('xx') 当一个模块没有类型声明文件的时候,可以使用 commonjs 原始的 require() 方式来导入模块,这样会默认该模块为 any。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容