引自:性能为什么相当重要javascript
网站开始加载时,用户须要等待一段时间才能看到要显示的内容。这部分时间是能够经过性能优化控制的。css
当用户等待的时间超过忍耐的程度时,必将抛弃该应用html
响应速度慢会对网站收入带来不利影响,反之亦然。前端
等待页面的加载时间就是在考验用户的忍耐力,而用户的忍受时间也是有一个阈值的。vue
性能低的网站和应用还会致使用户产生实际成本。java
大部分性能优化都是基于缓存用空间换时间,通常使用资源的缓存提升加载的性能。node
设置缓存对象的有效期jquery
由于工程化的发展,目前不多有人用这个参数去控制 http 请求的缓存了,用过的人可能知道,这个参数如今最多见的就是在设置 cookie 上webpack
若是有人想用这个参数,同样也能够设置到请求里,设置了 Expires 的请求头大概是这样的nginx
若是请求的时候找到缓存文件了,而且查看缓存的时间未过时,则不会再次给服务发起请求,而是直接使用缓存。
上面的 response header 图中可见,也设置了最大的缓存时间的 cache-control:max-age=3600
设置对象的最后修改时间
若是咱们启用了协商缓存,它会在首次请求的时候被携带在响应头里,以后咱们每次请求都会带上一个 if-modified-since 时间戳,服务器接收到这个值后会把先后两次时间戳进行对比,判断文件资源是否变化
last-modified 也会有弊端,若是咱们修改文件的时间过快或者修改了文件,但内容没有变化时,last-modified 的时间就不能处理文件是否发生变化了,这个时候 Etag 就诞生了
文件 hash 值
经过文件 hash 值判断缓存文件是否修改,从而判断是否请求新资源
若是还存在一些还在使用 http 1.0 的场景的话,Etag 将不会起做用。
众所周知,请求资源时,若是资源越大,延时越大
如今的工程化已经可以作到帮咱们处理这一项了。
减小文件大小。gzip压缩比率在3到10倍左右,能够大大节省服务器的网络带宽。
须要服务器和浏览器支持,对文件进行压缩后返回到用户端解压加载
通常在小的文件上配置 gzip 是没有必要的,毕竟若是服务器压缩,浏览器解压再加上加载文件的总时间都超过直接加载文件的时间的话,使用 gzip 还有什么意义呢?
若需配置 gzip,在请求的 headers 中加上这么一句就能够了
accept-encoding:gzip
复制代码
若是使用 nginx 做为 web 服务器的话,可在 nginx.conf 中对文件进行配置
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
gzip on; #开启gzip
gzip_min_length 1k; #低于1kb的资源不压缩
gzip_comp_level 3; #压缩级别【1-9】,越大压缩率越高,同时消耗cpu资源也越多,建议设置在4左右。
#须要压缩哪些响应类型的资源,多个空格隔开。不建议压缩图片,由于图片压缩后不只下降不了多少文件大小,反而还占用了大量的服务器压缩资源。
gzip_types text/plain application/javascript application/x-javascript text/javascript text/xml text/css;
gzip_disable "MSIE [1-6]\."; #配置禁用gzip条件,支持正则。此处表示ie6及如下不启用gzip(由于ie低版本不支持)
gzip_vary on; #是否添加“Vary: Accept-Encoding”响应头
server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
复制代码
gzip也有一个很是明显的缺点,毕竟不论是服务器的压缩仍是浏览器的解压,都会占用服务端和客户端的 CPU 资源。因此须要根据自身状况考虑后再决定是否有必要使用 gzip。
图片通常是页面中最大的资源,因此图片的优化很重要
在加载图片的时候,可经过配置(通常由建立图片的角色操做)渐进式图片提升用户的体验。关于渐进式图片的配置和使用效果可参见张鑫旭的渐进式图片及其相关
不少人会以为 webp 的图片的大小会小不少,因此加载会很快,但webp的兼容性并很差
若是使用图标型图片时,能够经过2种方式:
在通常的场景下,咱们均可以使用视觉欺诈的方式去处理图片
从网络层面,咱们前端能作获得的优化很是有限。相比之下,HTTP 链接这一层面的优化才是咱们网络优化的核心。
前面说的资源合并,合理利用浏览器的并行请求数量
前面说的资源压缩
如今大部分工程都会使用 webpack 打包处理。你们在使用时会不会以为打包的过程太长?打包完的文件体积太大?
资源搜索过程优化
告诉webpack去哪些目录下寻找第三方模块
默认值为['node_modules'],会依次查找 ./node_modules、 ../node_modules、 ../../node_modules
resolve.modules:[path.resolve(__dirname, 'node_modules')]
复制代码
能够给一些包(开发包/依赖包)设置别名,使得 webpack 在打包时查找文件时无需层层查找
resolve.alias:{
'@pages':patch.resolve(__dirname, '/src/pages')
}
复制代码
告诉Webpack没必要解析哪些文件,能够用来排除对非模块化库文件的解析
module:{ noParse:[/webim\.min\.js$/,/chart\.js$/] }
复制代码
减小没必要要的转译
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
}
复制代码
缓存没有发生改变的文件的转译资源,无需再次转译
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader?cacheDirectory=true',
options: {
presets: ['@babel/preset-env']
}
}
}
]
}
复制代码
webpack 是单线程的,就算存在多个任务,也是排队依次进行处理。而 webpack 打包过程当中,loader 解析最耗时。
HappyPack 能够充分利用 CPU 在多核并发的优点,把任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const HappyPack = require('happypack');
module.exports = {
module: {
rules: [
{
test: /\.js$/,
// 把对 .js 文件的处理转交给 id 为 babel 的 HappyPack 实例
use: ['happypack/loader?id=babel'],
// 排除 node_modules 目录下的文件,node_modules 目录下的文件都是采用的 ES5 语法,不必再经过 Babel 去转换
exclude: path.resolve(__dirname, 'node_modules'),
},
{
// 把对 .css 文件的处理转交给 id 为 css 的 HappyPack 实例
test: /\.css$/,
use: ExtractTextPlugin.extract({
use: ['happypack/loader?id=css'],
}),
},
]
},
plugins: [
new HappyPack({
// 用惟一的标识符 id 来表明当前的 HappyPack 是用来处理一类特定的文件
id: 'babel',
// 如何处理 .js 文件,用法和 Loader 配置中同样
loaders: ['babel-loader?cacheDirectory'],
// ... 其它配置项
}),
new HappyPack({
id: 'css',
// 如何处理 .css 文件,用法和 Loader 配置中同样
loaders: ['css-loader'],
}),
new ExtractTextPlugin({
filename: `[name].css`,
}),
],
};
复制代码
DllPlugin 插件会把第三方库单独打包到一个文件中,这个文件就是一个单纯的依赖库。这个依赖库不会跟着业务代码一块儿被从新打包,只有当依赖自身发生版本变化时才会从新打包。
注: 该插件主要使用的是已打包好的文件缓存
用 DllPlugin 处理文件,要分两步走:
// webpack_dll.config.js
const path = require('path')
const webpack = require('webpack')
module.exports = {
mode: 'production',
entry: {
lodash: ['lodash'],
jquery: ['jquery']
},
output: {
filename: '[name].dll.js',
path: path.resolve(__dirname, '../dll'),
library: '[name]'
},
plugins: [
new webpack.DllPlugin({
name: '[name]',
path: path.resolve(__dirname, '../dll/[name].manifest.json') // 用这个插件来分析打包后的这个库,把库里的第三方映射关系放在了这个 json 的文件下,这个文件在 dll 目录下
})
]
}
复制代码
配置执行 webpack_dll.config.js 文件的指令,并执行
"scripts": {
"build:dll": "webpack --config ./build/webpack.dll.js"
}
复制代码
最终构建出的文件:
|-- jquery.dll.js
|-- jquery.manifest.json
|-- lodash.dll.js
└── lodash.manifest.json
复制代码
在主config文件里使用 DllReferencePlugin 插件引入 xx.manifest.json 文件
//webpack.config.js
const path = require('path');
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');
module.exports = {
entry:{ main:'./main.js' },
//... 省略output、loader等的配置
plugins:[
new DllReferencePlugin({
// manifest就是咱们第一步中打包出来的json文件
manifest:require('./dist/jquery.manifest.json')
}),
new DllReferenctPlugin({
// manifest就是咱们第一步中打包出来的json文件
manifest:require('./dist/lodash.manifest.json')
})
]
}
复制代码
包组成可视化工具——webpack-bundle-analyzer,它会以矩形树图的形式将包内各个模块的大小和依赖关系呈现出来,截取官网图以下:
在使用时,咱们只须要将其以插件的形式引入:
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin({
openAnalyzer: true,
})
]
}
复制代码
在 webpack3 中,引入 UglifyJsPlugin 插件对 js 进行压缩,webpack4 如今已经默认使用 uglifyjs-webpack-plugin 对代码作压缩了—— 在 webpack4 中,咱们是经过配置 optimization.minimize 与 optimization.minimizer 来自定义压缩相关的操做。
module.exports = {
//... 省略其余配置
optimization: {
runtimeChunk: {
name: 'manifest'
},
minimizer: true, // [new UglifyJsPlugin({...})]
splitChunks:{
chunks: 'async',
}
}
}
复制代码
移除 JavaScript 上下文中的未引用代码(dead-code),从而减小打包后文件的体积。它依赖于 ES2015 模块系统中的静态结构特性,例如 import 和 export。
从定义中能够看出,Tree-Shaking 的针对性很强,它更适合用来处理模块级别的冗余代码。至于粒度更细的冗余代码的去除,能够经过配置插件对 js 和 css 进行压缩分离处理,如上面的 UglifyJsPlugin。
使用不一样的算法对压缩后的文件进行再压缩
const path = require('path');
const CompressionPlugin = require('compression-webpack-plugin')
module.exports = {
plugins: [
new CompressionPlugin({
test: /\.(js|css|html)$/,
// include: /\/src/,
filename: '[path].gz[query]',
algorithm: 'gzip', //算法
// threshold: 8192,
}),
]
}
复制代码
使用该插件后的文件大小差异以下图所示,能够看出至少减小了 一半 的大小,使用压缩文件也有一些缺点,在上面的gzip中也有提到。
主要场景:单页应用
举个例子:使用 vue 构建一个单页应用,其中有十个路由,经过 vue-router 控制这些路由,每一个路由对应的页面的业务都不简单。打包发布这个项目后,打开网站极大几率会出现长时间等待
这时候咱们能够选择按需加载路由或组件,当前路由对应的页面只加载当前页面相关的组件或路由。
// 按需加载路由
{
path: '/promisedemo',
name: 'PromiseDemo',
component: () => import('../components/PromiseDemo')
// 或者使用下面的写法
// component: resolve => resolve(require('../components/PromiseDemo'))
},
{
path: '/hello',
name: 'Hello',
// component: Hello
component: () => import('../components/Hello')
// 或者使用下面的写法
// component: resolve => resolve(require('../components/Hello'))
}
复制代码
有时候咱们想把某个路由下的全部组件都打包在同个异步块 (chunk) 中。只须要使用 命名 chunk,一个特殊的注释语法来提供 chunk name (须要 Webpack > 2.4)。引自:组件按组分块
const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue')
复制代码