不考虑服务端渲染情况下的一次页面加载过程:
- 用户打开页面,这个时候页面是完全空白的;
- 然后 html 和引用的 css 加载完毕,浏览器进行首次渲染,我们把首次渲染需要加载的资源体积称为 “首屏体积”;
- 然后 react、react-dom、业务代码加载完毕,应用第一次渲染,或者说首次内容渲染;
- 然后应用的代码开始执行,拉取数据、进行动态import、响应事件等等,完毕后页面进入可交互状态;
- 接下来 lazyload 的图片等多媒体内容开始逐渐加载完毕;
- 然后直到页面的其它资源(如错误上报组件、打点上报组件等)加载完毕,整个页面的加载就结束了。
首屏加载优化
html中提供了一个root节点
把应用挂载到这个节点下<div id="root"></div>
webpack 打包以后一般有三个文件ReactDOM.render(<App/ >, document.getElementById('root')
- HTML文件(1-4K左右)
- 一个体积很大的js(50-100KB不等)
- 一个css文件
这样造成的直接后果就是,用户在 50 - 1000 KB 的 js 文件加载、执行完毕之前,页面是 完!全!空!白!的!
首屏体积(首次渲染需要加载的资源体积) = html + js + css
在root节点写loading
原理是把首屏渲染的时间点提前,不用等待js的加载与渲染
首屏体积 = html + css
<div class="root">Loading...</div>
使用hmtl-webpack-plugin自动插入loading
在html-webpack-plugin中添加属性loading,并在模板中引用即可
使用prerender-spa-plugin渲染首屏
在一些比较大型的项目中,Loading 可能本身就是一个 React/Vue 组件,在不做服务器端渲染的情况下,想把一个已经组件化的 Loading 直接写入 html 文件中会很复杂,不过依然有解决办法。prerender-spa-plugin 是一个可以帮你在构建时就生成页面首屏 html 的一个 webpack 插件
除掉外链CSS
除了优化上文说的html+css,依然可以有优化空间。就是把外链css去掉,让浏览器再加载完html时,即可渲染首屏
首屏 —> 首次内容渲染
该过程中,浏览器主要做的工作就是加载,运行JS代码。所以如何提升JS代码的加载,运行性能就成为优化的关键。
- 基础框架,如 React、Vue 等,这些基础框架的代码是不变的,除非升级框架;
- Polyfill,对于使用了 ES2015+ 语法的项目来说,为了兼容性,polyfill 是必要的存在;
- 业务基础库,业务的一些通用的基础代码,不属于框架,但大部分业务都会使用到;
-
缓存基础框架
基础框架的特点是必需且不变,是一种非常适合缓存的内容。所以我们需要做的就是为基础框架代码设置一个尽量长的缓存时间,使用户的浏览器尽量通过缓存加载这些资源。
关于http缓存的方案有: expires 设置的过期时间
- cache-control 设置的过期时间长度(秒)
- last-modified / if-modified-since
- etag / if-none-match
上面四种缓存的优先级:cache-control > expires > etag > last-modified
动态polyfill
Polyfill 的特点是非必需和不变,因为对于一台手机来说,需要哪些 polyfill 是固定的,当然也可能完全不需要 polyfill。因此需要去掉构建中静态的 polyfill,换而使用 polyfill.io 这样的动态 polyfill 服务,保证只有在需要时,才会引入 polyfill。
动态polyfill的原理是,它会根据你的浏览器 UA (User Agent) 头,判断你是否支持某些特性,从而返回给你一个合适的 polyfill。对于最新的 Chrome 浏览器来说,不需要任何 polyfill,所以返回的内容为空。对于 iOS Safari 来说,需要 URL 对象的 polyfill,所以返回了对应的资源。
使用SplitChunksPlugin 自动拆分业务基础库
Webpack 4 抛弃了原有的 CommonChunksPlugin,换成了更为先进的 SplitChunksPlugin,用于提取公用代码。
它们的区别就在于,CommonChunksPlugin 会找到多数模块中都共有的东西,并且把它提取出来(common.js),也就意味着如果你加载了 common.js,那么里面可能会存在一些当前模块不需要的东西。
而 SplitChunksPlugin 采用了完全不同的 heuristics 方法,它会根据模块之间的依赖关系,自动打包出很多很多(而不是单个)通用模块,可以保证加载进来的代码一定是会被依赖到的。
正确使用Tree Shaking 减少业务代码体积
首次内容渲染 —> 可交互
这一段过程中,浏览器主要在做的事情就是加载及初始化各项组件
code splitting 代码切分
把代码从单个bundle拆分成多个bundle+个文件。改写成动态 import 的形式,让首次加载时不去加载 某个引用模块,从而减少首次加载资源的体积。
React Loadable 是一个专门用于动态 import 的 React 高阶组件,你可以把任何组件改写为支持动态 import 的形式。
编译到ES2015+, 提升代码运行效率
如今大多数项目的做法都是,编写 ES2015+ 标准的代码,然后在构建时编译到 ES5 标准运行。但是目前的浏览器大部分已经原生支持新的ES标准。我们只需要将代码编译到ES2015+,然后为少数老旧浏览器用户保留一个ES5标准的备胎即可。具体的解决方法就是