rollup打包产物解析及原理(对比webpack)

上篇: webpack打包产物解析及原理(含cjs/esm/代码分离/懒加载)

rollup定位

rollup比webpack晚出2年,对比webpack肯定是有差异化的

我们可以查看官网https://rollupjs.org/guide/en/#overview

得到以下几个特点

  1. 建议开发者使用esm写模块。
    1. 使用esm模块的好处就很多了:(可以参考我另一篇:https://juejin.cn/post/6959360326299025445
      1. 高版本浏览器原生支持(浏览器只有2种方法支持引入js模块,1是script标签,2就是esm模块)
      2. 可以做tree shaking(早期webpack版本是不支持tree shaking的)
      3. 可以解决循环引用的问题
  2. esm最终将在任何地方都可以实现(浏览器+node都可以用,是未来的标准),但 Rollup 让您今天就可以实现。
    • 这句话很重要,也是rollup的特点,也是诞生的原因
    • 里面有历史原因(详细可以参考我的上篇关于webpack的)
    • 简单的说就是:ESM - ECMAScript 模块是未来的官方标准和主流。但是浏览器的版本需要比较高,比如chorme都需要63版本以上
      • 所以rollup主要的出发点是:未来还没到,但rollup可以让你先写未来的代码(指esm)

rollup 使用流程

浏览器环境使用的应用程序的话:

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

打包成npm包的话:

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

举个小🌰简单的对比一下 webpack打包和rollup打包

此demo是纯esm的写法

  1. // 入口main。js
  2. import { b } from './test/a'
  3. console.log(b + 1)
  4. console.log(1111)
  5. // './test/a'
  6. export const b = 'xx'
  7. export const bbbbbbb = 'xx'

rollup打包效果(非常干净,无注入代码)

  1. const b = 'xx';
  2. console.log(b + 1);
  3. console.log(1111);

webpack打包效果(有很多注入代码)

  • 实际上,我们自己写的代码在最下面。上面注入的大段代码 都是webpack自己的兼容代码目的是自己实现require,modules.exports,export,让浏览器可以兼容cjs和esm语法
  • (可以理解为,webpack自己实现polyfill支持模块语法,rollup是利用高版本浏览器原生支持esm(所以rollup无需代码注入)
  1. /******/ (function(modules) { // webpackBootstrap
  2. /******/ // The module cache
  3. /******/ var installedModules = {};
  4. /******/
  5. /******/ // The require function
  6. /******/ function __webpack_require__(moduleId) {
  7. /******/
  8. /******/ // Check if module is in cache
  9. /******/ if(installedModules[moduleId]) {
  10. /******/ return installedModules[moduleId].exports;
  11. /******/ }
  12. /******/ // Create a new module (and put it into the cache)
  13. /******/ var module = installedModules[moduleId] = {
  14. /******/ i: moduleId,
  15. /******/ l: false,
  16. /******/ exports: {}
  17. /******/ };
  18. /******/
  19. /******/ // Execute the module function
  20. /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
  21. /******/
  22. /******/ // Flag the module as loaded
  23. /******/ module.l = true;
  24. /******/
  25. /******/ // Return the exports of the module
  26. /******/ return module.exports;
  27. /******/ }
  28. /******/
  29. /******/
  30. /******/ // expose the modules object (__webpack_modules__)
  31. /******/ __webpack_require__.m = modules;
  32. /******/
  33. /******/ // expose the module cache
  34. /******/ __webpack_require__.c = installedModules;
  35. /******/
  36. /******/ // define getter function for harmony exports
  37. /******/ __webpack_require__.d = function(exports, name, getter) {
  38. /******/ if(!__webpack_require__.o(exports, name)) {
  39. /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
  40. /******/ }
  41. /******/ };
  42. /******/
  43. /******/ // define __esModule on exports
  44. /******/ __webpack_require__.r = function(exports) {
  45. /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
  46. /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
  47. /******/ }
  48. /******/ Object.defineProperty(exports, '__esModule', { value: true });
  49. /******/ };
  50. /******/
  51. /******/ // create a fake namespace object
  52. /******/ // mode & 1: value is a module id, require it
  53. /******/ // mode & 2: merge all properties of value into the ns
  54. /******/ // mode & 4: return value when already ns object
  55. /******/ // mode & 8|1: behave like require
  56. /******/ __webpack_require__.t = function(value, mode) {
  57. /******/ if(mode & 1) value = __webpack_require__(value);
  58. /******/ if(mode & 8) return value;
  59. /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
  60. /******/ var ns = Object.create(null);
  61. /******/ __webpack_require__.r(ns);
  62. /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
  63. /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
  64. /******/ return ns;
  65. /******/ };
  66. /******/
  67. /******/ // getDefaultExport function for compatibility with non-harmony modules
  68. /******/ __webpack_require__.n = function(module) {
  69. /******/ var getter = module && module.__esModule ?
  70. /******/ function getDefault() { return module['default']; } :
  71. /******/ function getModuleExports() { return module; };
  72. /******/ __webpack_require__.d(getter, 'a', getter);
  73. /******/ return getter;
  74. /******/ };
  75. /******/
  76. /******/ // Object.prototype.hasOwnProperty.call
  77. /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
  78. /******/
  79. /******/ // __webpack_public_path__
  80. /******/ __webpack_require__.p = "./";
  81. /******/
  82. /******/
  83. /******/ // Load entry module and return exports
  84. /******/ return __webpack_require__(__webpack_require__.s = 0);
  85. /******/ })
  86. /************************************************************************/
  87. /******/ ([
  88. /* 0 */
  89. /***/ (function(module, __webpack_exports__, __webpack_require__) {
  90. "use strict";
  91. // ESM COMPAT FLAG
  92. __webpack_require__.r(__webpack_exports__);
  93. // CONCATENATED MODULE: ./src/test/a.js
  94. const b = 'xx';
  95. const bbbbbbb = 'xx';
  96. // CONCATENATED MODULE: ./src/main.js
  97. console.log(b + 1);
  98. console.log(1111);
  99. /***/ })
  100. /******/ ]);

两者处理源代码模块的对比

纯esm 纯cjs 两者混用
webpack 支持(有代码注入) 支持(有代码注入) 支持(有代码注入)
rollup 支持(无注入) 原生不支持(需增加插件@rollup/plugin-commonjs) 原生不支持(需增加插件@rollup/plugin-commonjs)

rollup的初衷也是希望开发者去写esm,而不是cjs。因为esm是javascript的新标准,是未来,有很多优点,高版本浏览器也支持

两者处理对外暴露模块,非常不一样!!(解释rollup为什么适合打包库)

上面的demo 加上export 导出

  1. // 入口main。js
  2. import { b } from './test/a'
  3. console.log(b + 1)
  4. console.log(1111)
  5. export { // 新增导出
  6. b
  7. }
  8. // './test/a'
  9. export const b = 'xx'
  10. export const bbbbbbb = 'xx'

rollup打包 导出(非常干净,无注入代码)

  • rollup本身不去做polyfill
  • rollup的配置文件无需特殊配置,而且还可以支持多种模块导出(esm,cjs,umd,amd) 打包的到 esm 和 cjs
    1. // rollup.config.js
    2. const OUTPUT_DIR = 'dist'
    3. const INPUT_FILE = 'src/main.js'
    4. export default[
    5. // esm
    6. {
    7. input: INPUT_FILE,
    8. output: {
    9. file: OUTPUT_DIR + '/esm/index.js',
    10. format: 'esm' // 导出esm模块
    11. }
    12. },
    13. // commonjs
    14. {
    15. input: INPUT_FILE,
    16. output: {
    17. file: OUTPUT_DIR + '/cjs/index.js',
    18. format: 'cjs' // 导出cjs模块
    19. }
    20. },
    21. // umd
    22. {
    23. input: INPUT_FILE,
    24. output: {
    25. file: OUTPUT_DIR + '/umd/index.js',
    26. format: 'umd' // 导出umd模块
    27. }
    28. },
    29. ]
    ```javascript // esm const b = ‘xx’; console.log(b + 1); console.log(1111); export { b };

// cjs const b = ‘xx’; console.log(b + 1); console.log(1111); exports.b = b;

// umd (兼容3种写法:cjs,amd,global(global可以初步理解为直接通过window传值)) (function (global, factory) { typeof exports === ‘object’ && typeof module !== ‘undefined’ ? factory(exports) : typeof define === ‘function’ && define.amd ? define([‘exports’], factory) : (global = typeof globalThis !== ‘undefined’ ? globalThis : global || self, factory(global.aa = {})); })(this, (function (exports) { ‘use strict’; const b = ‘xx’; console.log(b + 1); console.log(1111); exports.b = b; Object.defineProperty(exports, ‘__esModule’, { value: true }); }));

  1. webpack 导出 (区别巨大,注入代码较多,导出esm支持的不太好)
  2. - webpack需配置 (此处是 webpack 4.x <br />webpack暂时只能支持导出 cjs 更往前兼容的包(umd)<br />**不支持esm(实验性)**<br />![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cf95fb883bf6493ebce867bd78534f4f~tplv-k3u1fbpfcp-watermark.image?#crop=0&crop=0&crop=1&crop=1&id=lF4Ji&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)<br />我们此处导出 cjs的包, 和rollup对比一下
  3. ```javascript
  4. output: {
  5. ...,
  6. library: 'myLib', // 暴露出去的变量的名字
  7. libraryTarget: 'commonjs',
  8. }
  • 注入代码特别多,比较冗余 ```javascript exports[“myLib”] = /**/ (function(modules) { // webpackBootstrap /**/ // The module cache /**/ var installedModules = {}; /**/ /**/ // The require function /**/ function webpack_require(moduleId) { /**/ /**/ // Check if module is in cache /**/ if(installedModules[moduleId]) { /**/ return installedModules[moduleId].exports; /**/ } /**/ // Create a new module (and put it into the cache) /**/ var module = installedModules[moduleId] = { /**/ i: moduleId, /**/ l: false, /**/ exports: {} /**/ }; /**/ /**/ // Execute the module function /**/ modules[moduleId].call(module.exports, module, module.exports, webpack_require); /**/ /**/ // Flag the module as loaded /**/ module.l = true; /**/ /**/ // Return the exports of the module /**/ return module.exports; /**/ } /**/ /**/ /**/ // expose the modules object (webpack_modules) /**/ webpack_require.m = modules; /**/ /**/ // expose the module cache /**/ webpack_require.c = installedModules; /**/ /**/ // define getter function for harmony exports /**/ webpack_require.d = function(exports, name, getter) { /**/ if(!webpack_require.o(exports, name)) { /**/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); /**/ } /**/ }; /**/ /**/ // define esModule on exports /**/ webpackrequire.r = function(exports) { /**/ if(typeof Symbol !== ‘undefined’ && Symbol.toStringTag) { /**/ Object.defineProperty(exports, Symbol.toStringTag, { value: ‘Module’ }); /**/ } /**/ Object.defineProperty(exports, ‘esModule’, { value: true }); /**/ }; /**/ /**/ // create a fake namespace object /**/ // mode & 1: value is a module id, require it /**/ // mode & 2: merge all properties of value into the ns /**/ // mode & 4: return value when already ns object /**/ // mode & 8|1: behave like require /**/ webpack_require.t = function(value, mode) { /**/ if(mode & 1) value = webpack_require(value); /**/ if(mode & 8) return value; /**/ if((mode & 4) && typeof value === ‘object’ && value && value.esModule) return value; /**/ var ns = Object.create(null); /**/ webpackrequire.r(ns); /**/ Object.defineProperty(ns, ‘default’, { enumerable: true, value: value }); /**/ if(mode & 2 && typeof value != ‘string’) for(var key in value) webpackrequire.d(ns, key, function(key) { return value[key]; }.bind(null, key)); /**/ return ns; /**/ }; /**/ /**/ // getDefaultExport function for compatibility with non-harmony modules /**/ webpackrequire.n = function(module) { /**/ var getter = module && module.esModule ? /**/ function getDefault() { return module[‘default’]; } : /**/ function getModuleExports() { return module; }; /**/ webpack_require.d(getter, ‘a’, getter); /**/ return getter; /**/ }; /**/ /**/ // Object.prototype.hasOwnProperty.call /**/ webpack_require.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /**/ /**/ // webpack_public_path /**/ webpack_require.p = “./“; /**/ /**/ /**/ // Load entry module and return exports /**/ return webpack_require(webpack_require.s = 0); /**/ }) /**/ /**/ ([ / 0 / /*/ (function(module, __webpack_exports, __webpack_require) {

“use strict”; // ESM COMPAT FLAG webpack_require.r(webpack_exports);

// EXPORTS 这一行是处理esm的导出,因为我们用的是 export { b: xx }, 如果我们用cjs的导出 比如 module.exports = { b: xx }, 此处就会没有,会更简单,直接是 module.exports = { b: xx } webpack_require.d(webpack_exports, “b”, function() { return / reexport / b; });

// CONCATENATED MODULE: ./src/test/a.js const b = ‘xx’; const bbbbbbb = ‘xx’;

// CONCATENATED MODULE: ./src/main.js console.log(b + 1); console.log(1111);

// }) /*/ ]);

  1. 注意看 倒数第10多行,有个

// EXPORTS webpack_require.d(webpack_exports, “b”, function() { return / reexport / b; });

这一行是处理esm的导出,因为我们用的是 export { b: xx }, 如果我们用cjs的导出 比如 module.exports = { b: xx }, 此处就会没有此行,会更简单,直接是 module.exports = { b: xx } ( webpack会自己模拟实现 module.exports )

  1. <a name="fd14a4e6"></a>
  2. ## 为什么webpack需要注入这么多代码?
  3. 因为webpack比rollup早出2年,诞生在esm标准出来前,commonjs出来后
  4. - 当时的浏览器只能通过script标签加载模块
  5. - **script标签加载代码是没有作用域的,只能在代码内 用iife的方式 实现作用域效果**,
  6. - **这就是webpack打包出来的代码 大结构都是iife的原因**
  7. - 并且**每个模块都要装到function里面**,才能保证互相之间作用域不干扰。
  8. - **这就是为什么 webpack打包的代码为什么乍看会感觉乱,找不到自己写的代码的真正原因**
  9. - 关于webpack的代码注入问题,是因为**浏览器不支持cjs**,所以**webpack要去自己实现require和module.exports方法**(才有很多注入)(webpack自己实现polyfill)
  10. - 这么多年了,甚至到现在2022年,**浏览器为什么不支持cjs**?
  11. - **cjs是同步的,运行时的,node环境用cjs,node本身运行在服务器,无需等待网络握手,所以同步处理是很快的**
  12. - **浏览器是 客户端,访问的是服务端资源,中间需要等待网络握手,可能会很慢,所以不能 同步的 卡在那里等服务器返回的,体验太差**
  13. - **后续出来esm后,webpack为了兼容以前发在npm上的老包**(并且当时心还不够决绝,导致这种“丑结构的包”越来越多,以后就更不可能改这种“丑结构了”),所以保留这个iife的结构和代码注入,**导致现在看webpack打包的产物,乍看结构比较乱且有很多的代码注入,自己写的代码都找不到**
  14. rollup诞生于esm标准出来后,就是针对esm设计的,也没有历史包袱,所以可以做到真正的“打包”(精简,无额外注入)
  15. - (根据npm版本上传显示最早上传时间: **webpack是2013年左右,rollup是2015.5**)
  16. <a name="3c0fc8b2"></a>
  17. ## rollup如何打包第三方依赖 和 懒加载模块 和 公共模块?
  18. 和webpack打包一样,有两种:单chunk包 和 多chunk包
  19. 1. 单chunk包<br />无额外配置,一般会把所有js打成一个包。打包外部依赖(第三方)也是一样的。比如: <br />**此处rollup打包有个注意点**:
  20. ```javascript
  21. // 入口 main.js
  22. import Axios from 'axios'
  23. Axios.get()
  24. console.log(1111)
  25. ------ 打包后的结果 ------
  26. // 最终会把axios的源代码 和 main.js 主代码,打包到一个文件内,无额外代码注入
  27. // 以下是截取了一头一尾,中间省略
  28. import require$$1$1 from 'http';
  29. import require$$2 from 'https';
  30. import require$$0$1 from 'url';
  31. import require$$3 from 'assert';
  32. import require$$4 from 'stream';
  33. import require$$0 from 'tty';
  34. import require$$1 from 'util';
  35. import require$$7 from 'zlib';
  36. var axios$1 = {exports: {}};
  37. var bind$2 = function bind(fn, thisArg) {
  38. return function wrap() {
  39. var args = new Array(arguments.length);
  40. for (var i = 0; i < args.length; i++) {
  41. args[i] = arguments[i];
  42. }
  43. return fn.apply(thisArg, args);
  44. };
  45. };
  46. ...
  47. ...
  48. ...
  49. axios$1.exports = axios;
  50. // Allow use of default import syntax in TypeScript
  51. axios$1.exports.default = axios;
  52. var _axios_0_18_1_axios = axios$1.exports;
  53. _axios_0_18_1_axios.get();
  54. console.log(1111);
  • 很多第三方依赖很早就有了,所以用的是commonjs模块导出,rollup打包的话,需要安装插件@rollup/plugin-node-resolve。因为是cjs的包,所以也不存在tree shaking
    • 插件原理是,把cjs的包,转成esm包,在打包
  • 现在比较流行的monorepo,就是完全用esm写库+rollup打包,可以很轻易的做到tree shaking,让核心库变的更小,解析速度更快,还可以对外提供工具,扩大影响力
    1. 多个chunk包(代码分离)
  1. 配置多个入口,此法比较简单,可自行测试

    1. // rollup.config.js
    2. input: {
    3. index: 'src/main.js',
    4. other: 'src/other.js',
    5. },
  2. 代码分离 (动态import,懒加载, import(xxx).then(module => {})

  • 此处有一个官方的例子,再清楚不过了
    对于代码分割,还有一种方法可以通过 output.manualChunks 选项显式告诉 Rollup 哪些模块要分割成单独的块
    总结: ```javascript // 入口 main.js / DYNAMIC IMPORTS 动态import Rollup supports automatic chunking and lazy-loading Rollup支持自动分块和懒加载 via dynamic imports utilizing the import mechanism 通过dynamic imports动态导入 of the host system. / if (displayMath) { import(‘./maths.js’).then(function (maths) {
    1. console.log(maths.square(5));
    2. console.log(maths.cube(5));
    }); }

// ‘./maths.js’ import square from ‘./square.js’; export {default as square} from ‘./square.js’; export function cube (x ) { return square(x) * x; }

// ‘./square.js’ export default function square ( x ) { return x * x; }

———————— 打包结果 ———————— // main.js ‘use strict’; / DYNAMIC IMPORTS 动态import Rollup supports automatic chunking and lazy-loading Rollup支持自动分块和懒加载 via dynamic imports utilizing the import mechanism 通过dynamic imports动态导入 of the host system. / if (displayMath) { // 打包成cjs模块的话,import替换成 Promise + require // Promise.resolve(require(‘../chunk-0ee5c472.js’)).then(function (maths) { import(‘../chunk-c4d97f01.js’).then(function (maths) { console.log(maths.square(5)); console.log(maths.cube(5)); }); }

// ‘../chunk-0ee5c472.js’ ‘use strict’; function square ( x ) { return x x; } function cube (x ) { return square(x) x; } exports.cube = cube; exports.square = square;

  1. - **动态importrollup对比webpack 打包后的模块格式的支持度**
  2. | 打包后的模块格式: | esm | cjs | amd | umd |
  3. | --- | --- | --- | --- | --- |
  4. | webpack | 不支持,实验中 | 支持 | 支持 | 支持 |
  5. | rollup | 支持 | 支持 | 支持 | 不支持 |
  6. - 实现原理,对比webpack
  7. - webpack是**自己实现的“动态import“**(借助promise + script标签 + window对象 + 模拟import方法)
  8. - rollup (打包成esm模块)利用浏览器(chorme63 以上)天然支持[动态import](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/import#%E5%8A%A8%E6%80%81import)
  9. - (打包成cjs模块)promise + cjsrequire
  10. - **此处有个很重要细节点**
  11. - rollup打的包,如果要用 动态import(现在vuereact的单页项目 特别流行用动态import加载路由,算硬需求了),**注意 如果要在浏览器上跑,首先要是esm的包(浏览器不支持cjs),然后浏览器版本要注意(chorme63 以上)**
  12. - 因为rollup不做额外代码注入,完全利用高版本浏览器原生支持import(所以代码特别干净,webpack会做大量的兼容 自己实现requireimport)<br />![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f0b0bf8de76c41dc9f96ad8c4ad7c313~tplv-k3u1fbpfcp-watermark.image?#crop=0&crop=0&crop=1&crop=1&id=PYtWo&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
  13. 3. rollup如何处理公共模块?(比如, abc 3个模块 同时依赖 d)<br />**有2种情况:**
  14. 1. 源代码内 **不存在 动态import**,那么会打成**一个chunk**(abcd 4个模块都在一包内,d只正常有一份)
  15. 1. 源代码内 **存在 懒加载模块,并且懒加载的模块也访问了公共依赖**,比如 <br />总结:**对于公共依赖,rollup不会出现重复打包的情况!并且完全无注入代码!无需额外配置。** 对比webpack的话,webpack需要配置 optimization.splitChunks webpack4.x 以上)
  16. ```javascript
  17. // 入口 main.js
  18. import {deepCopy} from '@xxx/methods/deepCopy.js' // 这是放在公司的npm域内的一个包,可以理解为export一个简单的deepCopy函数
  19. console.log(deepCopy(a))
  20. import('./test/a').then(e => {
  21. console.log(e)
  22. })
  23. // './test/a' 懒加载模块 也依赖 同一公共模块
  24. import {deepCopy} from '@xxx/methods/deepCopy.js'
  25. const a = {a: 1}
  26. export const b = deepCopy(a)
  27. ---------- 是否会把 公共依赖 打包2份呢? --------------
  28. 答案是no,rollup还是牛p,公共依赖只会出来一份,然后对外 export (此处举例是导出esm格式, 亲测导出cjs格式一样的可以,此处就不赘述,有兴趣可以自己test一下)
  29. 生成的目录结构,有3个文件
  30. a-19173be8.js
  31. main.js
  32. main-219c2eaf.js
  33. // main.js
  34. import './main-219c2eaf.js';
  35. // main-219c2eaf.js
  36. const deepCopy = function (obj) {
  37. // do ..
  38. };
  39. console.log(deepCopy(a));
  40. import('./a-19173be8.js').then(e => {
  41. console.log(e);
  42. });
  43. // a-19173be8.js
  44. import { d as deepCopy } from './main-219c2eaf.js';
  45. const a = {a: 1};
  46. const b = deepCopy(a);
  47. export { b };

总结 rollup vs webpack

rollup 诞生在esm标准出来后

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

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打包的产物,乍看结构比较乱且有很多的代码注入,自己写的代码都找不到

最终使用推荐

  1. 打包开源库不用思考,rollup会是你更好的选择
    • rollup本身也支持很多插件,生态也成熟,各种场景几乎都能照顾到
  2. 打包应用程序:个人推荐,看您的 应用程序 需不需要兼容老浏览器
    兼容表如下(其实就是 动态import的兼容表 ) 以chorme为例,需要chorme63以上
    rollup打包产物解析及原理(对比webpack) - 图1
    如果不考虑兼容老浏览器,建议用 vite 开发应用程序vite官网(react/vue/ts都支持)
    • dev开发方面:vite提供dev服务器,以及比webpack快的多的热更新,dev开发的体验更好了
    • prd生产方面:vite 打生产包,实际上用的就是rollup,笔者用vite已经上过真实项目,开发体验很棒,打的生产包比用webpack小了很多,有不错的性能提升
    • 理论上 chorme63以上 可以开箱即用,chorme63以下也不是完全不能用,需要自己加polyfill或vite插件(vite推荐的兼容做法

篇幅有点长,最好先对webpack有充分的了解,在看此篇,会更好理解 和 全面对比。了解webpack可以先看笔者的上篇 webpack打包产物解析及原理(含cjs/esm/代码分离/懒加载)

笔者建议,最好自己上手打包 调试,得到的打包产物 并仔细分析。一时看不懂的话,也可以收藏本文,过段时间在看,先了解前置知识

最后,感谢爱学习的你,谢谢点赞!