前端首屏优化的一些总结

5,116 阅读4分钟

开篇

很多人在构建前端项目时会发现打包出来的chunk.vendors.xxx.js文件实在是太大,导致首屏加载速度很慢.这里我把阿童木聊天室的首屏优化步骤分享给大家.

阿童木聊天室在线地址 www.genal.fun

优化流程

1. 使用webpack-bundle-analyzer分析前端项目包大小, 找出问题源头.

// vue.config.js
chainWebpack: (config) => {
  config.plugin('webpack-bundle-analyzer').use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin);
}

2. 去掉vuecli打包生成的map文件

这些文件用于错误时给出准确的提示,线上环境是不需要的.

// vue.config.js
productionSourceMap: false

3.CDN引入需要的资源

vue.config.js中配置不打包相关资源. 这样可以大幅降低项目打包文件大小

// vue.config.js
const cdn = {
  css: [
    // antd css 由于引入失败只好放弃了antd的按需引入
  ],
  js: [
    // vue
    'https://cdn.bootcdn.net/ajax/libs/vue/2.6.10/vue.min.js',
    // vue-router
    'https://cdn.bootcdn.net/ajax/libs/vue-router/3.1.3/vue-router.min.js',
    // vuex
    'https://cdn.bootcdn.net/ajax/libs/vuex/3.1.2/vuex.min.js',
    // axios
    'https://cdn.bootcdn.net/ajax/libs/axios/0.18.0/axios.min.js',
    // moment
    'https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.js',
  ],
};

chainWebpack: (config) => {
  // 正式环境配置cdn引入
  if (process.env.NODE_ENV === 'production') {
    let externals = {
      vue: 'Vue',
      axios: 'axios',
      'vue-router': 'VueRouter',
      vuex: 'Vuex',
      moment: 'moment',
    };
    config.externals(externals);
    // 通过 html-webpack-plugin 将 cdn 注入到 index.html 之中
    config.plugin('html').tap((args) => {
      args[0].cdn = cdn;
      return args;
    });
  }
},

然后在public/index.html中加入以下代码, 正式环境才走cdn

<% if (process.env.NODE_ENV === 'production') { %>
<% for(var css of htmlWebpackPlugin.options.cdn.css) { %>
<link rel="stylesheet" href="<%=css%>" as="style">
<% } %>
<% for(var js of htmlWebpackPlugin.options.cdn.js) { %>
<script src="<%=js%>"></script>
<% } %>
<% } %>

开启了cdn加载资源后, 线上环境没有被webpack打包的资源都走了cdn加载, 如图

4. 开启gzip打包

// vue.config.js
const path = require('path');
const PrerenderSPAPlugin = require('prerender-spa-plugin');
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer;
configureWebpack: (config) => {
  // 代码 gzip
  const productionGzipExtensions = ['html', 'js', 'css'];
  config.plugins.push(
    new CompressionWebpackPlugin({
      filename: '[path].gz[query]',
      algorithm: 'gzip',
      test: new RegExp('\\.(' + productionGzipExtensions.join('|') + ')$'),
      threshold: 10240, // 只有大小大于该值的资源会被处理 10240
      minRatio: 0.8, // 只有压缩率小于这个值的资源才会被处理
      deleteOriginalAssets: false, // 删除原文件
    })
  );
},

同时需要配置nginx才可支持gzip

// nginx.conf
http {
  #nginx开启gzip
  #前端文件在build的时候已经配置好压缩,需要再配置一下nginx;
  gzip on; 
  gzip_static on;
  gzip_buffers 4 16k;
  gzip_comp_level 5;
  gzip_types text/plain application/javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg 
              image/gif image/png;   
}

配置完之后可以看到线上环境的资源会以gzip的格式传输

5. 配置路由懒加载

这样能让打包出来的代码分割, 而不会统统堆一个文件里. 这样的好处是首屏渲染在用户眼里更快了.

// router.js
const routes: Array<RouteConfig> = [
  {
    path: '/',
    name: 'Chat',
    component: () => import('@/views/GenalChat.vue'),
  },
];

6. 服务端渲染(SSR)/预渲染(Prerendering)

由于服务端渲染对代码侵入性比较高,因此我使用了预渲染方案.

// vue.config.js
configureWebpack: (config) => {
  // 预渲染
  return {
    plugins: [
      // 预渲染配置
      new PrerenderSPAPlugin({
        // 输出预渲染文件的路径。
        staticDir: path.join(__dirname, 'dist'),
        // 预渲染的路由
        routes: ['/'],
        // 要使用的实际渲染器
        renderer: new Renderer({
          // 等待渲染,直到检测指定事件
          renderAfterDocumentEvent: 'render-event',
          args: ['--no-sandbox', '--disable-setuid-sandbox'], // chentos8 构建失败需要加
        }),
      }),
    ],
  };
},
特别提醒
1. 第5条和第6条是冲突的! 配置了预渲染的路由不可以使用路由懒加载!
2. linux环境下构建预渲染代码很可能会报一些奇怪的错误, 需要百度解决!
3. 坑是真的多...

更多思路

  1. 静态资源使用cdn加速
  2. 浏览器缓存
  3. 各种库的按需引入
  4. 首屏加载太久时使用loading效果
  5. why not use promise.all? promise.all可以并发多个没有关联的数据请求(如获取多个用户的头像),提高资源加载速度,充分发挥js异步的特性.

优化前后文件对比

优化前 图1 优化后 图2

  • 需要注意的是优化前并没有开启gzip, 所以优化前资源大小参考图中Size栏.
  • 优化效果用chunk-vendors.js文件来说, 就是优化前 1544kb, 优化后 346kb !

总结

通过使用上述方法优化前端代码,我用 1m 带宽的腾讯云服务器居然做到了 3s 内渲染完成, 原本不做任何优化的加载速度是 12s+!!! 通过首屏优化, 网站响应速度足足快了 四倍以上 !

一些疑问

如果用webpacksplitchunks功能将chunk-vendors.xxx.js文件拆分成多个小文件,通过http多路并行加载会不会比以前快?

答案: 很遗憾,基本不会变快.我亲自试验过, splitchunks不会带来任何的性能提升.直接给图

  • 不拆分chunk-vendors.xxx.js
  • 拆分chunk-vendors.xxx.js成多个小文件

从图中看其实还变慢了,原因是在单条tcp连接传输多个文件,即便复用连接,浏览器在文件与文件之间仍然要停一下发送一次HTTP的请求头,这里会造成多一点的时间损耗.

有个文章写得不错,推荐一下 合并HTTP请求 vs 并行HTTP请求,到底谁更快?

最后,如果小伙伴们有更多的优化建议,可以在评论区留言.