webpack
构建发展史
grunt => gulp => rollup webpack parcel => vite
webpack打包构建流程
为什么需要打包?
- 前端开发有很多资源,img、font、css、js、ts、vue,输出产物
- 有些资源需要加工
- ts ts-loader
- js babel-loader
- css css-loader + (mini/style-loader)
- html html-webpack-plugin
- 对产物进行优化,不可能所有资源都打包到一个文件,提出了分 chunk css 提取
- optimization
- splitChunkPlugin
- 兼容性问题,很多浏览器不支持ES6
核心模块
webpack-cli、webpack、webpack-devServer
- 脚手架只是一个柯里化的调用webpack方法
js
// webpack.config.js
webpack({
...config
})
初始化操作
- 读取配置(包括配置文件和shell语句,合并以后得到最终的参数)
- entry 入口
- output 出口
- module
- loader
- plugins
- 用上一步的参数初始化 compiler 对象
- compiler
- compilation
- 所有的插件挂载,执行对象的run方法开始编译
- webpack最核心的内容,tapable 实现,插件编写 compliler.run.top
入口文件解析工作
- 根据配置中的entry找到所有的入口文件
- 单入口 - 字符串,""
- 多入口 - 数组,[]
- 指定多入口 - 对象,{}
- 解析依赖,生成依赖图,depsGraph
模块处理(module)
- 识别对应目标文件,然后匹配对应 loader 来进行解析处理,递归本步骤直到所有的文件都处理完成,看下面的正则匹配:
- js babel-loader (swc、esbilud、rsbuild)
- ts ts-loader
- css css-loader
- chunk
js
module: {
rules: [
{
test: /ts/
}
]
}
优化
- 模块去重合并
- 代码压缩,esbuild terser
- tree shaking
资源输出
- 根据配置给到 output,输出内容
- 根据入口和模块的依赖关系,组装为一个个包含多个模块的chunl,每个chunk转化为一个单独的文件加入输出列表,这是最后可以修改输出内容的机会
- 根据配置确定输出的路径和文件名,写入文件系统,fs操作
结束构建
- webpack钩子触发
热更新(HRM)
- 文件变化监听
- 局部模块更新
- 开一个ws 服务(
webpack-dev-server
和浏览器
) - 更新的内容抽象为json,将这个json(文件列表和hash)推送给客户端作对比
- 对比以后会请求ajax更新更改内容(文件列表和hash)
- 然后客户端在发送jsonp请求获取chunk的增量更新
- 后续利用
HotModulePlugin
完成处理
- 开一个ws 服务(
面试话术
- 先说一下webpack5背景,为什么会有他,解决什么问题
- 大致思路,整体流程,就以四级标题为模板
- 我之前某个场景,自定义了loader、plugin解决了什么问题
- webpack中有很多很好的思想
- 基于 tabpable 的钩子机制
- 面向切面的编程思想AOP(在不修改源代码的情况下给程序动态统一添加某种特定功能的一种技术)
简单介绍一下工作中常用的loader和plugin,又实现过自定义loader、plugin么
常用loader
- babel-loader
- css-loader
- style-loader
- url-loader
- sass-loader
常用plugin
- HtmlWebpackPlugin
- MiniCssExtractPlugin css并行加载,否则css会被打包到js中
- DefinePlugin
- TerserPlugin
自定义 loader、plugin
loader 本质是函数
myLoader.js
js
module.exports = function (source){
source = source.replace(/hei/g,'皮辟!hei!')
return source
}
plugin 本质是类
myPlugin.js
js
const htmlStr = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
{{js}}
</head>
<body>
</body>
</html>
`
class myPlugin {
constructor() {
}
apply(compiler) {
console.log("ppPlugin 启动", compiler)
compiler.hooks.emit.tap('ppPlugin', (compilation) => {
// assets是一个对象,直接可以拿到key操作
console.log("ppPlugin emit", compilation.assets)
const assetsKeys = Object.keys(compilation.assets)
const newhtml = htmlStr.replace("{{js}}",
`<script>alert(111)</script>
${assetsKeys.filter(key => key.indexOf('.map') === -1).map(key => `<script src="${key}"></script>`)}
`)
compilation.assets['pp.html'] = {
source: () => newhtml,
size: () => newhtml.length
}
})
}
}
module.exports = myPlugin
如何提高webpack的构建速度
代码压缩
- js压缩,webpack4默认在生产环境下支持代码压缩,且使用的是
terser-webpack-plugin
插件,此前使用的是uglifyjs-webpack-plugin
,后者对es6的压缩不是很好,我们可以开启parallel
参数使用多进程压缩,提高速度 - css压缩,主要是去除无用的空格,一般使用
css-minimizer-webpack-plugin
- html压缩,使用
HtmlWebpackPlugin
插件生成html模板的时候,通过配置属性minify
进行html
优化
js
module.exports = {
plugin:[
new HtmlwebpackPlugin({
minify:{
minifyCSS: false, // css
collapseWhitespace: false, //
removeComments: true //
}
})
]
}
图片压缩
image-webpack-loader
tree shaking
- 方案一:usedExports,通过标记某些函数是否被使用,之后通过
Terser
来进行优化,使用以后,没有被用上的代码在webpack
打包的时候会加入unused harmony export mul
注释,用来告知Terser
在优化的时候可以删除这段代码。
js
module.exports = {
//...
optimization:{
usedExports
}
}
方案二:sideEffects
:跳过整个模块/文件,直接查看该文件是否有副作用sideEffects
用于告知webpack compiler
哪些模块时有副作用,配置方法是在package.json
中设置sideEffects
属性。如果sideEffects
设置为false
,就是告知webpack
可以安全的删除未用到的exports
。如果有些文件需要保留,可以设置为数组的形式,如:
js
"sideEffecis":[
"./src/util/format.js",
"*.css" // css
]
缩小打包域
- 排除
webpack
不需要解析的模块,即在使用loader
的时候,在尽量少的模块中去使用。 - 可以借助
include
和exclude
这两个参数,规定loader
只在那些模块应用和在哪些模块不应用。
减少ES6转化ES5之后的冗余代码
使用 bable-plugin-transform-runtime
插件
提取公共代码
通过配置 CommonChunkPlugin
插件,将多个页面的公共代码抽离为单独的文件