构建工具的作用
- 转换 ES6 语法
- 转换 JSX
- CSS 预处理器
- 压缩混淆
- 图片压缩
plugins
- CommonsChunkPlugin:将 chunks 相同的模块代码提取成公共 js
- CleanWebpackPlugin:清理构建目录
- ExtractTextWebpackPlugin:将 CSS 从 bundle 文件里提取成一个独立的 CSS 文件
- HtmlWebpackPlugin:创建 html 文件去承载输出的 bundle
- UglifyjsWebpackPlugin:压缩 JS
- ZipWebpackPlugin:将打包出的资源生成一个 zip 包
presets
- 解析 ES6 (babel)
loader
- use 时是链式调用的,从右往左执行
Webpack watch 的原理(需要手动刷新页面)
轮询监听文件的更新时间有没有变化,若有变化,把文件的变化缓存起来,等待 aggregateTimeout 毫秒后将所有变化的文件列表一起打包更新,每秒轮询 poll 次
Webpack dev server(热更新)
- 不刷新浏览器
- 不输出文件,而是放在内存中(更新速度更快)
- 使用 HotModuleReplacementPlugin 插件
- 原理:
- Webpack Compile:将 JS 编译成 Bundle
- HMR Server:将热更新的文件输出给 HMR Runtime
- Bundle server:提供文件在浏览器的访问
- HMR runtime:会被注入到浏览器,更新文件的变化
- bundle.js:构建输出的文件
- 过程:
- 文件编译,初始代码经过 Webpack Compile 打包成 bundle.js 文件
- 将 bundle.js 文件传输给 Bundle server,以 server 的方式让浏览器访问这个文件
- 文件发生变化,代码经过 Webpack Compile 编译,发送给 HMR Server
- HMR Server 通知 HMR runtime 哪些文件发生了变化,通常是用 websocket 通过 JSON 形式传输
- HMR runtime 更新代码
文件指纹
- 含义:打包后输出的文件名的后缀
- 作用:做版本管理
- 种类
- hash:和整个项目的构建相关,只有项目文件有修改,整个项目构建的 hash 值就会改变(如果 A 页面变化 B 页面的 hash 也会改变,其实没有必要)
- chunkHash:和 Webpack 打包的 chunk 有关,不同的 entry 会生成不同的 chunkhash 值(如果修改了 js 资源但 css 资源并没有改变,css 的 chunkhash 值也会改变)
- contenthash:根据文件内容来定义 hash,文件内容不变,则 contenthash 不变
- css 文件指纹设置:设置 MiniCSSExtractPlugin 的 filename,使用 [contenthash]
- 图片文件指纹设置:设置 file-loader 的 name,使用 [hash],这里的 hash 其实代表的是 contenthash
占位符
- ext:资源后缀名
- name:文件名称
- path:文件的相对路径
- folder:文件所在的文件夹
- contenthash:文件的内容hash,默认 md5 生成
- hash:文件内容的 hash,默认是 md5 生成
- emoji:一个随机的指代文件内容的 emoji
增强功能
- 自动清除构建目录:clean-webpack-plugin,删除 output.path 的目录
- 自动补全浏览器支持的前缀:PostCSS、autoprefixer,增加 o-、moz- 等前缀
- 适配移动端不同的分辨率:px2rem-loader,编写样式时使用 px 单位,根据自动转换成 rem
资源内联
- 好处
- 页面框架的初始化脚本
- 上报相关打点(性能监控)
- css 内联避免页面闪动
- 减小 HTTP 网络请求数量
- 使用
- html 内联:raw-loader
<script>${require('raw-loader!babel-loader!./meta.html')}</script>
- js 内联:raw-loader
<script>${require('raw-loader!babel-loader!../node_modules/lib-flexible')}</script>
- css 内联
- style-loader
- html-inline-css-webpack-plugin:针对打包好的 chunk 内联
- html 内联:raw-loader
多页面打包
- glob 库,匹配目录下符合条件的文件
glob = require('glob');
path = require('path');
const setMPA = () => {
const entry = {};
const HtmlWebpackPlugins = [];
let entryFiles = glob.sync(path.join(__dirname, './src/**/index.js'));
Object.keys(entryFiles).map((file) => {
let match = file.match(/src\(.*)\/index.js/);
let name = match && match[1];
entry[name] = file;
HtmlWebpackPlugins.push({
new HtmlWebpackPlugin({
template:path.join(__dirname, `src/${name}/index.html`),
filename: `${name}.html`
chunks: [name],
})
})
})
return {
entry,
HtmlWebpackPlugins
};
}
const { entry, HtmlWebpackPlugins } = setMPA();
source map
- 开发环境开启,线上环境关闭(避免暴露源码逻辑)
- eval:使用 eval 包裹模块代码(没有 .map 文件,使用 eval() 方法)
- source map:产生 .map 文件
- cheap:不包含列信息
- inline:将 .map 作为 DataURI 嵌入,不单独生成 .map 文件,.js 文件会变大
- module:包含 loader 的 source map
提取公共资源
- externals
- 将基础包(react、vue)通过 cdn 引入,不打入 bundle 中
- 使用 html-webpack-externals-plugin
- SplitChunksPlugin 进行公共脚本分离
- Webpack4 内置,代替 CommonChunkPlugin
- 使用 DLL plugin 分包,DllReferencePlugin 对 manifest.json 引用
tree shaking(摇树优化)
- 概念:一个模块可能有多个方法,只要其中的某个方法使用到了,则整个文件都会被打包到 bundle 中,tree shaking 就是只把用到的方法打入 bundle,没用的方法在 uglify 阶段擦除
- webpack2 开始支持,在 webpack4 中 production 下默认是开启的,需要在 .babelrc 里设置 modules: false
- 要求 ES6 语法,不支持 require
- 优化:打包时擦除没有使用到代码
- 代码不会被执行,不可到达
- 代码执行的结构不会被使用
- 代码只会影响死变量(只写不读)
- 原理
- 利用 ES6 模块的特点
- import 只能作为模块顶层的语句出现
- import 的模块名只能是字符串常量
- import binding 是 immutable 的
- 代码擦除:uglify 阶段删除无用代码
- 利用 ES6 模块的特点
ScopeHoisting(作用域提升)
- 模块转换分析:模块转换成模块初始化函数
- 被 webpack 转换后的模块会带上一层包裹
function(module, __webpack_exports, __webpack_require)
- import 会被转换成
__webpack_require
- 被 webpack 转换后的模块会带上一层包裹
- webapck 的模块机制
- 打包出来的是一个 IIFE(匿名闭包)
- modules 是一个数组,每一项是一个模块初始化函数
__webpack_require
用来加载模块,返回 module.exports- 通过 WEBPACK_REQUIRE_METHOD(0) 启动程序
- scope hoisting 的原理:
- 将所有模块的代码按照引用顺序放在一个函数作用域里,然后适当的重命名一些变量以防止变量名冲突。
- 分析出模块之间的依赖关系,尽可能的把打散的模块合并到一个函数中去,但前提是不能造成代码冗余。
- 因此只有那些被引用了一次的模块才能被合并。
- 在 webpack4 中 production 环境下默认开启
动态 import
- @babel/plugin-syntax-dynamic-import
- import(’…’).then() // 返回的是一个 Promise
- 打包后通过 JSONP 加载
Webpack 打包库和组件
- 需要打包压缩版和非压缩版
- 支持 AMD、CJS、ESM 模块引入
- 设置 output 参数
- library([name].js) // 最终暴露的库的名字
- libraryExport(umd) // 支持各种模块引入方式
- libraryTarget(default)
- terser-webpack-plugin 压缩
- 设置入口文件:package.json 的 main 字段
- npm publish(需要注册登录 npm 账号,npm login)
优化命令行日志
- 统计信息 stats
- error-only // 只在错误时输出
- minimal // 只在发生错误或有新的编译时输出
- friendly-errors-webpack-plugin 插件
- 展示更详细的信息
- 不同颜色块标识日志类型(error、warning、success)
分析性能
- 查看 stats 统计信息
webpack --env production --json > stats.json
- 颗粒较粗,不能具体分析每个文件的性能
- 速度分析:speed-measure-webpack-plugins
- 分析整个打包的耗时
- 查看每个 loader 和 plugin 的耗时
- 体积分析:webpack-bundle-analyzer
- 访问 localhost:8888 查看分析效果(支持筛选)
webpack4 的优化原因
- V8 带来的优化(for of 代替 forEach、Map 和 Set 代替 Object、includes 代替 indexOf)
- 默认使用更好的 md4 hash 算法
- webpacks AST 可以直接从 loader 传递给 AST,减少解析时间
- 使用字符串方法替代正则表达式
多进程打包
- 并行构建
- HappyPack
- 原理:创建一个线程池,将一个模块及它的依赖分配给 worker 线程,解析完传输给主进程
- 目前不怎么维护了,在 webpack3 用的比较多
- thread-loader,webpack4 内置
- 原理:与 HappyPack 相同
- HappyPack
- 并行压缩
- parallel-uglify-plugin
- uglify-webpack-plugin 开启 parallel 参数(不支持压缩 ES6 语法)
- terser-webpack-plugin 开启 parallel 参数(webpack4 内置,支持压缩 ES6 语法)
缓存
- babel-loader 开启缓存
- terser-webpack-plugin 开启缓存
- cache-loader 或者 hard-source-webpack-plugin 提升模块转换阶段的缓存