岳阳找工作网站如何增加网站转化率
手动分包
基本原理
手动分包的总体思路是:先打包公共模块,然后再打包业务代码。
打包公共模块

公共模块会被打包成为动态链接库(dll Dynamic Link Library),并生成资源清单。
打包业务代码
打包时,如果发现模块中使用了资源清单中描述的模块,则不会形成下面的代码结构
//源码,入口文件index.js
import $ from "jquery"
import _ from "lodash"
_.isArray($(".red"));
由于资源清单中包含 jquery 和 lodash 两个模块,因此打包结果的大致格式是:
(function(modules){//...
})({// index.js文件的打包结果并没有变化"./src/index.js":function(module, exports, __webpack_require__){var $ = __webpack_require__("./node_modules/jquery/index.js")var _ = __webpack_require__("./node_modules/lodash/index.js")_.isArray($(".red"));},// 由于资源清单中存在,jquery的代码并不会出现在这里"./node_modules/jquery/index.js":function(module, exports, __webpack_require__){module.exports = jquery;},// 由于资源清单中存在,lodash的代码并不会出现在这里"./node_modules/lodash/index.js":function(module, exports, __webpack_require__){module.exports = lodash;}
})
打包公共模块
打包公共模块是一个独立的打包过程
package.json
{"name": "webpack","version": "1.0.0","private": true,"scripts": {"dll": "webpack --config webpack.dll.config.js","build": "webpack"},"keywords": [],"author": "","license": "ISC","description": "","devDependencies": {"html-webpack-plugin": "^5.6.3","webpack": "^5.96.1","webpack-cli": "^5.1.4"},"dependencies": {"jquery": "^3.7.1","lodash": "^4.17.21"}
}
webpack.dll.config.js
const webpack = require("webpack")
const path = require("path")module.exports = {mode: "production",entry: {lodash: "lodash",jquery: "jquery",},output: {filename: "dll/[name].js", // 不需要用[hash]library: "[name]", // 导出的全局变量名},plugins: [new webpack.DllPlugin({path: path.resolve(__dirname, "dll", "[name].manifest.json"), // 资源清单的保存位置name: "[name]" // 资源清单中,暴露的变量名})]
}
运行 npm run dll 命令,可以看到在根目录下生成了 dll 目录,目录下有 lodash 和 jquery 的资源清单文件;同时在 dist/dll 目录下也可以看到打包好的 lodash 和 jquery 文件。
打包业务代码
package.json
{"name": "webpack","version": "1.0.0","private": true,"scripts": {"dll": "webpack --config webpack.dll.config.js","build": "webpack"},"keywords": [],"author": "","license": "ISC","description": "","devDependencies": {"html-webpack-plugin": "^5.6.3","webpack": "^5.96.1","webpack-cli": "^5.1.4"},"dependencies": {"jquery": "^3.7.1","lodash": "^4.17.21"}
}
webpack.config.js
const path = require("path")
const webpack = require("webpack")
const HtmlWebpackPlugin = require("html-webpack-plugin")module.exports = {mode: "development",context: path.resolve(__dirname, "src"), entry: {index: "./index"},output: {filename: "[name].[contenthash:5].js",clean: {keep: /dll\//, // 保留 'dll/' 下的资源},},plugins: [new HtmlWebpackPlugin({title: "webpack-手动分包",}),new webpack.DllReferencePlugin({manifest: require("./dll/jquery.manifest.json"), // 应用 jquery 的资源清单}),new webpack.DllReferencePlugin({manifest: require("./dll/lodash.manifest.json"), // 应用 lodash 的资源清单}),],
}
index.js
import $ from "jquery"
import _ from "lodash"console.log($);
console.log(_);
运行 npm run build 命令,发现打包的文件中还是包含了 lodash 和 jquery,这是为什么呢?检查打包的文件发现:
dist/index.06f97.js
(function(modules){//...
})({"./index.js": (__unused_webpack_module,__webpack_exports__,__webpack_require__) => {// ...},"../node_modules/jquery/dist/jquery.js": function (module, exports) {// ...},"../node_modules/lodash/lodash.js": function (module, exports, __webpack_require__) {// ...}
})
发现 ../node_modules/jquery/dist/jquery.js 、../node_modules/lodash/lodash.js 与
dll/jquery.manifest.json
{"name": "jquery","content": {"./node_modules/jquery/dist/jquery.js": { "id": 692, "buildMeta": {} }}
}
dll/lodash.manifest.json
{"name": "lodash","content": {"./node_modules/lodash/lodash.js": { "id": 543, "buildMeta": {} }}
}
中的 ./node_modules/jquery/dist/jquery.js 、./node_modules/lodash/lodash.js 对不上,这是因为使用了 context 导致的,更改 webpack.config.js 文件:
const path = require("path")
const webpack = require("webpack")
const HtmlWebpackPlugin = require("html-webpack-plugin")module.exports = {// ...// context: path.resolve(__dirname, "src"), entry: {index: "./src/index"}// ...
}
再次运行 npm run build 命令,发现结果文件中已不再包含 lodash 和 jquery。
使用公共模块
webpack.config.js
const path = require("path")
const webpack = require("webpack")
const HtmlWebpackPlugin = require("html-webpack-plugin")module.exports = {mode: "development",// context: path.resolve(__dirname, "src"), entry: {index: "./src/index"},output: {filename: "[name].[contenthash:5].js",clean: {keep: /dll\//, // 保留 'dll/' 下的资源},},plugins: [new HtmlWebpackPlugin({title: "webpack-手动分包",template: "./index.ejs", // 使用 ejs 模板语法templateParameters: {scripts: ["./dll/jquery.js", "./dll/lodash.js"], // 将公共模块插入页面}}),new webpack.DllReferencePlugin({manifest: require("./dll/jquery.manifest.json"), // 应用 jquery 的资源清单}),new webpack.DllReferencePlugin({manifest: require("./dll/lodash.manifest.json"), // 应用 lodash 的资源清单}),],
}
index.ejs
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title><% if (scripts) { %><% for (item of scripts) { %><script src="<%= item %>"></script><% } %><% } %></head><body></body>
</html>
总结
手动打包的过程:
- 开启
output.library暴露公共模块 - 用
DllPlugin创建资源清单 - 用
DllReferencePlugin使用资源清单
手动打包的注意事项:
- 资源清单不参与运行,可以不放到打包目录中。
- 记得手动引入公共 JS,以及避免被删除。
- 不要对小型的公共 JS 库使用。
优点:
- 极大提升自身模块的打包速度。
- 极大的缩小了自身文件体积。
- 有利于浏览器缓存第三方库的公共代码。
缺点:
- 使用非常繁琐。
- 如果第三方库中都导入了相同的依赖,则第三方库打包后都会包含重复的代码,效果不太理想。
自动分包
基本原理
不同与手动分包,自动分包是从实际的角度出发,从一个更加宏观的角度来控制分包,而一般不对具体哪个包要分出去进行控制。
因此使用自动分包,不仅非常方便,而且更加贴合实际的开发需要。
要控制自动分包,关键是要配置一个合理的分包策略。
有了分包策略之后,不需要额外安装任何插件,webpack 会自动的按照策略进行分包。
实际上,webpack 在内部是使用
SplitChunksPlugin进行分包的,过去有一个库CommonsChunkPlugin也可以实现分包,不过由于该库某些地方并不完善,到了webpack4之后,已被SplitChunksPlugin取代。

从分包流程中至少可以看出以下几点:
- 分包策略至关重要,它决定了如何分包。
- 分包时,webpack 开启了一个新的 chunk,对分离的模块进行打包。
- 打包结果中,公共的部分被提取出来形成了一个单独的文件,它是新 chunk 的产物。
分包策略的基本配置
webpack 提供了 optimization 配置项,用于配置一些优化信息,其中 splitChunks 是分包策略的配置
module.exports = {optimization: {splitChunks: {// 分包策略}}
}
默认值
开箱即用的 SplitChunksPlugin 对于大部分用户来说非常友好。
默认情况下,它只会影响到按需加载的 chunks(动态导入的模块),因为修改 initial chunks 会影响到项目的 HTML 文件中的脚本标签。
webpack 将根据以下默认条件自动拆分 chunks:
- 新的 chunk 可以被共享,或者模块来自于
node_modules文件夹 - 新的 chunk 体积大于 20kb(在进行 min+gz 之前的体积)
- 当按需加载 chunks 时,并行请求的最大数量小于或等于 30
- 当加载初始化页面时,并发请求的最大数量小于或等于 30
当尝试满足最后两个条件时,最好使用较大的 chunks。
splitChunks.chunks
类型:string | function (chunk),默认值:async
-
'all'对于所有的 chunk 都要应用分包策略。
-
'async'仅针对异步 chunk 应用分包策略。
-
'initial'仅针对普通 chunk 应用分包策略。
-
function (chunk)函数返回
boolean值,决定是否对该 chunk 进行分包。webpack.config.js
const path = require("path") const HtmlWebpackPlugin = require("html-webpack-plugin")module.exports = {mode: "production",context: path.resolve(__dirname, "src"),entry: {index: "./index",main: "./main",},output: {filename: "[name].[contenthash:5].js",clean: true,},plugins: [new HtmlWebpackPlugin({title: "webpack-自动分包",}),],optimization: {splitChunks: {chunks(chunk) {return true // 都返回 true 相当于 'all'},},}, }
splitChunks.minChunks
类型:number,默认值:1
模块被 minChunks 个 chunk 共享才需要拆分成 chunk,优先级可能受其他设置影响。
splitChunks.minSize
类型:number,默认值:20000
生成 chunk 的最小体积(以 bytes 为单位),超过这个体积会被拆分。

可以看到 index.js + jquery 大小是 279 KiB,没有达到 400000 个字节,没有被拆分;而 main.js + lodash 大小是 531 KiB,超过 400000 个字节,所以被拆分。
splitChunks.minSizeReduction
类型:number
如果分割成一个 chunk 并没有减少主 chunk(bundle)的给定的 minSizeReduction 字节数,它将不会被分割,即使它满足 splitChunks.minSize。
为了生成 chunk,
splitChunks.minSizeReduction与splitChunks.minSize都需要被满足。

可以看到 index.js 如果拆分体积可以减少 279 KiB,没有达到 400000 个字节,即使达到 minSize,也没有拆分;再看 main.js 如果拆分体积可以减少 531 KiB,所以被拆分。
splitChunks.maxSize
类型:number,默认值: 0
使用 maxSize 告诉 webpack 尝试将大于 maxSize 个字节的 chunk 分割成较小的部分。 这些较小的部分在体积上至少为 minSize(仅次于 maxSize)。maxSize 只是一个提示,当模块大于 maxSize 或者拆分不符合 minSize 时可能会被违反。
maxSize比maxInitialRequest/maxAsyncRequests具有更高的优先级。实际优先级是maxInitialRequest/maxAsyncRequests < maxSize < minSize。
设置
maxSize的值会同时设置maxAsyncSize和maxInitialSize的值。
splitChunks.cacheGroups
类型:object
缓存组可以继承和/或覆盖来自 splitChunks.* 的任何选项。但是 test、priority 和 reuseExistingChunk 只能在缓存组级别上进行配置。将它们设置为 false以禁用任何默认缓存组。splitChunks 自带两个缓存组:
module.exports = {//...optimization: {splitChunks: {//...cacheGroups: {defaultVendors: {test: /[\\/]node_modules[\\/]/,priority: -10,reuseExistingChunk: true,},default: {minChunks: 2,priority: -20,reuseExistingChunk: true,},},},},
};
cacheGroups.{cacheGroup}.priority
类型:number ,默认值:-20
一个模块可以属于多个缓存组。优化将优先考虑具有更高 priority(优先级)的缓存组。默认组的优先级为负,以允许自定义组获得更高的优先级(自定义组的默认值为 0)。
cacheGroups.{cacheGroup}.reuseExistingChunk
类型:boolean,默认值:true
如果当前 chunk 包含已经从 main bundle 中分离出来的模块,它将被重用,而不是生成新的 module。这可能会影响块的结果文件名。
cacheGroups.{cacheGroup}.type
类型:function|RegExp|string
允许按模块类型将模块分配给缓存组处理。
webpack.config.js
module.exports = {//...optimization: {splitChunks: {cacheGroups: {json: {type: 'json',},},},},
};
cacheGroups.{cacheGroup}.test
类型:function (module, { chunkGraph, moduleGraph }) => boolean | RegExp | string
控制此缓存组选择的模块。省略它会选择所有模块。它可以匹配绝对模块资源路径或 chunk 名称。匹配 chunk 名称时,将选择 chunk 中的所有模块。
cacheGroups.{cacheGroup}.filename
类型:string | function (pathData, assetInfo) => string
仅在初始 chunk 时才允许覆盖文件名。 也可以使用 output.filename 中的所有占位符。
cacheGroups.{cacheGroup}.enforce
类型:boolean,默认值:false
告诉 webpack 忽略 splitChunks.minSize、splitChunks.minChunks、splitChunks.maxAsyncRequests 和 splitChunks.maxInitialRequests 选项,并始终为此缓存组创建 chunk。
示例
首先我们先按以下图片初始化项目

然后打包,可以看到在没有使用缓存组去提取 CSS 代码,只用 MiniCssExtractPlugin 插件去分离 CSS 代码,公共代码会被重复引用:

然后我们修改 webpack.config.js 使用缓存组去提取公共代码:

可以看到公共的 CSS 已经被拆分成独立的文件:

原理
自动分包的原理其实并不复杂,主要经过以下步骤:
- 检查每个 chunk 编译的结果
- 根据分包策略,找到那些满足策略的模块
- 根据分包策略,生成新的 chunk 打包这些模块(代码有所变化)
- 把打包出去的模块从原始包中移除,并修正原始包代码
在代码层面,有以下变动
- 分包的代码中,加入一个全局变量,类型为数组,其中包含公共模块的代码
- 原始包的代码中,使用数组中的公共代码
代码压缩
为什么要进行代码压缩
减少代码体积;破坏代码的可读性,提升破解成本;
什么时候要进行代码压缩
生产环境
使用什么压缩工具
目前最流行的代码压缩工具主要有两个:UglifyJs 和 Terser。
UglifyJs 是一个传统的代码压缩工具,已存在多年,曾经是前端应用的必备工具,但由于它不支持 ES6 语法,所以目前的流行度已有所下降。
Terser 是一个新起的代码压缩工具,支持 ES6+ 语法,因此被很多构建工具内置使用。webpack 安装后会内置 Terser,当启用生产环境后即可用其进行代码压缩。
因此,我们选择 Terser。
关于副作用 side effect
副作用:函数运行过程中,可能会对外部环境造成影响的功能
如果函数中包含以下代码,该函数叫做副作用函数:
- 异步代码
- localStorage
- 对外部数据的修改
如果一个函数没有副作用,同时,函数的返回结果仅依赖参数,则该函数叫做纯函数(pure function)
Terser
在 Terser 的官网可尝试它的压缩效果,Terser官网:https://terser.org/
我们可以通过 /*#__PURE__*/ 注释来帮助 terser。这个注释的作用是标记此语句没有副作用。这样一个简单的改变就能够 tree-shake 下面的代码了:
var Button$1 = /*#__PURE__*/ withAppProvider()(Button);
这将允许删除这段代码。但是除此之外,引入的内容可能仍然存在副作用的问题,因此需要对其进入评估。
为了解决这个问题,我们需要在 package.json 中添加 "sideEffects" 属性。
它与 /*#__PURE__*/ 类似,但是作用于模块层面,而非代码语句的层面。"sideEffects" 属性的意思是:“如果没有使用被标记为无副作用的模块的直接导出,那么捆绑器会跳过对此模块的副作用评估”。
webpack+Terser
webpack 自动集成了 Terser,如果你想更改、添加压缩工具,又或者是想对 Terser 进行配置,使用下面的 webpack 配置即可:
const TerserPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {optimization: {// 是否要启用压缩,默认情况下,生产环境会自动开启minimize: true, minimizer: [ // 压缩时使用的插件,可以有多个new TerserPlugin(), new OptimizeCSSAssetsPlugin()],},
};
Tree Shaking
代码压缩可以移除模块内部的无效代码,tree shaking 可以移除模块之间的无效代码。
背景
某些模块导出的代码并不一定会被用到
// myMath.js
export function add(a, b){console.log("add")return a+b;
}export function sub(a, b){console.log("sub")return a-b;
}
// index.js
import {add} from "./myMath"
console.log(add(1,2));
tree shaking 用于移除掉不会用到的导出。
使用
webpack2 开始就支持了 tree shaking,只要是生产环境,tree shaking 自动开启。
原理
webpack 会从入口模块出发寻找依赖关系,当解析一个模块时,webpack 会根据 ES6 的模块导入语句来判断,该模块依赖了另一个模块的哪个导出,webpack 之所以选择 ES6 的模块导入语句,是因为 ES6 模块有以下特点:
- 导入导出语句只能是顶层语句
- import 的模块名只能是字符串常量
- import 绑定的变量是不可变的
这些特征都非常有利于分析出稳定的依赖
在具体分析依赖时,webpack 坚持的原则是:保证代码正常运行,然后再尽量 tree shaking
所以,如果你依赖的是一个导出的对象,由于 JS 语言的动态特性,以及 webpack 还不够智能,为了保证代码正常运行,它不会移除对象中的任何信息。
因此,我们在编写代码的时候,尽量:
- 使用
export xxx导出,而不使用export default {xxx}导出 - 使用
import {xxx} from "xxx"导入,而不使用import xxx from "xxx"导入
依赖分析完毕后,webpack会根据每个模块每个导出是否被使用,标记其他导出为dead code,然后交给代码压缩工具处理,最终移除掉那些 dead code 代码。
使用第三方库
某些第三方库可能使用的是 commonjs 的方式导出,比如 lodash,又或者没有提供普通的 ES6 方式导出,对于这些库,tree shaking 是无法发挥作用的,因此要寻找这些库的 es6 版本,好在很多流行但没有使用的 ES6 的第三方库,都发布了它的 ES6 版本,比如 lodash-es。

可以看到使用 commonjs 方式导出的 lodash ,tree shaking 并不起作用;而使用 esmodule 导出的 lodash-es,并且使用 import {xxx} from "xxx" 方式导入,tree shaking 就会删除 dead code ,极大的减少代码体积:

作用域分析
tree shaking 本身并没有完善的作用域分析,可能导致在一些 dead code 函数中的依赖仍然会被视为依赖。
插件 webpack-deep-scope-plugin 提供了作用域分析,可解决这些问题
副作用问题
webpack 在 tree shaking 的使用,有一个原则:一定要保证代码正确运行,在满足该原则的基础上,再来决定如何 tree shaking
因此,当 webpack 无法确定某个模块是否有副作用时,它往往将其视为有副作用。
因此,某些情况可能并不是我们所想要的。
//common.js
var n = Math.random();//index.js
import "./common.js"
虽然我们根本没用有 common.js 的导出,但 webpack 担心 common.js 有副作用,如果去掉会影响某些功能。
如果要解决该问题,就需要标记该文件是没有副作用的
在 package.json 中加入sideEffects
{"sideEffects": false
}
有两种配置方式:
- false:当前工程中,所有模块都没有副作用。注意,这种写法会影响到某些 css 文件的导入
- 数组:设置哪些文件拥有副作用,例如:
["!src/common.js"],表示只要不是src/common.js的文件,都有副作用
这种方式我们一般不处理,通常是一些第三方库在它们自己的
package.json中标注
css tree shaking
webpack 无法对 css 完成 tree shaking,因为 css 跟 es6 没有任何关系,因此对 css 的 tree shaking 需要其他插件完成
例如:purgecss-webpack-plugin
注意:
purgecss-webpack-plugin对css module无能为力。
动态导入
webpack 提供了 ECMAScript 提案的 import() 语法实现动态导入。让我们先尝试以下。

打开 dist/index.html 打开控制台,然后点击按钮,可以看到 lodash 通过网络实现了动态加载;并且更重要的是 tree shaking 起作用了,lodash 代码被极大的压缩了。
预获取/预加载模块
Webpack v4.6.0+ 增加了对预获取(prefetch)和预加载(preload)的支持。
在声明 import 时,使用下面这些内置指令,可以让 webpack 输出“resource hint”,来告知浏览器:
- prefetch(预获取):将来某些导航下可能需要的资源
- preload(预加载):当前导航下可能需要资源
预获取
下面这个预获取的简单示例中,有一个按钮,然后在点击后按需加载 lodash,只需在 import() 中添加 /* webpackPrefetch: true */ 即可。

打包完成后。打开 dist/index.html 页面,在控制台中可以看到在父 chunk 完成加载时,会将 <link rel="prefetch" as="script" href="http://127.0.0.1:5500/dist/185.34d25.js"> 追加到页面头部,指示浏览器在闲置时间预获取 185.34d25.js 文件。
预加载
想要使用预加载,只需在 import() 中添加 /* webpackPreload: true */ 即可。

预获取与预加载的区别
- 预加载 chunk 会在父 chunk 加载时,以并行方式开始加载。预获取 chunk 会在父 chunk 加载结束后开始加载。
- 预加载 chunk 具有中等优先级,并立即下载。预获取 chunk 在浏览器闲置时下载。
- 预加载 chunk 会在父 chunk 中立即请求,用于当下时刻。预获取 chunk 会用于未来的某个时刻。
- 浏览器支持程度不同。
gzip
gzip 是一种压缩文件的算法
B/S结构中的压缩传输

优点:传输效率可能得到大幅提升
缺点:服务器的压缩需要时间,客户端的解压需要时间
使用 webpack 进行预压缩
使用 compression-webpack-plugin 插件对打包结果进行预压缩,可以移除服务器的压缩时间。

安装
npm i -D compression-webpack-plugin
使用
const HtmlWebpackPlugin = require("html-webpack-plugin")
const CompressionWebpackPlugin = require('compression-webpack-plugin')module.exports = {mode: "production",entry: { index: "./src/index" },output: {filename: "[name].[contenthash:5].js",clean: true,},plugins: [new HtmlWebpackPlugin({ title: "webpack-treeShaking", template: "./index.html" }),new CompressionWebpackPlugin()]
}
更多关于 compression-webpack-plugin 的配置信心请查看文档。
输效率可能得到大幅提升
缺点:服务器的压缩需要时间,客户端的解压需要时间
使用 webpack 进行预压缩
使用 compression-webpack-plugin 插件对打包结果进行预压缩,可以移除服务器的压缩时间。
[外链图片转存中…(img-SWI0oElW-1738589697670)]
安装
npm i -D compression-webpack-plugin
使用
const HtmlWebpackPlugin = require("html-webpack-plugin")
const CompressionWebpackPlugin = require('compression-webpack-plugin')module.exports = {mode: "production",entry: { index: "./src/index" },output: {filename: "[name].[contenthash:5].js",clean: true,},plugins: [new HtmlWebpackPlugin({ title: "webpack-treeShaking", template: "./index.html" }),new CompressionWebpackPlugin()]
}
更多关于 compression-webpack-plugin 的配置信心请查看文档。
