性能优化记录

1,513 阅读5分钟

您好,如果喜欢我的文章,可以关注我的公众号「量子前端」,将不定期关注推送前端好文~

前言

最近零零散散的对刚接手的一个新项目做了一些优化,白屏、打包相关的内容都涉及到了,写一篇文章来记录一下。

白屏相关

DNS预解析、资源预加载

对于项目中有很多静态资源涉及到的公共域名,如g.alicdn.cmon,采用DNS预连接 + 解析:

<link rel="preconnect" href="//g.alicdn.com" crossorigin />
<link rel="dns-prefetch" href="//g.alicdn.com" />

对于项目中一些必要的JS资源,采用资源预加载,可以大幅度缩短资源加载时间:

<link rel="preload" href="https://g.alicdn.com/eleme-risk/xxxxxxx-pc/0.0.99/js/index.js" as="script" />
<link rel="preload" href="//g.alicdn.com/alilog/mlog/aplus_v2.js" as="script" />

结果:整体白屏时间降低400~600ms。

页面级路由懒加载

原本项目打包出来的JS文件只有一个bundle.js,涵盖了整个项目的业务代码,对于业务方来说来说,可能访问最多的就是新增和详情两个页面,所以对于首屏加载是不友好的,应该优化成访问哪个页面加载对应页面的资源,基于Ice2.0调研,将路由中的组件都转换为懒加载模式:

routes.ts

import { lazy, IRouterConfig } from 'ice';
// ice不支持layout组件设置为懒加载
import Layout from '@/layouts/BasicLayout';

const Home = lazy(() => import(/* webpackChunkName: 'Home' */ '@/pages/Home'));
const NotFound = lazy(() => import(/* webpackChunkName: 'NotFound' */ '@/components/NotFound'));
const ManualDetect = lazy(() => import(/* webpackChunkName: 'ManualDetect' */ '@/pages/ManualDetect'));
const AddMission = lazy(() => import(/* webpackChunkName: 'addMission' */ '@/pages/ReconnaissanceMission/add-mission'));
const MissionDetail = lazy(
  () => import(/* webpackChunkName: 'missionDetail' */ '@/pages/ReconnaissanceMission/missionDetail'),
);
const NewMissionDetail = lazy(
  () => import(/* webpackChunkName: 'newMissionDetail' */ '@/pages/ReconnaissanceMission/newMissionDetail'),
);
const NoPermission = lazy(() => import(/* webpackChunkName: 'NoPermission' */ '@/pages/NoPermission'));
const Board = lazy(() => import(/* webpackChunkName: 'Board' */ '@/pages/Board'));
const BusinessInsight = lazy(() => import(/* webpackChunkName: 'BusinessInsight' */ '@/pages/BusinessInsight'));
const ChuangDaoInsight = lazy(() => import(/* webpackChunkName: 'ChuangDaoInsight' */ '@/pages/ChuangDaoInsight'));
const Report = lazy(() => import(/* webpackChunkName: 'Report' */ '@/pages/Report'));

const routes: IRouterConfig[] = [
  {
    path: '/',
    component: Layout,
    children: [
      {
        path: '/manualDetect',
        component: ManualDetect,
      },
      {
        path: '/addMission',
        component: AddMission,
      },
      {
        path: '/MissionDetail',
        component: MissionDetail,
      },
      {
        path: '/newMissionDetail',
        component: NewMissionDetail,
      },
      {
        path: '/',
        exact: true,
        component: Home,
      },
      {
        path: '/noPermission',
        exact: true,
        component: NoPermission,
      },
      {
        path: '/board',
        exact: true,
        component: Board,
      },
      {
        path: '/businessInsight',
        exact: true,
        component: BusinessInsight,
      },
      {
        path: '/chuangDaoInsight',
        exact: true,
        component: ChuangDaoInsight,
      },
      {
        path: '/report',
        exact: true,
        component: Report,
      },
      {
        component: NotFound,
      },
    ],
  },
];

export default routes;

看一下效果。

在改动前是这样的:

image.png

无论访问哪个页面,请求固定的JS文件,大小为2.3MB。

改动以后发版:

首屏刷新: image.png

切换一个路由:

image.png

效果很明显了,文件资源也小了很多,白屏时间自然就下降了。

详细的文章在这里:

React中的懒加载以及在Ice中实践

结果:白屏时间整体降低,请求资源大小整体下降。

构建相关

优化本地热更新时间

项目本地热更新时间比较慢,大约在8~9秒,基于ice运行时中间件在每次代码变更时加入缓存同时移除对node_module目录下的babel转换,可以写一段这样的代码:

module.exports = ({ onGetWebpackConfig }) => {
  onGetWebpackConfig((config) => {
    config.module
      .rule('tsx')
      .test(/\.jsx?|\.tsx?$/)
      .exclude.add(/node_modules/)
      .end()
      .use('babel-loader')
      .tap((options) => {
        return {
          ...options,
          cacheDirectory: true,
        };
      });
  });
};

build.json中注入该插件:

{
  // ...
  "plugins": [
    "@ali/build-plugin-faas",
    [
      "build-plugin-ignore-style",
      {
        "libraryName": "antd"
      }
    ],
    "@ali/build-plugin-ice-def",
    "./src/index.ts"
  ]
}

image.png

结果:热更新时间降低到4秒左右,降低50%。

构建包大小优化

CDN资源替代项目依赖包

利用Webpack模块可视化工具,项目中的依赖是这样的:

image.png

image.png

从上图可以看到:在开发环境整个构建包体积达到了19.44MB,echartsantvmoment这些包,体积都比较大,达到了MB量级,并且在项目中前两者使用频率很低,只有引用过一次,对于这种情况,考虑将依赖包转换为CDN引入的方式,原因如下:

  • 减少打包产物大小;
  • 减少白屏时间;
  • 版本固定,使用频率低,通过CDN单独引入还会有浏览器强缓存的效益;

通过Webpackexternals,取消对于node_modules中枚举包的计算,并且在项目index.html中从CDN引入所列举到的包。

{
	// ...
  "externals": {
    "echarts": "echarts",
    "moment": "moment"
  },
}

externals这里的keyvalue值分别对应npm中的包名和CDN引入后在window下的全局变量名,找包的CDN路径很简单,但是如何知道全局变量名是什么呢?

可以打开CDN链接,格式化代码,大概是这个样子的:

function(e, t) {
    "object" == typeof exports && "object" == typeof module ? //判断环境是否支持commonjs模块规范
    module.exports = t(require("vue")) :
    "function" == typeof define && define.amd ? //判断环境是否支持AMD模块规范
    define("ELEMENT", ["vue"], t) :
    "object" == typeof exports ? //判断环境是否支持CMD模块规范
    exports.ELEMENT = t(require("vue")) : 
    e.ELEMENT = t(e.Vue)
} ("undefined" != typeof self ? self: this,function(e){
    //省略...
});

从这个JS文件可以看到,这个全局变量是ELEMENT咯~这块更详细的教程可以看一下这篇文章,这位博主总结的很全:

Webpack系列』—— externals用法详解

代码分割

这里利用Webpack现有的能力,对使用频繁的第三方库和模块进行统一抽离,这一部分可以写在上面提到的Ice中间件里去:

module.exports = ({ onGetWebpackConfig }) => {
  onGetWebpackConfig((config) => {
    config.optimization.splitChunks({
      cacheGroups: {
        vendor: {
          priority: 1,
          test: /node_modules/,
          chunks: 'initial',
          minChunks: 1,
          minSize: 0,
          name: 'vendor',
          filename: 'vendor.js',
        },
        common: {
          chunks: 'initial',
          name: 'common',
          minSize: 100,
          minChunks: 3,
          filename: 'common.js',
        },
      },
    });
  });
};

抽离出来的模块如图:

image.png

结果:优化后的构建包体积为9.1MB,降低了50%以上大小。

写在最后

本文总结了一下博主近期性能优化的一些点,对于这些点有什么处理不好的地方可以一起讨论~

如果喜欢我的文章,可以关注我的公众号「量子前端」,将不定期关注推送前端好文~