| 5 min read

大概这是之前看 The Cost Of JavaScript In 2018 中提到的以参考索引,其实里面很多点挺重要的,当然推荐大家阅读下全文 A Pinterest Progressive Web App Performance Case Study; 后面其实说几点自己印象非常深刻的点;

为什么做这次性能改造

其实很多时候做性能改造,都会面临这样的质疑,它不是对现有版本的任何功能性的迭代,而是从多个方面去提升一些所谓数据指标。因此很多 产品(🐩) 都会去质疑这一件事情,但是 Pinterest 这次改造给出了更为直接的数据,性能的优化与用户数据方面的正相关:

其中相比老版本,用户的停留时间和核心参与度都有非常大的提升;其中和广告相关的数据,也有非常显著的增加;不难理解,当用户用更短的时间触达到页面,

合理的设置 chunks

如果我们是做基于 vue 项目或者 React 的项目,我们可以参考下 Pinterest 的 JS Bundles;

  • js vendor 包含一些第三方的库比如(React, Redux, React-Router) 等;

  • entry chunk 也就是我们存放主要代码逻辑的部分比如 页面的结构或者redux store;

  • async router 则是包含一些并不在首页的子页面的 bundle;

webpack 的配置:

onst bundles = {
  'vendor-mweb': [
    'app/mobile/polyfills.js',
    'intl',
    'normalizr',
    'react-dom',
    'react-redux',
    'react-router-dom',
    'react',
    'redux'
  ],
  'entryChunk-webpack': 'app/mobile/runtime.js',
  'entryChunk-mobile': 'app/mobile/index.js'
};

const chunkPlugins = [
  new webpack.optimize.CommonsChunkPlugin({
    name: 'vendor-mweb',
    minChunks: Infinity,
    chunks: ['entryChunk-mobile']
  }),
  new webpack.optimize.CommonsChunkPlugin({
    name: 'entryChunk-webpack',
    minChunks: Infinity,
    chunks: ['vendor-mweb']
  }),
  new webpack.optimize.CommonsChunkPlugin({
    children: true,
    name: 'entryChunk-mobile',
    minChunks: (module, count) => {
      return module.resource && (isCommonLib(resource) || count >= 3);
    }
  })
];

可以参考 code splitting 的技巧: https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/docs/guides/code-splitting.md

使用 Webpack Bundle Analyzer

webpack-bundle-analyzer 是一款非常有用的工具,可以很好的帮助我们降低 bundle 的小小,比如下面图中紫色和蓝色的快,它们是会惰性加载的模块,通过分析,Pinterest 将一些重复的模块移动到主 entry ,虽然这增加 entry 20% 的大小,但是却可以明显降低子 chunk 的 90% 的大小;

图片的 lazyload

Pinterest 由于是专注于图片的网站,因此文章分析了自己是如何使用懒加载的图片。

Pinterest 采用了瀑布流布局,大概在移动端,会懒加载下一屏的图片,而且基于 color-placeholder 的加载体验,总之效果还是赞的;

自己之前写过一篇 Color placeholder 的加载,有兴趣可以看下简单实现;

Service Worker

如果你想使用 Service Worker; 我们还是建议大家使用 workbox, 它可以帮助你非常轻松的管理自己的 Service Worker;

/* global $VERSION, $Cache, importScripts, WorkboxSW */
importScripts('https://unpkg.com/workbox-sw@1.1.0/build/importScripts/workbox-sw.prod.v1.1.0.js');

// Add app shell to the webpack-generated precache list
$Cache.precache.push({ url: 'sw-shell.html', revision: $VERSION });

// Register precache list with Workbox
const workbox = new WorkboxSW({ handleFetch: true, skipWaiting: true, clientClaim: true });
workbox.precache($Cache.precache);

// Runtime cache all js
workbox.router.registerRoute(/webapp\/js\/.*\.js/, workbox.strategies.cacheFirst());

// Prefer app-shell for full-page loads
workbox.router.registerNavigationRoute('sw-shell.html', {
  blacklist: [
    // bunch of non-app routes
  ],
});

Pinterest 缓存了所有的 JS 或者 CSS 的 bundle 资源,同时还包括了整个 APP 的架子;

  • Pinterest 只会先缓存 runtime 的 或者惰性加载依赖的脚本;这可以提高 V8 的编译和解析的速度;
  • Pinterest 接下来会缓存 vendor 和 entry 的脚本资源;
  • 随之 Pinterest 会缓存一些经常用的路由资源;
  • 最后 会生成 一个 service worker 给每个本地的资源版本
/* Create a service worker for every locale to precache the locale bundle */
const ServiceWorkerConfigs = locales.reduce((configs, locale) => {
  return Object.assign(configs, {
    [`mobile-${locale}`]: Object.assign({}, BaseConfig, {
      template: path.join(__dirname, 'swTemplates/mobileBase.js'),
      cache: {
        template: path.join(__dirname, 'swTemplates/mobileCache.js'),
        precache: [
          'vendor-mweb-.*\\.js$',
          'entryChunk-mobile-.*\\.js$',
          'entryChunk-webpack-.*\\.js$',
          `locale-${locale}-mobile.*js$`,
          'pjs-HomePage.*\\.js$',
          'pjs-SearchPage.*\\.js$',
          'pjs-CloseupPage.*\\.js$'
        ]
      }
    })
  });
}, {});

// Add to webpack
plugins: [
  new ServiceWorkerPlugin(BaseConfig, ServiceWorkerConfigs);
]

使用 lighthouse 查看性能测试

lighthouse 推出来很久了,但是在最新的 66 还是 67 版本开始正式集成到了 devtool 上,是非常全面的性能测试工具。这其中包含了对 PWA 的可靠性进行测试;

You Can Speak "Hi" to Me in Those Ways