Appearance
webpack
Webpack 是一个 JavaScript 打包和构建工具,可帮助你编译、打包和优化代码,使其可供生产环境使用。Webpack 本身只能处理 JavaScript 文件。但是通过使用加载程序(loader)和插件(plugins),我们可以将其扩展以处理另外的文件也,包括 CSS、HTML 或图片文件。
提示
无论是 rollup.js 还是 webpack,在寻找资源时,如果 package.json 中存在 module 字段,那么会优先使用 module 字段指向的资源来代替 main 字段指向的资源。
基本概念
- Chunk 是 webpack 生成的文件块,用来拆分代码,它可以被多个 bundle 引用;
- Bundle 是 webpack 生成的最终需要被加载的文件,它通过复用多个 chunk 实现代码复用;
- Module 是一个 JavaScript 对象,它包含了整个应用程序所需的资源,例如图像、CSS、JavaScript 行为等;
构建流程
Webpack 从启动到结束会依次执⾏以下流程:
- 初始化参数:从配置⽂件和 Shell 语句中读取与合并参数,得出最终的参数;
- 开始编译:⽤上⼀步得到的参数初始化 Compiler 对象,加载所有配置的插件,执⾏对象的 run ⽅法开始执⾏编译;
- 确定⼊⼝:根据配置中的 entry 找出所有的⼊⼝⽂件;
- 编译模块:从⼊⼝⽂件出发,调⽤所有配置的 Loader 对模块进⾏翻译,找出该模块依赖的模块,再递归本步骤直到所有⼊⼝依赖的⽂件都经过了本步骤的处理;
- 输出资源:根据⼊⼝和模块之间的依赖关系,组装成⼀个个包含多个模块的 Chunk,加⼊到输出列表,这步是可以修改输出内容的最后机会;
- 输出完成:在确定好输出内容后,根据配置确定输出的路径和⽂件名,把⽂件内容写⼊到⽂件系统。
在以上过程中,Webpack 会在特定的时间点⼴播出特定的事件,插件在监听到感兴趣的事件后会执⾏特定的逻辑,并且插件可以调⽤ Webpack 提供的 API 改变 Webpack 的运⾏结果。
loader 与 plugin 区别
- loader 处理指定类型的文件;
- plugin 是拓展功能;
编写一个 loader
一个 source 为参数的方法,返回处理完成后的 source。
js
module.exports = function (source) {
// todo
return source
}
编写一个 plugin
Tapable 是 Webpack 实现所有生命周期回调和插件方式的核心。它提供了 apply 方法可以注册某个函数到某个事件,让这个函数可以在某个时间节点执行。
常用生命周期
- entryOption 生命周期发生在 Webpack 配置文件 entry 属性配置之前。如果当前项目是多页应用,会使用 entryOption 生命周期来通过 Node 动态添加多个 entry,用来设置多个 html-webpack-plugin 进行多页面处理。
- afterPlugins 生命周期发生在 Plugins 加载完毕以后,可以通过此生命周期修改 plugins 内部嵌套的 options 进行优化或增加代码。
- beforeRun 生命周期发生在 compiler.run 之前,在此阶段可以修改 options,添加 loaders 或 plugins。
- run 生命周期在 compiler.run 执行时触发,可以修改 beforeRun 生命周期加载的 Loaders 和 Plugins 。
- emit 生命周期发生在编译完成,打包可执行文件输出前,可以获得 complier 和 compilation 的编译实例,修改打包输出文件。
- done 生命周期是编译完成后,可以修改打包文件后最后一步,可以做一些输出文件的处理。
以下是 emit 生命周期列子:
- 一个 JavaScript 类函数。
- 在函数原型 (prototype)中定义一个注入 compiler 对象的 apply 方法。
- apply 函数中通过 compiler 插入指定的事件钩子,在钩子回调中拿到 compilation 对象。
- 使用 compilation 操纵修改 webapack 内部实例数据。
js
module.exports = class RemoveCommentPlugin {
constructor(opts) {}
apply(compiler) {
compiler.hooks.emit.tap('RemoveComment', compilation => {
let content = compilation.assets[item].source()
Object.keys(compilation.assets).forEach(item => {
compilation.assets[item] = {
source: () => content,
size: () => content.length
}
})
})
}
}
优化前端性能
- 压缩代码:删除多余的代码、注释、简化代码的写法等等⽅式。可以利⽤ webpack 的 UglifyJsPlugin 和 ParallelUglifyPlugin 来压缩 JS ⽂件, 利⽤ cssnano (css-loader?minimize)来压缩 css
- 利⽤ CDN 加速: 在构建过程中,将引⽤的静态资源路径修改为 CDN 上对应的路径。可以利⽤ webpack 对于 output 参数和各 loader 的 publicPath 参数来修改资源路径
- Tree Shaking: 将代码中永远不会⾛到的⽚段删除掉。可以通过在启动 webpack 时追加参数 --optimize-minimize 来实现
- Code Splitting: 将代码按路由维度或者组件分块(chunk),这样做到按需加载,同时可以充分利⽤浏览器缓存
- 提取公共第三⽅库: SplitChunksPlugin 插件来进⾏公共模块抽取,利⽤浏览器缓存可以⻓期缓存这些⽆需频繁变动的公共代码
优化打包速度
- 外部扩展(externals): 将不怎么需要更新的第三⽅库脱离 webpack 打包,不被打⼊ bundle 中,从⽽减少打包时间,⽐如 jQuery ⽤ script 标签引⼊;
- 动态库(dll): 采⽤ webpack 的 DllPlugin 和 DllReferencePlugin 引⼊ dll,让⼀些基本不会改动的代码先打包成静态资源,避免反复编译浪费时间
- 利⽤缓存: webpack.cache 、babel-loader.cacheDirectory、 HappyPack.cache 都可以利⽤缓存提⾼ rebuild
- 限制 loader 文件范围:include;
- 多线程打包:thread-loader;
优化打包体积
文件数量尽可能少、文件大小尽可能小。
- 模块懒加载:一般都是返回一个 promise;
- Tree Shaking;
- 代码压缩;