首页 » PHP教程 » phpfmpcpu多核技巧_记前端一次机能优化实战记一次vuecli3项目的优化过程

phpfmpcpu多核技巧_记前端一次机能优化实战记一次vuecli3项目的优化过程

duote123 2024-11-18 0

扫一扫用手机浏览

文章目录 [+]

目前,前端性能监控的办法有两类,一类是 合成监控 (Synthetic Monitoring, SYN ),另一类是 真实用户监控 (Real User Monitoring, RUM )。
合成监控是在一个仿照环境里,通过一些工具、规则去仿照运行一个页面,记录干系性能指标,末了得到一份报告。
chrome 浏览器开拓者工具自带的 Lighthouse (须要科学上网)便是一种合成监控。
但是,合成监控产生的数据量较小,且测试结果与测试机的网络状况、硬件性能等关系很大,无法代表真实用户利用的情形。
RUM监控是同在代码里埋点、调用浏览器接口等办法,网络线上用户的实际性能数据,样本量大,且更能解释真实场景下页面的性能状况。
因此本次性能优化时以RUM监控的数据为准。

RUM 性能指标,详细又分为用户体验指标和技能性能指标两类。

phpfmpcpu多核技巧_记前端一次机能优化实战记一次vuecli3项目的优化过程

用户体验指标

基于 以用户为中央的性能指标 ,常用的用户体验指标有:

phpfmpcpu多核技巧_记前端一次机能优化实战记一次vuecli3项目的优化过程
(图片来自网络侵删)

技能性能指标

技能性能指标是页面加载的过程中通过各事宜的韶光打算得到的性能指标,它对用户的影响没有用户体验指标直不雅观,但是更能反应一些性能问题,也更随意马虎针对性优化。
而且与各公司有不同定义的用户体验指标不同,它有十分明确和统一的打算办法。

Navigation Timing 2.0 定义的页面加载阶段模型:

根据 Navigtaion Timing 2.0 模型,有如下常用的技能性能指标:

本次优化关注的紧张指标

由于这次优化的项目因此用户操作为主的运用,而不因此展示为主(比如新闻网站),因此弱化了对FP、FMP等指标的关注,紧张关注 TTI 。
次要关注FCP、FMP、DOM Ready,由于FCP、FMP的优化可以一定程度降落用户由于没有看到内容失落去耐心而流失落的情形,DOM Ready可以反应项目整体的加载韶光。

本次优化的紧张目标是 TTI降落30% 。

剖析工具webpack-chart : webpack stats 可交互饼图webpack-visualizer : 可视化并剖析你的 bundle,检讨哪些模块占用空间,哪些可能是重复利用的webpack-bundle-analyzer : 一款剖析 bundle 内容的插件及 CLI 工具,以便捷的、交互式、可缩放的树状图形式展现给用户。
在vue-cli中,运行 npx vue-cli-service build --report 在 /dist 下天生 report.html 来查看打包剖析运行 npx vue-cli-service inspect > output.js --mode production 天生webpack生产环境配置文件调试webpack插件: node --inspect-brk ./node_modules/@vue/cli-service/bin/vue-cli-service.js serve --inline --progress全局安装 @vue/cli 后,运行 vue ui 可视化展示依赖构造Lighthouse :Chrome开拓者工具自带的性能报告工具,可以给出很多建议vue-cli 已经默认做了的优化

vue-cli默认配置已经添加了很多常用的优化,包括:

cache-loader 会默认为 Vue/Babel/TypeScript 编译开启。
文件会缓存在 node_modules/.cache 中图片等多媒体文件利用 url-loader ,小于4K的图片会转base64编码内联在js文件中生产环境下利用 mini-css-extract-plugin ,将css提取成单独的文件thread-loader 会在多核 CPU 的机器上为 Babel/TypeScript 转译开启并行处理。
提取公共代码:两个缓存组 chunk-vendors 和 chunk-common代码压缩( terser-webpack-plugin )preload-webpack-plugin :所有入口js、css文件加上 preload ,按需加载文件加上 prefetch检讨项目中哪些地方还有优化空间

所谓优化实在质便是少做多余的事,因此我们得知道哪些是必要的哪些是不必要的。
比如在用户还没滑动到网页下方的时候就加载完全个网页,在多数情形下是不必要的。
因此要知道项目运行的完全过程中的完全行为。
这一点常常被忽略,而难以得到比较好的效果。

在开始做优化之前我们先检讨一些项目里有哪些地方还有优化空间,避免做无用功,或者一顿操作猛如虎,一看数据心里苦。

静态资源图片:头像、封面还有压缩空间,现在均匀100K旁边,大的超过1Majax返回的json:目前均匀每个ajax 1K旁边,压缩空间不大js、css:首屏加载的几个紧张的加起来有几M,可以分割代码减少加载量video、font、doc:加起来不超过50K,不影响性能,没有压缩必要other:prefetch的文件过多,占用过多做事器带宽TTFBajax:200-800msjs、css:150-800ms,大多数700+ms (后来创造实在是我开了代理,实际是20-100ms)图片、video、font:50-250msdoc:60ms其它有些地方可以加缓存,减少ajax要求量,可以考虑用什么形式(session storage、cache storage、index db、http 缓存)部分页面可以考虑文档构造在做事端渲染有些地方有不必要的重绘可能有效的方法相应式图片: picture 元素Intersection Observer 优化前:

原vue-cli默认配置(可以通过 npx vue-cli-service inspect > output.js --mode production 导出 output.js 查看当前配置):

splitChunks: { cacheGroups: { vendors: { name: 'chunk-vendors', test: /[\\/]node_modules[\\/]/, priority: -10, chunks: 'initial' }, common: { name: 'chunk-common', minChunks: 2, priority: -20, chunks: 'initial', reuseExistingChunk: true } }},复制代码

各参数的含义这里不再阐明,可以参考前面的链接

打包后几个紧张入口大小: 7-8M

入口(entry)定义了webpack打包代码后可以可以从哪开始实行代码,多页面的运用一样平常是多入口

这里简要说一下 Webpack打包和分片的实行办法:

对付所有入口,从入口文件开始为一个chunk,其 import (不包括动态 import 和 require )的所有文件加入chunk,再把新加进来的文件所依赖的文件加入,也便是依赖链中的所有文件。
这些是initialChunk。
前一步中碰着的所有动态 import 的文件各自为一个chunk。
这些是asyncChunk。
提取公共代码,即 cacheGroups ,会产生新的chunk。
根据配置中的规则,把多个chunk中重复文件提取出来成为新的chunk。
每个chunk中同一类型的文件合并为一个。

例如上图是实行到某时候时webpack的输出,个中 767/833 指已经编译好了767个模块,通过这767个模块已知要编译的模块有833个,随着下一个模块被编译,该模块的依赖也会被加入todo。
因此左边数字在涨的同时右边数字也在涨,直到编译完所有模块。
以是最左边的65%实在并不是指编译总进度,在编译完之前根本不知道一共有多少个模块。

在修正 cacheGroups 之前,先通过可视化工具看一下目前的分片情形,这是运行打包剖析后的可视化分片结果:

可以看到 chunk-vendors 体积非常大,这是由于项目依赖了很多库。
chunk-common 体积也非常大,这是由于所有入口的公共文件都会被打包进这个chunk。
这两个chunk是所有入口都会加载的,以是比较影响性能。
还涌现了很多重复代码,和一些不常用的代码被打包进入口chunk。
下面将会针对这些问题“开刀”。

修正分片的原则node_modules 中的文件还是打包进 chunk-vendors ,被引用得太多,已经难以改成按需加载,而且变动频率低,可以作为浏览器缓存(304)长期不失落效。
被引用少且体量大的文件单独分一个chunk。
其它公共代码分成多个chunk,这样可以避免从某个入口访问时下载全部公共代码,以及部分代码变动时不会导致全部公共代码的缓存失落效。
体量很小的异步chunk合并进其它干系异步chunk中,或者合并进入口chunk。
路由

路由

路由修正成asyncChunk的原则:

把大的入口chunk分割成小chunk的好处:减少用户首次访问时须要下载的文件大小,以及实行的代码量。
减少不同入口间的重复代码。

坏处:用户后续操作可能就得再下载新文件,增加等待韶光。
异步chunk可能会依赖一些该入口chunk中就有的文件,导致下载重复代码。

根据路由划分,符合上述规则的每个路由一个chunck,别的保留直接静态import,或者合并进其它chunk(同名chunk会合并)

{ path: '/user/XXX', name: 'user-XXX', // 改成动态import component: () => import( / webpackChunkName: "chunk-user-XXX" / 'src/pages/XXX' ),},复制代码

改完往后多了一些小chunk,但影响不大,都是异步加载的chunk,不会造成单次要求文件数过多而降落性能。

利用频率低的库单独分一片以 tinymce ,一个富文本编辑器控件为例。
体历年夜的模块,可以改成按需加载。

const tinymce = () => import( / webpackChunkName: "chunk-tinymce" / '../plugins/tinymce');...beforeMount() { // 动态import tinymce,在第一次mount之前再下载这个chunk(压缩后300K) tinymce().then(() => { this.loading = false; });},复制代码

经由这两步后,chunks总大小:16M => 10.8M(parsed)

分片情形:

可以看到重复代码少了很多,但是两个大chunk还是很大。
连续分割。

echarts(672K):一个图表控件库,不是所有入口都用得到,但是被用的地方太多了,已经难以改成按需加载了

// 对vue.config.js进行修正cacheGroups: { // 增加一个cacheGroup echarts: { name: 'chunk-echarts', test: /[\\/]node_modules[\\/]echarts[\\/]/, priority: 0, chunks: 'all', },}复制代码增加公共代码分片数量

现在入口chunck还是太大,可以增加提取的公共chunck数量。
提高会使重复代码变少,但是文件数会增加,以是也不是越高越好。

splitChunks: { maxInitialRequests: 5, //默认3 maxAsyncRequests: 6, //默认5 ...},复制代码vendors提取

默认的策略是提取入口chunck依赖的所有 node_modules 下的文件,这有个问题,async的chunck又会把 node_modules 下的文件再打包一次,以是有一些库浏览器会下载两份。
而且只被引用了一次的库没有提取的必要,即不减小chuncks的总大小,也不减少文件数,反而增加了 chunk-vendors 的大小,使得所有入口总文件大小增加。

cacheGroups: { ... vendors: { name: 'chunk-vendors', test: /[\\/]node_modules[\\/]/, priority: -10, // 增加下面两行 minChunks: 2, chunks: 'all', },}复制代码踩坑

修正分片规则后dom中的js文件有部分缺失落: HtmlWebpackPlugin 的chunks插入办法

chunk-common分割

原来所有入口chunck的代码公共部分(至少引用2次)打包成一个chunk,但实在各个角色的用户不太可能访问别的入口,而所有入口都必须下载 chunk-common ,因此将 chunk-common 分割成多份(不再合并所有公共代码为一个文件)

common: { name: true, // 默认是name: 'chunk-common' minChunks: 2, minSize: 60000, // 大小超过60kb的模块才会被提取,防止一堆小chunck priority: -20, chunks: 'initial', reuseExistingChunk: true,},复制代码

经由这些处理后,chunks总大小:10.8M => 15.8M(parsed)

看上去总大小变大了,由于重复代码多了,但是各入口所需加载的文件从7-8M减少到了3-4M,均匀减少了60%。
和这比起来,多占用做事器5M存储空间根本无关紧要。

为什么把 chunk-common 分割开,却不把 chunk-verdors 分割开? 前面也提到了 chunk-verdors 是项目依赖的库,变更频率低,可以利用304缓存,如果分割开又会增加文件数量和首次加载时的要求数。
而 chunk-common 里是项目本身的代码,常常变动,因此缓存常常失落效,用户每次都得重新下载。

代码最小化

vue-cli 默认配置了 terser 进行代码压缩,可以修正其默认配置进一步压缩代码。

terser 会通过简化表达式和函数等办法来减小代码体积,和加快运行时的速率、减少运行时内存占用。

对 vue.config.js 进行如下修正:

// webpack-chain 的用法见:https://github.com/neutrinojs/webpack-chainchainWebpack: config => { if (process.env.NODE_ENV !== 'development') { config.optimization.minimizer('terser').tap((args) => { args[0] = { test: /\.m?js(\?.)?$/i, chunkFilter: () => true, warningsFilter: () => true, extractComments: false, // 注释是否单独提取成一个文件 sourceMap: true, cache: true, cacheKeys: defaultCacheKeys => defaultCacheKeys, parallel: true, include: undefined, // 对哪些文件生效 exclude: undefined, minify: undefined, // 自定义minify函数 // 完全参数见 https://github.com/terser/terser#minify-options terserOptions: { compress: { arrows: true, // 转换成箭头函数 collapse_vars: false, // 可能有副浸染,以是关掉 comparisons: true, // 简化表达式,如:!(a <= b) → a > b computed_props: true, // 打算变量转换成常量,如:{["computed"]: 1} → {computed: 1} drop_console: true, // 去除 console. 函数 hoist_funs: false, // 函数提升声明 hoist_props: false, // 常量工具属性转换成常量,如:var o={p:1, q:2}; f(o.p, o.q) → f(1, 2); hoist_vars: false, // var声明变量提升,关掉由于会增大输出体积 inline: true, // 只有return语句的函数的调用变成inline调用,有以下几个级别:0(false),1,2,3(true) loops: true, // 优化do, while, for循环,当条件可以静态决定的时候 negate_iife: false, // 当返回值被丢弃的时候,取消立即调用函数表达式。
properties: false, // 用圆点操作符更换属性访问办法,如:foo["bar"] → foo.bar reduce_funcs: false, // 旧选项 reduce_vars: true, // 变量赋值和利用时常量工具转常量 switches: true, // 撤除switch的重复分支和未利用部分 toplevel: false, // 扔掉顶级浸染域中未被利用的函数和变量 typeofs: false, // 转换typeof foo == "undefined" 为 foo === void 0,紧张用于兼容IE10之前的浏览器 booleans: true, // 简化布尔表达式,如:!!a ? b : c → a ? b : c if_return: true, // 优化if/return 和 if/continue sequences: true, // 利用逗号运算符连接连续的大略语句,可以设置为正整数,以指定将天生的最大连续逗号序列数。
默认200。
unused: true, // 扔掉未被利用的函数和变量 conditionals: true, // 优化if语句和条件表达式 dead_code: true, // 扔掉未被利用的代码 evaluate: true, // 考试测验打算常量表达式 // passes: 2, // compress的最大运行次数,默认是1,如果不在乎实行韶光可以调高 }, mangle: { safari10: true, }, }, }; return args; }); } ...}复制代码

对付各参数,该当根据项目详细情形设定,加快运行速率或者减小代码体积的代价是增加了打包韶光,如果不知道某个参数的含义最好不要修正。
复制代码

chunks总大小:15.8M => 15.7M(parsed)(紧张提升运行时速率)

HtmlWebpackPlugin

HtmlWebpackPlugin 是 webpack 的一个plugin,许可给每个入口指定一个html模板,来简化html文件的创建。
vue.config.js 的配置中的 pages 选项实际指定的便是 HtmlWebpackPlugin 的选项。

升级到了 V4.3,支持根据入口的chunk插入标签

升级版本的缘故原由可以看前面踩坑中我写的博客

PreloadWebpackPlugin

这是一个 HtmlWebpackPlugin 的plugin,用于插入 <link rel="prefetch"> 和 <link rel="preload"> 标签。
由于 PreloadWebpackPlugin V2.3不支持 HtmlWebpackPlugin V4 ,这里升级到v3.0.0-beta.3(详细见 issues )。
但是v3.0.0-beta.3 在多入口下会默认插入所有asyncChunks,必须用正则匹配,并且放弃了原来的按入口插入的选项。
估计因此为 prefetch 不因被滥用,应该专门指定。

prefetch 用来见告浏览器在空闲时提前下载用户在接下来的浏览中可能用到的文件, preload 则由于浏览器实行css时会壅塞DOM解析,以是常日用 preload 来提前下载当前页面立时要用到的文件

目前策略改为: prefetch 入口干系的常用的asyncChunks, preload chunk-vendors 和入口chunk

const HtmlWebpackPlugin = require('html-webpack-plugin');const PreloadWebpackPlugin = require('preload-webpack-plugin');...chainWebpack: config => { Object.keys(config.entryPoints.entries()).forEach(page => { config.plugins.delete(`html-${page}`); config.plugins.delete(`preload-${page}`); config.plugins.delete(`prefetch-${page}`); / vue-cli内置的 v3.2 HtmlWebpackPlugin,插入的chunks必须显示指定 vue-cli默认指定的chunks是['chunk-vendors', 'chunk-common', page] 但是修正了分片规则往后,完身分片之前不知道有哪些chunks 这里全部更换为v4.3的HtmlWebpackPlugin / config .plugin(`html-${page}`) .use(HtmlWebpackPlugin, [{ filename: `${page}.html`, // v4.3中,名为chunks,实为entries chunks: [page], template: templates[page], }]); / PreloadWebpackPlugin v2.3.0 不支持HtmlWebpackPlugin V4 , 这里更换为 v3.0.0-beta.3 @see https://github.com/GoogleChromeLabs/preload-webpack-plugin/issues/79 v3.0.0-beta.3 在多入口下会默认插入所有asyncChunks,必须用正则匹配 @see https://github.com/GoogleChromeLabs/preload-webpack-plugin/issues/96 有人提了按入口插入的 pull request,将来也容许以不用这么麻烦 @see https://github.com/GoogleChromeLabs/preload-webpack-plugin/pull/109 / config .plugin(`prefetch-${page}`) .use(PreloadWebpackPlugin, [{ rel: 'prefetch', include: 'asyncChunks', fileWhitelist: [ // your RegExp here ], fileBlacklist: [ /\.map$/, /hot-update\.js$/, ], // v3.0.0-beta.3 没有includeHtmlNames选项了,只能通过excludeHtmlNames掌握 excludeHtmlNames: Object.keys(config.entryPoints.entries()) .filter(entry => entry !== page) .map(entry => `${entry}.html`), }]); config .plugin(`preload-${page}`) .use(PreloadWebpackPlugin, [{ rel: 'preload', include: ['chunk-vendors', page], fileBlacklist: [ /\.map$/, /hot-update\.js$/, ], excludeHtmlNames: Object.keys(config.entryPoints.entries()) .filter(entry => entry !== page) .map(entry => `${entry}.html`), }]); }); ...}复制代码

要把稳的是, preload 要放在 prefetch 之前。
其余 prefetch 不应该被滥用,否则会造成占用大量做事器带宽。
复制代码

如果是在html模板中添加的脚本, PreloadWebpackPlugin 是不会自动添加 preload 和 prefetch 的,须要自己手动添加

CorsPlugin

这是一个vue-cli内置的WebpackPlugin,用于给 HtmlWebpackPlugin 插入的 tags 加上 crossorigin 属性(脚本跨域)。
由于我们的项目的静态资源(包括js和css)终极会放到CDN上,因此和项目的域名不同源,如果没有 crossorigin 属性,js报错是 Script error ,前端监控网络不到js报错的位置。
如果 <script> 有 crossorigin 属性,那么 prefetch 和 preload 也必须有 crossorigin 属性,否则 prefetch 和 preload 的资源是不会被利用的,会造成2次下载。
CORS settings attributes

然而 CorsPlugin 不支持 HtmlWebpackPlugin V4,因此须要重写一下 CorsPlugin ,在 PreloadWebpackPlugin 实行完后,通过正则表达式更换 html 里的 prefetch 和 preload 标签。

图片压缩

在 vue.config.js 中添加一个图片压缩的loader: image-webpack-loader

npm install image-webpack-loader --save-dev复制代码

chainWebpack: config => { config.module .rule('images') .use('image-webpack-loader') .loader('image-webpack-loader') .options({ bypassOnDebug: true, // webpack 'debug' 模式下不实行 }) .end() .end()}复制代码

压缩效果: /dist 大小:91M=>83.1M,chunks总大小:16.3M=> 15.7M(parsed)

调度文件加载顺序

HtmlWebpackPlugin 会把入口chunk插入到 <body> 末端,因此写在 html 模板里的脚本会在入口chunk之前加载,它们会壅塞DOM解析,直到加载完成之后才开始加载入口chunk。
以是,该当给不必要优先加载的脚本加上 defer 属性来延后加载,以及给须要优先加载的脚本加上 preload (在所有css文件之前)。

defer:这个布尔属性被设定用来关照浏览器该脚本将在文档完成解析后,触发 DOMContentLoaded 事宜前实行。

(图片来源于网络,详细出处不明)

页面中文件的加载顺序随意马虎被忽略,应该合理调度使得下载和实行能充分并行,不要让浏览器“闲下来”,这能很大程度长进步FMP复制代码

HTTP/2 中引入了多路复用的技能。
多路复用很好的办理了浏览器限定同一个域名下的要求数量的问题,基本不用再担心文件数量过多。
可惜项目的CDN不支持HTTP/2。

加快webpack打包速率

由于项目弘大,之前打包韶光就很长,添加了 image-webpack-loader 之后,每次打包支配的韶光增加了约1min,已经不太能忍受了。
项目是在docker容器中拉代码打包支配的。
检讨了一下,打包韶光的大头都在 npm install 和各种 loader 的实行上,如果不用每次打包都从0开始实行的话会快很多。
首先可以在 image-webpack-loader 之前加上 cache-loader ,这样这个 loader 实行过一次后的数据会缓存在 node_modules/.cache 目录下,下次再打包就会利用缓存。

config.module .rule('images') // 给 image-webpack-loader 加上缓存来加快编译 .use('cache-loader') .before('url-loader') .loader('cache-loader') .options({ cacheDirectory: path.join(__dirname, 'node_modules/.cache/image-webpack-loader'), })复制代码

把稳:只有实行韶光很长的loader才适宜用缓存,由于读写文件也是有开销的,滥用反而会导致变慢复制代码

在创建镜像时先打包一次得到 node_modules 目录。
之后每次支配的时候,拉完代码后,将镜像中的 node_modules 直接移到代码目录下再实行 npm install 和 npm run build 。
或者用 npm install --cache-min Infinity 利用npm缓存安装,再移动镜像中的 .cache 目录到代码目录下的 node_modules 里。
而且能很大程度上避免由于网络不稳定导致的 npm install 下载文件慢的问题。

其余还有 Freightnpmbox 等离线包安装工具,可以根据项目情形利用

其余,如果生产环境不须要 SourceMap ,在 vue.config.js 中配置关掉。

productionSourceMap: false复制代码

处理之后打包韶光比拟:

平时的代码写法建议

实在多数项目的性能问题有很多这天常平常写代码时一些不好的写法一点点引入的,你写的差一点,我写得差一点,韶光长了就很难进行优化,由于涉及大范围的改动。
这里给出一些写法的建议。

新增的路由,考虑下要不要

chunks总大小: 16.09 MB (parsed)

各入口大小: 2.3-4.8M (-60%)(parsed)

紧张指标数据

经由前面的一顿操作,来看下产生了多少效果吧。

其余慢开比降落了67%,首次加载跳出率降落了47%。

可以看到TTI低落没有达到之前设定的目标,一方面是加载的资源太多了,有一些可以改成按需加载,另一方面是有些ajax要求比较慢,这个不在前端可控范围内了。
后续会想其它办法类办理(可能考虑用 service worker )。

如果你现在也想学习前端开拓技能,在学习前真个过程当中有遇见任何关于学习方法,学习路线,学习效率等方面的问题,你都可以申请加入我的Q群:前114中6649后671,里面有许多前端学习资料 大厂口试真题免费获取,希望能够对你们有所帮助。

标签:

相关文章

Java代码虚拟化保护技术与应用前景

软件应用的需求日益增长,软件开发过程中对代码的保护成为了一个重要议题。Java作为一种广泛应用于企业级应用的编程语言,其代码虚拟化...

PHP教程 2025-03-02 阅读1 评论0

CAD插件错误代码与应对步骤

CAD(计算机辅助设计)软件在工程设计领域得到了广泛应用。CAD插件作为提升设计效率的重要工具,在提高设计师工作效率的也带来了一定...

PHP教程 2025-03-02 阅读1 评论0

上古卷轴代码规则大全游戏背后的编程奥秘

《上古卷轴》作为一款深受玩家喜爱的角色扮演游戏,自问世以来便以其丰富的世界观、独特的游戏体验和深厚的文化底蕴吸引了无数玩家。在这款...

PHP教程 2025-03-02 阅读1 评论0