性能优化
工程化相关(webpack):
- 提取公共代码(Webpack4 webpack.optimize.splitChunks,mode:production已默认配置)
拆分出入口文件main.js中import导入的依赖,不包括异步加载(() => import())的依赖。 - 组件懒加载 import()(关于为什么使用import却没有懒加载,请移步至:import()分片问题)
vue "component: () => import()" - 按需引入echarts,elementUI,antdesign,lodash等包。
echarts4/5按需引入官网例子不相同。 echarts5官网按需引入例子
lodash: babel-plugin-lodash + lodash-webpack-plugin。 lodash按需引入 - 代码压缩,
1. 压缩JS:UglifyJS/UglifyES 。TerserWebpackPlugin(v5自带)(webpack production模式会自动压缩)
2. 提取CSS:MiniCssExtractPlugin(v4,v5)
2. 压缩CSS:cssnano ,CssMinimizerWebpackPlugin(v5)
3. 开启gzip,brotli,sdch,deflate,... CompressionWebpackPlugin(v5)。具体支持什么压缩方式,需要服务支持。 - Webpack 配置externals,并在index.html用<script>引入依赖的min.js文件,vue,echarts等。不打包,也提升了构建速度。
使用CDN加载静态资源(同上配置externals)
CDN资源通过<script>标签引入。如果首屏没有使用到,则可以defer。
- prerender-spa-plugin 设定预加载页面 (前端SEO优化)
prerender-spa-plugin 利用了 Puppeteer 的爬取页面的功能。原理是在 Webpack 构建阶段的最后,在本地启动一个 Puppeteer 的服务,访问配置了预渲染的路由,然后将 Puppeteer 中渲染的页面输出到 HTML 文件中,并建立路由对应的目录。引用自[参考资料2]
- web-webpack-plugin为每个js生成一个入口文件 AutoWebPlugin插件的使用(每页都是首屏,相当于不要SPA特性)(很久没更新了,低版本webpack可能有用)
使用 Magic comment(Webpack4.6.0+)[
参考资料1
]
/* webpackPreload: true */预加载首屏需要的资源 (基于<link> preload)
/* webpackPrefetch: true */预下载即将可能用到的资源(基于<link> prefetch)雪碧图 svg-sprite-loader (减少图片请求数)
- 使用url-loader(file-loader)将小图片通过base64的方式嵌入html
(减少图片请求数)
- Webpack 5不需要使用url-loader,使用自带资源模块替代url-loader的能提升构建速度。
- 开启Tree Shaking去除无用代码,优化文件大小,配置optimization.usedExports = true 。仅支持es module
使用es module语法import {a, b} from './util.js' 引入。假设a,b是function。
若代码中使用了a() ,则打包时会抽取出util.js的function a(){}的部分代码。util.js中的其他未使用的function代码不会打包。(可以优化lodash-es等工具类)
若util.js用export default导出。则无法触发TreeShaking。打包后:
unesed harmony export 未使用的function不打包
- babel 配置可能导致打包过大 (babel入门见参考资料8)
babel合理配置浏览器版本,一般版本越高,polyfill代码越少,按实际情况配置module.exports = { presets: [ ["@babel/preset-env", { //打包不同的模块cmd,umd,cjs,esm等,尤其umd代码会比较多| modules: false, // false 代表esm //按需引入需要polyfill的内容,@babel/polyfill已废弃,详细百度@babel/polyfill与core-js useBuiltIns: 'usage', // webpack中使用usage会有点问题 corjs: 3.x.x, // 注意:不带小版本则视为3.0.0。使用useBuiltIns需要配置这个,默认为corejs2 targets: { // 需要兼容的浏览器版本,众所周知,浏览器版本越低,需要polyfill的代码越多,与babel有关的代码越多。 chrome: '84', } } ] ] }
babel 配置plugin-transform-runtime,用于减少重复引入的polyfill - 使用DefinePlugin 通过编译的方式,减少代码体积。如__VUE_OPTIONS_API__: 'false'。
- core-js(swc)
- 根据需要引入 core-js/stable 可以减少core-js 大小。
- 不要放在webpack/rspack 配置文件中的entry中。
如 entry: { app: ['core-js','./src/indexjs'] },这样在swc配置兼容chrome 版本会没用。
要在 js 的文件头部 import 'core-js/stable' 。
偏前端:
- 懒加载:图片懒加载,滚动到底部后加载
- 虚拟滚动:一般用于表格、列表、树等每个元素等高的场景。不分页展示大量数据方案。
- 分批加载:可基于requestAnimationFrame 分帧加载。通俗点说就是,长页面,由上至下,一个个加载,后一个(一批)元素等前一个(一批)元素加载完成后再插入dom。
- preload / prefetch link标签的属性 。预下载,预拉取资源。
- 合理使用防抖、节流。作用于请求:有利于缓解服务器压力。作用于onresize,有利于减少浏览器重绘重排。
- 合理使用事件委托,不需要每个元素上加事件。
- 合理使用前端持久化localStorage/sessionStorage/indexedDB
- 合理使用WebSocket(需服务端支持),取代传统轮询。
- 合理采用loading动画,骨架屏等。
- 合理使用Web Worker开启多线程,进行复杂CPU密集任务的计算。
- 数据采样:折线图如果点很多,可以考虑每几个点采样一个(平均/最大/最小/不计算)点,因为总体图的趋势是相同的。
- 序列化大量json提升性能:使用 fast-json-stringify
- 利用 script 标签的defer将首屏不用的依赖后置执行,以优化LCP。
- 利用 requestIdleCallback 运行首屏不需要执行的代码,以优化LCP。
- vue:
- 合理使用functional函数式组件。(vue3不需要)
- 合理使用keep-alive 缓存组件。
- data中不需要响应式的大对象
vue2:使用Object.freeze()包装,以减少vue中defineProperty的耗时
vue3:shallowRef - DefinePlugin 配置
- __VUE_OPTIONS_API__: 'false' 表示打包vue不将optionialApi打包进产物。
- __VUE_PROD_DEVTOOLS__: 'false'
- __VUE_I18N_LEGACY_API__: 'false' // 用vue-i18n 却不用legacyApi时,可以优化掉legacyApi的代码。(gz减少2.4KB)
- __VUE_I18N_FULL_INSTALL__: 'false' // 移除vue-i18n组件指令代码。(gz减少1.2KB)
- 注意减少重绘重排:
1. dom.style.cssText = 'color:#000;' // 一次设置完css
2. 合理使用 DocumentFragment
3. HTMLElement.offset/client/scroll[Height/Width] 访问这些属性会造成浏览器重绘。因此不要多次重复通过e.target.offsetWidth 这样的方式获取元素的信息。可以先保存到临时变量。 - css
1. content-visibility 可提升页面渲染速度 (chrome > 85),对table标签无效。content-visibility:auto,表示,若该元素在用户可见区,则渲染其子元素。否则不渲染。
2. 尽量使用transform位移元素,而非top/left/right/bottom/margin/padding等
3. will-change 优化动画(实验性)
4. contain 属性
5. border-collapse: separate 性能更好
高端性能方案
-
WebAssambly(wasm)
-
WebWorker 开启多线程cpu执行密集任务
-
ServiceWorker (PWA) 离线应用解决方案
-
asm.js (旧)
移动端web
- 使用 passive 改善的滚屏性能 (chrome 51-55)
偏服务端:
- 启用HTTP/2 (使用HTTP/2就可以不做,业内的一些在http1.1时做的,关于减少请求个数的优化,需要服务器支持)
- 开启gzip, brotli 等压缩。请求头Accept-Encoding,响应头Content-Encoding 。这些一般由服务端的框架或插件提供,不用手写。如koa插件koa-compress。
- 合理使用浏览器缓存。强缓存、协商缓存。
- 服务端在返回html页面时将一些基础数据嵌入html中,以减少ajax请求数量。(前后端不分离)
提升构建速度
- Webpack 配置 resolve.extensions常用的后缀放最前面。开发时导入其他模块时尽量带上后缀,此次导入会跳过自动加后缀这一步。
- Webpack loader配置项中配置include,exclude指定文件路径,缩小搜索范围。
- Webpack DLLPlugin / DLLReferencePlugin //预打包,预先打包指定的文件(一般是一些不会变动的依赖),之后打包的时候就不会再编译这些文件。
- 多进程执行loader插件(项目小就不要用,反而可能由于多进程通信原因增加打包时间)
1.happypack //(很久没更新了,低版本webpack可能有用)
2.thread-loader、cache-loader // webpack4官网文档 请仅在耗时的 loader 上使用
3.parallel-webpack - fast-sass-loader // 多进程sass-loader(低版本webpack可能有用)
-
多进程多实例并行压缩 [参考资料5]
- ParallelUglifyPlugin // 多进程UglifyJS压缩(低版本webpack可能有用)
- uglifyjs-webpack-plugin 开启 parallel 参数
- terser-webpack-plugin 开启 parallel 参数 (推荐使用这个,支持 ES6 语法压缩)
- hard-source-webpack-plugin // 缓存第一次打包的结果(/node_modules/.cache下),之后再打包,速度起飞。(webpack5用不了,已经集成了打包缓存如下)
- webpack5 配置cache.type='filesystem' // 开发模式默认为'memory'
配成filesystem效果比默认明显,会有node-sass报错,换成dart-sass解决
配成filesystem可能导致node_modules改变会不生效。此时应该清除node_modules/.cache目录的缓存,重新启动。 - babel-loader开启缓存 // cacheDirectory: true
- sass 编译实在很慢的话,考虑使用node-sass替换sass(dart-sass),但是node-sass在安装时容易出问题。(据说node-sass(libsass C/C++) 比dart-sass(dart-VM) 快,未验证。)
- typescript
- ts-loader 配置 transpileOnly: true,跳过语法检查。
- babel @babel/preset-typescript 替换ts-loader 编译ts。与ts-loader会有一点点区别。
- 使用esbuild编译js/ts,esbuild-loader。
如果项目用了babel 转换js代码,并使用了babel一些插件如(babel-plugin-import),则esbuild可能没有等价替换的插件。因此,esbuild目前并不能完全取代babel
使用esbuild-loader转换js/ts前后速度对比: - 使用swc 替换babel转换代码。使用esbuild压缩代码。swc-loader / esbuild-loader。
我尝试了一下这种方案:Webpack + swc + esbuild 优化构建速度尝试 效果还可以。 - vue-cli
- lintOnSave: false // 关闭eslint校验
- productionSourceMap: false // 关闭正式包source-map
- webpack.config.js module.noParse 指定不解析的库
npm 依赖优化
对于发布正式时,在服务器中执行 npm run build 构建的项目:
- 将webpack这类打包工具安装到 dependencies。
- 将eslint 这类依赖工具安装到 devDependencies。
- 线上构建脚本npm i 替换为 npm i --production,仅安装dependencies 中指定的模块。
使用轻量依赖替换
大 -> 小
- lodash -> lodash-es
- decimal.js -> decimal.js-light
- moment -> dayjs
moment可通过webpack.ContextReplacementPlugin()移除非中文国际化
Ant-design-vue
- 用antd-dayjs-webpack-plugin插件使moment替换为day.js 。Ant-design-vue 替换moment为dayjs方式记录_JA+的博客-CSDN博客
- 使用webpack.ContextReplacePlugin,优化moment语言包(若使用dayjs替换可无优化)
- 通过配置webpack alias,使antdv图标按需引入。
resolve.alias:// yourIcon.js export { default as CaretDownOutline } from '@ant-design/icons/lib/outline/CaretDownOutline'
'@ant-design/icons/lib/dist$': path.resolve(__dirname, 'yourIcon.js')
工具
- webpack-bundle-analyzer //热门打包分析工具,可以看到打包后的文件大小。
- speed-measure-webpack-plugin //打印各loader、plugin的耗时
附
配置dev-tool: 'source-map' 打出来的包就不会使用eval函数封装代码
其他