vue项目性能优化方案

背景
项目采用vue cli3搭建,集成前端组件以及地图效果,导致项目打包后资源包文件特别大,打包速度慢,首屏渲染耗时长,甚至出现左右界面图表数据不渲染的问题。

优化前准备
首先我们需要先排查影响性能、导致打包资源文件过大的原因,以及代码的使用率

  • webpack-bundle-analyzer:
npm install webpack-bundle-analyzer

// vue.config.js文件(vue cli3根目录下的文件,如果没有,可创建此文件,用于webpack配置)

module.exports = {
    chainWebpack: config => {
        /* 添加分析工具 */
        if (process.env.NODE_ENV === 'production') {
            if (process.env.npm_config_report) {
                config
                    .plugin('webpack-bundle-analyzer')
                    // eslint-disable-next-line global-require
                    .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin)
                    .end();
                config.plugins.delete('prefetch');
            }
        }
    },
};

通过命令 npm run build --report 会在打包完成后本地启动一个服务,自动打开127.0.0.1:8888网页,可以查看打包后各个依赖包占用的资源大小,我们可以针对各个依赖包的相关大小作出体积的优化,如果开源库过大可以考虑按需引入,不要全部引入,如果是自己公司封装的私有组件库、类库,可以排查哪部分占用文件过大,打包进行优化处理,如下图:

在这里插入图片描述

在这里插入图片描述
  • coverage:
    通过打开控制台(F12),ctrl+shift+p,搜索coverage,然后选中,然后点击开始录制,之后做一些操作,比如刷新界面,开始录制,查看我们代码的实际使用率,也可以点击文件查看这个文件中具体未使用的那段代码(红色部分)


    在这里插入图片描述
    在这里插入图片描述

    优化方案:

  • gzip压缩:gzip压缩可以特别明显的提高我们的代码加载效果,提升效率5-6倍左右,它会把诸如js、css等文件进行压缩,并且让我们在加载时去请求那些gz文件而提升请求效率,这部分可以参考博主的另一篇文章 gzip压缩方案

  • 路由懒加载:实际来说,我们在首页就不需要加载其他路由的文件以及数据,而当我们执行到某个具体路由,再去加载当前路由的才是最正确的方案,所以路由懒加载是我们必须要做的,使用方法如下:

这里拿home路由举例,博主每个页面都是分为左、右、中上、中下四个模块的,所以每个路由中都有四个组件,当然,路由懒加载写法是一样的,通过箭头函数返回一个组件,webpackChunkName就是最早的打包后的文件名,同一路由可以写同一个名字,推荐写路由名,方便我们知道是哪个路由下的。

const MainLeft = () => import(/* webpackChunkName: "Home" */ '../components/Home/MainLeft.vue');
const MainRight = () => import(/* webpackChunkName: "Home" */ '../components/Home/MainRight.vue');
const MainCenterTop = () => import(/* webpackChunkName: "Home" */ '../components/Home/MainCenterTop.vue');
const MainCenterBottom = () => import(/* webpackChunkName: "Home" */ '../components/Home/MainCenterBottom.vue');

而在router.js里就是正常写法:

routes: [
        {
            path: '/',
            name: 'Home',
            title: '首页',
            components: {
                routerLeft: Home.MainLeft,
                routerRight: Home.MainRight,
                routerCenterTop: Home.MainCenterTop,
                // routerCenterBottom: Home.MainCenterBottom,
            },
        },
]

开启路由懒加载后,vue cli3项目还需要在vue.config.js文件中配置如下

// vue.config.js文件(vue cli3根目录下的文件,如果没有,可创建此文件,用于webpack配置)
module.exports = {
    chainWebpack: config => {
        // 移除 prefetch 插件(避免会预先加载模块/路由)
        config.plugins.delete('prefetch');
    },
};

(注:之前博主遇到个小坑,项目中有个文件夹,里面写了一些全局封装并且全局注册的组件供其他页面使用,而有些组件中使用了require去动态加载图片导致打包后路由懒加载不生效没有生成对应路由的文件,大家留意一下即可,如果遇到此问题,可以找博主提供解决思路)

  • 组件库按需引入:这一点不需要过多赘述了,使用了诸如element ui等组件库的话,官网都有很详细的介绍如何按需加载
  • 图片压缩:安装此插件的时候有可能出现部分依赖安装失败,导致压缩失败,可以先卸载此插件然后用cnpm或淘宝源重新安装
npm install image-webpack-loader

// vue.config.js文件(vue cli3根目录下的文件,如果没有,可创建此文件,用于webpack配置)
module.exports = {
    chainWebpack: config => {
        // 开启图片压缩
        // config.module
        //     .rule('images')
        //     .test(/\.(png|jpe?g|gif|svg)(\?.*)?$/)
        //     .use('image-webpack-loader')
        //     .loader('image-webpack-loader')
        //     .options({ bypassOnDebug: true });
    },
};

  • map映射文件:vue打包会保留源文件的map映射,方便我们打包部署后依然可以通过控制台的source查看搜索源文件的代码,方便定位问题,但是多生成map文件会导致打包文件过大,所以仁者见仁智者见智,看各位是否需要打包部署后方便定位问题吧,不需要的话可以考虑不生成map文件
module.exports = {
    productionSourceMap: true,
}

  • 分包加载:vue打包后会把依赖包都打包到app.js文件里,这样会导致单文件过大,请求耗时,加载缓慢,所以可以考虑把各依赖单独拆分打包,实现多个小文件请求加载的方式,代码如下:
// vue.config.js文件(vue cli3根目录下的文件,如果没有,可创建此文件,用于webpack配置)
module.exports = {
    configureWebpack: {
        optimization: {
            runtimeChunk: 'single',
            splitChunks: {
                chunks: 'all',
                maxInitialRequests: Infinity,
                minSize: 20000,
                cacheGroups: {
                    vendor: {
                        test: /[\\/]node_modules[\\/]/,
                        name(module) {
                            // get the name. E.g. node_modules/packageName/not/this/part.js
                            // or node_modules/packageName
                            const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
                            // npm package names are URL-safe, but some servers don't like @ symbols
                            return `npm.${packageName.replace('@', '')}`;
                        },
                    },
                },
            },
        },
    },
};

上述代码会把所有的包括异步请求的模块分割,针对大于20000k的文件,最终命名方式是npm.依赖包.js文件,比如npm.echarts.js,命名可以自定义。

  • 重复引用依赖包:封装了一个公共组件库(可通过npm下载),但是临时为了省事项目中也封装了一些组件单独放在一个文件夹中,最终一些相同的依赖就会被重复引用,比如组件库中用了amcharts图表,而项目中又安装使用了这个图表库,会导致此依赖被重复引用,解决方案:把项目中的组件挪到组件库中,这样同一份依赖就只会被使用一次。(不过我记得webpack有配置可以针对同一依赖多次引用的处理,把依赖进行缓存,避免被多次引用,感兴趣的童鞋可以去查阅下相关资料)
  • fps:之前项目中封装了一个方法,针对数据请求多的情况,做了fps处理,如果fps小于30就先不执行数据请求,先等现有数据、界面、地图等渲染完毕,fps稳定后再去执行下一部分数据请求、界面渲染,本身是为了能更好更流畅的渲染,但是如果电脑性能本身就很差,fps本身就很低,会导致一直数据不渲染(哈哈哈,给自己埋了个坑),最终定位问题后发现了这段不合理的处理。大家写代码也要多注意这些情况。

完整的vue.config.js配置:

const fs = require('fs');
const CompressionPlugin = require('compression-webpack-plugin');

let devServer = {};
if (fs.existsSync('./dev-config.js')) {
    // eslint-disable-next-line global-require
    devServer = require('./dev-config');
} else {
    console.error('!!!please create the dev-config.js file from dev-config-template.js');
}

module.exports = {
    lintOnSave: true,
    productionSourceMap: true,
    chainWebpack: config => {
        // 移除 prefetch 插件(避免会预先加载模块/路由)
        config.plugins.delete('prefetch');
        // Loader
        config.module
            .rule('svg')
            .test(/\.(swf|ttf|eot|svg|woff(2))(\?[a-z0-9]+)?$/)
            .use('file-loader')
            .loader('file-loader')
            .end();
        // 开启图片压缩
        // config.module
        //     .rule('images')
        //     .test(/\.(png|jpe?g|gif|svg)(\?.*)?$/)
        //     .use('image-webpack-loader')
        //     .loader('image-webpack-loader')
        //     .options({ bypassOnDebug: true });
        /* 添加分析工具 */
        if (process.env.NODE_ENV === 'production') {
            if (process.env.npm_config_report) {
                config
                    .plugin('webpack-bundle-analyzer')
                    // eslint-disable-next-line global-require
                    .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin)
                    .end();
                config.plugins.delete('prefetch');
            }
        }
    },
    devServer,
    configureWebpack: {
        resolve: {
            alias: {
                src: '@',
                components: '@/components',
                views: '@/views',
            },
        },
        plugins: [
            new CompressionPlugin({
                algorithm: 'gzip', // 使用gzip压缩
                test: /\.js$|\.html$|\.css$/, // 匹配文件名
                filename: '[path].gz[query]', // 压缩后的文件名(保持原文件名,后缀加.gz)
                minRatio: 1, // 压缩率小于1才会压缩
                threshold: 10240, // 对超过10k的数据压缩
                deleteOriginalAssets: false, // 是否删除未压缩的源文件,谨慎设置,如果希望提供非gzip的资源,可不设置或者设置为false(比如删除打包后的gz后还可以加载到原始资源文件)
            }),
        ],
        optimization: {
            runtimeChunk: 'single',
            splitChunks: {
                chunks: 'all',
                maxInitialRequests: Infinity,
                minSize: 20000,
                cacheGroups: {
                    vendor: {
                        test: /[\\/]node_modules[\\/]/,
                        name(module) {
                            // get the name. E.g. node_modules/packageName/not/this/part.js
                            // or node_modules/packageName
                            const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
                            // npm package names are URL-safe, but some servers don't like @ symbols
                            return `npm.${packageName.replace('@', '')}`;
                        },
                    },
                },
            },
        },
    },
};

好了,以上就是博主自身项目中关于性能优化的一些处理,大家可以参考下。

如有问题,请指出,接收批评。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,393评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,790评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,391评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,703评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,613评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,003评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,507评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,158评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,300评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,256评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,274评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,984评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,569评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,662评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,899评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,268评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,840评论 2 339

推荐阅读更多精彩内容