webpack优化
1.production 模式打包自带优化
tree shaking
- 创建一个 math.js, 抛出两个方法
export const add = (a, b) => a + b export const minus = (a, b) => a- b
- 在 main.js 中使用
// tree shaking 分析 // 若是此时使用 require 引入,不管 math 中的方法是否使用,都会被打包 const math = require('./utils/math') // 若是使用 import 引入, 只会打包使用了 math 的方法 import { add } from './utils/math' console.log('index 页面',math.add(1,2)); console.log('index 页面',add(1,2));
- 根据不同的引入方式进行打包,观察打包后的文件
scope hoisting
在 main.js 中定义几个变量并输出
const a = 1 const b = 2 const c = 3 // webpack 在这里会进行 预执行,将结果推断后打包放在这里 console.log(a + b + c) console.log(a, b, c)
打包之后代码变成
console.log(6),console.log(1,2,3)
代码压缩
2.CSS优化
2.1 将CSS提取到独立文件中
只能用于webpack4中,优势
- 异步加载
- 不重复编译,性能更好
- 更容易使用
- 只针对css
使用
安装
npm i -D mini-css-extract-plugin
引用
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
创建插件对象,配置抽离的css文件名,支持placeholder语法
new MiniCssExtractPlugin({ filename:'[name].css' // [name] 就是 placeholder 语法 })
将原来配置的所有 style-loader 替换为 MiniCssExtractPlugin.loader
{ test:/\.css$/, use:[MiniCssExtractPlugin.loader, 'css-loader'] }, { test:/\.less$/, use:[MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'] }, { test:/\.scss$/, use:[MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'] },
2.2 自动添加CSS前缀
安装
npm i -D postcss-loader autoprefixer
修改配置文件,将 postcss-loader 放置在 css-loader 右边
{ test:/\.css$/, use:[MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader',] }, { test:/\.less$/, use:[MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'less-loader'] }, { test:/\.scss$/, use:[MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'sass-loader'] },
项目根目录下添加 postcss 的配置文件: postcss.config.js
module.exports = { plugins: [ require('autoprefixer')({ browsers: [ // 加这个后可以出现额外的兼容性前缀 "> 0.01%" ] }) ] }
2.3 开启CSS压缩
安装
npm i -D terser-webpack-plugin optimize-css-assets-webpack-plugin
引用
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); const TerserPlugin = require('terser-webpack-plugin');
配置
optimization:{ minimizer: [ new TerserPlugin({}), new OptimizeCssAssetsPlugin({}) ] }
3.JS优化
- 三种常见的代码分离方法
- 入口起点:使用entry配置,手动的分离代码
- 放置重复:使用 SplitChunksPlugin 去重和分离 chunk
- 动态导入:通过模块的内联函数调用来分离代码
3.1手动配置多入口
- 手动配置多入口会存在一些问题
- 如果入口chunks之间包含重复的模块,哪些重复的模块都会被引入到各个打包后的js文件中
- 方法不够灵活,并且不能将核心应用程序逻辑进行动态拆分代码
在webpack配置文件中配置多个入口
entry:{ main: './src/main.js', other: './src/other.js' }, output:{ path: path.join(__dirname, '..', './dist'), filename: '[name].js', publickPath: '/' }
在main.js 和 other.js 中都共同引入一个模块, 并使用其功能
Main.js
import $ from 'jquery' $(() => { $('<div></div>').html('main').appendTo('body') })
other.js
import $ from 'jquery' $(() => { $('<div></div>').html('other').appendTo('body') })
打包文件,可以看到 main 和 other 打包的文件中都加载的了 jquery
3.2抽取公共代码
修改配置文件
optimization splitChunks:{ chunks: "all" } }
打包查看文件
splitChunksPlugin 配置参数
- webpack 会基于如下默认原则自动分割代码
- 公用代码块或者来自 node_modules 文件夹的组件模块
- 打包的代码块大小超过30kb,最小化压缩之前的
- 按需加载代码块时,同时发送的请求最大数量不应该超过5
- 页面初始化时,同时发送的请求最大数量不应该超过3
- SplitChunksPlugin 默认配置
module.exports = { optimization: { splitChunks: { chunks: 'async', // 只对异步加载的模块进行拆分,import('jquery').then()就是典型的异步加载,可选项还有 all | initial minSize: 30000, // 模块最少大于 30kb 才会拆分 maxSize: 0, // 为0时模块大小无上限,只要大于 30kb 都会拆分。若是非0,超过了maxSize的值,会进一步拆分 minChunks: 1, // 模块最少引用一次才会拆分 maxAsyncRequests: 5, // 异步加载时同时发送的请求数量最大不能超过5,超过5的部分不拆分 maxInitialRequests: 3, // 页面初始化时,同时发送的请求数量最大不能超过3,超过3的不跟不拆分 automaticNameDelimiter: '~', // 默认的连接符 name: true, // 拆分的chunk名,设置为true表示根据模块名和CacheGroup的key来自动生成,使用上面的连接符连接 cacheGroups: { // 缓存组配置,上面配置读取完成后进行拆分,如果需要把多个模块拆分到一个文件,就需要缓存,所以命名为缓存组 vendors: { // 自定义缓存组名 test: /[\\/]node_modules[\\/]/, // 检查 node_modules 目录,只要模块在该目录下就使用上面配置拆分到这个组 priority: -10, // 权重为-10,决定了那个组优先匹配,假如node_modules下面有个模块要拆分,同时满足vendors和default组,此时就会分到 priority 值比较大的组,因为 -10 > -20 所以分到 vendors 组 filename:'vendoes.js' }, default: { // 默认缓存组名 minChunks: 2, // 最少引用两次才会被拆分 priority: -20, // 权重 -20 reuseExistingChunk: true // 如果主入口中引入了两个模块,其中一个正好也引用了后一个,就会直接复用,无需引用两次 } } } } };
- webpack 会基于如下默认原则自动分割代码
3.3动态导入(懒加载)
安装
npm i -D @babel/plugin-syntax-dynamic-import
修改 .babelrc ,添加 @babel/plugin-syntax-dynamic-import 插件
{ "presets": ["@babel/env"], "plugins": [ "@babel/plugin-proposal-class-properties", "@babel/plugin-syntax-dynamic-import" ] }
将jq模块动态导入
function getDivDom(){ // import('jquery') 返回的是一个 promise,若是低版本需要注意 return import('jquery').then(({default: $}) => { return $('<div></div>').html('动态导入') }) }
给某个按钮添加点击事件,点击后调用getDivDom函数创建元素并添加到页面
window.onload = () => { document.getElementById('btn').addEventListener('click',() => { getDivDom().then(item => { item.appendTo('body') }) }) }
4.noParse
module:{
noParse: /jquery|bootstrap/ // jquery|bootstrap 之间不能加空格变成 jquery | bootstrap, 会无效
}
5.IgnorePlugin
以moment为例
import moment from 'moment' moment.locale('zh-CN') // 设置为中文 console.log(moment().subtract(6, 'days').calendar())
首先要找到moment依赖的语言包时什么,通过查看moment的源码来分析
function loadLocale(name) { var oldLocale = null; // TODO: Find a better way to register and load all the locales in Node if (!locales[name] && (typeof module !== 'undefined') && module && module.exports) { try { oldLocale = globalLocale._abbr; var aliasedRequire = require; aliasedRequire('./locale/' + name); getSetGlobalLocale(oldLocale); } catch (e) {} } return locales[name]; }
使用IgnorePlugin插件忽略其依赖
new webpack.IgnorePlugin(/\.\/locale/, /moment/)
需要使用某些依赖时自行手动引入
import moment from 'moment' import 'moment/locale/zh-cn' // 需要手动引入方可生效 moment.locale('zh-CN') console.log(moment().subtract(6, 'days').calendar())
6.DLLPlugin
DLLPlugin
- 配置参数
- context(可选):manifest文件中请求的上下文,默认为该webpack文件上下文
- name:公开的dll函数的名称,和output.library保持一致即可
- path:manifest.json 生成的文件夹及名称
- 配置参数
DLLReferencePlugin
- 配置参数
- context: manifest文件中的请求上下文
- manifest: DLLPlugin插件生成的manifest.json
- content(可选): 请求的映射模块id(默认为manifest.content)
- name(可选): dll暴露的名称
- scope(可选): 前缀用于访问dll的文件
- sourceType(可选): dll是如何暴露(libraryTarget)
- 配置参数
将VUE项目中的库抽取成DLL
- 准备一份将VUE打包成DLL的webpack配置文件。
- 在build目录下新建一个文件webpack.vue.js,专门用于打包vue的DLL的。
- 配置入口:将多个要做成dll的库全放进来
- 配置出口:一定要设置library属性,将打包好的结果暴露在全局
- 配置plugin:设置打包后dll文件名和manifest文件所在地
// 此配置文件 是打包VUE全家桶的
const path = require('path')
const webpack = require('webpack')
module.exports = {
mode: 'production',
entry:{
vue: [
'vue/dist/vue',
'vue-router'
]
},
output:{
path: path.resolve(__dirname, '../dist'),
filename: '[name]_dll.js',
library: '[name]_dll' // 最终会在全局暴露出一个[name]_dll的对象
},
plugins:[
new webpack.DllPlugin({
name: '[name]_dll',
path: path.resolve(__dirname, '../dist/manifest.json'),
})
]
}
- 在
webpack.base.js
中进行插件的配置
new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, '../dist/manifest.json'),
})
- 由于[name]_dll文件生成之后,并没有动态的引入进去,所以需要一个插件可以动态的将生成的dll文件引入
npm i -D add-asset-html-webpack-plugin
new AddAssetHtmlWebpackPlugin({
filepath: path.resolve(__dirname, '../dist/vue_dll.js')
})
7.浏览器缓存
output: {
path: path.join(__dirname, '..','./dist'),
filename:'[name].[contenthash:8].bundle.js',
publicPath: '/'
},
8.打包分析
- 使用
--profile --josn
参数,以json格式来输出打包后的结果到某个指定的文件中
webpack --profile --json > stats.json
- 将stats.json文件放到工具中进行分析
官方工具: analyse
webpack-chart:webpack stats 可交互饼图。
webpack-visualizer:可视化并分析你的 bundle,检查哪些模块占用空间,哪些可能是重复使用的。
webpack-bundle-analyzer:一个 plugin 和 CLI 工具,它将 bundle 内容展示为便捷的、交互式、可缩放的树状图形式。是一个插件,可以以插件安装到项目中
- 安装
npm i -D webpack-bundle-analyzer
- 使用, 配置在一个单独的文件中 webpack.analyse.js (直接拷贝的web pack.prod.js,仅仅是多了此插件的使用)
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; module.exports = { plugins: [ new BundleAnalyzerPlugin() ] }
webpack bundle optimize helper:此工具会分析你的 bundle,并为你提供可操作的改进措施建议,以减少 bundle 体积大小。