【使你的页面飞起来】2-资源优化

305 阅读7分钟

1. 资源压缩与合并

为什么要压缩&合并?

  1. 减少http请求数量
  2. 减少请求资源的大小

1.1 JavaScript

1. tree shaking

tree shaking也是通过检测源码中不会被使用到的部分,将其删除,从而减小代码的体积,如下代码:

// 模块 A
export function add(a, b) {
    return a + b;
}

export function minus(a, b) {
    return a - b;
}

// 模块 B 
import {add} from 'module.A.js'; 
console.log(add(1, 2));

以上代码打包只会打包add方法,minus方法没有用到,不会被打包进去。

注意:只有ES Module支持tree shaking,因为 Tree Shaking 算是一种静态分析,而 ESM 本身是一种的静态的模块化规范,所有依赖可以在编译期确定。

在webpack中使用tree shaking: webpack.js.org/guides/tree…

tree shaking原理:juejin.cn/post/684490…

有一些流行第三方库lodash,如果想使用tree shaking,需要安装它的 ESM 版本lodash-es
ts使用可以参考:TypeScript 如何使用 Webpack 的 tree-shaking 功能?

2. 动态加载

动态加载代码,可以提高首屏的代码使用覆盖率,从而减小包体积大小。

与首屏渲染无关的代码可以动态加载,如下图:

document.getElementById('btn').addEventListener('click', e => {
    // 在这里加载 chat 组件相关资源 chat.js
    const script = document.createElement('script');
    script.src = '/static/js/chat.js';
    document.getElementsByTagName('head')[0].appendChild(script);
});

使用import动态加载

document.getElementById('btn').addEventListener('click', e => {
    import(`./section-modules/${link.dataset.entryModule}.js`)
        .then(module => {
            module.loadPageInto(main);
        })
        .catch(err => {
            main.textContent = err.message;
        });
});

上面提到的代码使用覆盖率问题可以参考:JavaScript 代码的使用覆盖率

前端按需加载:《前端那些事》聊聊前端的按需加载

3. 合理使用polyfill

合理使用polyfill也是为了减小包体积。

按照业务的场景来确定引入哪些 polyfill。我们可以使用 browserslist ,许多前端工具(babel-preset-env/autoprefixer/eslint-plugin-compat)都依赖于它。使用方式参考:babeljs.io/docs/en/bab…

还可以通过Differential Serving技术,通过浏览器原生模块化 API 来尽量避免加载无用 polyfill。

<script type="module" src="main.mjs"></script>
<script nomodule src="legacy.js"></script>

在能够处理 module 属性的浏览器(具有很多新特性)上只需加载 main.mjs(不包含 polyfill),而在老式浏览器下,则会加载 legacy.js(包含 polyfill)

理想情况下,polyfill 最优的使用方式应该是根据浏览器特性来分发,同一个项目在不同的浏览器,会加载不同的 polyfill 文件。例如 Polyfill.io 就会根据请求头中的客户端特性与所需的 API 特性来按实际情况返回必须的 polyfill 集合

4. JS压缩

需要压缩的内容:

  • 无效字符的删除
  • 剔除注释
  • 代码语义的缩减和优化
  • 代码保护

压缩方法:

  • 使用在线工具进行压缩
  • 使用Webpack对JS在构建时压缩:UglifyJS

1.2 HTML压缩

HTML代码压缩就是压缩这些在文本文件中有意义,但是在HTML中不显示的字符,包括空格,制表符,换行符等,还有一些其他意义的字符,如HTML注释也可以被压缩。

压缩方法:

1.3 CSS压缩

压缩内容:

  • 无效代码删除
  • css语义合并

压缩方法:

  • 使用在线工具进行压缩
  • 使用clean-css等npm工具

1.4 CSS/JS文件合并(看情况)

一般合并文件不能大于28k,一个域名下请求平均不超过5个

  • 若干小文件, maybe...
  • 无冲突,服务相同的模块, ok
  • 优化加载, NO!(渐进式加载,需要拆分,不能合并)

渐进式加载: 对于暂时不用的js,我们可以通过设置defer async使它空余时间下载。对于不需要立即使用的js,可以使用异步加载的写法。

2. 图片优化

2.1 常见的图片格式

image.png

  1. JPEG/JPG
  • 优点:压缩率高, 在压缩比较大的情况下还能保证较高的色彩展示
  • 缺点:有损压缩,不支持透明
  • 适用场景:大部分不需要透明图片的业务场景,线条、纹理、图标不适合JPG

压缩方案:imagemin

  1. PNG
  • 优点:支持透明图片,线条、纹理、图标等,色彩展示度度与JPG不相上下
  • 缺点:体积较大
  • 适用场景:大部分需要透明图片的业务场景

png8/png24/png32之间的区别

  • png8 —— 256色 + 支持透明
  • png24 —— 2^24色 + 不支持透明
  • png32 —— 2^24色 + 支持透明

压缩方案:imagemin-pngquant

  1. WebP
  • 压缩程度更好,与png质量相同,但是比png小
  • 在ios webview有兼容性问题

2.2 图片加载优化

  • 原生的图片懒加载方案(新版img标签支持loading属性)
  • 第三方图片懒加载方案
    • verlok/lazyload
    • yall.js
    • Blazy
  • 渐进式图片(缓慢加载出来)

1. 懒加载

原生的图片懒加载方案,新版img标签支持loading属性:

'loading' in HTMLImageElement.prototype === true

image.png

2. 渐进式图片

渐进式图片如下图: image.png

渐进式图片的优点和不足:

image.png

渐进式图片解决方案:

  • progressive-image
  • ImageMagick
  • libjpeg
  • jpegtran
  • jpeg-recompress
  • imagemin

参考:渐进式jpeg(progressive jpeg)图片及其相关

3. 响应式图片

  • Srcset属性的使用
  • Sizes属性的使用
  • picture的使用(新标签)

image.png

2.3 图片压缩

  1. CSS雪碧图,目的:减少你的网站的HTTP请求数量,在线生成雪碧图网站:www.spritecow.com
  2. Image inline(类似这种:background-image: url(data:image/gif;base64...),小于8kb时,将图片的内容内嵌到html当中,减少你的网站的HTTP请求数量
  3. 使用SVG进行矢量图的绘制,使用iconfont解决icon问题
  4. 在安卓下使用webp格式

2.4 拯救移动端图标SVG

@svgr/webpack 使用SVG替换IconFont

  • 保持了图片能力,支持多色彩
  • 独立的矢量图形
  • XML语法,搜索引擎SEO和无障碍读屏软件读取(iconfont无法被SEO)

推荐文章

【前端性能优化指南】5.3 - 优化你的图片资源

3. 字体优化

3.1 什么是FOIT和FOUT?

字体未下载完成时,浏览器隐藏或自动降级,导致字体闪烁

  • Flash Of Invisible Text
  • Flash Of Unstyled Text

3.2 font-display

针对字体闪烁问题,我们可以使用 font-display 属性。

  • auto
  • block(3s后如果字体下载完成就用新字体,否则用默认字体)
  • swap(先展示一个默认字体)
  • fallback(对block的优化)
  • optional(根据网络判断)

image.png

3.3 unicode-range(设置字符的特定范围)

@font-face.unicode-range

@font-face {
  font-family: 'Ampersand';
  src: local('Times New Roman');
  unicode-range: U+26;
}

推荐文章

【前端性能优化指南】5.4 - 字体的优化

4. 优化资源加载的顺序

4.1 preload,prefetch

  • 浏览器默认安排资源加载优先级
  • 使用preload, prefetch调整优先级(注意,只负责加载,不负责解析)

preload和prefetch适用场景

  • Preload: 提前加载较晚出现,但对当前页面非常重要的资源,强制浏览器在不阻塞 document 的 onload 事件的情况下请求资源
  • Prefetch: 空闲时间提前加载后继路由需要的资源,优先级低

提前加载图片:

image.png

image.png

提前加载字体文件:

image.png

image.png

image.png

参考:juejin.cn/post/684490…

4.2 defer、async

当 DOM 解析遇到 JavaScript 脚本时,会停止解析,开始下载脚本并执行,再恢复解析,相当于是阻塞了 DOM 构建。

那除了将脚本放在 body 的最后,还有什么优化方法么?是有的。

可以使用 deferasync 属性。两者都会防止 JavaScript 脚本的下载阻塞 DOM 构建。但是两者也有区别,最直观的表现如下

image.png

  • defer 下载时不阻塞 HTML 解析成 DOM,下载完成会等待 DOM 构建完毕且在 DOMContentLoaded 事件触发之前执行,多个 defer 脚本保证脚本执行顺序。

  • async 下载时不阻塞 HTML 解析成 DOM,下载完毕后尽量安排JS执行。意思说执行时间不确定,早下载早执行。如果 DOM 未构建完,脚本可能会阻塞DOM构建。

推荐在一些与主业务无关的 JavaScript 脚本上使用 async。例如统计脚本、监控脚本、广告脚本等。这些脚本一般都是一份独立的文件,没有外部依赖,不需要访问 DOM,也不需要有严格的执行时机限制。在这些脚本上使用 async 可以有效避免这些非核心功能的加载影响页面解析速度