1.概念
[webpack5地址](https://webpack.docschina.org/)<br /> [从 v4 升级到 v5迁移](https://webpack.docschina.org/migrate/5/)
webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。可以将所有资源(包括Javascript,图像,字体和CSS等)打包后置于依赖关系中,构建成一个 依赖图(dependency graph)。将 Es Module 、 CommonJs 的语法处理成浏览器可以运行的代码,然后将你项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,用于展示你的内容
- Entry: 指定webpack开始构建的入口模块,从该模块开始构建并计算出直接或间接依赖的模块或者库。
- Output:告诉webpack如何命名输出的文件以及输出的目录
- Module: 模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。
- Chunk:coding split(代码分割)的产物,我们可以对一些代码打包成一个单独的chunk,比如某些公共模块,去重,更好的利用缓存。或者按需加载某些功能模块,优化加载时间。在webpack3及以前我们都利用CommonsChunkPlugin将一些公共代码分割成一个chunk,实现单独加载。在webpack4 中CommonsChunkPlugin被废弃,使用SplitChunksPlugin
- Loader:模块转换器,用于把模块原内容按照需求转换成新内容。
- Plugin:扩展插件,在 Webpack 构建流程中的特定时机会广播出对应的事件,插件可以监听这些事件的发生,在特定时机做对应的事情。
mode:
开发模式:能让代码本地调试的环境会将process.env.NODE_ENV = development.
生产模式:能让代码优化上线运行的环境。process.env.NODE_ENV = production.
webpack 的构建流程是什么

- 初始化参数:解析webpack配置参数,合并shell传入和webpack.config.js文件配置的参数,形成最后的配置结果;
- 开始编译:上一步得到的参数初始化compiler对象,注册所有配置的插件,插件监听webpack构建生命周期的事件节点,做出相应的反应,执行对象的run方法开始执行编译;
- 确定入口:从配置的entry入口,开始解析文件构建AST语法树,找出依赖,递归下去;
- 编译模块:递归中根据文件类型和loader配置,调用所有配置的loader对文件进行转换,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
- 完成模块编译并输出:递归完事后,得到每个文件结果,包含每个模块以及他们之间的依赖关系,根据entry或 码块chunk;
-
webpack 打包是hash码是如何生成的
1.webpack生态中存在多种计算hash的方式
hash
- chunkhash
- contenthash
hash代表每次webpack编译中生成的hash值,所有使用这种方式的文件hash都相同。每次构建都会使webpack计算新的hash。chunkhash基于入口文件及其关联的chunk形成,某个文件的改动只会影响与它有关联的chunk的hash值,不会影响其他文件contenthash根据文件内容创建。当文件内容发生变化时,contenthash发生变化
2.避免相同随机值
- webpack在计算hash后分割chunk。产生相同随机值可能是因为这些文件属于同一个chunk,可以将某个文件提到独立的chunk(如放入entry)
2.Webpack5与webpack4的区别
1、Tree Shaking(树摇)
webpack4
作用: 如果我们的项目中引入了 lodash 包,但是我只有了其中的一个方法。其他没有用到的方法是不是冗余的?此时 tree-shaking 就可以把没有用的那些东西剔除掉,来减少最终的bundle体积。
usedExports : true, 标记没有用的叶子
minimize: true, 摇掉那些没有用的叶子
// webpack.config.js中module.exports = {optimization: {usedExports: true, //只导出被使用的模块minimize : true // 启动压缩}}
由于 tree shaking 只支持 esmodule ,如果你打包出来的是 commonjs,此时 tree-shaking 就失效了。不过当前大家都用的是 vue,react 等框架,他们都是用 babel-loader 编译,以下配置就能够保证他一定是 esmodule
webpack5
webpack5的 mode=“production” 自动开启 tree-shaking
- webpack5 以前,tree-shaking 比较简单,主要是找import进来的变量是否在这个模块内出现过,出现过则不剔除,不出现过则剔除。并且用于 esModule 中
- webpack5,可以进行根据作用域之间的关系进行优化。比如:
- a.js 中到处了两个方法 a 和 b,在 index.js 中引入了 a.js 到处的 a 方法,没有引用 b 方法。那么 webpack4 打包出来的结果包含了 index.js 和 a.js 的内容,包含了没有用到的 b 方法。但是 webpack5 的 treeshaking,会进行作用域分析,打包结果只有 index 和 a 文件中的 a 方法,没有用到的 b 方法是不会被打包进来的。
- 所以:webpack4 的 treeshaking 是关注 import 了某个库的什么模块,那么我就打包什么;webpack5 更精细化,直接分析到哪些变量有效地用到了,那么我就打包哪些变量。
2、压缩代码
2.1 webpack4
webpack4 上需要下载安装 terser-webpack-plugin 插件,并且需要以下配置:
const TerserPlugin = require('terser-webpack-plugin')module.exports = {// ...other configoptimization: {minimize: !isDev,minimizer: [new TerserPlugin({extractComments: false,terserOptions: {compress: {pure_funcs: ['console.log']}}}) ]}
2.2 webpack5
内部本身就自带 js 压缩功能,他内置了 terser-webpack-plugin 插件,我们不用再下载安装。而且在 mode=“production” 的时候会自动开启 js 压缩功能。
如果你要在开发环境使用,就用下面:
// webpack.config.js中module.exports = {optimization: {usedExports: true, //只导出被使用的模块minimize : true // 启动压缩}}
3、webpack 缓存
3.1 webpack4 缓存配置
支持缓存在内存中
npm install hard-source-webpack-plugin -D
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')module.exports = {plugins: [// 其它 plugin...new HardSourceWebpackPlugin(),] }
3.2 webpack5 缓存配置
webpack5 内部内置了 cache 缓存机制。直接配置即可。
cache 会在开发模式下被设置成 type: memory 而且会在生产模式把cache 给禁用掉。
// webpack.config.jsmodule.exports= {// 使用持久化缓存cache: {type: 'filesystem',cacheDirectory: path.join(__dirname, 'node_modules/.cac/webpack')}}
type 的可选值为: memory 使用内容缓存,filesystem 使用文件缓存。
当 type=filesystem的时候设置cacheDirectory才生效。用于设置你需要的东西缓存放在哪里
- webpack会缓存生成的webpack模块和chunk,来改善构建速度
- 缓存在webpack5中默认开启,缓存默认是在内存里,但可以对cache进行设置
- webpack 追踪了每个模块的依赖,并创建了文件系统快照。此快照会与真实文件系统进行比较,当检测到差异时,将触发对应模块的重新构建
4、对loader的优化
webpack 4 加载资源需要用不同的 loader
- raw-loader 将文件导入为字符串
- url-loader 将文件作为 data url 内联到 bundle文件中(加载图片与file-loader区别在可以配置是否转换base64URI,优点在于可以减少http请求)
- file-loader 将文件发送到输出目录中

webpack5 的资源模块类型替换 loader
- asset/resource 替换 file-loader(发送单独文件)
- asset/inline 替换 url-loader (导出 url)
- asset/source 替换 raw-loader(导出源代码)
- asset
5、启动服务的差别
1.webpack4 启动服务
2.webpack5 启动服务
内置使用 webpack serve 启动,但是他的日志不是很好,所以一般都加都喜欢用 webpack-dev-server 优化。
6.devtool的差别
sourceMap需要在 webpack.config.js里面直接配置 devtool 就可以实现了。而 devtool有很多个选项值,不同的选项值,不同的选项产生的 .map 文件不同,打包速度不同。
一般情况下,我们一般在开发环境配置用“cheap-eval-module-source-map”,在生产环境用‘none’。
devtool在webpack4和webpack5上也是有区别的
v4: devtool: ‘cheap-eval-module-source-map’
v5: devtool: ‘eval-cheap-module-source-map’
接下来,我们稍微总结一下:
| devtool | build | rebuild | 显示代码 | SourceMap 文件 | 描述 |
|---|---|---|---|---|---|
| (none) | 很快 | 很快 | 无 | 无 | 无法定位错误 |
| eval | 快 | 很快(cache) | 编译后 | 无 | 定位到文件 |
| source-map | 很慢 | 很慢 | 源代码 | 有 | 定位到行列 |
| eval-source-map | 很慢 | 一般(cache) | 编译后 | 有(dataUrl) | 定位到行列 |
| eval-cheap-source-map | 一般 | 快(cache) | 编译后 | 有(dataUrl) | 定位到行 |
| eval-cheap-module-source-map | 慢 | 快(cache) | 源代码 | 有(dataUrl) | 定位到行 |
| inline-source-map | 很慢 | 很慢 | 源代码 | 有(dataUrl) | 定位到行列 |
| hidden-source-map | 很慢 | 很慢 | 源代码 | 有 | 无法定位错误 |
| nosource-source-map | 很慢 | 很慢 | 源代码 | 无 | 定位到文件 |
对照一下校验规则 ^(inline-|hidden-|eval-)?(nosources-)?(cheap-(module-)?)?source-map$ 分析一下关键字
| 关键字 | 描述 |
|---|---|
| inline | 代码内通过 dataUrl 形式引入 SourceMap |
| hidden | 生成 SourceMap 文件,但不使用 |
| eval | eval(…) 形式执行代码,通过 dataUrl 形式引入 SourceMap |
| nosources | 不生成 SourceMap |
| cheap | 只需要定位到行信息,不需要列信息 |
| module | 展示源代码中的错误位置 |
官方:https://webpack.js.org/configuration/devtool/
7.热更新差别
热更新原理:其实是自己开启了express应用,添加了对webpack编译的监听,添加了和浏览器的websocket长连接,当文件变化触发webpack进行编译并完成后,会通过sokcet消息告诉浏览器准备刷新。而为了减少刷新的代价,就是不用刷新网页,而是刷新某个模块,webpack-dev-server可以支持热更新,通过生成 文件的hash值来比对需要更新的模块,浏览器再进行热替换
webpack4设置
webpack5 设置
如果你使用的是bable6,按照上述设置,你会发现热更新无效,需要添加配置:
module.hot.accept('需要热启动的文件',(source)=>{//自定义热启动})
3.webpack5 打包优化
webpack从主要配置(entry、output、resolve、module、performance、externals、module、plugins,其他)进行优化
oneOf
每个不同类型的文件在loader转换时,会遍历module中rules中所有loader,即使已经匹配到某个规则了也会继续向下匹配。而如果将规则放在 oneOf 属性中,则一旦匹配到某个规则后,就停止匹配了
rules:[{test: /\.js$/,exclude: /node_modules/,loader: "eslint-loader",},{// 以下loader一种文件只会匹配一个oneOf: [// 不能有两个配置处理同一种类型文件,如果有,另外一个规则要放到外面。{test: /\.js$/,exclude: /node_modules/,use: [{loader: "babel-loader",},],},{test: /\.css$/,use: ["style-loader","css-loader",],},],},]
使用新增cache属性,缓存进行优化(推荐)
通过配置 webpack 持久化缓存cache: filesystem,来缓存生成的 webpack 模块和 chunk,改善构建速度。
简单来说,通过 cache: filesystem 可以将构建过程的 webpack 模板进行缓存,大幅提升二次构建速度、打包速度,当构建突然中断,二次进行构建时,可以直接从缓存中拉取,可提速 90% 左右。
module.exports = {cache: {type: 'filesystem', // 使用文件缓存},}
module 部分优化
include和exclude:排除不需要处理loader文件(exclude优先include)
rules: [{test: /.ext$/,include: path.resolve('src'),},],
压缩
optimize-css-assets-webpack-plugin(推荐):对进行css压缩
module.exports = {optimization: {minimize: true,minimizer: [new OptimizeCSSAssetsPlugin({assetNameRegExp: /\.css$/,safe: true,cache: true,parallel: true,discardComments: {removeAll: true,},}),],},};
其他优化项
使用mode属性
- purgecss-webpack-plugin : 进行CSS文件进行Tree-Shaking (推荐,css文件可能会有大幅降低)
esbuild-loader: 相比babel-loader速度更快
4.webpack5 如何编写plugin
webpack 插件由以下组成:
一个 JavaScript 命名函数。
- 在插件函数的 prototype 上定义一个 apply 方法。
- 指定一个绑定到 webpack 自身的事件钩子。
- 处理 webpack 内部实例的特定数据。
功能完成后调用 webpack 提供的回调。
webpack本质是一个事件流机制,核心模块:tabable(Sync + Async)Hooks 构造出 === Compiler(编译) + Compiletion(创建bundles)
- compiler对象代表了完整的webpack环境配置。这个对象在启动webpack时被一次性建立,并配置好所有可操作的设置,包括options、loader和plugin。当在webpack环境中应用一插件时,插件将收到此compiler对象的引用。可以使用它来访问webpack的主环境
- compilation对象代表了一次资源版本构建。当运行webpack开发环境中间件时,每当检测到一个文件变化,就会创建一个新的compilation,从而生成一个新的编译资源。一个compilation对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态的信息。compilation对象也提供了很多关键时机的回调,以供插件做自定义处理时选择使用
- 创建一个插件函数,在其prototype上定义apply方法,指定一个webpack自身的事件钩子
- 函数内部处理webpack内部实例的特定数据
- 处理完成后,调用webpack提供的回调函数
class MyPlugin {apply(compiler) {console.log("MyPlugin 启动");// emit钩子的执行时机是:输出打包好的资源文件 asset 到 output 目录之前执行。compiler.hooks.afterCompile.tap("MyPlugin", (compilation) => {// compilation.assets => 获取dist目录的所有生成的资源文件信息,例如:bundle.js// console.info('compilation.assets',compilation.assets)for (const name in compilation.assets) {// 获取到资源的内容,例如:bundle.js里面的内容// 判断文件是否为index.html文件if (name === "index.html") {// 替换掉script文件中的注释const contents = compilation.assets[name].source();const newContents = contents.replace(/<script [^>]*src=['"]([^'"]+)[^>]*>/gi,function (match, capture) {return `<script src="${capture}?${new Date().getTime()}"> `;});const withoutComments = `${newContents}`;// 覆盖原有内容compilation.assets[name] = {source: () => withoutComments, // 新的内容size: () => withoutComments.length, // 内容的大小,webpack必须的方法};}}});}}module.exports = MyPlugin;
https://www.webpackjs.com/contribute/writing-a-plugin/#compiler-%E5%92%8C-compilation
4.webpack5 与其他打包工具的区别
#一、模块化工具
模块化是一种处理复杂系统分解为更好的可管理模块的方式
可以用来分割,组织和打包应用。每个模块完成一个特定的子功能,所有的模块按某种方法组装起来,成为一个整体(bundle)
在前端领域中,并非只有webpack这一款优秀的模块打包工具,还有其他类似的工具,例如Rollup、Parcel、snowpack,以及最近风头无两的Vite
通过这些模块打包工具,能够提高我们的开发效率,减少开发成本
这里没有提及gulp、grunt是因为它们只是定义为构建工具,不能类比
#Rollup
Rollup 是一款 ES Modules 打包器,从作用上来看,Rollup 与 Webpack 非常类似。不过相比于 Webpack,Rollup要小巧的多
现在很多我们熟知的库都都使用它进行打包,比如:Vue、React和three.js等
举个例子:
// ./src/messages.jsexport default {hi: 'Hey Guys, I am zce~'}// ./src/logger.jsexport const log = msg => {console.log('---------- INFO ----------')console.log(msg)console.log('--------------------------')}export const error = msg => {console.error('---------- ERROR ----------')console.error(msg)console.error('---------------------------')}// ./src/index.jsimport { log } from './logger'import messages from './messages'log(messages.hi)
然后通过rollup进行打包
打包结果如下图
可以看到,代码非常简洁,完成不像webpack那样存在大量引导代码和模块函数
并且error方法由于没有被使用,输出的结果中并无error方法,可以看到,rollup默认开始Tree-shaking 优化输出结果
优点:
- 代码效率更简洁、效率更高
- 默认支持 Tree-shaking
缺点:
加载其他类型的资源文件或者支持导入 CommonJS 模块,又或是编译 ES 新特性,这些额外的需求 Rollup需要使用插件去完成
综合来看,rollup并不适合开发应用使用,因为需要使用第三方模块,而目前第三方模块大多数使用CommonJs方式导出成员,并且rollup不支持HMR,使开发效率降低
但是在用于打包JavaScript 库时,rollup比 webpack 更有优势,因为其打包出来的代码更小、更快,其存在的缺点可以忽略
#Parcel
Parcel ,是一款完全零配置的前端打包器,它提供了 “傻瓜式” 的使用体验,只需了解简单的命令,就能构建前端应用程序
Parcel 跟 Webpack 一样都支持以任意类型文件作为打包入口,但建议使用HTML文件作为入口,该HTML文件像平时一样正常编写代码、引用资源。如下所示:
<!-- ./src/index.html --><!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Parcel Tutorials</title></head><body><script src="main.js"></script></body></html>
main.js文件通过ES Moudle方法导入其他模块成员
// ./src/main.jsimport { log } from './logger'log('hello parcel')// ./src/logger.jsexport const log = msg => {console.log('---------- INFO ----------')console.log(msg)}
运行之后,使用命令打包
npx parcel src/index.html
执行命令后,Parcel不仅打包了应用,同时也启动了一个开发服务器,跟webpack Dev Server一样
跟webpack类似,也支持模块热替换,但用法更简单
同时,Parcel有个十分好用的功能:支持自动安装依赖,像webpack开发阶段突然使用安装某个第三方依赖,必然会终止dev server然后安装再启动。而Parcel则免了这繁琐的工作流程
同时,Parcel能够零配置加载其他类型的资源文件,无须像webpack那样配置对应的loader
打包命令如下:
npx parcel src/index.html
由于打包过程是多进程同时工作,构建速度会比Webpack 快,输出文件也会被压缩,并且样式代码也会被单独提取到单个文件中
可以感受到,Parcel给开发者一种很大的自由度,只管去实现业务代码,其他事情用Parcel解决
#Vite
vite ,是一种新型前端构建工具,能够显著提升前端开发体验
它主要由两部分组成:
- 一个开发服务器,它基于 原生 ES 模块 提供了丰富的内建功能,如速度快到惊人的 [模块热更新HMR
- 一套构建指令,它使用 Rollup打包你的代码,并且它是预配置的,可以输出用于生产环境的优化过的静态资源
其作用类似webpack+ webpack-dev-server,其特点如下:
- 快速的冷启动
- 即时的模块热更新
- 真正的按需编译
vite会直接启动开发服务器,不需要进行打包操作,也就意味着不需要分析模块的依赖、不需要编译,因此启动速度非常快
利用现代浏览器支持ES Module的特性,当浏览器请求某个模块的时候,再根据需要对模块的内容进行编译,这种方式大大缩短了编译时间
原理图如下所示:
在热模块HMR方面,当修改一个模块的时候,仅需让浏览器重新请求该模块即可,无须像webpack那样需要把该模块的相关依赖模块全部编译一次,效率更高
#webpack
相比上述的模块化工具,webpack大而全,很多常用的功能做到开箱即用。有两大最核心的特点:一切皆模块和按需加载
与其他构建工具相比,有如下优势:
- 智能解析:对 CommonJS 、 AMD 、ES6 的语法做了兼容
- 万物模块:对 js、css、图片等资源文件都支持打包
- 开箱即用:HRM、Tree-shaking等功能
- 代码分割:可以将代码切割成不同的 chunk,实现按需加载,降低了初始化时间
- 插件系统,具有强大的 Plugin 接口,具有更好的灵活性和扩展性
- 易于调试:支持 SourceUrls 和 SourceMaps
- 快速运行:webpack 使用异步 IO 并具有多级缓存,这使得 webpack 很快且在增量编译上更加快
- 生态环境好:社区更丰富,出现的问题更容易解决
/**、webpack 多环境配置 webpack merge*/const { resolve } = require("path");/**生成html,并把打包的js插入 */const HtmlWebpackPlugin = require("html-webpack-plugin");/**css单独提取*/const MiniCssExtractPlugin = require("mini-css-extract-plugin");/**css压缩*/const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");/**html压缩*/const HtmlMinimizerPlugin = require("html-minimizer-webpack-plugin");/**eslint*/const ESLintPlugin = require("eslint-webpack-plugin");const commonCss = [MiniCssExtractPlugin.loader, "css-loader"];const isProMode = process.env.NODE_ENV !== "production";console.log("isProMode", process.env.NODE_ENV);module.exports = {/**模式 development或者production */mode: "development",target: "web",/**入口文件 */// entry: ["./src/index.js", "./src/index.html"],entry: {index: "./src/index.js",app: {dependOn: "index",import: "./src/app.js",},},/**输出文件 */output: {filename: "[name].[hash:8].js",path: resolve(__dirname, "dist"),/**清除了dist 代替了html-clean-plugin */clean: true,},/**模块loader */module: {rules: [/**将 css-loader转换并插入js style-loader将js中的css通过style插入*/// test: /\.css$/i,// use: ["style-loader", "css-loader"],{test: /\.css$/,use: [// MiniCssExtractPlugin.loader的作用就是把css-loader处理好的样式资源(js文件内),单独提取出来 成为css样式文件//生产环境下使用,开发环境还是推荐使用style-loaderisProMode ? MiniCssExtractPlugin.loader : "style-loader","css-loader",{//css的兼容性处理loader: "postcss-loader",options: {/**设置 PostCSS 选项与插件*/postcssOptions: {plugins: [["postcss-preset-env"]],// 如果你在 JS 文件中编写样式,请使用 postcss-js parser,并且添加 execute 选项。// parser: "postcss-js",// execute: true,},},},],},{test: /\.less$/,use: [isProMode ? MiniCssExtractPlugin.loader : "style-loader","css-loader","less-loader",],},{test: /\.m?js$/,exclude: /(node_modules|bower_components)/,use: {loader: "babel-loader",options: {presets: ["@babel/preset-env"],},},},/**处理图片文件*/{test: /\.(jpg|png|jpeg|gif)$/,loader: "asset/inline",options: {/**图片文件大小*/limit: 10 * 1024,/**node是command打包 关闭esModule打包*/esModule: false,/**文件名称*/name: "[name][hash:8][ext]",},},/**处理html中图片文件*/{test: /\.html$/,loader: "html-loader",},/**处理文件的路径并输出文件到输出目录 */// {// exclude: /\.(html|jpg|png|jpeg|gif|less|css)$/,// loader: "asset",// },],},plugins: [/**以哪个html作为模板进入打包*/new HtmlWebpackPlugin({template: "./src/index.html",}),/**提取js里面的文件到css文件*/new MiniCssExtractPlugin({filename: "css/[name].css",}),new ESLintPlugin(),],optimization: {minimize: true,minimizer: [// 在 webpack@5 中,你可以使用 `...` 语法来扩展现有的 minimizer(即 `terser-webpack-plugin`),将下一行取消注释new CssMinimizerPlugin(),new HtmlMinimizerPlugin(),],},/**本地服务器*/devServer: {/**监听打包的文件夹*/static: {directory: resolve(__dirname, "dist"),},/**热更新 会造成html改变不更新 所以将入口增加html*/hot: true,port: 8080,/**压缩文件*/compress: true,open: true,watchFiles: ["./src/index.html"],},};

