编译分析插件
webpack-bundle-analyzer
webpack-bundle-analyzer
可以生成代码分析报告,可以直观地分析打包出的文件有哪些,及它们的大小、占比情况、各文件 Gzipped 后的大小、模块包含关系、依赖项等
npm i -D webpackbar webpack-bundle-analyzer
javascript
复制代码
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
json
复制代码
"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 文件
webpackbar
webpackbar
提供了友好的编译进度提示
ini
复制代码
const WebpackBar = require('webpackbar'); module.exports = { // ... plugins: [ new WebpackBar(), ] }
speed-measure-webpack-plugin
优化 webpack 构建速度,首先需要知道是哪些插件、哪些 loader 耗时长,方便我们针对性的优化。通过 speed-measure-webpack-plugin 插件进行构建速度分析,可以看到各个 loader、plugin 的构建时长,后续可针对耗时 loader、plugin 进行优化。
css
复制代码
npm i -D speed-measure-webpack-plugin
构建速度优化
缓存
Webpack
Webpack 中几种缓存方式:
cache-loader
hard-source-webpack-plugin
以上这些缓存方式都有首次启动时的开销,即它们会让 “冷启动” 时间会更长,但是二次启动能够节省很多时间
babel-loader
babel-loader的options设置中增加cacheDirectory属性,属性值为true。表示:开启babel缓存,第二次构建时会读取之前的缓存,构建速度会更快一点。
yaml
复制代码
{ test: /.js$/, loader: 'babel-loader', options: { cacheDirectory: true } }
cache-loader
webpack.docschina.org/loaders/cac…
在一些性能开销较大的 loader 之前添加 cache-loader,将结果缓存中磁盘中。默认保存在 node_modueles/.cache/cache-loader 目录下。
java
复制代码
module.exports = { //... module: { //我的项目中,babel-loader耗时比较长,所以我给它配置了`cache-loader` rules: [ { test: /.jsx?$/, use: ['cache-loader','babel-loader'] } ] } }
如果你跟我一样,只打算给 babel-loader 配置 cache 的话,也可以不使用 cache-loader,给 babel-loader 增加选项 cacheDirectory。
持久化缓存
通过配置 webpack 持久化缓存 cache: filesystem
,来缓存生成的 webpack 模块和 chunk,改善构建速度。
简单来说,通过 cache: filesystem
可以将构建过程的 webpack 模板进行缓存,大幅提升二次构建速度、打包速度,当构建突然中断,二次进行构建时,可以直接从缓存中拉取,可提速 90% 左右。
java
复制代码
module.exports = { cache: { type: 'filesystem', // 使用文件缓存 }, }
hard-source-webpack-plugin
HardSourceWebpackPlugin 和 speed-measure-webpack-plugin 不能一起使用
vue
cache-loader
Vue-Cli自带cache-loader ,会默认为 Vue/Babel/TypeScript
编译开启。文件会缓存在 node_modules/.cache
中。
cache-loader
进行以下两个的缓存了
- babel-loader 的 cacheDirectory 标志
- vue-loader 的 cacheDirectory 标志
hard-source-webpack-plugin
这个插件能正常使用的版本是
webpack5
以下的版本。
npm install --save-dev hard-source-webpack-plugin
为模块提供中间缓存,缓存路径是:node_modules/.cache/hard-source
php
复制代码
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等等。
lua
复制代码
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
复制代码
// css 提取 plugins: [ new MiniCssExtractPlugin({ filename: 'css/[name].[contenthash:10].css', }), ]
dll
将我们项目中的依赖使用dll插件进行动态链接,这样依赖就不会进行编译,从而极大地提高编译速度
webpack5 开箱即用的持久缓存是比 dll 更优的解决方案
将dll和缓存进行对比可以发现:
缓存 | DLL |
---|---|
把常用的文件存储到内存或硬盘中 | 把公共代码打包为dll文件放到硬盘中 |
再次打包时,直接取读取缓存 | 再次打包时,读取dll文件,不重新打包 |
加载时间减少 | 打包时间减少 |
autodll-webpack-plugin
多线程
将文件解析任务分解成多个子进程并发执行,发挥多核 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
。
javascript
复制代码
//提升 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
lua
复制代码
//把对.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
javascript复制代码
const path = require("path"); module.exports = { module: { rules: [ { test: /.js$/, include: path.resolve('src'), use: [ "thread-loader", // 耗时的 loader (例如 babel-loader) ], }, ], }, };
-
Vue-Cli已经内置
javathread-loader
,thread-loader 会在多核 CPU 的机器上为Babel/TypeScript
转译开启。复制代码
module.exports = { parallel: true, }
-
Type:
boolean
-
Default:
require('os').cpus().length > 1
是否为 Babel 或 TypeScript 使用
thread-loader
。该选项在系统的 CPU 有多于一个内核时自动启用,仅作用于生产构建。
-
缩小文件检索解析范围
alias
- 为避免无用的检索与递归遍历,可以使用alias指定引用时候的模块
extensions
-
extensions 表示需要解析的文件类型列表。
根据项目中的文件型,定义 extensions,以覆盖 webpack 默认的 extensions,加快解析速度。
由于 webpack 的解析顺序是从左到右,因此要将使用频率高的文件类型放在左侧,如下我将
javatsx
放在最左侧复制代码
module.exports = { resolve: { extensions: ['.tsx', '.js'], // 因为我的项目只有这两种类型的文件,如果有其他类型,需要添加进去。 } }
noParse
- noParse,对不依赖本地代码的第三方依赖不进行解析,比如CDN引用的第三方依赖。
include
- 为 loader 指定 include,减少 loader 应用范围,仅应用于最少数量的必要模块,。
import优化
运用这个插件能在代码使用了import语法的情况下,大大提高代码的编译速度。
安装 babel-plugin-dynamic-import-node
kotlin
复制代码
npm install --save-dev babel-plugin-dynamic-import-node
vue-cli3
修改babel.config.js文件
css
复制代码
module.exports = { presets: ["@vue/cli-plugin-babel/preset"], env: { development: { plugins: ["dynamic-import-node"] } } };
vue.cli2
.babelrc文件
json
复制代码
"env": { "test": { "plugins": [] }, "development":{ "presets": ["env", "stage-2"], "plugins": ["dynamic-import-node"] } }
打包体积优化
某些 utility, plugin 和 loader 都只用于生产环境。例如,在开发环境下使用 TerserPlugin
来 minify(压缩) 和 mangle(混淆破坏) 代码是没有意义的。通常在开发环境下,应该排除以下这些工具:
TerserPlugin
[fullhash]
/[chunkhash]
/[contenthash]
代码分离
www.cnblogs.com/Mr-Hou88888…
blog.csdn.net/qq_41887214…
代码分离code splitting是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的 bundle,加快打包速度,以及控制资源加载优先级,如果使用合理,会极大影响加载时间。
多入口起点
入口起点(entry points)
src/index.js
arduino
复制代码
console.log('Hello world!');
src/another-module.js
javascript
复制代码
import _ from 'lodash' console.log(_.join(['another', 'module', 'chunk'], ' '));
这个模块依赖了 lodash ,需要安装一下:
复制代码
npm install lodash
webpack.config.js
css
复制代码
module.exports = { mode: 'development', entry: { // 配置多入口文件 index: './src/index.js', another: './src/another_module.js' }, output: { filename: 'bundle.js', path: path.resolve(__dirname, './dist'), }, }
执行webpack
命令,可以看到报错了 ̄□ ̄||
css
复制代码
module.exports = { mode: 'development', entry: { index: './src/index.js', another: './src/another_module.js' }, output: { filename: '[name].bundle.js', // 对应多个出口文件名 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
呢?
javascript
复制代码
src/index.js import _ from 'lodash' console.log(_.join(['index', 'module', 'chunk'], ' '));
lodash
在两个引用文件中都被打包了,我们期望lodash
应该是公用的
配置 dependOn option
选项,这样可以在多个 chunk 之间共享模块
css
复制代码
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这个模块时,就提取出来并命名为shared chunk }, output: { filename: '[name].bundle.js', // 对应多个出口文件名 path: path.resolve(__dirname, './dist'), }, }
执行webpack
命令,可以看到打包结果
已经提取出来common_chunk.bundle.js
,即为提取打包了lodash
公用模块
index.bundle.js
another.bundle.js
体积也变小
分离 Vendor
chunk-vendors.js :顾名思义,chunk-vendors.js 是捆绑所有不是自己的模块,而是来自其他方的模块的捆绑包,它们称为第三方模块或供应商模块。
通常,它意味着(仅和)来自项目 /node_modules 目录的所有模块,会将所有 /node_modules 中的第三方包打包到 chunk-vendors.js 中。
将所有的第三方包集中到一个文件,自然也会出现文件过大的问题。
可以看到,当前只有一个 chunk 也就是 app.js ,他是一个 entry chunk 。因为我们的 webpack 配置是这样子的:
java
复制代码
// webpack.config.js module.exports = { entry: { app: './src/main.js', // entry chunk } }
app.js 包含了我们的第三方库 vue 和 axios ,以及我们的业务代码 src 。
分离 Vendor,最简单方法就是:加一个 entry ( File Changes ):
java
复制代码
// webpack.config.js module.exports = { entry: { app: './src/main.js', vendor: ['vue', 'axios'], }, }
虽然 vendor.js 这个 entry chunk 包含了我们想要的 vue 和 axios ,但是细心的同学会发现, app.js 也包含了他们!为什么!?
其实这是很正常的事情:每个 entry 都包含了他自己的依赖,这样他才能作为一个入口,独立地跑起来。
很难受,事实上我们并不想 app.js 还包含了 vue 和 axios 。如果可以把他们俩相同的依赖提取出来就好了,就像这样:
SplitChunksPlugin
SplitChunksPlugin 插件可以将公共的依赖模块提取到已有的 chunk 中,或者提取到一个新生成的 chunk。让我们使用这个插件,将之前的示例中重复的 lodash 模块去除:
webpack.config.js
css
复制代码
module.exports = { entry: { // 多入口 index: './src/index.js', another: './src/another_module.js', }, output: { filename: '[name].bundle.js', // 对应多个出口文件名 path: path.resolve(__dirname, './dist'), }, optimization: { splitChunks: { // 代码分割 // include all types of chunks chunks: 'all' } }, }
使用 optimization.splitChunks 配置选项之后,现在应该可以看出,index.bundle.js
和 another.bundle.js
中已经移除了重复的依赖模块。需要注意的是,插件将 lodash
分离到单独的 chunk,并且将其从 main bundle 中移除,减轻了大小
CommonsChunkPlugin(已废弃)
现在,修改我们的 webpack 配置文件( File Changes ):
php
复制代码
new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', })
但是!随着业务的增长,我们依赖的第三方库代码很可能会越来越多,这时候我们的 webpack.config.js 就变成这样了:
java
复制代码
module.exports = { entry: { app: './src/main.js', vendor: [ 'vue', 'axio', 'vue-router', 'vuex', 'element-ui', // 很长很长 ], }, }
vendor entry 会变成很长很长,更糟糕的是,我们每次引入了新的第三方库,都需要在 vendor 手动增加对应的包名。
动态导入懒加载
- import() 为动态加载脚本,webpack 会生成类似以上动态创建
script
标签的代码 - import 里的注释为特殊含义的魔法注释,如果不设置 webpackChunkName,加载的脚本将被按数字次序命名
如果我们想「按需加载」路由组件的话,只要改几行代码就好了。
javascript
复制代码
//index.js setTimeout(function () { //文件会等5秒后加载,实现懒加载 // webpackChunkName: "dynamicImport":这是webpack动态导入模块命名的方式。 //浏览器看到的文件名:dynamicImport.chunk-test.js。output.chunkFilename配置命名格式 const add = () => import( /* webpackChunkName: "dynamicImport" */ './dynamicImport.js') console.log(add(1, 2)); }, 5000) //dynamicImport.js export default function add(a,b){ return a+b; }
动态import使用最多的一个场景是懒加载(比如路由懒加载)
- 封装一个component.js,返回一个component对象;
- 我们可以在一个按钮点击时,加载这个对象;
如果你用了 Babel ,就需要装上这个插件@babel/plugin-syntax-dynamic-import:babel plugin syntax dynamic import 来解析 import() 语法。
预加载
在声明 import 时,使用下面这些内置指令,可以让 webpack 输出 "resource hint(资源提示)",来告知浏览器:
- prefetch(预获取):将来某些导航下可能需要的资源。只会缓存资源不会解析
- preload(预加载):当前导航下可能需要资源。会缓存资源并解析
prefetch
下面这个 prefetch 的简单示例中,有一个 HomePage
组件,其内部渲染一个 LoginButton
组件,然后在点击后按需加载 LoginModal
组件。
LoginButton.js
go
复制代码
//... import(/* webpackPrefetch: true */ './path/to/LoginModal.js');
这会生成 <link rel="prefetch" href="login-modal-chunk.js">
并追加到页面头部,指示着浏览器在闲置时间预取 login-modal-chunk.js
文件。
只要父 chunk 完成加载,webpack 就会添加 prefetch hint(预取提示)。
不同
与 prefetch 指令相比,preload 指令有许多不同之处:
- preload chunk 会在父 chunk 加载时,以并行方式开始加载。prefetch chunk 会在父 chunk 加载结束后开始加载。
- preload chunk 具有中等优先级,并立即下载。prefetch chunk 在浏览器闲置时下载。
- preload chunk 会在父 chunk 中立即请求,用于当下时刻。prefetch chunk 会用于未来的某个时刻。
- 浏览器支持程度不同。
babel
动态导入
如果你用了 Babel ,就需要装上这个插件@babel/plugin-syntax-dynamic-import:babel plugin syntax dynamic import 来解析 import() 语法。
json
复制代码
// .babelrc { "plugins": ["syntax-dynamic-import"] }
资源模块
使用 webpack 资源模块 (asset module) 代替旧的 assets loader(如 file-loader
/url-loader
/raw-loader
等),减少 loader 配置数量。
配置方式如下:
bash
复制代码
module.exports = { rules: [ { test: /.(png|svg|jpg|jpeg|gif)$/i, include: [ paths.appSrc, ], type: 'asset/resource', }, ] }
SourceMap
最佳选择是 eval-cheap-module-source-map
详细区分可至 webpack devtool 查看。
css抽离
mini-css-extract-plugin插件会将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 和 SourceMaps 的按需加载
ini
复制代码
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); plugins: [ new MiniCssExtractPlugin({ filename: "css/[name].[hash].css", // 定义抽离的入口文件的文件名 chunkFilename: "css/[name].[hash].css", // 定义非入口块文件的名称,如动态导入的文件 }) ],
css压缩
ini
复制代码
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(), + ], + }, plugins: [new MiniCssExtractPlugin()], };
这将仅在mode: production
生产环境
开启 CSS 优化
如果还想在开发环境
下启用 CSS 优化,optimization.minimize
设置为 true
gzip压缩
前端将文件打包成 .gz
文件,然后通过 nginx
的配置,让浏览器直接解析 .gz
文件,可以大大提升文件加载的速度,浏览器可以直接解析 .gz
文件并解压。
javascript
复制代码
启用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 } ]) } }
js压缩
blog.csdn.net/qq_29722281…
-
terser-webpack-plugin和uglifyjs-webpack-plugin(不推荐)
不再维护 uglify-es ,并且 uglify-js 不支持 ES6 +。
terser 是 uglify-es 的一个分支,主要保留了与 uglify-es 和 uglify-js@3 的 API 和 CLI 兼容性。
webpack5 自带最新的
terser-webpack-plugin
,无需手动安装。
yamlterser-webpack-plugin
默认开启了parallel: true
配置,并发运行的默认数量:os.cpus().length - 1
,本文配置的 parallel 数量为 4,使用多进程并发运行压缩以提高构建速度。复制代码
const TerserPlugin = require('terser-webpack-plugin'); module.exports = { optimization: { minimizer: [ new TerserPlugin({ parallel: 4, terserOptions: { parse: { ecma: 8, }, compress: { ecma: 5, warnings: false, comparisons: false, inline: 2, }, mangle: { safari10: true, }, output: { ecma: 5, comments: false, ascii_only: true, }, }, }), ] } }
-
vue-cli
arduino复制代码
config.optimization.minimize(true)// 开启压缩js代码 config.optimization.splitChunks({ // 开启代码分割 chunks: 'all' })
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
配置CDN
线上使用 cdn ,如何库有问题,项目就会有问题,除非公司有自己的 cdn 库,不过这确实也是一种优化方案,效果也还不错。它的配置也很简单,在 externals 中配置,例子:
arduino
复制代码
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 地址:
xml
复制代码
<!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 的引入
ini
复制代码
//生产环境标记 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中添加
javascript
复制代码
<% for (var i in htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.js) { %> <script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script> <% } %>
按需加载
1. lodash
类似 import { throttle } from 'lodash'
就属于有副作用的引用,会将整个 lodash 文件进行打包。
优化方式是使用 import { throttle } from 'lodash-es'
代替 import { throttle } from 'lodash'
, lodash-es 将 Lodash 库导出为 ES 模块,支持基于 ES modules 的 tree shaking,实现按需引入。
2. ant-design
ant-design 默认支持基于 ES modules 的 tree shaking,对于 js 部分,直接引入 import { Button } from 'antd'
就会有按需加载的效果。
假如项目中仅引入少部分组件,import { Button } from 'antd'
也属于有副作用,webpack不能把其他组件进行tree-shaking。这时可以缩小引用范围,将引入方式修改为 import { Button } from 'antd/lib/button'
来进一步优化。
Tree Shaking
javascript
复制代码
tree shaking` 是一个术语,用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块语法的 静态结构 特性,例如 `import` 和 `export
babel
Babel 为编译的每个文件都插入了辅助代码,使代码体积过大!默认情况下会被添加到每一个需要它的文件中。可以将这些辅助代码作为一个独立模块,来避免重复引入。
@babel/plugin-transform-runtiome:
禁用了 Babel 自动对每个文件的 runtime 注入,而是引入 @babel/plugin-transform-runtiome 并且使所有辅助代码从这里引用
先下载包:
npm i @babel/plugin-transform-runtime -D
sideEffects(不推荐)
sideEffects 有三种情况
sideEffects:true 所有文件都有副作用,全都不可 tree-shaking sideEffects:false 有这些文件有副作用,所有其他文件都可以 tree-shaking,但会保留这些文件 sideEffects:[] 部分 tree-shaking , 除了数组外都 tree-shaking
"side effect(副作用)" 的定义是,在导入时会执行特殊行为的代码,而不是仅仅暴露一个 export 或多个 export。举例说明,例如 polyfill,它影响全局作用域,并且通常不提供 export。
如果你的代码确实有一些副作用,可以改为提供一个数组:
json
复制代码
{ "name": "your-project", "sideEffects": ["./src/some-side-effectful-file.js"] }
所有导入文件都会受到 tree shaking 的影响。这意味着,如果在项目中使用类似 css-loader
并 import 一个 CSS 文件,则需要将其添加到 side effect 列表中,以免在生产模式中无意中将它删除:
json
复制代码
{ "name": "your-project", "sideEffects": ["./src/some-side-effectful-file.js", "*.css"] }
IgnorePlugin
- 这是webpack内置插件
- 这个插件的作用是:忽略第三方包指定目录,让这些指定目录不要被打包进去
javascript
复制代码
//虽然我设置了语言为中文,但是在打包的时候,是会将所有语言都打包进去的。这样就导致包很大,打包速度又慢 plugins:[ new Webpack.IgnorePlugin(/./locale/,/moment/),//moment这个库中,如果引用了./locale/目录的内容,就忽略掉,不会打包进去 ]
css
打包时把没有用的 CSS 代码摇走,可以大幅减少打包后的 CSS 文件大小。
使用 purgecss-webpack-plugin 对 CSS Tree Shaking。
插件
autoprefixer兼容css
webpak 引入autoprefixer,自动加上各种前缀让不同的浏览器得以支持