JavaScript

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > rollup输出6种格式

rollup输出的6种格式详解

作者:摸鱼的春哥

这篇文章主要为大家介绍了rollup输出的6种格式详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

学习本文 📖

之前找工作的时候因为在简历上写“熟练使用 rollup.js 进行构建”,但回答不出 rollup 能打包出哪几种格式,我差点被面试官鄙视。你将:

为什么要学这个?

伴随着 vite.js 越来越火,它也变成了工作生产和面试问答时被经常提及的问题。

众所周知,vite.js 构建的核心是 rollup.js

那为什么在学习它们之前,有必要先学习 js 模块化规范?

因为我们需要理解它们最终的产物是什么,是如何运作的。

“崇拜魔法”是编程学习者的大忌,“好奇心”才是我们成长与进步的阶梯。

让我们解开神秘的面纱,看看那些打包后的代码,都是些“什么玩意儿”!

DEMO与示例构建

请配合 demo 食用,你会收获更多!

我为本文构建了一个简单的 demo 实例,源码就放在 github 上。

demo 地址: github.com/zhangshichu…demo 的代码结构如下:

├── answer.js
├── index.js
├── out
├── package.json
├── rollup.config.js
└── yarn.lock

其中 index.jsanswer.js 属于业务代码,是需要被打包的对象。

index.js 中依赖了 answer.js

如下:

// answer.js
export default 42
// index.js
import answer from "./answer";
import { repeat } from 'lodash'
// 定义一个无用变量, 测试 tree-shaking
const unusedVar = 'May the 4th'
export const printAnswer = () => {
  // 打印
  console.log(`the answer is ${answer}`)
  // 测试 lodash 的能力,打印42个1
  console.log(repeat('1', answer))
}

rollup.config.jsrollup 的配置文件,在此文件中我们分别指定其输出 amd , cjs , esm , iife , umd , system 六种格式的文件。

输出的文件会被打包到 out 文件夹下。

当我们执行 yarn build 或者 npm build 之后,会在 out 下产生如下格式的代码:

├── out
│   ├── amd
│   │   └── bundle.js
│   ├── cjs
│   │   └── bundle.js
│   ├── ems
│   │   └── bundle.js
│   ├── iife
│   │   └── bundle.js
│   ├── system
│   │   └── bundle.js
│   └── umd
│       └── bundle.js

为了弄清楚每种格式的 bundle.js 文件是如何运作的,我专门为它们订制添加了一些附属的小 demo

接下来,就让我们一种格式一种格式地学习和分析吧。

一、IIFE 自执行函数

IIFE 的全称是 “immediately invoked function expression”。

1.1 打包结果分析

让我们先看看本 demoiife 格式打出来的包长什么样。

对上述代码做一些简化:

var Test = (function (exports, lodash) {
  'use strict'; // 自带严格模式,避免一些奇怪的兼容性问题
  /**
   * 下面折行无用代码被 tree-shaking 掉了
   * const unusedVar = 'May the 4th'
   * */
  var answer = 42; // 业务中被单一引用的模块,被直接抹平了
  const printAnswer = () => {
    console.log(`the answer is ${answer}`);
    console.log(lodash.repeat('1', answer));
  };
  exports.printAnswer = printAnswer; // 把要export的属性挂在到exports上
  return exports;
})({}, $); // exports是第一个入参,依赖的jquery是第二个入参

IIFE 是前端模块化早期的产物,它的核心思路是:

1.2 如何运行

IIFE 的运行其实很容易,如果它没有其他依赖,只需要去引入文件,然后在 window 上取相应的变量即可。
如:

  <script src="http://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
  <script> 
    // jquery 就是典型的自执行函数模式,当你引入后,他就会挂在到 window.$ 上
    window.$ // 这样就能取到 jquery 了
  </script>

但是如果你像本 demo 中那样依赖了其他的模块,那你就必须保证以下两点才能正常运行:

以本 demoIIFE 构建结果为例:

因此,运行时,代码如下:

<head>
  <script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
  <script>window.lodash = window._</script>  
  <script src="./bundle.js"></script>
</head>
<body>
  <script>
    window.Test.printAnswer();
  </script>
</body>

1.3 优缺点

优点:

缺点:

优点就不细说了,缺点详细解释一下。

缺点一:输出的变量可能影响全局变量;引入依赖包时依赖全局变量。

前半句:输出的变量可能影响全局变量; 其实很好理解,以上面 demo 的输出为例: window.Test 就已经被影响了。
这种明显的副作用在程序中其实是有隐患的。

后半句:引入依赖包时依赖全局变量; 我们为了让 demo 正常运行,因此加了一行代码让 window.lodash 也指向 lodash,但它确实是太脆弱了。

<!-- 没有这一行,demo就无法正常运行 -->
<script>window.lodash = window._</script>

你瞧,IIFE 的执行对环境的依赖是苛刻的,除非它完全不依赖外部包。(Jquery: 正是在下!)

虽然 IIFE 的缺点很多,但并不妨碍它在 Jquery 时代极大地推动了 web 开发的进程,因为它确实解决了 js 本身存在的很多问题。

那么?后续是否还有 更为优秀 的前端模块化方案问世呢?

当然有,往下看吧。

二、CommonJS

2.1 分析打包结果

先看看 CommonJs 打包的结果:

简化一下,就长这样了:

'use strict';
var lodash = require('lodash');
var answer = 42;
const printAnswer = () => {
  // 打印
  console.log(`the answer is ${answer}`);
  // 测试 lodash 的能力,打印42个1
  console.log(lodash.repeat('1', answer));
};
exports.printAnswer = printAnswer;

以上格式,就是 CommonJS 规范的写法。

// CommonJS 通过一个全局 require 方法进行模块的引入 
var lodash = require('lodash');
// CommonJS 进行模块内方法输出的方式
module.exports.printAnswer = printAnswer;
// 上面写法也等价于:
exports.printAnswer = printAnswer;
// 因为 exports 变量等价于 module.exports

为了解决 node.js 在模块化上的缺失, 2009年10月 CommonJS 规范首次被提出。

注意这个关键词: node.js

是的,CommonJS 并不是在浏览器环境运行的规范,而是在 node.js 环境下运行的。

2.2 如何运行

因此,我写了一个 run.js 脚本。 如下:

// run.js
const Test = require('./bundle.js')
Test.printAnswer()

然后,执行以下命令:

# 执行脚本
node ./out/cjs/run.js 
# 输出1: 
> the answer is 42
# 输出2: 
> 111111111111111111111111111111111111111111

可以看出,node.js 环境是天然支持 CommonJS 的。

2.3 优缺点

优点

完善的模块化方案,完美解决了 IIFE 的各种缺点。

缺点

不支持浏览器环境,因为这种同步的引入方式可能导致浏览器假死。

因此,前端界迫切地需要一种能在浏览器环境完美运行,完善的模块化方案。

三、AMD 和 requirejs !

AMD,YES!

2011年, amdjs-api 在业内被正式提出。

3.1 打包结果分析

amd 格式的打包结果如下:

可以看到,核心内容是一个全局方法 define

define 方法有三个入参,分别是:

相比于 IIFECommonJs 而言,AMD 的写法无疑是复杂且别扭的。

但它却实实在在是解决了 IIFECommonJS 所面临的问题,对“浏览器里完善的JS模块方法” 提供了一套完善的方案。

尤其是 amd 标准的实现方案:requirejs

requirejs 所实现的 AMD 不仅解决了 CommonJS 在浏览器端的不适,通过异步的方式进行模块加载实现了不会导致假死的能力;更是完全弥补了 IIFE 存在的各类缺陷。

requirejs 在使用时,一般情况下是以下四步法:

3.2 如何运行

out/amd 打包目录下的 index.html 里,按如下方式编排代码:

<head>
  <!-- 1. 引入 require.js -->
  <script src="./require.js"></script>
  <!-- 2. 定义全局依赖 -->
  <script>
    window.requirejs.config({
      paths: {
        "lodash": "https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.min"
      }
    });
  </script>
  <!-- 3. 定义模块 -->
  <script src="./bundle.js"></script>
</head>
<body>
  <script>
    // 4. 开销模块
    window.requirejs(
      ['Test'],
      function   (test) {
        test.printAnswer()
      }
    );
  </script>
</body>

打开浏览器,我们可以正常地看到]控制台里被打印出来的 4242个1 了。

3.3 优缺点

优点

缺点

但好在我们拥有了各类打包工具,浏览器内的代码可读性再差也并不影响我们写出可读性ok的代码。

现在,我们拥有了面向 node.jsCommonJs面向浏览器的 AMD 两套标准。

如果我希望我写出的代码能同时被浏览器nodejs识别,我应该怎么做呢?

四、UMD 伟大的整合

它没有做什么突破性的创造,但它是集大成者。

4.1 打包分析

umd 格式构建出来的代码的可读性进一步降低了。

我相信任何正常人看到下面这段代码都会感到一阵头大:

是的,整整一大段代码,只是在处理兼容性问题,判断当前应该使用 amd 亦或是 CommonJS

因此 umd 的代码和实现不在此进行过多分析,它所做的无非便是让同一段代码兼容了 amdCommonJS

4.2 如何运行?

4.3 优缺点

优点

抹平了一个包在 AMDCommonJS 里的差异

缺点

会为了兼容产生大量不好理解的代码。(理解难度与包体积)

虽然在社区的不断努力下,CommonJSAMDUMD 都给业界交出了自己的答卷。

但很显然,它们都是不得已的选择。

浏览器应该有自己的加载标准。

ES6 草案里,虽然描述了模块应该如何被加载,但它没有 “加载程序的规范”。

五、SystemJs

因此 WHATWG(Web Hypertext Application Technology Working Group) 即网页超文本应用技术工作小组,提出了一套更有远见的规范:whatwg/loader

也就是 JavaScript Loader Standard (JS 加载标准)。

本规范描述了从 JavaScript 宿主环境中加载 JavaScript 模块的行为。它还提供了用于拦截模块加载过程和自定义加载行为的 api。

基于此规范,SystemJS 诞生了。

SystemJS 是目前 whatwg/loader 规范的最佳实践者。

可以看出来,system 的打包结果其实和 amd 类似,提供了全局的对象 System,并提供了注册的方式和统一的写法。

就单纯的从打包结果上,其实看不出它相比对 AMD + require.js 有什么优势,难道只是写法上存在差异?

并不止于此!

相比于 require.jsSystemJSSystem.import('module/name') 方式允许你更为“懒”地加载模块,这意味着你无需每次都加载一大堆的 bundle,用户只需要为他能看见的页面开销带宽。

另外,正因为 SystemJS 是面向 whatwg/loader 规范实践的,因此它是面向未来的模块依赖方式。

抱歉,这个的 demo 我也没玩明白,就不误导大家了。希望有明白的大佬可以帮忙完善下demo。

六、ESM

ECMAScript modules, 也叫 ESM, Javascript 模块化官方标准格式。

6.1 打包分析

ESM 被提出来之前,JavaScript 一直没有真正意义上的模块(module)体系。

它的规范是通过 export 命令显式指定输出的代码,再通过 import 命令输入。

// 导入模块
import { foo } from 'bar';
// 导出命令
export { zap };

这也是我们日常开发中最为熟悉的写法。

因此,esm 格式打出来的包,可读性确实非常棒:

和阅读我们平时所写的业务代码完全没有区别。(rollup 依然没忘记做 tree-shaking

6.2 如何运行

祝贺你,是这个时代的前端开发。

部分现代浏览器已经开始实装 <script type="module> 了,因此在浏览器上直接使用 esm 已成为现实。

但运行起来扔需要做一些前置步骤。

# 在js-modules目录下起一个本地静态服务
cd js-modules && http-server
import repeat from '../../node_modules/lodash-es/repeat.js';
// 因为默认的lodash并不是输出的 esm 格式,因此为了demo我们需要做一些特殊处理

这样一来,代码就能成功运行,控制台就可以成功打印 42 和 42个1 了。

总结:分别适合在什么场景使用?

以上就是rollup输出的6种格式详解的详细内容,更多关于rollup输出6种格式的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文