0%

webpack 和 rollup 对比

诞生背景

在打包工具出现之前,在浏览器中运行 Javascript 中有两种写法:

  • 第一种方式,引用一些脚本(script 标签)来存放每个功能;此解决方案很难扩展,因为加载太多脚本会导致网络瓶颈。
  • 第二种方式,使用一个包含所有项目代码的大型 .js 文件,但是这会导致作用域、文件大小、可读性和可维护性方面的问题。

历史的解决方案:

  • iife
  • commonjs(最大的问题是浏览器不支持 commonjs,因为 commonjs 是运行时 动态加载的,是同步的,浏览器同步的话,太慢了)
  • ESM
    • 未来的官方标准和主流。但是浏览器的版本需要比较高,比如 chorme 都需要 63 版本以上
    • esm 是静态的,可以在编译的时候就分析出对应的依赖关系,不用像 commonjs 一样,运行时加载

背景总结:

  1. commonjs 很好,推出 npm 管理 JavaScript 模块包,但浏览器不支持
  2. esm 更好,浏览器也支持,但只有很新的浏览器才支持。 你可以源代码内写 esm 模块,webpack 可以帮忙打包,让不兼容 esm 的浏览器,也能兼容

打包原理

在了解了背景之后,理解打包原理就很简单了。

webpack

所有内容打包到一个 chunk 包内(单 chunk 包)

无额外配置,webpack 一般会把所有 js 打成一个包。实现步骤

  • 读文件,扫描代码,按模块加载顺序,排列模块,分为模块 1,模块 2,…,模块 n 。放到一个作用域内,用 modules 保存,modules 是一个数组,所有模块按加载顺序,索引排序
  • webpack 自己实现对应的 api(比如自己实现 require),让浏览器支持源代码内的模块化的写法(比如:module.export, require, esm 稍微有些不同 见下方)打包外部依赖也是一样的
结果
  • 纯 commonjs
    • 所有的 js 依赖,打包到一个文件内,然后自己实现一套 require 和 module.exports,让浏览器可以执行源代码
    • 源代码的 require 会被换成 webpack_require
    • 源代码的 module.exports 不变,会由 webpack 作为函数的参数传给源代码
  • 纯 esm
    • webpack 会做 tree shaking,最终的产物,会和 rollup 的产物比较接近,不会有过多的 webpack 注入的兼容代码
    • 实现思路类似 rollup,通过 esm 的静态特性,可以在编译的时候,就分析出对应的依赖关系
  • esm + commonjs 混用
    • webpack 很强大,他是支持混用的!!
    • 你可以 module.exports 导出, import xx from xx 导入
    • 也可以 exports { } 导出,require 引入
    • 实现的思路和上面的模拟 module.exports 和提供webpack_require替代 require 的思路类似,webpack 会去模拟 esm 的 exports 对象 让浏览器支持

多个 chunk 包

多个打包入口
  • 多个入口分离多个包,然后生成多个 script 标签(按入口的顺序
  • 分离出来的多个包,都包含同样多的模拟代码(webpack 注入的代码)
分离公共依赖
  • 先加载 venders 包(第三方公共依赖),此加载不是解析代码,只是把第三方依赖的模块,以 webpack 能解析的格式,存到全局对象 window[“webpackJsonp”]内,方便后续的代码能访问到
  • 只需要把 window[“webpackJsonp”]内的 venders 内的模块,放到 main 代码作用域内的 modules 里面,后面就和单 chunk 解析是一样的了
import() 动态加载(懒加载)
  • 先执行 main 模块的内容,从上到下执行,关注 import(‘xx’).then()行。打包后,import()会被替换成 webpack 的 api(webpack_require.e(/_ import() _/ 1).then(webpack_require.bind(null, 1)).then())

  • 替换后的 api 做了几件事

    • 生成 script 标签,并 appendChild 到 ducument.head 内
    • return 一个 Promise 对象,状态是 pending(pending 状态不会往后执行.then(webpack_require.bind(null, 1)).then(),但不会阻塞主程序,因为是异步的,不懂的可以了解一下 promise)
    • (异步)等了一段时间后,需求懒加载的模块通过 script 标签,被下载到浏览器后会直接解析执行,触发 window[“webpackJsonp”].push(此方法被改写了,和生成同步多 chunk 有点不一样,会触发 webpackJsonpCallback 函数
    • webpackJsonpCallback 函数的作用
      • 懒加载的模块 内容 会被加入到 main 的 mudules 的模块列表内去(等效 push 的作用)
      • 会把 Promise 的状态从 pending 改成 fulfilled,因为要懒加载的模块,通过 script 标签,已经解析完成了,所以.then()可以往后了
    • 后面就是正常解析包,和单 chunk 解析多模块是一样的了

rollup

浏览器环境使用的话:

  1. 无需考虑浏览器兼容问题的话:
    • 开发者写 esm 代码 -> rollup 通过入口,递归识别 esm 模块 -> 最终打包成一个或多个 bundle.js -> 浏览器直接可以支持引入
  2. 需考虑浏览器兼容问题的话
    • 可能会比较复杂,需要用额外的 polyfill 库,或结合 webpack 使用

打包成 npm 包的话:

  • 开发者写 esm 代码 -> rollup 通过入口,递归识别 esm 模块 -> (可以支持配置输出多种格式的模块,如 esm、cjs、umd、amd)最终打包成一个或多个 bundle.js
    • (开发者要写 cjs 也可以,需要插件@rollup/plugin-commonjs) 初步看来
  • 很明显,rollup 比较适合打包 js 库(react、vue2 等的源代码库都是 rollup 打包的)或 高版本无需往下兼容的浏览器应用程序
  • 这样打包出来的库,可以充分使用上 esm 的 tree shaking,使源库体积最小

单 chunk 包

无额外配置,一般会把所有 js 打成一个包。打包外部依赖(第三方)也是一样的

多 chunk 包

  • 配置多个入口,此法比较简单,可自行测试
  • 代码分离 (动态 import,懒加载, import(xxx).then(module => {}) )

总结

webpack

webpack 诞生在 esm 标准出来前,commonjs 出来后

  • 当时的浏览器只能通过 script 标签加载模块

    • script 标签加载代码是没有作用域的,只能在代码内 用 iife 的方式 实现作用域效果
      • 这就是 webpack 打包出来的代码 大结构都是 iife 的原因
      • 并且每个模块都要装到 function 里面,才能保证互相之间作用域不干扰。
      • 这就是为什么 webpack 打包的代码为什么乍看会感觉乱,找不到自己写的代码的真正原因
  • 关于 webpack 的代码注入问题,是因为浏览器不支持 cjs,所以 webpack 要去自己实现 require 和 module.exports 方法(才有很多注入)

    • 这么多年了,甚至到现在 2022 年,浏览器为什么不支持 cjs
      • cjs 是同步的,运行时的,node 环境用 cjs,node 本身运行在服务器,无需等待网络握手,所以同步处理是很快的
      • 浏览器是 客户端,访问的是服务端资源,中间需要等待网络握手,可能会很慢,所以不能 同步的 卡在那里等服务器返回的,体验太差
  • 后续出来 esm 后,webpack 为了兼容以前发在 npm 上的老包(并且当时心还不够决绝,导致这种“丑结构的包”越来越多,以后就更不可能改这种“丑结构了”),所以保留这个 iife 的结构和代码注入,导致现在看 webpack 打包的产物,乍看结构比较乱且有很多的代码注入,自己写的代码都找不到

rollup

rollup 诞生在 esm 标准出来后

  • 出发点就是希望开发者去写 esm 模块,这样适合做代码静态分析,可以做 tree shaking 减少代码体积,也是浏览器除了 script 标签外,真正让 JavaScript 拥有模块化能力。是 js 语言的未来
  • rollup 完全依赖高版本浏览器原生去支持 esm 模块,所以无额外代码注入,打包后的代码结构也是清晰的(不用像 webpack 那样 iife)
    • 目前浏览器支持模块化只有 3 种方法:
      • script 标签(缺点没有作用域的概念
      • script 标签 + iife + window + 函数作用域(可以解决作用域问题。webpack 的打包的产物就这样
      • esm (什么都好,唯一缺点 需要高版本浏览器)

最终使用推荐

  1. 打包开源库:rollup 会是更好的选择
  2. 打包应用程序:看是否需要兼容老浏览器
    如果不考虑兼容老浏览器,建议用 vite 开发应用程序,vite打包实际使用的就是rollup,开发体验很棒,打的生产包比用webpack小很多,有不错的性能提升
    如果需要考虑兼容,则选择webpack