1. 资源压缩与合并
为什么要压缩&合并?
- 减少http请求数量
- 减少请求资源的大小
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注释也可以被压缩。
压缩方法:
- 使用在线工具进行压缩
- 使用html-minifier等npm工具
1.3 CSS压缩
压缩内容:
- 无效代码删除
- css语义合并
压缩方法:
- 使用在线工具进行压缩
- 使用clean-css等npm工具
1.4 CSS/JS文件合并(看情况)
一般合并文件不能大于28k,一个域名下请求平均不超过5个
- 若干小文件, maybe...
- 无冲突,服务相同的模块, ok
- 优化加载, NO!(渐进式加载,需要拆分,不能合并)
渐进式加载: 对于暂时不用的js,我们可以通过设置defer async使它空余时间下载。对于不需要立即使用的js,可以使用异步加载的写法。
2. 图片优化
2.1 常见的图片格式
- JPEG/JPG
- 优点:压缩率高, 在压缩比较大的情况下还能保证较高的色彩展示
- 缺点:有损压缩,不支持透明
- 适用场景:大部分不需要透明图片的业务场景,线条、纹理、图标不适合JPG
压缩方案:imagemin
- PNG
- 优点:支持透明图片,线条、纹理、图标等,色彩展示度度与JPG不相上下
- 缺点:体积较大
- 适用场景:大部分需要透明图片的业务场景
png8/png24/png32之间的区别
- png8 —— 256色 + 支持透明
- png24 —— 2^24色 + 不支持透明
- png32 —— 2^24色 + 支持透明
压缩方案:imagemin-pngquant
- WebP
- 压缩程度更好,与png质量相同,但是比png小
- 在ios webview有兼容性问题
2.2 图片加载优化
- 原生的图片懒加载方案(新版img标签支持loading属性)
- 第三方图片懒加载方案
- verlok/lazyload
- yall.js
- Blazy
- 渐进式图片(缓慢加载出来)
1. 懒加载
原生的图片懒加载方案,新版img标签支持loading属性:
'loading' in HTMLImageElement.prototype === true
2. 渐进式图片
渐进式图片如下图:
渐进式图片的优点和不足:
渐进式图片解决方案:
- progressive-image
- ImageMagick
- libjpeg
- jpegtran
- jpeg-recompress
- imagemin
参考:渐进式jpeg(progressive jpeg)图片及其相关
3. 响应式图片
- Srcset属性的使用
- Sizes属性的使用
- picture的使用(新标签)
2.3 图片压缩
- CSS雪碧图,目的:减少你的网站的HTTP请求数量,在线生成雪碧图网站:www.spritecow.com
- Image inline(类似这种:background-image: url(data:image/gif;base64...),小于8kb时,将图片的内容内嵌到html当中,减少你的网站的HTTP请求数量
- 使用SVG进行矢量图的绘制,使用iconfont解决icon问题
- 在安卓下使用webp格式
2.4 拯救移动端图标SVG
@svgr/webpack 使用SVG替换IconFont
- 保持了图片能力,支持多色彩
- 独立的矢量图形
- XML语法,搜索引擎SEO和无障碍读屏软件读取(iconfont无法被SEO)
推荐文章
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(根据网络判断)
3.3 unicode-range(设置字符的特定范围)
@font-face {
font-family: 'Ampersand';
src: local('Times New Roman');
unicode-range: U+26;
}
推荐文章
4. 优化资源加载的顺序
4.1 preload,prefetch
- 浏览器默认安排资源加载优先级
- 使用preload, prefetch调整优先级(注意,只负责加载,不负责解析)
preload和prefetch适用场景
- Preload: 提前加载较晚出现,但对当前页面非常重要的资源,强制浏览器在不阻塞
document的 onload 事件的情况下请求资源 - Prefetch: 空闲时间提前加载后继路由需要的资源,优先级低
提前加载图片:
提前加载字体文件:
4.2 defer、async
当 DOM 解析遇到 JavaScript 脚本时,会停止解析,开始下载脚本并执行,再恢复解析,相当于是阻塞了 DOM 构建。
那除了将脚本放在 body 的最后,还有什么优化方法么?是有的。
可以使用 defer 或 async 属性。两者都会防止 JavaScript 脚本的下载阻塞 DOM 构建。但是两者也有区别,最直观的表现如下
-
defer下载时不阻塞 HTML 解析成 DOM,下载完成会等待DOM构建完毕且在DOMContentLoaded事件触发之前执行,多个defer脚本保证脚本执行顺序。 -
async下载时不阻塞 HTML 解析成 DOM,下载完毕后尽量安排JS执行。意思说执行时间不确定,早下载早执行。如果DOM未构建完,脚本可能会阻塞DOM构建。
推荐在一些与主业务无关的 JavaScript 脚本上使用 async。例如统计脚本、监控脚本、广告脚本等。这些脚本一般都是一份独立的文件,没有外部依赖,不需要访问 DOM,也不需要有严格的执行时机限制。在这些脚本上使用 async 可以有效避免这些非核心功能的加载影响页面解析速度