status
type
tags
category
slug
summary
date
finished_date
icon
password
前言
历史上,浏览器需要打包 JavaScript 脚本,主要原因有:
- Node.js 的模块机制与浏览器不兼容,必须通过打包工具进行兼容处理。
- 浏览器加载一个大脚本,要比加载多个小脚本,性能更好。
- 早期的浏览器不支持模块,大型网页项目只能先合并成单一脚本再执行。
但多年来,浏览器已经发生了很大变化。浏览器本身支持 ES 模块,绝大多数开发者也使用 ES 模块语法。为什么仍然需要打包工具呢?
- 不幸的是,许多第三方库虽然是用 ESM 编写的,但发布到 npm 时是以 CJS 模块的形式发布的,所以我们仍然需要将它们合并。
bundle 即指将多个源文件(例如 JS、CSS、图像等)合并为单个文件的过程。
打包工具
Webpack
webpack 是最古老的捆绑器之一,当时开发人员希望在浏览器中重用 Node.js 模块。
例子
loaders: 作为 webpack 的 “翻译官”, 对模块的源代码进行转换。因为 webpack 本身只能理解 JavaScript 和 JSON 文件。
- loader 从下到上执行。在以上示例中,先执行
ts-loader
,然后执行babel-loader
。
- 它本质是一个函数(链),通过回调函数,接收输入(源文件内容),然后返回输出(转换后的内容)。
plugins: webpack 的支柱功能。插件目的在于解决 loader 无法实现的其他事情,比如代码压缩和分割。
- 插件系统基于
Tapable
的库。Tapable 提供了多种 hooks,这些钩子可以让插件在构建的不同阶段插入自己的逻辑。 - 即系统使用了发布-订阅模式,也称为事件驱动架构。在这种模式中,webpack 在构建过程中会触发各种事件,这些事件相当于 “发布者”,而插件则相当于 “订阅者”。当某个事件被触发时,所有订阅了这个事件的插件都会被调用,从而执行相应的任务。
- 每个插件需要定义一个
apply
方法,并传递一个compilation
对象,插件可以通过这个对象来访问 webpack 的内部资源。
webpack 使用 CommonJS 作为其主要模块系统,并将所有内容转换为该系统。但是因为浏览器不支持 CJS,所以 webpack 自己做了 polyfill,实现了
require
和 module.exports
方法,生成了很多额外注入。导致 webpack 打包出来的代码看起很 “丑”。<script>
标签加载代码是没有作用域的,这就是 webpack 打包出来的代码结构都是 IIFE 的原因,并且每个模块都要装到 function 里面,才能保证作用域之间互不干扰。
- 打包后的代码中,经常出现类似注释的代码:
/***/
后面跟随着一些数字。这些数字通常是指代模块的 ID 在生成的 bundle 中被用来标识不同的模块,为了帮助调试和定位问题时能够追踪到源码中具体的位置。
总结
Webpack for apps, Rollup for libraries.
这并不是一条硬性规定。许多网站和应用程序使用 Rollup 构建,也有许多库使用 webpack 构建的,但这是一个很好的经验法则。
- Webpack 能更好处理热模块替换(HMR),方便在本地进行开发
- Rollup 的 Tree Shaking 功能非常强大,能够有效地移除未使用的代码