编译优化
编译分析插件
webpack-bundle-analyzer
webpack-bundle-analyzer 可以生成代码分析报告,可以直观地分析打包出的文件有哪些,及它们的大小、占比情况、各文件 Gzipped 后的大小、模块包含关系、依赖项等
npm i -D webpackbar webpack-bundle-analyzer
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
module.exports = {
// ...
plugins: [
new BundleAnalyzerPlugin(),
]
}
//package.json
"scripts": {
"analyz": "webpack-bundle-analyzer --port 8888 ./build/stats.json",
}
新版的 vue-cli 也内置了 webpack-bundle-analyzer
"scripts": {
"analyz": "vue-cli-service build --report",
},
配置:
-
analyzerMode:server / static / json / disabled
默认值:server。 在 server 模式下,分析器将启动 HTTP 服务器以显示 bundle 报告。 在 static 模式下,将生成带有 bundle 报告的单个 HTML 文件。 在 json 模式下,将生成带有捆绑报告的单个 JSON 文件。 在 disable 模式下,您可以使用此插件通过将 generateStatsFile 设置为 true 来生成 Webpack Stats JSON 文件。
-
analyzerHost:默认值:127.0.0.1。 在 server 模式下用于启动 HTTP 服务器的主机。
-
analyzerPort:默认值:8888。在 server 模式下用于启动 HTTP 服务器的端口
-
reportFilename:默认值:report.html。 在 static 模式下生成的捆绑报告文件的路径。 它可以是绝对路径,也可以是相对于 bundle 文件输出目录的路径(在 webpack 配置中是 output.path)。
-
defaultSizes:stat / parsed / gzip
默认值:parsed。 默认情况下在报告中显示的模块大小。
stat:这是文件的“输入”大小,在进行任何转换(如缩小)之前。之所以称为“stat size”,是因为它是从 Webpack 的 stats 对象中获取的。
parsed:这是文件的“输出”大小。 如果你使用的是 Uglify 之类的 Webpack 插件,那么这个值将反映代码的缩小后的大小。
gzip:这是通过 gzip 压缩运行解析的包/模块的大小。
-
openAnalyzer:默认值:true。 在默认浏览器中自动打开报告。
-
genarateStatsFile:默认值:false。 如果为 true,将在 bundle 输出目录中生成 webpack stats JSON 文件
rollup-plugin-visualizer(vite)
webpackbar
webpackbar 提供了友好的编译进度提示
const WebpackBar = require('webpackbar');
module.exports = {
// ...
plugins: [
new WebpackBar(),
]
}
speed-measure-webpack-plugin
优化 webpack 构建速度,首先需要知道是哪些插件、哪些 loader 耗时长,方便我们针对性的优化。通过 speed-measure-webpack-plugin 插件进行构建速度分析,可以看到各个 loader、plugin 的构建时长,后续可针对耗时 loader、plugin 进行优化。
npm i -D speed-measure-webpack-plugin
构建速度优化
缓存
缺点
cache-loaderhard-source-webpack-plugin
以上这些缓存方式都有首次启动时的开销,即它们会让 “冷启动” 时间会更长,但是二次启动能够节省很多时间
Webpack:cache
通过配置 webpack 持久化缓存 cache: filesystem,来缓存生成的 webpack 模块和 chunk,二次进行构建/打包时,可以直接从缓存中拉取,改善构建速度。
-
cache.type
string: 'memory' | 'filesystem'
将 cache 类型设置为内存或者文件系统。memory 选项很简单,它告诉 webpack 在内存中存储缓存
module.exports = { cache: { type: 'filesystem', // 使用文件缓存 }, }
babel-loader
babel-loader 的 options 设置中增加 cacheDirectory 属性,属性值为 true。表示:开启 babel 缓存,第二次构建时会读取之前的缓存,构建速度会更快一点。
{
test: /.js$/,
loader: 'babel-loader',
options: {
cacheDirectory: true
}
}
cache-loader
webpack.docschina.org/loaders/cac…
在一些性能开销较大的 loader 之前添加 cache-loader,将结果缓存中磁盘中。默认保存在 node_modueles/.cache/cache-loader 目录下。
module.exports = {
//...
module: {
//我的项目中,babel-loader耗时比较长,所以我给它配置了`cache-loader`
rules: [
{
test: /.jsx?$/,
use: ['cache-loader','babel-loader']
}
]
}
}
如果你跟我一样,只打算给 babel-loader 配置 cache 的话,也可以不使用 cache-loader,给 babel-loader 增加选项 cacheDirectory。
hard-source-webpack-plugin
HardSourceWebpackPlugin 和 speed-measure-webpack-plugin 不能一起使用。这个插件能正常使用的版本是
webpack5以下的版本。
npm install --save-dev hard-source-webpack-plugin
为模块提供中间缓存,缓存路径是:node_modules/.cache/hard-source
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')
module.exports = {
configureWebpack: config => {
config.plugin.push(
// 为模块提供中间缓存,缓存路径是:node_modules/.cache/hard-source
new HardSourceWebpackPlugin({
root: process.cwd(),
directories: [],
environmentHash: {
root: process.cwd(),
directories: [],
files: ['package.json', 'yarn.lock']
}
})
// 配置了files的主要原因是解决配置更新,cache不生效了的问题,配置后有包的变化,plugin会重新构建一部分cache
)
}
}
hash 缓存
防止编译文件名字重复,部署版本的时候,浏览器使用缓存文件。同时,如果编译时文件未改动,不会改变文件名和文件的
hash、chunkhash、contenthash
hash 是一整个项目,一次打包,只有一个 hash 值,是项目级的
chunhash 是从入口 entry 出发,到它的依赖,以及依赖的依赖,依赖的依赖的依赖,等等,一直下去,所打包构成的代码块(模块的集合)叫做一个 chunk,也就是说,入口文件和它的依赖的模块构成的一个代码块,被称为一个 chunk。
contenthash 是哈希只跟内容有关系,内容不变,哈希值不变。与 chunkhash 的区别可以举上面 contenthash 的例子,同时可以说明 contenthash 跟内容有关,但是 chunkhash 会考虑很多因素,比如模块路径、模块名称、模块大小、模块 id 等等。
output: {
filename: '[name].[contenthash].js', // contenthash 只有在内容发生改变才会变
path: path.resolve(__dirname, 'dist'), //输出路径 __dirname 代表当前文件的绝对路径
clean: true, //在生成文件之前清空 output 目录
},
vue-cli
configureWebpack ->
config.output.filename = `js/[name].[contenthash].js`;
config.output.chunkFilename = `js/[name].[contenthash].js`;
在提取 css 时我们也可以这么命名文件名
// css 提取
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:10].css',
}),
]
dll
将我们项目中的依赖使用 dll 插件进行动态链接,这样依赖就不会进行编译,从而极大地提高编译速度
webpack5 开箱即用的持久缓存是比 dll 更优的解决方案
将 dll 和缓存进行对比可以发现:
| 缓存 | DLL |
|---|---|
| 把常用的文件存储到内存或硬盘中 | 把公共代码打包为 dll 文件放到硬盘中 |
| 再次打包时,直接取读取缓存 | 再次打包时,读取 dll 文件,不重新打包 |
| 加载时间减少 | 打包时间减少 |
多线程
将文件解析任务分解成多个子进程并发执行,发挥多核 CPU 电脑的威力。子进程处理完任务后再将结果发送给主进程。所以可以大大提升 Webpack 的项目构建速度
happypack
happypack 同样是用来设置多线程,但是在 webpack5 就不要再使用 happypack 了,官方也已经不再维护了,推荐使用 thread-loader。
npm install happypack -D
HappyPack 参数
id: String用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件.loaders: Array用法和 webpack Loader 配置中一样.threads: Number代表开启几个子进程去处理这一类型的文件,默认是 3 个,类型必须是整数。verbose: Boolean是否允许 HappyPack 输出日志,默认是 true。threadPool: HappyThreadPool代表共享进程池,即多个 HappyPack 实例都使用同一个共享进程池中的子进程去处理任务,以防止资源占用过多。verboseWhenProfiling: Boolean开启webpack --profile, 仍然希望 HappyPack 产生输出。debug: Boolean启用 debug 用于故障排查。默认false。
//提升 Webpack 构建速度
const HappyPack = require('happypack');
//安装 OS 模块 这个主要是拿到当前电脑的CPU核数
const os = require('os');
//这个是设置共享线程池中的数量 size 控制设置数量 类型 只能是 整数类型
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
module.exports = {
module: {
rules: [
{
test: /.js$/,
//把对.js 的文件处理交给id为happyBabel 的HappyPack 的实例执行
loader: 'happypack/loader?id=happyBabel',
//排除node_modules 目录下的文件
exclude: /node_modules/
},
{
test: /.(css|less)$/,
use: 'happypack/loader?id=styles'
},
]
},
plugins: [
new HappyPack({
//用id来标识 happypack处理那里类文件
id: 'happyBabel',
//用法和loader 的配置一样
loaders: [{
loader: 'babel-loader?cacheDirectory=true',
}],
//共享进程池
threadPool: happyThreadPool,
//允许 HappyPack 输出日志
verbose: true,
}),
new HappyPack({
id: 'styles',
loaders: [ 'style-loader', 'css-loader', 'less-loader' ],
//共享进程池
threadPool: happyThreadPool,
});
]
}
vue
//把对.js 的文件处理交给id为happyBabel 的HappyPack 的实例执行
// config.module.rule('js').test(/.js$/)
// .include.add('/src/').end()
// .exclude.add('/node_modules/').end()
// .use().loader('happypack/loader?id=happyBabel').end()
thread-loader
-
Webpack
npm install --save-dev thread-loader
const path = require("path"); module.exports = { module: { rules: [ { test: /.js$/, include: path.resolve('src'), use: [ "thread-loader", // 耗时的 loader (例如 babel-loader) ], }, ], }, }; -
Vue-Cli 已经内置
thread-loader,thread-loader 会在多核 CPU 的机器上为Babel/TypeScript转译开启。module.exports = { parallel: true, }-
Type:
boolean -
Default:
require('os').cpus().length > 1是否为 Babel 或 TypeScript 使用
thread-loader。该选项在系统的 CPU 有多于一个内核时自动启用,仅作用于生产构建。
-
缩小文件检索解析范围
Resolve解析模块路径
-
alias:为避免无用的检索与递归遍历,可以使用 alias 指定引用时候的模块
-
extensions:extensions字段用来在导入模块时,自动带入后缀尝试去匹配对应的文件。由于 webpack 的解析顺序是从左到右,因此要将使用频率高的文件类型放在左侧,如下我将
tsx放在最左侧module.exports = { resolve: { extensions: ['.tsx', '.js'], // 因为我的项目只有这两种类型的文件,如果有其他类型,需要添加进去。 } }
noParse
一些第三方模块没有使用AMD/CommonJs规范,可以使用noParse来标记这个模块,这样Webpack在导入模块时,就不进行解析和转换,提升Webpack的构建速度
//可以接受一个正则表达式或者一个函数
{
module: {
//noParse: /jquery|lodash|chartjs/,
noParse: function(content){
return /jquery|lodash|chartjs/.test(content)
}
}
}
include/exclude
include表示哪些目录中的文件需要进行babel-loader,exclude表示哪些目录中的文件不要进行babel-loader。这是因为在引入第三方模块的时候,很多模块已经是打包后的,不需要再被处理,比如vue、jQuery等;如果不设置include/exclude就会被loader处理,增加打包时间。
{
rules: [{
test: /.js$/,
use: {
loader: 'babel-loader'
},
// exclude: /node_modules/,
include: [path.resolve(__dirname, 'src')]
}]
}
预加载
Preload和Prefetch是两种优化前端性能的技术,它们可以让浏览器在某些条件下提前加载一些资源,从而加快应用程序的加载速度。
-
Preload告诉浏览器立即加载资源。Preload可以使用rel="preload"属性来实现,比如:
<link rel="preload" href="path/to/resource" as="type"/>href表示需要预加载的资源路径,as属性指定预加载资源的类型
-
Prefetch告诉浏览器在空闲时才开始加载资源。Prefetch的实现方式是通过添加rel="prefetch"属性来实现
<link rel="prefetch" href="path/to/resource"/>
需要注意的是,Preload和Prefetch都不是浏览器强制加载资源,而是给浏览器提供了一些提示,让它在空闲时间主动加载一些资源,从而提升应用程序的性能体验。这两项技术它们的兼容性不是很好
prefetch
下面这个 prefetch 的简单示例中,有一个 HomePage 组件,其内部渲染一个 LoginButton 组件,然后在点击后按需加载 LoginModal 组件。
//...
import(/* webpackPrefetch: true */ './path/to/LoginModal.js');
这会生成 <link rel="prefetch" href="login-modal-chunk.js"> 并追加到index.html,指示着浏览器在闲置时间预取 login-modal-chunk.js 文件。
代码分离
代码分离 code splitting 把代码分离到不同的 bundle( Chunk 是打包产物的基本组织单位) 中用于获取更小的 bundle,然后可以按需加载或并行加载这些文件,控制资源加载优先级,提供代码的加载性能
- 「资源冗余」:客户端必须等待整个应用的代码包都加载完毕才能启动运行,但可能用户当下访问的内容只需要使用其中一部分代码
- 「缓存失效」:将所有资源达成一个包后,所有改动 —— 即使只是修改了一个字符,客户端都需要重新下载整个代码包,缓存命中率极低
常用的代码分离方法有三种:
- 入口起点:使用
entry配置手动地分离代码。 - 防止重复:使用
Entry dependencies或者SplitChunksPlugin去重和分离chunk。 - 动态导入:通过模块的 内联函数
import调用来分离代码。
optimization
optimization 用于自定义 webpack 的内置优化配置,一般用于生产模式提升性能,常用配置项如下:
-
minimize:是否压缩代码,默认为 true。
-
minimizer:配置压缩工具,常用的压缩工具有 UglifyJS、TerserJS 和 CSSMinimizerPlugin 等
-
splitChunks:代码分割,将公共代码提取出来,避免重复打包。
-
chunks 指的是分离包的作用范围。"initial"(同步包) | "all"(推荐,同步或异步包) | "async" (默认就是async,异步包)
-
cacheGroups:通过
cacheGroups,我们可以定义自定义 chunk 组,通过test条件对模块进行过滤,符合条件的模块分配到相同的组。- test: /[/]node_modules[/]/
- name: 'vendors', // 打包出来的文件名
lodash: { // 针对lodash的特定规则 test: /[\/]node_modules[\/]lodash[\/]/, name: 'lodash', // 自定义文件名 chunks: 'all', },
-
-
runtimeChunk:除业务代码外,Webpack 编译产物中还需要包含一些用于支持 webpack 模块化、异步加载等特性的支撑性代码,这类代码在 webpack 中被统称为
runtime。虽然每段运行时代码可能都很小,但随着特性的增加,最终结果会越来越大,特别对于多 entry 应用,在每个入口都重复打包一份相似的运行时代码显得有点浪费,为此 webpack 5 专门提供了entry.runtime配置项用于声明如何打包运行时代码。将运行时代码单独打包成一个文件,避免重复打包。- 模块解析:运行时代码负责解析模块的依赖关系,确定哪些模块需要加载。
- 模块加载:负责加载模块,包括初始加载和按需加载的模块。
- 缓存管理:帮助管理浏览器缓存,确保模块在更新后能够正确加载。
- 热模块替换:在开发环境中,支持热模块替换(HMR),允许在不刷新页面的情况下更新模块1。
在多 entry 场景中,只要为每个 entry 都设定相同的 runtime 值,webpack 运行时代码最终就会集中写入到同一个 chunk,例如对于如下配置:
module.exports = { entry: { index: { import: "./src/index", runtime: "solid-runtime" }, home: { import: "./src/home", runtime: "solid-runtime" }, } }; -
usedExports:是否只导出被使用的代码。
-
sideEffects:是否开启副作用标记,用于 tree shaking。
-
concatenateModules:是否开启模块合并。
-
emitOnErrors:是否在编译出错时生成文件
module.exports = {
optimization: {
runtimeChunk: 'single',
minimizer: [
new CssMinimizerPlugin(),
],
splitChunks: {
chunks: 'all',
minSize: 30000,
maxSize: 0,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
name: true,
cacheGroups:{
vendors:{ //node_modules里的代码
test: /[\/]node_modules[\/]/,
chunks: "all",
name: 'vendors', //chunks name
priority: 10, //优先级
enforce: true
}
}
},
},
}
多入口起点
src/index.js
console.log('Hello world!');
src/another-module.js
import _ from 'lodash'
console.log(_.join(['another', 'module', 'chunk'], ' '));
这个模块依赖了 lodash ,需要安装一下:
npm install lodash
webpack.config.js
module.exports = {
mode: 'development',
entry: { // 配置多入口文件
index: './src/index.js',
another: './src/another_module.js'
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist'),
},
}
执行 webpack 命令,可以看到报错了Error: Entry index depends on common_chunk, but this entry was not found
这个错误表明发生了冲突,多个入口文件打包后出现了相同的 filename,所以我们要对多个入口文件设置多个出口不同文件名文件
module.exports = {
mode: 'development',
entry: {
index: './src/index.js',
another: './src/another_module.js'
},
output: {
filename: '[name].bundle.js', // 对应多个出口文件名,name对应的是entry的属性名 对应index和another
path: path.resolve(__dirname, './dist'),
},
}
执行 webpack 命令,可以看到不报错了,并且 dist 输出了两个 js 文件
文件 another.bundle.js 来源于 entry.another,即 src/another.js,文件大小为 554kb,因为被 lodash 被打包进去了
文件 index.bundle.js 来源于 entry.index,即 src/index.js,文件大小为 1.21kb
防止重复
如果我们的其他入口也需要使用 lodash 呢?
src/index.js
import _ from 'lodash'
console.log(_.join(['index', 'module', 'chunk'], ' '));
lodash 在两个引用文件中都被打包了,我们期望 lodash 应该是公用的
配置 dependOn option 选项,这样可以在多个 chunk 之间共享模块
module.exports = {
mode: 'development',
entry: {
index: {
import: './src/index.js', // 启动时需加载的模块
dependOn: 'common_chunk', // 当前入口所依赖的入口
},
another: {
import: './src/another_module.js',
dependOn: 'common_chunk',
},
common_chunk: 'lodash' // 当上面两个模块有lodash这个模块时,就提取出来并命名为common chunk
},
output: {
filename: '[name].bundle.js', // 对应多个出口文件名
path: path.resolve(__dirname, './dist'),
},
}
执行 webpack 命令,可以看到打包结果
已经提取出来 common_chunk.bundle.js,即为提取打包了 lodash 公用模块。index.bundle.js another.bundle.js 体积也变小
SplitChunksPlugin
但是不能每有一个依赖就需要配置common_chunk
SplitChunksPlugin 插件可以将公共的依赖模块提取到已有的 chunk 中,或者提取到一个新生成的 chunk。
webpack.config.js
module.exports = {
entry: { // 多入口
index: './src/index.js',
another: './src/another_module.js',
},
output: {
filename: '[name].bundle.js', // 对应多个出口文件名
path: path.resolve(__dirname, './dist'),
},
optimization: {
splitChunks: { // 代码分割
chunks: 'all'
}
},
}
使用 optimization.splitChunks 配置选项之后,现在应该可以看出,index.bundle.js 和 another.bundle.js 中已经移除了重复的lodash依赖模块。
动态导入/按需加载
概念
webpack的按需加载是一种优化技术,允许应用在需要某个资源或模块时才进行加载,而不是在初始加载时加载所有资源。这样可以显著提高应用的启动速度,减少初始加载的资源体积,节省带宽和流量。
import() 动态的加载模块。调用 import 的之处,被视为分割点,即被请求的模块和它引用的所有子模块,会分割到一个单独的 chunk 中。
注意当调用 ES6 模块的 import() 方法(引入模块)时,必须指向模块的 .default 值,因为它才是 promise 被处理后返回的实际的 module 对象。
function(string path):Promise
chunk 模块命名
-
设置webpackChunkName: "name":这是webpack动态导入模块命名的方式,打包会生成对应模块名的文件
//webpack.config.js output: { filename: "js/[name].bundle.js", chunkFilename: "js/[name].chunk-test.js",//打包动态导入代码 path: path.join(__dirname, 'dist'), clean: true, },const useDynamicImport = await import( /* webpackChunkName: "dynamicImport" */ './dynamicImport.js') -
如果不设置,加载的脚本将被按数字次序命名
场景
-
路由懒加载
-
封装一个 component.js,返回一个 component 对象;
-
按需加载
//index.js setTimeout(async function () { //文件会等5秒后加载 // webpackChunkName: "name":这是webpack动态导入模块命名的方式 const useDynamicImport = await import( /* webpackChunkName: "dynamicImport" */ './dynamicImport.js') console.log(useDynamicImport.default(1, 2)) }, 2000) //dynamicImport.js export default function add(a,b){ return a+b; }
babel-plugin-dynamic-import-node
babel-plugin-dynamic-import-node 是一个 Babel 插件,它用于将动态 import() 语句转换为 CommonJS 的 require() 调用。如果你在使用 Node.js 并希望利用动态导入的特性(比如在打包工具中或在某些构建场景下),你可以通过使用此插件
-
Node.js 环境支持:
- 在 Node.js 环境中,动态
import()语法可能不被原生支持(特别是在较旧版本的 Node.js 中)。这个插件可以将动态import()转换为 Node.js 的require()调用,从而在 Node.js 环境中正常工作。
- 在 Node.js 环境中,动态
-
开发环境优化:
- 在开发环境中,特别是当你需要快速启动服务器或进行热重载时,使用
require()可以减少启动时间,因为require()是同步加载模块,而动态import()是异步加载。
- 在开发环境中,特别是当你需要快速启动服务器或进行热重载时,使用
-
代码兼容性:
- 如果你的项目需要在不同环境下运行,并且某些环境不支持动态
import(),这个插件可以帮助你确保代码在这些环境中也能正常运行。
- 如果你的项目需要在不同环境下运行,并且某些环境不支持动态
-
安装:npm install --save-dev babel-plugin-dynamic-import-node
-
配置
-
vue-cli3
修改 babel.config.js 文件
module.exports = { presets: ["@vue/cli-plugin-babel/preset"], env: { development: { plugins: ["dynamic-import-node"] } } }; -
vue.cli2
.babelrc 文件
"env": { "test": { "plugins": [] }, "development":{ "presets": ["env", "stage-2"], "plugins": ["dynamic-import-node"] } }
-
打包优化
按需打包
在主文件或者组件文件中引入其他模块中的代码,但实际上我们只用其中的一部分,剩下的代码则不需要引入
Tree Shaking
cloud.tencent.com/developer/a…
tree shaking 是一个术语,用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块语法的 静态结构 特性,例如 import 和 export
Tree Shaking 只支持 ESM 的引入方式,不支持 Common JS 的引入方式。
- ESM: export + import
- Common JS: module.exports + require
开发环境下的配置
// webpack.config.js
module.exports = {
// ...
mode: 'development',
optimization: {
usedExports: true,
}
};
生产环境下的配置
// webpack.config.js
module.exports = {
// ...
mode: 'production',
};
sideEffects
- sideEffects: 默认为 true, 告诉 Webpack ,所有文件都有副作用,他们不能被 Tree Shaking。
- sideEffects: 为 false 时,告诉 Webpack ,没有文件是有副作用的,他们都可以 Tree Shaking
- sideEffects: 为一个数组时,告诉 Webpack ,数组中那些文件不要进行 Tree Shaking,其他的可以 Tree Shaking
如果你的代码确实有一些副作用,可以改为提供一个数组:
{
"name": "your-project",
"sideEffects": ["./src/some-side-effectful-file.js"]
}
所有导入文件都会受到 tree shaking 的影响。这意味着,如果在项目中使用类似 css-loader 并 import 一个 CSS 文件,则需要将其添加到 side effect 列表中,以免在生产模式中无意中将它删除:
{
"name": "your-project",
"sideEffects": ["./src/some-side-effectful-file.js", "*.css"]
}
//或者
rules: [
{
test: /.css$/i,
use: ["style-loader", "css-loader"],
sideEffects: true
}
]
IgnorePlugin
- 这是 webpack 内置插件
- 这个插件的作用是:忽略第三方包指定目录,让这些指定目录不要被打包进去
//虽然我设置了语言为中文,但是在打包的时候,是会将所有语言都打包进去的。这样就导致包很大,打包速度又慢
plugins:[
new Webpack.IgnorePlugin(/./locale/,/moment/),//moment这个库中,如果引用了./locale/目录的内容,就忽略掉,不会打包进去
]
lodash
-
使用时直接访问对应的方法文件。但是有缺点,lodash.com/per-method-…
//按需打包 import trim from 'lodash/trim' // or // const trim = require('lodash/trim.js') console.log(trim(' 123123 ')) -
通过lodash-es,Lodash 提供 lodash-es 版本以支持 Tree Shaking。
-
babel-plugin-lodash, & lodash-webpack-plugin
import _ from 'lodash' import { add } from 'lodash/fp' const addOne = add(1) _.map([1, 2, 3], addOne) //roughly to import _add from 'lodash/fp/add' import _map from 'lodash/map' const addOne = _add(1) _map([1, 2, 3], addOne)
babel
console 移除
babel-plugin-transform-remove-console 插件,配置在 babel.config.js 中,vue-cli5 实测可行,vue-cli3,4 也可行。(尝试后,谷歌浏览器控制台仅 websocket 的打印输出未清除,其他打印输出都是清除干净了的)
下载依赖 npm install babel-plugin-transform-remove-console -D
babel.config.js 中
const proPlugins = [];
// 判断环境
if (process.env.NODE_ENV === 'production') {
proPlugins.push('transform-remove-console');
}
module.exports = {
plugins: [...proPlugins],
};
辅助代码
Babel 为编译的每个文件都插入了辅助代码,使代码体积过大!默认情况下会被添加到每一个需要它的文件中。可以将这些辅助代码作为一个独立模块,来避免重复引入。
@babel/plugin-transform-runtime: 禁用了 Babel 自动对每个文件的 runtime 注入,而是引入 @babel/plugin-transform-runtiome 并且使所有辅助代码从这里引用
npm i @babel/plugin-transform-runtime -D
js 压缩
使用 TerserPlugin 来压缩和丑化 JavaScript 文件,移除无用代码、空白符等,减少体积
webpack5 自带最新的 terser-webpack-plugin,无需手动安装。
terser-webpack-plugin 默认开启了 parallel: true 配置,并发运行的默认数量: os.cpus().length - 1 ,使用多进程并发运行压缩以提高构建速度。
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
test: /.js(?.*)?$/i,
parallel: true,
extractComments: true,
sourceMap: config.build.productionSourceMap,
terserOptions: {
output: {
// 是否输出可读性较强的代码,即会保留空格和制表符,默认为输出,为了达到更好的压缩效果,可以设置为false
beautify: false,
// 是否保留代码中的注释,默认为保留,为了达到更好的压缩效果,可以设置为false
comments: false
},
compress: {
// 是否在UglifyJS删除没有用到的代码时输出警告信息,默认为输出,可以设置为false关闭这些作用不大的警告
warnings: false,
// 是否删除代码中所有的console语句,默认为不删除,开启后,会删除所有的console语句
drop_console: true,
drop_debugger: true,
// 是否内嵌虽然已经定义了,但是只用到一次的变量,比如将 var x = 1; y = x, 转换成 y = 5, 默认为不转换,为了达到更好的压缩效果,可以设 置为false
collapse_vars: true,
// 是否提取出现了多次但是没有定义成变量去引用的静态值,比如将 x = 'xxx'; y = 'xxx' 转换成var a = 'xxxx'; x = a; y = a; 默认为 不转换,为了达到更好的压缩效果,可以设置为false
reduce_vars: true,
pure_funcs: ['console.log'] // 移除console
}
}
}),
]
}
}
vue-cli
config.optimization.minimize(true)// 开启压缩js代码
config.optimization.splitChunks({ // 开启代码分割
chunks: 'all'
})
const TerserPlugin = require("terser-webpack-plugin")
chainWebpack: config => {
config.optimization.minimize(true);// 开启压缩js代码
config.optimization.minimize(new TerserPlugin({
terserOptions:{
compress:{
warnings: false,
drop_console: true,
drop_debugger: true,
pure_funcs: ["console.log"]
}
}
}));
config.optimization.splitChunks({// 开启代码分割
chunks: 'all',
});
}
css
css 抽离
mini-css-extract-plugin 插件会将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 和 SourceMaps 的按需加载
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module: {
rules: [
{
test: /.css$/,
use: [
// "style-loader",
MiniCssExtractPlugin.loader,
"css-loader",
// 如果想要启用 CSS 模块化,可以为 css-loader 添加 modules 参数即可
],
},
],
plugins: [
new MiniCssExtractPlugin({
filename: "css/[name].[hash].css", // 定义抽离的入口文件的文件名
chunkFilename: "css/[name].[hash].css", // 定义非入口块文件的名称,如动态导入的文件
}),
],
},
css 压缩
这将仅在 mode: production 生产环境 开启 CSS 优化
如果还想在 开发环境 下启用 CSS 优化,optimization.minimize 设置为 true
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
module.exports = {
module: {
rules: [
{
test: /.(css|less)$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "less-loader"],
},
],
},
+ optimization: {
+ minimizer: [
+ new CssMinimizerPlugin(),
+ ],
+ },
};
img 压缩
image-minimizer-webpack-plugin: 用来压缩图片的插件
npm i image-minimizer-webpack-plugin imagemin -D 还有剩下包需要下载,有两种模式:
无损压缩 npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo -D 有损压缩 npm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo -D
gzip 压缩
前端将文件打包成 .gz 文件,然后通过 nginx 的配置,让浏览器直接解析 .gz 文件,可以大大提升文件加载的速度,浏览器可以直接解析 .gz 文件并解压。
启用gzip压缩(需要配置nginx,可以看出压缩后的文件大小明显变化)
highlighter- PHP
const CompressionWebpackPlugin = require('compression-webpack-plugin')
chainWebpack(config) {
// 生产模式下启用gzip压缩 需要配置nginx支持gzip
if (process.env.NODE_ENV === 'production') {
config.plugin('CompressionWebpackPlugin').use(CompressionWebpackPlugin, [
{
filename: '[path][base].gz',
algorithm: 'gzip',
test: new RegExp('\.(js|css)$'),
// 只处理大于xx字节 的文件,默认:0
threshold: 10240,
// 示例:一个1024b大小的文件,压缩后大小为768b,minRatio : 0.75
minRatio: 0.8, // 默认: 0.8
// 是否删除源文件,默认: false
deleteOriginalAssets: false
}
])
}
}
配置 CDN
线上使用 cdn , 如何库有问题,项目就会有问题,除非公司有自己的 cdn 库。它的配置也很简单,在 externals 中配置
module.exports = {
configureWebpack: config => {
if (process.env.NODE_ENV === 'production') {
// 配置 cdn,这里将 vue,vue-router 和 axios 三个包配置成 cdn 引入
// 其中 Vue,VueRouter 等名称是该库暴露在全局中的变量名
config.externals = {
vue: 'Vue',
'vue-router': 'VueRouter',
axios: 'axios'
}
}
}
}
然后在 public/index.html 模板文件中引入 cdn 地址:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
<title></title>
<!-- 引入 cdn 地址 -->
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.5.10/vue.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/vue-router/3.0.1/vue-router.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.18.0/axios.min.js"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>
我这里使用的是 bootcdn 的地址,需要注意版本问题。
也可以 借助 HtmlWebpackPlugin 插件 来方便插入 cdn 的引入。
使用 cdn 引入的方式虽然能极大改善网页加载速度,但我还是不会用这个功能,项目还不需要非得这样的优化,也怕 cdn 不稳定。
借助 HtmlWebpackPlugin 插件来方便插入 cdn 的引入
//生产环境标记
const IS_PRODUCTION = process.env.NODE_ENV === "production";
const path = require("path");
// 生产配置
const cdn_production = {
js: ["/librarys/vue@2.6.11/vue.min.js"]
};
// 开发配置
const cdn_development = {
js: ["/librarys/vue@2.6.11/vue.js"]
};
module.exports = {
configureWebpack: {
externals: {
vue: "Vue",
},
},
chainWebpack: config => {
config.plugin("html").tap(args => {
args[0].cdn = IS_PRODUCTION ? cdn_production : cdn_development;
return args;
});
}
};
index.html 中添加
<% for (var i in htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.js) { %>
<script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
<% } %>
vite
不需要配置 externals,且开发环境还是要下载依赖
-
依赖安装 npm i vite-plugin-cdn-import -D
-
使用方法及引入
importToCDN({ // prodUrl:可选,默认指向 https://cdn.jsdelivr.net/npm/{name}@{version}/{path} modules: [ { name: 'jquery', var: 'jQuery', version: '3.6.4', path: 'dist/jquery.min.js' } // { // name: 'element-plus', // // ElementPlus 为什么不是同下面第二种配置的elementPlus是因为这个配置同CDN资源一致,而下面的配置同需同main.ts的引入名称一致 // var: 'ElementPlus', // 外部化的依赖提供一个全局变量 同rollupOptions配置中的globals的值 // // https://unpkg.com/element-plus@2.2.32/dist/index.full.js 或者 dist/index.full.js // path: 'dist/index.full.js', // // 可选 // css: 'dist/index.css' // }, ] })
使用第三方库的精简版本
避免在生产环境下才会用到的工具
某些 utility, plugin 和 loader 都只用于生产环境。例如,在开发环境下使用 TerserPlugin 来 minify(压缩) 和 mangle(混淆破坏) 代码是没有意义的。通常在开发环境下,应该排除以下这些工具:
TerserPlugin[fullhash]/[chunkhash]/[contenthash]AggressiveSplittingPluginAggressiveMergingPluginModuleConcatenationPlugin