「温故而知新」amd/cjs/esm/umd与js模块化

431 阅读5分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情 >>

最近在写单包的sdk时用到了rollup打包,于是必然的注意到了配置项中的format这一栏:

Specifies the format of the generated bundle. One of the following:

  • amd – Asynchronous Module Definition, used with module loaders like RequireJS
  • cjs – CommonJS, suitable for Node and other bundlers (alias: commonjs)
  • es – Keep the bundle as an ES module file, suitable for other bundlers and inclusion as a <script type=module> tag in modern browsers (alias: esmmodule)
  • iife – A self-executing function, suitable for inclusion as a <script> tag. (If you want to create a bundle for your application, you probably want to use this.). "iife" stands for "immediately-invoked Function Expression"
  • umd – Universal Module Definition, works as amdcjs and iife all in one

于是温故一下js的几种模块类型。

我的未打包代码为:

// log.js
export const log = (params) => {
  console.log(params);
};

// main.js
import chalk from "chalk";
import { log } from "./utils/log";

let a = "a1";
setTimeout(() => {
  a = "a2";
  log(a);
}, 1000);
console.log(chalk.green("hello " + a));

1.1 AMD

Asynchronous Module Definition, used with module loaders like RequireJS

amd指异步模块定义,一般用于requirejs

amd代码形如:

define(['chalk'], (function (chalk) { 'use strict';

  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }

  var chalk__default = /*#__PURE__*/_interopDefaultLegacy(chalk);

  const log = (params) => {
    console.log(params);
  };

  let a = "a1";
  setTimeout(() => {
    a = "a2";
    log(a);
  }, 1000);
  console.log(chalk__default["default"].green("hello " + a));

}));

AMD一般是配合requirejs使用。

AMD 是异步(asynchronously)导入模块的(因此得名),但是这个例子里看不出来,如果是异步导入,会如下:

define(['dep1', 'dep2'], function (dep1, dep2) {
    //Define the module value by returning a value.
    return function () {};
});

// 或者

// "simplified CommonJS wrapping" https://requirejs.org/docs/whyamd.html
define(function (require) {
    var dep1 = require('dep1'),
        dep2 = require('dep2');
    return function () {};
});

1.2 CJS

CommonJS, suitable for Node and other bundlers (alias: commonjs)

CommonJS,为Node或其他模块使用。

CJS的代码形如:

'use strict';

var chalk = require('chalk');

function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }

var chalk__default = /*#__PURE__*/_interopDefaultLegacy(chalk);

const log = (params) => {
  console.log(params);
};

let a = "a1";
setTimeout(() => {
  a = "a2";
  log(a);
}, 1000);
console.log(chalk__default["default"].green("hello " + a));
  • 也就是我们在node中最常见的使用方式,require 导入模块。
  • 与AMD相比,CJS 不可以在浏览器中直接使用,并且是同步导入,是现在最常用的模块化方法之一。
  • 当 CJS 导入时,它会给你一个导入对象的副本,具体看下面与ES的对比部分。

1.3 ES

  • es – Keep the bundle as an ES module file, suitable for other bundlers and inclusion as a <script type=module> tag in modern browsers (alias: esmmodule)
  • 简单来说,保持import的方式不变,可以通过 <script type=module> 的方式直接运行在浏览器中。
import chalk from 'chalk';

const log = (params) => {
  console.log(params);
};

let a = "a1";
setTimeout(() => {
  a = "a2";
  log(a);
}, 1000);
console.log(chalk.green("hello " + a));

1.4 ES与CJS的对比

模块依赖关系建立的阶段引入导出
CommonJS动态 —— 代码运行阶段require —— 表达式,可以动态指定导出本身值的拷贝,可被修改,不会影响原文件
ES6 module静态 —— 代码编译阶段import —— 声明式,必须位于顶层作用域导出值的动态映射,只读

本质区别

两者的本质区别是:前者对模块依赖的解决是“动态的”,模块依赖关系的建立发生在代码运行阶段;而后者是“静态的”,模块依赖关系的建立发生在代码编译阶段。

在CommonJS中,require是一个表达式,并且可以接受动态指定,require 甚至可以写在if语句里,因此在代码运行到之前,没有办法确定明确的依赖关系。

在ES6 module中,导入、导出语句都是声明式的,它不支持导入的路径是一个表达式,并且导入、导出语句必须位于模块的顶层作用域(比如不能放在if语句中),这让他的依赖关系在代码的编译阶段就比较明了。

相比CommonJS,ES6 module的好处是:

  • 死代码检测和排除。我们可以用静态分析工具检测出哪些模块没有被调用过。

    • 比如,在引入工具类库时,工程中往往只用到了其中一部分组件或接口,但有可能会将其代码完整地加载进来。未被调用到的模块代码永远不会被执行,也就成为了死代码。通过静态分析可以在打包时去掉这些未曾使用过的模块,以减小打包资源体积。
  • 模块变量类型检查。JavaScript属于动态类型语言,不会在代码执行前检查类型错误(比如对一个字符串类型的值进行函数调用)。ES6 Module的静态模块结构有助于确保模块之间传递的值或接口类型是正确的。
  • 编译器优化。在CommonJS等动态模块系统中,无论采用哪种方式,本质上导入的都是一个对象,而ES6 Module支持直接导入变量,减少了引用层级,程序效率更高。

\

1.4 IIFE

  • iife – A self-executing function, suitable for inclusion as a <script> tag. (If you want to create a bundle for your application, you probably want to use this.). "iife" stands for "immediately-invoked Function Expression"
  • 简单来说,生成一个立即执行函数,将外在的依赖作为参数传入这个函数,这样可以获得一个纯净的function包。
  • 应该是rollup特有的?他可以最大程度上减少包本身的体积。
(function (chalk) {
  'use strict';

  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }

  var chalk__default = /*#__PURE__*/_interopDefaultLegacy(chalk);

  const log = (params) => {
    console.log(params);
  };

  let a = "a1";
  setTimeout(() => {
    a = "a2";
    log(a);
  }, 1000);
  console.log(chalk__default["default"].green("hello " + a));

})(chalk);

1.5 UMD

  • umd – Universal Module Definition, works as amdcjs and iife all in one
  • 最具兼容性的模块管理格式,正如它官方文档的介绍:UMD模式通常试图与当天最受欢迎的脚本加载程序(例如Requirej等)提供兼容性。在许多情况下,它将AMD用作基础,并添加了特殊评估来处理commonjs兼容性。
(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('chalk')) :
  typeof define === 'function' && define.amd ? define(['chalk'], factory) :
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.chalk));
})(this, (function (chalk) { 'use strict';

  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }

  var chalk__default = /*#__PURE__*/_interopDefaultLegacy(chalk);

  const log = (params) => {
    console.log(params);
  };

  let a = "a1";
  setTimeout(() => {
    a = "a2";
    log(a);
  }, 1000);
  console.log(chalk__default["default"].green("hello " + a));

}));