[TOC]
Perfume.js调研
Perfume.js是监测FP、FCP、FID三个性能条目和组件生命周期性能的工具,作者是谷歌开发团队的工程师Leonardo Zizzamia。
性能指标
- First Paint(FP) is the exact time the browser renders anything as visually different from what was on the screen before navigation, e.g. a background change after a long blank white screen time.FP 标记浏览器渲染任何在视觉上不同于导航前屏幕内容之内容的时间点。
- First Contentful Paint(FCP) is the exact time the browser renders the first bit of content from the DOM, which can be anything from an important image, text, or even the small SVG at the bottom of the page. FCP 标记的是浏览器渲染来自 DOM 第一位内容的时间点,该内容可能是文本、图像、SVG 甚至
<canvas>元素。 - First Input Delay(FID) measures the time from when a user first interacts with your site (i.e. when they click a link, tap on a button, or use a custom, JavaScript-powered control) to the time when the browser is actually able to respond to that interaction.第一输入延迟(FID)测量用户首次与站点交互时(即,当他们单击链接,点击按钮或使用自定义的,由JavaScript驱动的控件)到浏览器实际能够的时间回应这种互动的延时。
源码
perfume.js核心源码仅1200余行,共5个文件,用ts写成。
- detect-browser.ts:定义了浏览器和系统环境类型和检测方法
- emulated-performance.ts:模拟Performance API,提供了获取FCP的方法。当浏览器不支持Performance API时使用
- idle-queue.ts:提供了创建宏任务和微任务队列的方法,并提供了操作任务队列的方法
- performance.ts:提供Performance API的now、measure、mark等方法,提供获取FCP的方法。当浏览器支持Performance API时使用
- perfume.ts:获取FP FCP FID指标方法和其他自定义性能指标的方法
性能指标算法
FP & FCP
支持PerformanceObserver的浏览器
-
仅chrome完全支持PerformanceObserver和类型为'paint'的性能条目
this.perfObserver = new PerformanceObserver(cb); this.perfObserver.observe({ entryTypes: ['paint'] });指定监测的entry types集合,记录到PerformanceEntry.entryType为'paint'的性能条目(包括
'first-paint'或'first-contentful-paint'),性能监测对象的回调函数被调用。 -
IssueFP&FCP可能不被触发:如果网站进行了预渲染,FCP和FP不会被触发。模拟更慢的速度,预渲染内容在Angular包下载之前进行FCP,Perfume.js尚未启动。结果这两个指标不被触发。
**重要说明:**您必须确保
PerformanceObserver在任何样式表之前于文档的<head>中注册,以使其在 FP/FCP 发生前运行。实现第 2 级 Performance Observer 规范后就不必再执行这项注册,因为该级别引入 [
buffered](w3c.github.io/performance… buffered) 标记,可用于访问在创建PerformanceObserver之前排队的性能条目。PerformanceObserver.observe(entryTypes, type, bufferd)
buffered: A flag to indicate whether buffered entries should be queued into observer's buffer.Perfume.js注册监听时没有
buffered标记,不能访问创建PerformanceObserver之前排队的性能条目。 -
Firefox存在问题:PerformanceObserver should not throw on empty/unknown list of entryTypes
A list of entry types to be observed. If present, the list MUST NOT be empty and all other members MUST NOT be present. Types not recognized by the user agent MUST be ignored.
不支持PerformanceObserver的浏览器
Date.now() - navTiming.navigationStart
因为浏览器渲染在宏任务之间执行,使用setTimeout在宏任务队列中增加一个宏任务,计算FCP
// Perfume constructor()
this.observeFirstContentfulPaint = new Promise(resolve => {
this.logDebug('observeFirstContentfulPaint');
this.observers.set('fcp', resolve);
this.initFirstPaint();
});
// initFirstPaint()
this.perf.firstContentfulPaint(this.firstContentfulPaintCb.bind(this));
// firstContentfulPaint()
firstContentfulPaint(cb: (entries: any[]) => void): void {
setTimeout(() => {
cb(this.getFirstPaint());
});
}
// getFirstPaint()
Date.now() - navTiming.navigationStart
// firstContentfulPaintCb
this.queue.pushTask(() => {/**打印FP和FCP的开始时间*/})
FID
在支持Performance的浏览器中使用谷歌开发的FID腻子脚本[github.com/GoogleChrom…]得到
自定义时间测量
- 支持Performance及其mark、now方法的浏览器:使用User Timing API
- 不支持的,使用模拟的Performance。
perfume.start('fibonacci');// 在性能时间线中创建事件的时间戳
fibonacci(400);
perfume.end('fibonacci'); // 在性能时间线中创建事件结束的时间戳
// Perfume.js: fibonacci 0.14 ms
Component First Paint
测量从组件创建开始到浏览器将像素渲染到屏幕上的时间。利用浏览器在宏任务之间渲染内容实现。
perfume.start('openDialog');
this.dialog.open();
perfume.endPaint('openDialog');
// Perfume.js: openDialog 10.54 ms
框架
Angular
模块级别,声明性能条目
// Perfume.js config, supports AOT and DI
export const PerfumeConfig = {
firstContentfulPaint: true,
firstInputDelay: true,
};
@NgModule({
declarations: [AppComponent],
imports: [PerfumeModule.forRoot(PerfumeConfig)],
bootstrap: [AppComponent],
})
export class AppModule {}
组件级别,监测组件首次渲染及其他自定义时间测量
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.less'],
})
@PerfumeAfterViewInit('AppComponent')
export class AppComponent implements AfterViewInit {
data: AwesomeType;
constructor(public perfume: NgPerfume) {
// 记录事件开始时间
this.perfume.start('AppComponentAfterPaint');
}
// 必须实现AfterViewInit钩子
ngAfterViewInit() {
this.loadAwesomeData();
}
loadAwesomeData = async () => {
await AppApi.loadAmazingData();
this.data = AppApi.loadAwesomeData();
// 记录事件结束时间
this.perfume.endPaint('AppComponentAfterPaint');
}
}
React
在与React框架的组合中,配置Perfume以收集初始性能指标(例如,FCP,FID)。
将perfume.start()和perfume.endPaint()用于与API调用混合的组件生命周期,以测量绘制组件所需的时间。