定义
webpack,是一个模块打包工具,实现了核心的task调度,剩下都是一些插件
- plugin,在webpack运行的生命周期中会广播出许多事件,plugin可以监听这些事件,在合适的时机通过webpack提供的api改变输出结果。(在webpack运行到某一时刻执行一些额外的操作,比如生成HTML文件、压缩代码、提取css等)
- babel, js编译器,把js的高级语言编译成浏览器能识别语言
- babel-loader,webpack通过babel-loader使用babel
- loader,翻译器,webpack默认只识别js,对非js文件需要借助loader来翻译,本质是个函数,在该函数中对接收到的内容进行转换,返回转换后的结果。(模块转换器,如less->css)
常用插件
html-webpack-plugin,打包结束后生成html文件,将打包后的js或css文件自动引入html
clean-webpack-plugin,打包前清理上一次bundle文件
hotModuleReplacePlugin, 热更新依赖于webpack-dev-serve
mini-css-extract-plugin,把css打包生成单独文件,支持按需加载
optimize-css-assets-webpack-plugin, 对css代码做压缩
uglifyjs-webpack-plugin 对js文件进行压缩
speed-measure-webpack-plugin,可以看到每个loader和plugin执行耗时(整个打包耗时,loader和plugin执行耗时)
webpack-bundle-analyzer,可视化webpack打包后文件的体积(业务组件,第三方模块)
常见loader
file-loader,把文件输出到一个文件夹中,在代码中通过相对url去引用文件(处理图片和地址)
url-loader,与file-loader类似,区别是可以设置一个阈值,大于阈值交给file-loader处理,小于阈值时返回文件base64形式编码(处理图片和文字)
image-loader,加载并压缩图片
babel-loader,把es6转换成es5
less-loader,将less代码转换成css
css-loader,加载css,支持模块化,压缩,文件导入等特性
style-loader,把css代码注入到js中,通过dom操作去加载css
postcss-loader,扩展css语法,使用下一代css,可配合autoprefixer插件自动补齐css3前缀
webpack的配置项
1. mode:
- production: 生成模式,会开启tree-shaking和压缩代码,打包比较慢
- development:打包更加快速,省了代码优化步骤
- none:不使用任何默认优化选项
2. entry: 打包入口地址
3. output:
{
filename //输出文件名
path //输出文件目录
}
4. module://webpack默认支持js和json文件,其他类型都处理不了。需借助loadr来处理
{
rules: [
test: /\.css$/ //匹配所有css文件
use: 'css-loader'
]
}
5. plugin:[
new HtmlWepackPlugin({template: './src/index.html'}),
]
6. devServer: {
contentBase: path.resolve(__dirname, 'public') //静态文件目录(webpack-dev-server版本大于4.0.0时,使用static配置项,不在有contentBase)
compress: true,//是否启动压缩
port: 8088 //端口号
}
//配置contentBase的原因是,webpack在打包的时候,对静态文件的处理,例如图片,都是直接copy到dist目录下面,
对本地开发来说这个过程太费时,设置contentBase之后,就直接到对应目录下读取文件,节省了时间和性能开销
6. 使用cross-env 向process.env中注入变量,区分环境
7. css-loader,webpack默认只支持处理js和json文件,这里需要借助css-loader来处理css文件
style-loader,将css-loader处理好的css通过style标签的方式添加到页面上
postcss-loader, 自动添加css3的部分属性的浏览器前缀
less-loader,
style核心逻辑:
const content = `${样式内容}`
const style = document.createElement('style')
style.innerhtml = content
document.head.appendChild(style)
在开发环境的时候,可以通过设置contentBase去直接读取图片类静态文件。
但实际上webpack无法识别图片文件,需要在打包的时候借助loader处理下。
file-loader, 解决图片引入问题,并将图片copy到指定目录,默认dist下面
url-loader, 依赖file-loader,当图片小于一个阈值limit的时候,将图片转成base64编码,大于的时候,依然使用file-loader进行copy
img-loader, 压缩图片
8. mini-css-extract-plugin, 将css通过css文件的形式引入页面
file-loader
9. babel-loader,使用babel加载es5+代码,并将其转换成es5
@babel/core, babel编译的核心包
@babel/preset-env, babel编译的预设,可以理解为babel插件的超集
10.
webpack构建流程
1. 初始:启动构建,读取并合并配置参数,加载Plugin,并实例化compiler
2. 解析模块依赖:从Entry入口文件开始,递归解析模块之间的依赖关系,构建模块依赖图
3. 根据模块依赖图,针对每个module串行的调用对应loader去翻译文件内容,再找到该module依赖的module,递归地进行编译处理
4. 执行插件: webpack在打包过程中执行一系列插件,用于完成各种任务,比如压缩代码、生成html文件等等
5. 输出,将编译后的module组合成chunk,将chunk转换成文件,输出到文件系统
6. 监听变化:在开发模式下,webpack会在代码修改后重新构建打包流程,并将修改后的代码热更新到浏览器中
提高效率的插件
webpack-merge, 提取公共配置,减少重复配置代码
webpack-dashboard,可以更友好的展示打包相关信息
speed-measure-webpack-plugin,分析出webpack打包过程中plugin和loader执行耗时,有助于找到构建过程中的性能瓶颈
hotModuleReplacementPlugin,模块热替换,可以做到不用刷新浏览器而将新变更的模块替换掉旧的模块,依赖webpack-dev-serve
source map
source map是将编译、打包、压缩后的代码映射回源代码的过程,打包压缩后的代码不具备可读性。想要调试源码就需要source map
map文件只要不打开开发者工具,浏览器是不会加载的
原理:sourceMap包含了源代码和编译后代码之间的映射关系,通常是一个json文件,包含了每行代码的映射信息,比如源文件路径、行号、列号等。当浏览器执行编译后的代码时,会通过sourceMap将执行位置映射回源代码的位置。
配置devtool:
- source-map,生成独立的source-map文件,适合生成环境,但是会增加国建时间和文件大小
- cheap-source-map 生成source-map,但不包含列信息,适合大型项目
- cheap-module-source-map 生成source-map,同时会将loader的sourcemap加进来
tree Shaking
- 编译器对ESM模块做静态分析,确定模块中哪些导出值未在其他模块中使用过,并将其删除,以此来实现打包的优化
- 原理:先标记处模块导出值哪些没有被用过,然后再用Terser删掉这些没用被用过的导出语句。标记过程如下:
1. make阶段,收集模块导出的变量,并记录到模块依赖关系图moduleGraph变量中
2. seal阶段,遍历模块关系图,标记出导出变量有没有被使用。
3. 生成产物时,如果变量没用被其他模块使用,则使用Terser插件提供的DCE功能删除
tree shaking:juejin.cn/post/700241…
webpack热更新原理
HMR可以做到不刷新浏览器,而将新变更的模块替换掉旧的模块,并在更新过程中保持应用程序状态
- 首先,浏览器和开发服务器(webpack Dev Server)之间建立了一个websocket连接,开发服务器启动一个websocket服务器,监听文件系统的变动
1. 初始化HMR环境,HMR插件(HotModuleReplacementPlugin)以及webpack Dev Server共同初始化HMR环境,开发服务器会启动一个websocket服务器,监听文件系统变动
2. webpack在构建过程中会构建一个模块依赖图,这个图记录了所有模块之间的依赖关系,webpack会通过它来确定哪些模块需要重新编译。
3. 当应用程序加载时,webpack会注入一个HMR运行时到每个模块。这个运行时代码负责与websocket服务器建立连接,还有应用程序动态的接收、更新、替换模块,无需加载整个页面。
(module.hot.accept)
4. 当本地资源变动时,webpack会检测到这个变动,并根据依赖图确定需要重新编译的模块。webpack会为这些模块生成更新的代码和更新的资源
5. 更新的模块和资源会被发送到客户端,客户端的HMR运行时接受到更新后,会根据收到的信息来执行不同的操作。使用module.hot.accept来接受更新,并可能执行一些额外的代码来处理模块状态变化。
6. 更新Dom:
对于css等资源的更新,webpack会生成<style>标签,将其插入到dom中,替换旧的标签;
对于JavaScript模块的更新,新的代码会替换旧的代码,激活的新模块会替换旧模块的功能
文件监听原理
轮询判断文件最后的编辑时间是否变化,如果变化等待aggregateTimeout后再执行
cra,扩展webpack配置
1. npm run eject暴露webpack配置
缺点,react-scripts已经以文件形式存在于项目,无法升级
2. 可以通react-app-rewired和customize-cra来对cra创建的项目进行扩展
- 项目根目录下创建config-override.js
- 修改package.json文件里scripts的相关命令
- 原理:react-app-rewired拿到create-react-app生成的默认webpackage config,然后调用override(config)方法,对config进行修改,webpack最终使用新的config进行打包
webpack打包优化
启动慢、编译慢、占用内存高
1. 使用缓存
- cache持久化缓存,通过配置cache,缓存生成的模块和chunk,来改善构建速度。(webpack5已经内置了模块缓存,不需要单独再配置)
- cache-loader, 缓存一些性能开销比较大的loader的处理结果(speed-measure-webpack-plugin,可以看到每个loader和plugin执行耗时)
- 比如开启babel-loader的缓存(cacheDirectory: true),将babel-loader的执行结果缓存起来,重新打包的时候直接读取缓存(node_modules/.cache/babel-loader)
- hard-source-webpack-plugin,为模块提供了中间缓存,重复构建时间可减少80%
2. 多线程并行打包:可以使用thread-loader或者happypack开启多线并行构建,配置在thread-loader之后的loader都会在一个单独的worker池中运行
注意:在小型项目中,开启多进程打包反而会增加时间成本,因为启动进程和进程通信都会增加时间成本(适合单个耗时长的任务)
优化构建结果:
- webpack-bundle-analyzer,可以直观的看到打包后各文件的大小,依赖关系。
- 压缩css,MiniCssExtractPlugin
- 压缩js,生产环境下会自动压缩js(手动配置optimization后不再默认对js压缩,需要手动配置),teser-webpack-plugin
- 清除无用的css,purgucss-webpack-plugin,会单独提取css,并清除用不到的css
- tree-Shaking,使用tree-shaking剔除没有使用的代码,减少包的体积(webpack默认支持,在.bablerc文件中设置mode:false,即可在生产环境下默认开启)
优化resolve配置
用alias创建别名,来简化模块的引用
extensions,如果引入模块时不带后缀名,那么webpack就会按照extensions的配置的数组从做左到右去尝试解析,注意高频文件后缀名放前面
modules,告诉webpack解析模块时应该优先搜索的目录(默认情况下webpack会查找node_modules目录),会大大减少查找时间
resolveLoader,跟resolve对象的属性集合相同,但仅用于解析webpack的loader包
externals,该配置提供了从‘输出的bundle中排除依赖’的方法
配置loader的时候,更精确的指定loader作用的目录或者需要排除的目录,通过include和exclude这两个配置项实现
noparse,不需要解析依赖的第三方大型类库,通过这个字段进行配置,可以提高构建速度
优化运行时体验:
- 降低首屏文件加载体积,使用按需加载
- prefetch和preload
prefetch:浏览器空闲的时候进行资源加载
preload:提前加载资源,提高资源加载的优先级
vite/webpack
1. vite设计理念是,采用按需方法把你的应用程序提供给浏览器,vite不会打包源码,而是将我们编写的模块即时
转换成浏览器可以理解的原生ESModule模块,利用原生的ES模块导入作为其核心特性,实现快速的开发环境。
2. webpack是一个更为通用的模块打包器,可以处理多种类型的模块(CommonJS、ESModules、AMD等),并且具有强大的插件系统,允许开发者自定义构建流程。
性能上:
- 通常认为在开发环境下比webpage更快,因为利用了浏览器的原生导入特性,避免了打包操作,从而实现快速重载。
- webpack在生产环境下通过优化和压缩代码来提高最终构建的性能,但在开发模式下可能会因为打包过程而显得较慢
配置和易用性上:
- vite提供了更少的配置项,开发者可以快速开始项目,默认的配置足够用于大多数项目
- webpack配置相对复杂,需要开发者对各种插件和loader有深入的了解,以实现定制化的构建流程
模块处理:
- vite支持的是ESModules
- webpack支持多种模块系统,包括ESModules,CommonJs和AMDModules,这使得它可以更好的与现有的代码库和第三方代码
开发服务器:
- vite使用原生的ES模块特性来提供开发服务器,这意味着它可以提供非常快速的模块热替换
- webpack通过WDS来托管打包好的模块,并提供HMR功能
自定义loader
loader本质是一个函数,接收源代码作为参数,返回处理后的结果
1. 实现loader函数可以借助loader-util库提供的api来获取loader选项、文件路径、查询字符串等信息
2. 然后在module/rules中配置,可以通过options属性来为该loader提供选项
plugin
本质是个类,这个类实现了apply方法,在apply方法中通过compiler对象注册webpack的生命周期事件监听器,在监听器中实现自定义的逻辑。
常见的事件
before-run:在webpack开始执行构建之前触发,可以用于清理上一次构建的临时文件或状态
run:在webpack开始执行构建时触发
before-compile:在webpack开始编译代码之前触发,可以用于添加一些额外的编译配置或预处理代码
compile:在webpack开始编译代码时触发,用于监听编译过程或处理编译储位
this-compilation
compilation
emit: 在webpack输出文件之前触发,可以用于修改输出文件或生成一些附加文件
after-emit:在webpack生成输出文件后触发,可以用于清理中间文件或者执行一些其他操作
done:在webpack完成构建时触发,可以用于生成构建报告或通知开发者构建结果