必懂的wenpack优化
webpack优化
1.production 模式打包自带优化
tree shaking
tree shaking是一个术语、通常用于打包时移除js中未引用的代码(dead-code),它依赖于ES6模块系统中的import 和 export 的静态结构特性
开发时引入一个模块时,如果只引用其中一个功能,上线打包时只会把用到的功能打包进bundle中,其他没有用到的功能都不会打包进来,可以实现最简单的基本优化
- 创建一个 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
Scope hositing 作用:是将模块之间的关系进行结果推测,可以让webpack文件打包出来的代码文件更小、运行的更快
scope hositing实现原理:分析出模块之间的依赖关系,尽可能的把打散的模块合并到一个函数中,但是前提是不能造成代码冗余, 因此只有哪些被引用了一次的模块可能被合并
由于scope hositing 需要分析出模块之间的依赖关系,因此源码必须使用ES6模块化语句,不然就不能生效,原因和 tree shaking一样
在 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)
因为三个变量只是在这个地方定义并且使用,并没有在其他位置使用,webpack会直接以具体的数值进行打包,节省了三个变量的定义
代码压缩
所有代码使用UglifyJsPlugin进行压缩、混淆
2.CSS优化
2.1 将CSS提取到独立文件中
Mini-css-extract-plugin 是用于将 CSS 提取为独立的文件的插件,对每个包含css的js文件都会创建一个css文件,支持按需加载css和sourceMap
只能用于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前缀
使用 postcss,需要使用 postcss-loader 和 autoprefixer
安装
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压缩
需要使用 optimize-css-assets-webpack-plugin 插件来完成css压缩
但是由于配置css压缩时会覆盖掉webpack默认的优化设置,导致JS代码无法压缩,所以还需要把JS代码压缩插件倒入进来 terser-webpack-plugin
安装
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({})
]
}
webpack4默认采用的JS压缩插件是 uglifyjs-webpack-plugin,在 mini-css-extract-plugin上一个版本中还推荐使用该插件,但是新的版本却建议使用 terser-webpack-plugin
3.JS优化
code splitting 是webpack打包时用到的重要的优化特性之一、此特性能够把代码分离到不同的bundle中,然后可以按需加载或者并行加载这些文件,代码分离可以用于获取更小的bundle,以及控制资源加载优先级,如果能够合理的使用能够极大影响加载时间
- 三种常见的代码分离方法
- 入口起点:使用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抽取公共代码
webpack4 以上使用的插件为 SplitChunksPlugin,webpack4 之前的使用的 CommonChunkPlugin已经被移除,最新版本的webpack中只需要在配置文件中的optimization节点下添加一个splitChunks属性即可进行相关配置
修改配置文件
optimization
splitChunks:{
chunks: "all"
}
}
打包查看文件
打包之后会将各自的入口文件进行打包,额外会再生产一份js文件,此文件中就是各个chunk中所引用的公共部分
splitChunksPlugin 配置参数
SplitChunksPlugin 的配置只需要在 optimization 节点下的 splitChunks 进行修改即可,如果没有任何修改,则会使用默认设置
默认的 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 // 如果主入口中引入了两个模块,其中一个正好也引用了后一个,就会直接复用,无需引用两次
}
}
}
}
};
3.3动态导入(懒加载)
webpack4默认是允许import语法动态导入的,但是需要babel的插件支持,最新版babel的插件包为:@babel/plugin-syntax-dynamic-import,需要注意动态导入最大的好处就是实现了懒加载,用到那个模块才会加载那个模块,可以提高SPA应用程序的首屏加载速度,三大框架的路由懒加载原理一样
安装
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
在引入一些第三方模块时,如jq等,我们知道其内部肯定不会依赖其他模块,因为我们用到的只是一个单独的js或者css文件,所以此时如果webpack再去解析他们的内部依赖关系,其实是非常浪费时间的,就需要阻止webpack浪费精力去解析这些明知道没有依赖的库,可以在webpack的配置文件的module节点下加上noParse,并配置正则来确定不需要解析依赖关系的模块
module:{
noParse: /jquery|bootstrap/ // jquery|bootstrap 之间不能加空格变成 jquery | bootstrap, 会无效
}
5.IgnorePlugin
在引入一些第三方模块时,例如momentJS、dayJS,其内部会做i18n处理,所以会包含很多语言包,而语言包打包时会比较占用空间,如果项目只需要用到中文或者少数语言,可以忽略掉所有的语言包,然后按需引入语言包,从而使得构建效率更高,打包生成的文件更小
以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];
}
通过 aliasedRequire('./locale/' + name) 可以知道momentJS的多语言目录是locale,所有的语言JS文件都在这个目录中
使用IgnorePlugin插件忽略其依赖
将momentJS的多语言目录locale忽略
new webpack.IgnorePlugin(/\.\/locale/, /moment/)
需要使用某些依赖时自行手动引入
忽略其依赖之后,moment.locale('zh-CN')就会失效,因为其所依赖的语言包全都被忽略了,需要手动将其引入
import moment from 'moment'
import 'moment/locale/zh-cn' // 需要手动引入方可生效
moment.locale('zh-CN') console.log(moment().subtract(6, 'days').calendar())
6.DLLPlugin
在引入一些第三方模块时,例如Vue、React等,这些框架的文件一般都是不会修改的,而每次打包都需要去解析他们,也会影响打包速度,就算是做了拆分,也只是提高了上线后的用户访问速度,并不会提高构建速度,所以如果需要提高构建速度,应该使用动态链接库的方式,类似windows的dll文件
借助DLLPlugin插件实现将这些框架作为一个个的动态链接库,只构建一次,以后的每次构建都只会生成自己的业务代码,可以很好的提高构建效率
猪哟思想在于,讲一些不做修改的依赖文件,提前打包,这样我们开发代码发布的时候就不需要再对这些代码进行打包,从而节省了打包时间,主要使用两个插件: DLLPlugin和DLLReferencePlugin
需要注意的是,若是使用的DLLPlugin,CleanWebpackPlugin插件会存在冲突,需要移除CleanWebpackPlugin插件
DLLPlugin
使用一个单独webpack配置创建一个dll文件,并且它还创建一个manifest.json,DLLReferencePlugin使用该json文件来做映射依赖性,这个文件会告诉webpack哪些文件已经提取打包好了
- 配置参数
- context(可选):manifest文件中请求的上下文,默认为该webpack文件上下文
- name:公开的dll函数的名称,和output.library保持一致即可
- path:manifest.json 生成的文件夹及名称
- 配置参数
DLLReferencePlugin
该插件主要用于主webpack配置,它引用的dll需要预先构建的依赖该系
- 配置参数
- 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.vue.js 只是用来打包生成 [name]_dd.js 文件和 manifest.json文件的,是不需要参与到业务代码打包的,因为只会在每一次修改了需要生成dll文件的时间才会执行一次,否则不需要参与到打包
- 在
webpack.base.js
中进行插件的配置
使用DllReferencePlugin指定manifest文件的位置即可
new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, '../dist/manifest.json'),
})
- 由于[name]_dll文件生成之后,并没有动态的引入进去,所以需要一个插件可以动态的将生成的dll文件引入
安装add-asset-html-webpack-plugin
npm i -D add-asset-html-webpack-plugin
配置插件自动添加script标签到HTML中,需要注意的是,必须在HtmlWebpackPlugin后面引入,因为HtmlWebpackPlugin是生产一个html文件,AddAssetHtmlWebpackPlugin是在已有的html中注入一个script,否则会被覆盖
new AddAssetHtmlWebpackPlugin({
filepath: path.resolve(__dirname, '../dist/vue_dll.js')
})
7.浏览器缓存
在做了众多代码分离的优化后,其目的是为了更好的利用浏览器缓存,达到提高访问速度的效果,所以构建项目时做代码分割是必须的,
例如将固定的第三方模板抽离,下次修改了业务代码,重新发布上线不重启服务器,用户再次访问服务器就不需要再次加载第三方模板了
但是此时会遇到一个问题,如果再次打包上线不重启服务器,客户端会把以前的业务代码和第三方模块同时缓存,再次访问时依旧会访问缓存中的业务代码,所以会导致业务代码也无法更新
需要在output节点的filename中使用placeholder语法,根据代码内容生产文件名的hash,之后每次打包业务代码时,如果有改变,会生成新的hash作为文件名,浏览器就不会使用缓存了,而第三方模块不会重新打包生成新的名字,则会继续使用缓存
output: {
path: path.join(__dirname, '..','./dist'),
filename:'[name].[contenthash:8].bundle.js',
publicPath: '/'
},
8.打包分析
项目构建完成后,需要通过一些工具对打包后的bundle进行分析,通过分析可以得到一些有用的信息
- 使用
--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 体积大小。
9.prefetching && preloading
在优化访问性能时,除了利用浏览器缓存之外,还需要涉及到一个性能指标: 覆盖率(coverage rate)
可以在chrome浏览器的控制台中按 ctrl + shift + p,查找 coverage,打开覆盖率面板,开始录制后刷新页面,即可看到每个js文件的覆盖率,以及总的覆盖率
想提高覆盖率,需要尽可能多的使用impor动态导入,也就是懒加载的功能,将一切能使用懒加载的地方都是用懒加载,这样可以大大的提高覆盖率
但是有时候使用懒加载会影响用户体验,所以可以在使用懒加载的时候使用魔法注释(Magic Comments): prefetching,是指在首页资源加载完毕后,空闲的时候,将动态导入的资源加载进来,这样既可以提高首屏加载速度,也可以解决懒加载可能会影响用户体验的问题
必懂的wenpack优化的更多相关文章
- 必懂的webpack高级配置
webpack高级配置 1.HTML中img标签的图片资源处理 使用时.只需要在html中正常引用图片即可.webpack就会找到对应的资源进行打包.并修改html中的引用路径 主要是将html中的i ...
- 深度剖析HashMap的数据存储实现原理(看完必懂篇)
深度剖析HashMap的数据存储实现原理(看完必懂篇) 具体的原理分析可以参考一下两篇文章,有透彻的分析! 参考资料: 1. https://www.jianshu.com/p/17177c12f84 ...
- [转帖]K8s 工程师必懂的 10 种 Ingress 控制器
K8s 工程师必懂的 10 种 Ingress 控制器 https://www.kubernetes.org.cn/5948.html 控制器有好多啊. 2019-10-18 23:07 中文社区 分 ...
- hiho一下 第二十九周 最小生成树三·堆优化的Prim算法【14年寒假弄了好长时间没搞懂的prim优化:prim算法+堆优化 】
题目1 : 最小生成树三·堆优化的Prim算法 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 回到两个星期之前,在成功的使用Kruscal算法解决了问题之后,小Ho产生 ...
- 一本彻底搞懂MySQL索引优化EXPLAIN百科全书
1.MySQL逻辑架构 日常在CURD的过程中,都避免不了跟数据库打交道,大多数业务都离不开数据库表的设计和SQL的编写,那如何让你编写的SQL语句性能更优呢? 先来整体看下MySQL逻辑架构图: M ...
- web前端开发必懂之一:JS继承和继承基础总结
首先,推荐一篇博客豪情的博客JS提高: http://www.cnblogs.com/jikey/p/3604459.html ,里面的链接全是精华, 一般人我不告诉他; 我们会先从JS的基本的设计模 ...
- javascript中的闭包,超简单论述,保证小学生必懂
js中的闭包已经有很多论断了,大家伙有没有听懂了,先引用一片比较高端 的 ”汤姆大叔“ 深入理解JavaScript系列(16):闭包(Closures) 好了,为了引起大家的兴趣,先来小诗一首 v ...
- Spring事务配置的五种方式 巨全!不看后悔,一看必懂!
前段时间对Spring的事务配置做了比较深入的研究,在此之间对Spring的事务配置虽说也配置过,但是一直没有一个清楚的认识.通过这次的学习发觉Spring的事务配置只要把思路理清,还是比较好掌握的. ...
- 新手必看的jQuery优化笔记十则
jQuery优化 1.简介 jQuery正在成为Web开发人员首选的JavaScript库,作为Web开发者,除了要了解语言和框架的应用技巧外,如何提升语言本身的性能也是开发人员应该思考的问题.文章就 ...
随机推荐
- Spring与IoC
控制反转(IOC,Inversion of Control),是一个概念,是一种思想. 指将传统上由程序代码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理.控制反转就是对对象控制权的转移 ...
- Python时间戳的一些使用
Python时间戳的一些使用 为什么写下这篇文档? 由于我本身是做Python爬虫的,在爬取网站的过程当中,会遇到形形色色的验证码,目前对于自己而言,可能简单的验证码可以进行自己识别 发现大多数的网站 ...
- docker部署asp.net core
上一篇文章我们成功的在win10上边安装了docker,这篇文章,我们将在docker中部署asp.net core程序, 先来一张运行成功的hello world镇楼 现在开始,首先创建一个asp. ...
- Java NIO学习系列三:Selector
前面的两篇文章中总结了Java NIO中的两大基础组件Buffer和Channel的相关知识点,在NIO中都是通过Channel和Buffer的协作来读写数据的,在这个基础上通过selector来协调 ...
- java Https工具类
import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import ja ...
- Codeforces 617E:XOR and Favorite Number(莫队算法)
http://codeforces.com/problemset/problem/617/E 题意:给出n个数,q个询问区间,问这个区间里面有多少个区间[i,j]可以使得ai^ai+1^...^aj ...
- 使用JavaScript实现量化策略并发执行——封装Go函数
在实现量化策略时,很多情况下,并发执行可以降低延时提升效率.以对冲机器人为例,需要获取两个币的深度,顺序执行的代码如下: 请求一次rest API存在延时,假设是100ms,那么两次获取深度的时间实际 ...
- TCP中的粘包问题,以及用TCP和UDP实现多次聊天
TCP协议 在连接内多和客户端说几句 #server端 import socket sk = socket.socket() sk.bind(('127.0.0.1',9001)) sk.listen ...
- POI 设置Excel单元格背景色(setFillForegroundColor)
背景介绍:使用Java开发信息系统项目,项目中往往会涉及到报表管理部分,而Excel表格首当其冲称为最合适的选择,但是对单元格操作时对于设置单元格的背景颜色却很少提及,本文旨在方便单元格背景颜色设计. ...
- 对比 C++ 和 Python,谈谈指针与引用
花下猫语:本文是学习群内 樱雨楼 小姐姐的投稿.之前已发布过她的一篇作品<当谈论迭代器时,我谈些什么?>,大受好评.本文依然是对比 C++ 与 Python,来探讨编程语言中极其重要的概念 ...