Web bundler是前端避不开的基础构建,webpack,rollup,vite。以及现在的rolldown和rspack。
Bundlerd 的核心作用有:
解决依赖关系:bundler(如Webpack、Rollup)会分析入口文件,递归构建依赖图,将分散的模块合并为少数几个文件。
代码优化与压缩
- 减少体积:通过压缩(Terser)、Tree Shaking(删除未使用代码)、作用域提升(Scope Hoisting)等方式减小文件体积。
- 性能优化:代码分割(Code Splitting)、懒加载(Lazy Loading)按需加载资源,提升页面加载速度。
关于bundler的研究我主要参考rollup的。这篇大致讲解bundler几个阶段。
启动
bundler要解析代码的话,必须在配置文件指定一个入口文件(entry)。rollup的配置如下:
export default{
input:'src/main.js',
output:{
file:'bundle.js',
format:'cjs'
}
}
input属性告诉rollup的入口文件,output属性则是规定生成后的代码文件名和生成后的代码格式。从src/main.js 为起点,rollup会生成一个绝对路径并判断该路径是否存在。存在的话根据绝对路径读取文件内容一般js文件是以下这种格式。
import specifier from 'relativePath'
content
export语句
分析import语句可以根据relativePath得到依赖文件的绝对路径,然后依次进行这样分析import语句得到绝对路径的操作。将所有文件串联起来。
解析
在开始阶段生成文件的绝对路径后,bundler就会对文件加载读取然后进行解析。bundler会将文件解析成一个Module类型Module是rollup内部定义的一个类型。
class Module {
...
id:string;
//源代码
source: string;
//AST
ast:Program,
//依赖
dependencies:string[]= []
//imports
imports:Record<string, any> ={}
//exports
exports:Record<string,any> = {}
//是否是入口文件
isEntry: boolean;
//文件中作用域
scope:ModuleScope;
astContext:ASTContext;
...
}
rollup将文件解析为Module过程,要做的事很多。首先将代码也就是source解析为AST,迭代解析后的AST
把import和export语句转入Module的imports和exports,解析文件的作用域以及需要context。
当把所有文件全部解析为Module的时候就进行下一步链接。
链接
链接这个逻辑很简单就是根据上一步得到的Modules根据执行顺序将Modules排序。请看示例1代码:
示例1
//main.js
import { foo } from './foo.js'
console.log(foo)
//foo.js
export const foo = 42
main.js 依赖foo.js 但实际运行逻辑是foo.js ,main.js 。因此链接这个阶段可以根据import来确定实际的执行顺序,对Modules进行排序得到sorted Modules。然后进入第三阶段。
输出
这个阶段我们已经得到了sorted Modules,现在可以对它进行代码压缩了。示例1中有两个文件,我们可以将两个文件压缩合并为1个文件,这样浏览器就只需加载一个文件提高了渲染速度。那要合并不是只是简单的代码合并,首先要将import和export语句或者export关键词删除,在rollup中有时候还会将这些语句进行转换。当把不需要的语句全部删除之后,就得到最终的代码也就是输出阶段生成的chunk。
const foo = 42
console.log(foo)
这也是浏览器最终载入的文件。