webpack 知识点梳理
webpack 知识点梳理
基本配置
拆分配置和 merge
拆分配置为 webpack.common.js、webpack.dev.js、webpack.prod.js
webpack.dev.js、webpack.prod.js 通过 webpack-merge 的 smart 继承 webpack.common.js
1 | import { smart } from 'webpack-merge' |
在 webpack5 中,更改为
1 | import merge from 'webpack-merge' |
dev、prod 区别
mode、plugin: webpack.DefinePlugin、devServer
启动本地服务
安装 webpack-dev-server,通过 –config 指定 config 文件
proxy 端口代理
处理 es6
对 js 文件处理,使用 babel-loader
使用 babelrc 文件进行配置 preset & plugin
处理样式
css:
Loader: [‘style-loader’, ‘css-loader’, ‘postcss-loader’]
postcss-loader: 配置文件 postcss.config.js 其内容 plugins: [require(‘autoprefix’)]
less:
Loader: [‘style-loader’, ‘css-loader’, ‘less-loader’]
loader 执行顺序从后往前
处理图片
url-loader: 小于指定 limit 大小的图片使用 base64,可以通过指定 output 把文件统一放在某个目录下
url-loader file-loader 区别
它们都可以用于载入静态资源文件,url-loader 比 file-loader 的优势在于可以将文件转为 base64
they are both deprecated in webpack v5,使用 asset module 代替
url-loader file-loader 原理:
loader 本质上是一个函数,它们遇到需要解析的文件,会使用 webpack loader 的 api emitFile 将文件移动到指定的路径,并将文件改写为以下内容
1 | export default '/absolute/path' |
or
1 | export default 'data:imgae/jpeg:base64....' |
这样的内容。
output
1 | output: { |
使用 currentHash 的好处:当内容不变时,hash 值不变,浏览器可以使用缓存。
模块化
TODO: 为什么可以使用 import output 的语法?
高级配置
多入口
需要配置多个 entry
1
2
3
4
5// webpack.common.js
entry: {
index: path.join(srcPath, 'index.js'),
other: path.join(srcPath, 'other.js'),
}对应的 output 需要需改
1
2
3
4
5// webpack.prod.js
output: {
filename: 'bundle.[currentHash:8].js',
path: distPath, // 输出文件路径
}使用 currentHash 的好处:当内容不变时,hash 值不变,浏览器可以使用缓存。
HtmlWebpackPlugin 需要多个
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// webpack.common.js
// 配置多入口
plugins: [
new HtmlWebpackPlugin({
template: path.join(srcPath, 'index.html'),
filename: 'index.html',
// 如果不指定 chunks,会引入所有 chunks
chunks: ['index']
}),
new HtmlWebpackPlugin({
template: path.join(srcPath, 'other.html'),
filename: 'other.html',
chunks: ['other']
})
]其他
CleanWebpackPlugin: 会清空 output.path 指定的路径
抽离压缩 css 文件
使用 MiniCssExtractPlugin
1 | rules: [ |
抽离公共代码
公用代码和第三方代码不会经常变,如果不抽离,每次都需要被重新打包,因为每次的hash值都发生变化,所以不能使用缓存,浏览量每次都会加载很慢。
1 | plugins: [ |
怎么懒加载
使用方式:
1 | setTimeout(() => { |
处理 jsx 和 vue
react 使用 @babel/preset-react
1
2
3
4
5// .babelrc
{
"presets": ["@babel/preset-react"],
"plugins": []
}vue 使用 vue-loader
1
2
3
4
5
6
7
8
9module: {
rules: [
{
test: '/\.vue$/',
loader: ['vue-loader'],
include: srcPath
}
]
}
性能优化 - 优化打包效率
1. 优化 babel-loader
1 | { |
- 开启缓存
- 限定范围
2. IgnorePlugin
避免引用无用模块
例子:
1 | // webpack.prod.js |
使用的地方
1 | import moment from 'moment' // 235.4K (gzipped: 66.3K) |
3. noParse
1 | module: { |
- IgnorePlugin 直接不引于,代码中没有(同时优化产出代码)
- noParse 引入,但不打包
4. HappyPack 多进程打包
JS 是单线程的,要开启多进程打包
提高构建速度,特别是对于多核 CPU
1 | // webpack.prod.js |
1 | // webpack.prod.js |
5. ParallelUglifyPlugin 多进程压缩代码
使用多进程压缩代码
1 | // webpack.prod.js |
1 | // webpack.prod.js |
PS:
- 项目较大,打包较慢,开启多进程能提高速度
- 项目较小,打包很开,开启多进程返回会降低速度(进程开销)
- 按需使用
6. webpack 配置自动刷新和热更新
自动刷新 vs. 热更新
- 自动刷新: 整个网页全部刷新,速度慢,状态丢失
- 热更新: 新代码生效,页面不刷新,速度快,状态不丢失
wepack 有自带的配置字段 watch 配置自动刷新
1 | module.exports = { |
使用的地方
1 | import { sum } from './math' |
7. 使用 DllPlugin
动态链接库插件
- 前端框架如 vue、react 体积大、构建慢
- 比较稳定,不常升级版本
- 同一个版本只构建一次即可,不用每次都重新构建
webpack 已经内置 DllPlugin 支持
步骤:
通过 DllPlugin 打包出 dll 文件
通过 DllReferencePlugin 引用 dll 文件
性能优化 - 优化产出代码
好处:
- 体积更小
- 合理分包,不重复加载
- 速度更快、内存使用更少
1. 小图片 base64 编码
使用 url-loader 配置 limit
2. bundle 使用 hash
可以使用缓存
3. 懒加载
1 | import('./dynamic-data.js').then() |
4. 提取公共代码
optimization.splitChunks
5. IgnorePlugin
IgnorePlugin 直接不引于,代码中没有无用的代码
6. 使用 cdn 加速
1 | output: { |
- output 中配置 publicPath,以及静态资源配置 publicPath
- 上传静态文件
7. 使用 mode production(包括 Tree-Shaking)
- 自动开启代码压缩
- Vue React 等会自动删掉调试代码(如开发环境的 warning)
- 启用 Tree-Shaking,只有 ES6 Module 才能让 tree-shaking 生效
Tree-Shaking 举例
1 | // math.js |
在实际使用的地方只有使用到 add,那么 multi 就不会被打包
ES6 Module vs. Commonjs
- ES6 Module 静态引入,编译时引入
- commonjs 动态引入,执行时引入
- 只有 ES6 Module 才能静态分析,实现 Tree-shaking
8. Scope Hoisting
打包出来的代码中,每个文件一个函数会合并成一个函数
好处:
- 代码体积更小
- 创建函数作用域更少
- 代码可读性好
1 | const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin') |
babel
babel 只对语法进行转换,对于新的 api 不进行转换,例如 Promise 或者 [].includes
新的语法需要使用 babel-polyfill 进行转换
babel 也不管模块化,所以需要配合 webpack 使用
环境搭建 & 基本配置
.babelrc
1 | { |
使用 babel 转换 js 文件
1 | npx babel index.js |
babel-polyfill
polyfill 即布丁,babel-polyfill = core-js + regenerator,在 babel 7.4 之后弃用,推荐直接使用 core-js & regenerator
core-js & regenerator
core-js 是一系列 polyfill 的合集
regenerator 对 generator 语法进行补充支持
配置按需引入
.babelrc
1 | { |
babel-runtime
babel-polyfill 会污染全局环境,
使用 babel-runtime 不会去污染全局环境
如果开发第三方库,必须使用 babel-runtime
构建流程概述
webpack 5
主要是内部效率优化
面试真题
1. 前端为何要进行打包和构建
- 代码层面
- 体积更小(tree-shaking、压缩、合并),加载更快
- 编译高级语言和语法(TS、ES6、模块化、scss)
- 兼容性和错误检查(polyfill、postcss、eslint)
- 研发流程方面
- 统一高效的开发环境
- 统一的构建流程和产出标准
- 集成公司的构建规范(提测、上线等)
2. module chunk bundle 的区别
- module 各个源码文件,webpack 中一切皆模块
- chunk 多模块合并成的,来源:entry、import()、splitChunk
- bundle 最终的输入文件,一般一个 chunk 对应一个 bundle
chunk 是偏抽象的一个概念,bundle 是 chunk 的输出实体
3. loader vs. plugin
loader: 模块转换器 如:less => css
plugin: 扩展插件,如 HtmlWebpackPlugin
4. 常见的 loader 和 plugin 有哪些
略
5. babel vs. webpack
- babel 只关心语法,不关心 api 不关心模块
- webpack 打包构建工具,是多个 loader plugin 的集合,通常会结合 babel 使用
6. 如何产出一个 lib
配置 output.library
7. babel-polyfill vs. babel-runtime
略
8. webpack 如何实现懒加载 & vue react 异步加载路由
import()
9. 为啥 proxy 不能被 polyfill
如 class 可以用 function 模拟
如 Promise 可以用 callback 模拟
但是 proxy 的功能无法用 Object.defineProperty 模拟
10. webpack 优化构建速度
略