如果数据(列表、树状)很多特别卡的时候,可参考: 列表参考:npm包react-window,https://react-window.vercel.app/#/examples/list/fixed-size 树状参考:npm包react-vtree,https://lodin.github.io/react-vtree/index.html?path=/story/tree--fixedsizetree
-
提取第三方库(利用webpack的splitChunks,下方有代码demo)。各种第三方的库,变化很少,我们完全可以单独打包,并设置强缓存(expires、cache-control)和协商缓存(lastmodify、Etag)。当项目迭代时,不会再将第三方库打包进包里面了,可以减小包的大小,且配合缓存,即可增加打包速度及页面的加载速度。拆分包后势必会增加http请求,我们可以将资源上传至不同域名或cdn,增加浏览器的并行下载能力,或者使用http2,多路复用
- 针对低频率基本不变化的文件,并且所有页面都需要用的包(如react、ant),单独打包,并且配置固定版本号
- 针对中频率变化很少的文件,部分页面才用的包(如lodash,echarts等),单独打包,并且配置固定版本号
- 针对高频率变化的文件,基本是业务代码,单独打包,并且配置动态版本号
- 针对其他npm动态加载的包,但是项目中没用到的包,单独打包,并且不引入页面,减小打包文件大小
-
去除没用到的npm包
- 通过工具分析(利用npm包depcheck),找出没用到的npm包,进行删除处理
- 使用Tree Shaking
-
启用gzip压缩
- 通过webpack配置,将能在构建时压缩的文件(如css、js等,图片不能gzip压缩),构建时进行压缩(压缩等级为1-9,我们设置为9),避免服务器动态实时压缩,消耗服务器的cpu资源,而且也能节省服务器压缩所需要的时间
- 压缩好文件后,通过Nginx配置gzip_static,如果已经压缩文件(比如.gz结尾的文件),服务器直接返回压缩文件至浏览器即可;同时配置gzip,对一些构建时无法压缩的文件,进行动态压缩
- 入口html不是直接引人.gz文件,而是会引入和.gz文件一样文件名的文件
-
其他
- 固定package.js中npm包的版本。避免由于第三方库的升级而导致bug
- 修改webpack配置,使得打包时不生成sourceMap和License文件,优化打包时间及包大小
- 域名预解析
- 很少变更的数据做localstorage缓存,比如城市编码、行业分类等
- 通过配置,能够展示包的构成情况(webpack-bundle-analyzer),将公用的包提出,将无用的包删除;
- 明细化的展示各个包的打包时间(speed-measure-webpack-plugin),然后进行响应的优化(比如cacheDirectory),优化打包速度
- 通过配置,能够将开发环境构建时的文件写入硬盘(默认是写入内存的),方便查看优化
- babel配置增加@babel/transform-runtime、["@babel/preset-env", {modules: false}]、"compact": false
- webpack的optimization增加usedExports: true
-
Tree-shaking。参考:https://zhuanlan.zhihu.com/p/417686391;参考:https://blog.csdn.net/weixin_45047039/article/details/110384743
-
webpack5可以更好的打包。比如optimization.usedExports删除未使用的属性等等;
// 开发环境方便源码调试 devtool: isDev ? 'cheap-module-source-map' : false,
// 提取第三方库。splitChunks配置参考 // 如果碰见报错:Cannot read property 'pop' of undefined,只要加上enforce: true就好了,参考echarts optimization: { ...config.optimization, splitChunks: { // chunks: 'initial', cacheGroups: { // 第三方不变的包,单独打包插入HTML。当项目迭代时,不会再将第三方库打包进包里面了,可以减少包的大小,提高性能 'ant-react': { chunks: 'all', test: /ant|react/, name: "ant-react", priority: -1, reuseExistingChunk: true, }, echarts: { chunks: 'all', test: /echarts/, name: "echarts", priority: -1, reuseExistingChunk: true, enforce: true }, // 以下是使用不到的包(比如其他npm包内部依赖的包),将其单独打包,避免打包进入bundle.js 'monaco-editor': { chunks: 'all', test: /monaco-editor/, name: "monaco-editor", priority: -1, reuseExistingChunk: true, }, // 以下是可能变化的包 nodeModules: { // 将node_modules打包在一起 chunks: 'all', test: /[\\/]node_modules[\\/]/, priority: -3, // reuseExistingChunk: true, name: 'nodeModules', }, qikecommon: { // 将其他代码打包在一起 chunks: 'all', priority: -4, enforce: true, // reuseExistingChunk: true, name: 'qikecommon' } } } }
// webpack-bundle-analyzer配置参考。注意判断环境,debug环境才配置这个 plugins: [ ...config.plugins, new BundleAnalyzerPlugin({ analyzerMode: 'server', analyzerHost: '127.0.0.1', analyzerPort: 8889, reportFilename: 'report.html', defaultSizes: 'parsed', openAnalyzer: true, generateStatsFile: false, statsFilename: 'stats.json', statsOptions: null, logLevel: 'info' }), ]
// webpack的gzip压缩。注意判断环境,生产环境才配置这个 const CompressionPlugin = require("compression-webpack-plugin"); plugins: [ ...config.plugins, new CompressionPlugin({ test: /\.(js|css|html|svg)$/i, // 使用不到的包,禁用压缩,节省时间 exclude: /monaco\-editor|tinymce|exceljs/, algorithm: 'gzip', compressionOptions: { level: 9 }, // minRatio: 0.8, filename: '[file].gz[query]' }), ]
// 删除CSS的sourcemaps。开发环境和生成环境都需要 const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); webpackConfig.optimization.minimizer.push(new CssMinimizerPlugin({ sourceMap: false, minimizerOptions: { sourceMap: false }, minify: async (data, inputMap, minimizerOptions) => { // eslint-disable-next-line global-require const CleanCSS = require('clean-css'); const [ [filename, input] ] = Object.entries(data); const minifiedCss = await new CleanCSS({ sourceMap: false }).minify({ [filename]: { styles: input, sourceMap: inputMap, }, }); return { css: minifiedCss.styles, map: minifiedCss.sourceMap ? minifiedCss.sourceMap.toJSON() : '', warnings: minifiedCss.warnings, }; }, }));
// 删除license文件。开发环境和生成环境都需要 webpackConfig.optimization.minimizer.push(new TerserPlugin({ terserOptions: { parse: { ecma: 8, }, compress: { ecma: 5, warnings: false, comparisons: false, inline: 2, }, mangle: { safari10: true, }, output: { ecma: 5, comments: false, ascii_only: true, }, }, // 不打包LICENSE文件 extractComments: false, sourceMap: false }))
- 为了减少页面白屏时间,须JS放页面后端,也要注意JS处理事件效率等,减少阻塞。资源延迟加载等提高首屏展示时间
- 减少HTTP请求数。请求数多时,为了提高浏览器的并行能力,可采用多域名及cdn,配置域名预解析
- 图片选择合适的格式(如webp,注意优雅降级)、大小及质量,icon可以通过雪碧图、font icon及base64等方式
- 压缩静态资源。对于敏感资源,可以采用混淆压缩
- 页面布局应语义化,也应避免标签、css层级太深
- 善于使用本地存储(localStorage、sessionStorage)来缓存一些配置数据,比如国家、省份等信息
- 可以通过performance.timing和performance.getEntries()查看页面资源的请求情况
- 写代码时,时刻注意闭包、重绘、重排等,善于启用GPU,善于使用requestAnimationFrame、createDocumentFragment等等
- 页面静态化
- 更多建议请参考pagespeed或者yslow
- 尽量减少转发代理等次数
- 对资源设置Content-Length属性,可以让浏览器提前知道数据的多少,而无需自己去实时检测数据是否传输完毕,提高其效率。
- 对实时性要求不高的数据做缓存
- 尽量瘦身接口返回内容,避免将一些用不到的数据也返回前端,导致传输数据量太大
- 增加Server-Timing响应标头,方便我们了解请求的数据各个阶段所花费的时间,比如db时间等
- 接口缓慢原因,是否因为配置太低,太多计算耗费CPU,导致性能降低,考虑更换更高性能的服务器
- 可以采用http2协议。多路复用、头部压缩等等优势
- 对静态资源启用gzip压缩,压缩等级看需求而定,等级越高压缩比例越大,但是也越耗CPU。注意图片别gzip压缩
- 对于常年不变或变化很少的资源,启用强制缓存(expires、cache-control),不请求网络;其他资源可以启用协商缓存,尽量使用lastModify,304返回头部,不返回body。尽量少用eTag,比较耗CPU,一定要etag时,尽量部分内容etag,避免全量etag
- 对于重要项目或者pv、uv高项目,采用负载均衡
- CDN
- dns缓存
- 域名预解析
- 负载均衡