spy-client 2.1.8 → 2.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +54 -11
  2. package/dist/spy-client.esm.js +14 -9
  3. package/dist/spy-client.iife.js +14 -9
  4. package/dist/spy-client.iife.min.js +1 -1
  5. package/dist/spy-client.js +14 -9
  6. package/dist/spy-client.min.js +1 -1
  7. package/dist/spy-client.mjs +14 -9
  8. package/dist/spy-head.js +15 -10
  9. package/dist/spy-head.min.js +1 -1
  10. package/package.json +1 -1
  11. package/coverage/Chrome 92.0.4515.159 (Mac OS 10.15.7)/html/base.css +0 -224
  12. package/coverage/Chrome 92.0.4515.159 (Mac OS 10.15.7)/html/basicSpec.ts.html +0 -740
  13. package/coverage/Chrome 92.0.4515.159 (Mac OS 10.15.7)/html/block-navigation.js +0 -79
  14. package/coverage/Chrome 92.0.4515.159 (Mac OS 10.15.7)/html/checkSpec.ts.html +0 -479
  15. package/coverage/Chrome 92.0.4515.159 (Mac OS 10.15.7)/html/favicon.png +0 -0
  16. package/coverage/Chrome 92.0.4515.159 (Mac OS 10.15.7)/html/headSpec.ts.html +0 -695
  17. package/coverage/Chrome 92.0.4515.159 (Mac OS 10.15.7)/html/index.html +0 -171
  18. package/coverage/Chrome 92.0.4515.159 (Mac OS 10.15.7)/html/markSpec.ts.html +0 -284
  19. package/coverage/Chrome 92.0.4515.159 (Mac OS 10.15.7)/html/metricSpec.ts.html +0 -977
  20. package/coverage/Chrome 92.0.4515.159 (Mac OS 10.15.7)/html/prettify.css +0 -1
  21. package/coverage/Chrome 92.0.4515.159 (Mac OS 10.15.7)/html/prettify.js +0 -2
  22. package/coverage/Chrome 92.0.4515.159 (Mac OS 10.15.7)/html/sort-arrow-sprite.png +0 -0
  23. package/coverage/Chrome 92.0.4515.159 (Mac OS 10.15.7)/html/sorter.js +0 -170
  24. package/coverage/Chrome 95.0.4638.69 (Mac OS 10.15.7)/html/base.css +0 -224
  25. package/coverage/Chrome 95.0.4638.69 (Mac OS 10.15.7)/html/basicSpec.ts.html +0 -740
  26. package/coverage/Chrome 95.0.4638.69 (Mac OS 10.15.7)/html/block-navigation.js +0 -79
  27. package/coverage/Chrome 95.0.4638.69 (Mac OS 10.15.7)/html/checkSpec.ts.html +0 -479
  28. package/coverage/Chrome 95.0.4638.69 (Mac OS 10.15.7)/html/favicon.png +0 -0
  29. package/coverage/Chrome 95.0.4638.69 (Mac OS 10.15.7)/html/headSpec.ts.html +0 -695
  30. package/coverage/Chrome 95.0.4638.69 (Mac OS 10.15.7)/html/index.html +0 -171
  31. package/coverage/Chrome 95.0.4638.69 (Mac OS 10.15.7)/html/markSpec.ts.html +0 -284
  32. package/coverage/Chrome 95.0.4638.69 (Mac OS 10.15.7)/html/metricSpec.ts.html +0 -977
  33. package/coverage/Chrome 95.0.4638.69 (Mac OS 10.15.7)/html/prettify.css +0 -1
  34. package/coverage/Chrome 95.0.4638.69 (Mac OS 10.15.7)/html/prettify.js +0 -2
  35. package/coverage/Chrome 95.0.4638.69 (Mac OS 10.15.7)/html/sort-arrow-sprite.png +0 -0
  36. package/coverage/Chrome 95.0.4638.69 (Mac OS 10.15.7)/html/sorter.js +0 -170
  37. package/coverage/Chrome 96.0.4664.110 (Mac OS 10.15.7)/html/base.css +0 -224
  38. package/coverage/Chrome 96.0.4664.110 (Mac OS 10.15.7)/html/basicSpec.ts.html +0 -740
  39. package/coverage/Chrome 96.0.4664.110 (Mac OS 10.15.7)/html/block-navigation.js +0 -79
  40. package/coverage/Chrome 96.0.4664.110 (Mac OS 10.15.7)/html/checkSpec.ts.html +0 -479
  41. package/coverage/Chrome 96.0.4664.110 (Mac OS 10.15.7)/html/favicon.png +0 -0
  42. package/coverage/Chrome 96.0.4664.110 (Mac OS 10.15.7)/html/headSpec.ts.html +0 -695
  43. package/coverage/Chrome 96.0.4664.110 (Mac OS 10.15.7)/html/index.html +0 -171
  44. package/coverage/Chrome 96.0.4664.110 (Mac OS 10.15.7)/html/markSpec.ts.html +0 -284
  45. package/coverage/Chrome 96.0.4664.110 (Mac OS 10.15.7)/html/metricSpec.ts.html +0 -977
  46. package/coverage/Chrome 96.0.4664.110 (Mac OS 10.15.7)/html/prettify.css +0 -1
  47. package/coverage/Chrome 96.0.4664.110 (Mac OS 10.15.7)/html/prettify.js +0 -2
  48. package/coverage/Chrome 96.0.4664.110 (Mac OS 10.15.7)/html/sort-arrow-sprite.png +0 -0
  49. package/coverage/Chrome 96.0.4664.110 (Mac OS 10.15.7)/html/sorter.js +0 -170
  50. package/coverage/Chrome 96.0.4664.55 (Mac OS 10.15.7)/html/base.css +0 -224
  51. package/coverage/Chrome 96.0.4664.55 (Mac OS 10.15.7)/html/basicSpec.ts.html +0 -740
  52. package/coverage/Chrome 96.0.4664.55 (Mac OS 10.15.7)/html/block-navigation.js +0 -79
  53. package/coverage/Chrome 96.0.4664.55 (Mac OS 10.15.7)/html/checkSpec.ts.html +0 -479
  54. package/coverage/Chrome 96.0.4664.55 (Mac OS 10.15.7)/html/favicon.png +0 -0
  55. package/coverage/Chrome 96.0.4664.55 (Mac OS 10.15.7)/html/headSpec.ts.html +0 -695
  56. package/coverage/Chrome 96.0.4664.55 (Mac OS 10.15.7)/html/index.html +0 -171
  57. package/coverage/Chrome 96.0.4664.55 (Mac OS 10.15.7)/html/markSpec.ts.html +0 -284
  58. package/coverage/Chrome 96.0.4664.55 (Mac OS 10.15.7)/html/metricSpec.ts.html +0 -977
  59. package/coverage/Chrome 96.0.4664.55 (Mac OS 10.15.7)/html/prettify.css +0 -1
  60. package/coverage/Chrome 96.0.4664.55 (Mac OS 10.15.7)/html/prettify.js +0 -2
  61. package/coverage/Chrome 96.0.4664.55 (Mac OS 10.15.7)/html/sort-arrow-sprite.png +0 -0
  62. package/coverage/Chrome 96.0.4664.55 (Mac OS 10.15.7)/html/sorter.js +0 -170
  63. package/src/head/base.ts +0 -69
  64. package/src/head/error.ts +0 -118
  65. package/src/head/observer.ts +0 -40
  66. package/src/head/readme.md +0 -1
  67. package/src/head/whitescreen.ts +0 -105
  68. package/src/lib/data.ts +0 -14
  69. package/src/lib/huffman.ts +0 -307
  70. package/src/lib/interface.ts +0 -331
  71. package/src/lib/spyHeadInterface.ts +0 -40
  72. package/src/lib/util.ts +0 -101
  73. package/src/module/fid.ts +0 -56
  74. package/src/module/layoutShift.ts +0 -71
  75. package/src/module/lcp.ts +0 -63
  76. package/src/module/longtask.ts +0 -193
  77. package/src/module/memory.ts +0 -43
  78. package/src/module/navigatorInfo.ts +0 -61
  79. package/src/module/resource.ts +0 -433
  80. package/src/module/timing.ts +0 -75
  81. package/src/module/tti.ts +0 -139
  82. package/src/spy-client-basic.ts +0 -389
  83. package/src/spy-client.ts +0 -198
  84. package/src/spy-head.ts +0 -31
  85. package/src/spy-local-cache.ts +0 -478
  86. package/src/types/globals.d.ts +0 -93
@@ -1,433 +0,0 @@
1
-
2
- import {
3
- Module, ResourceMetric, ResourceCB, ResourceErrorCB, ResType,
4
- ResOption,
5
- BigImgOption,
6
- HttpResOption,
7
- SlowOption,
8
- ResourceHostMetric,
9
- } from '../lib/interface';
10
- import {getUrlInfo, URLINFO, assign, getResTiming, getxpath} from '../lib/util';
11
-
12
- // 字体
13
- // svg使用场景比较灵活,可能作为一种字体格式,也可能当做图片使用
14
- // 但因woff在所有现代浏览器中都支持,在css中使用iconfont时
15
- // 往往优先加载woff格式字体文件,所以这里将svg划分为图片一类
16
- enum FontsTypes {
17
- ttf,
18
- eot,
19
- woff,
20
- woff2
21
- };
22
-
23
-
24
- // 忽略图片
25
- // 下述图片路径往往是用来进行日志统计,不是正常图片
26
- const ignoreDefaultPaths = [
27
- '/mwb2.gif',
28
- ];
29
-
30
- function ignorePath(url: string, paths: string[]) {
31
- for (const path of paths) {
32
- if (url.indexOf(path) > -1) {
33
- return true;
34
- }
35
- }
36
- return false;
37
- }
38
-
39
- function f(n: number): number {
40
- return +n.toFixed(1);
41
- }
42
-
43
- interface ListItem {
44
- timing: PerformanceResourceTiming;
45
- type: string;
46
- }
47
-
48
- interface ListWithHost {
49
- [host: string]: ListItem[];
50
- }
51
-
52
- export default class Resource implements Module {
53
- private cb: ResourceCB;
54
- private bigImgCB: ResourceErrorCB;
55
- private httpResCB: ResourceErrorCB;
56
- private slowResCB: ResourceErrorCB;
57
- private resOption: ResOption;
58
- private bigImgOption: BigImgOption;
59
- private httpResOption: HttpResOption;
60
- private slowOption: SlowOption;
61
- private trigger: string = 'load';
62
-
63
- private readonly jsList: PerformanceResourceTiming[];
64
- private readonly cssList: PerformanceResourceTiming[];
65
- private readonly imgList: PerformanceResourceTiming[];
66
- private readonly fontList: PerformanceResourceTiming[];
67
-
68
- private readonly hostList: ListWithHost;
69
- private readonly bigImgList: ListWithHost;
70
- private readonly httpResList: ListWithHost;
71
- private readonly slowList: ListWithHost;
72
-
73
- constructor() {
74
- this.jsList = [];
75
- this.cssList = [];
76
- this.imgList = [];
77
- this.fontList = [];
78
-
79
- this.hostList = {};
80
- this.bigImgList = {};
81
- this.httpResList = {};
82
- this.slowList = {};
83
-
84
- this.resOption = {ignorePaths: [], trigger: 'load'};
85
- this.bigImgOption = {ignorePaths: [], maxSize: 150 * 1024, trigger: 'load'};
86
- this.httpResOption = {ignorePaths: [], trigger: 'load'};
87
- this.slowOption = {ignorePaths: [], trigger: 'load', threshold: 1000};
88
- }
89
-
90
- check() {
91
- return performance && performance.getEntriesByType;
92
- }
93
-
94
- listenResource(cb: ResourceCB, option: ResOption = {}) {
95
- if (!this.check()) {
96
- return;
97
- }
98
- this.resOption = assign(this.resOption, option);
99
- this.trigger = this.resOption.trigger as string;
100
- this.cb = cb;
101
- }
102
-
103
- listenBigImg(cb: ResourceErrorCB, option: BigImgOption = {}) {
104
- if (!this.check()) {
105
- return;
106
- }
107
- this.bigImgOption = assign(this.bigImgOption, option);
108
- this.trigger = this.bigImgOption.trigger as string;
109
- this.bigImgCB = cb;
110
- }
111
-
112
- listenHttpResource(cb: ResourceErrorCB, option: HttpResOption = {}) {
113
- if (!this.check()) {
114
- return;
115
- }
116
- this.httpResOption = assign(this.httpResOption, option);
117
- this.trigger = this.httpResOption.trigger as string;
118
- this.httpResCB = cb;
119
- }
120
-
121
- listenSlowResource(cb: ResourceErrorCB, option: SlowOption = {}) {
122
- if (!this.check()) {
123
- return;
124
- }
125
- this.slowOption = assign(this.slowOption, option);
126
- this.trigger = this.slowOption.trigger as string;
127
- this.slowResCB = cb;
128
- }
129
-
130
- report() {
131
- const metricRes = this.getMetric();
132
- if (metricRes) {
133
- const {metric, hostMetric} = metricRes;
134
- this.cb && this.cb(metric, hostMetric);
135
- }
136
-
137
- if (this.bigImgCB) {
138
- // 发送大图监控数据
139
- if (typeof window.requestIdleCallback === 'function' && Object.keys(this.bigImgList).length) {
140
- window.requestIdleCallback(() => {
141
- for (const host of Object.keys(this.bigImgList)) {
142
- for (const entry of this.bigImgList[host]) {
143
- const timing = entry.timing;
144
- const type = entry.type;
145
- try {
146
- const img = document.body.querySelector('img[src="' + timing.name + '"]');
147
- this.bigImgCB({
148
- msg: timing.name,
149
- dur: timing.duration,
150
- xpath: getxpath(img).xpath,
151
- host,
152
- type,
153
- });
154
- }
155
- catch (e) {
156
- console.error(e);
157
- }
158
- }
159
- }
160
- });
161
- }
162
- }
163
-
164
- if (this.httpResCB) {
165
- // 发送http资源监控数据
166
- if (typeof window.requestIdleCallback === 'function' && Object.keys(this.httpResList).length) {
167
- window.requestIdleCallback(() => {
168
- for (const host of Object.keys(this.httpResList)) {
169
- for (const entry of this.httpResList[host]) {
170
- const timing = entry.timing;
171
- const type = entry.type;
172
- try {
173
- const img = document.body.querySelector('[src="' + timing.name + '"]');
174
- this.httpResCB({
175
- msg: timing.name,
176
- dur: timing.duration,
177
- xpath: getxpath(img).xpath,
178
- host,
179
- type,
180
- });
181
- }
182
- catch (e) {
183
- console.error(e);
184
- }
185
- }
186
- }
187
- });
188
- }
189
- }
190
-
191
- if (this.slowResCB) {
192
- // 发送慢资源监控数据
193
- if (typeof window.requestIdleCallback === 'function' && Object.keys(this.slowList).length) {
194
- window.requestIdleCallback(() => {
195
- for (const host of Object.keys(this.slowList)) {
196
- for (const entry of this.slowList[host]) {
197
- const timing = entry.timing;
198
- const type = entry.type;
199
- try {
200
- const img = document.body.querySelector('[src="' + timing.name + '"]');
201
-
202
- const info = getResTiming(timing);
203
-
204
- this.slowResCB({
205
- ...info,
206
- dur: timing.duration,
207
- msg: timing.name,
208
- xpath: getxpath(img).xpath,
209
- host,
210
- type,
211
- });
212
- }
213
- catch (e) {
214
- console.error(e);
215
- }
216
- }
217
- }
218
- });
219
- }
220
- }
221
- }
222
-
223
- load() {
224
- if (this.trigger !== 'load') {
225
- return;
226
- }
227
- if (!this.check()) {
228
- return;
229
- }
230
-
231
- setTimeout(() => {
232
- this.report();
233
- }, 500);
234
- }
235
-
236
- leave() {
237
- if (this.trigger !== 'leave') {
238
- return;
239
- }
240
- this.report();
241
- }
242
-
243
- private push(type: string, list: any, timing: PerformanceResourceTiming, urlInfo: URLINFO) {
244
- if (!ignorePath(urlInfo.pathname, this.resOption.ignorePaths || [])) {
245
- list.push(timing);
246
- this.pushWithHost(type, this.hostList, timing, urlInfo);
247
- }
248
-
249
- if (!ignorePath(urlInfo.pathname, this.slowOption.ignorePaths || [])) {
250
- if (timing.duration > (this.slowOption.threshold as number)) {
251
- this.pushWithHost(type, this.slowList, timing, urlInfo);
252
- }
253
- }
254
-
255
- }
256
-
257
- private pushWithHost(type: string, list: any, timing: PerformanceResourceTiming, urlInfo: URLINFO) {
258
- const host = urlInfo.host;
259
- if (!list[host]) {
260
- list[host] = [] as ListItem[];
261
- }
262
- list[host].push({
263
- timing,
264
- type,
265
- });
266
- }
267
-
268
- private collectHttpResInHttps(type: string, timing: PerformanceResourceTiming, urlInfo: URLINFO) {
269
- if (location.protocol === 'https:'
270
- && timing.name.indexOf('http://') === 0
271
- && !ignorePath(urlInfo.pathname, this.httpResOption.ignorePaths || [])
272
- ) {
273
- this.pushWithHost(type, this.httpResList, timing, urlInfo);
274
- }
275
- }
276
-
277
- // 有些jsonp也属于script,这里只统计js后缀的文件
278
- private addScript(timing: PerformanceResourceTiming, urlInfo: URLINFO) {
279
- if (urlInfo.ext === 'js') {
280
- if (timing.decodedBodySize !== 0) {
281
- this.push('js', this.jsList, timing, urlInfo);
282
- }
283
- }
284
- }
285
-
286
- // 暂时将css文件或代码块发起的请求归位三类(主要为这两类)
287
- // 1、加载字体
288
- // 2、加载背景图(图片不容易区分,有的没有明确后缀名)
289
- // 3、光标文件(后缀为.cur,这里也划分为图片)
290
- // (svg当做图片,前述已说明)
291
- private addResFromCss(timing: PerformanceResourceTiming, urlInfo: URLINFO) {
292
- if (urlInfo.ext && FontsTypes.hasOwnProperty(urlInfo.ext)) {
293
- this.push('font', this.fontList, timing, urlInfo);
294
- }
295
- else {
296
- this.addImg(timing, urlInfo);
297
- }
298
- }
299
-
300
- // link一般加载css资源
301
- // 也可以通过preload可以预下载一些资源
302
- // 这里只统计js类型的preload
303
- private addLink(timing: PerformanceResourceTiming, urlInfo: URLINFO) {
304
- if (urlInfo.ext === 'css') {
305
- this.push('css', this.cssList, timing, urlInfo);
306
- }
307
- // preload as script
308
- else if (urlInfo.ext === 'js') {
309
- this.push('js', this.jsList, timing, urlInfo);
310
- }
311
- }
312
-
313
- private addImg(timing: PerformanceResourceTiming, urlInfo: URLINFO) {
314
- this.push('img', this.imgList, timing, urlInfo);
315
-
316
- // 大于指定size的图片采集
317
- if (
318
- timing.decodedBodySize > (this.bigImgOption.maxSize || 0)
319
- && !ignorePath(urlInfo.pathname, this.bigImgOption.ignorePaths || [])
320
- ) {
321
- this.pushWithHost('img', this.bigImgList, timing, urlInfo);
322
- }
323
- }
324
-
325
- private handleTimings(list: PerformanceEntry[]) {
326
- const len = list.length;
327
- for (let i = 0; i < len; i++) {
328
- const timing = list[i] as PerformanceResourceTiming;
329
- const urlInfo = getUrlInfo(timing.name);
330
-
331
- if (ignorePath(urlInfo.pathname, ignoreDefaultPaths)) {
332
- continue;
333
- }
334
- switch ((list[i] as any).initiatorType) {
335
- case 'script':
336
- this.addScript(timing, urlInfo);
337
- break;
338
- case 'css':
339
- this.addResFromCss(timing, urlInfo);
340
- break;
341
- case 'img':
342
- this.addImg(timing, urlInfo);
343
- break;
344
- case 'link':
345
- this.addLink(timing, urlInfo);
346
- break;
347
- case 'audio':
348
- this.collectHttpResInHttps('audio', timing, urlInfo);
349
- break;
350
- case 'video':
351
- this.collectHttpResInHttps('video', timing, urlInfo);
352
- break;
353
- default:
354
- break;
355
- }
356
- }
357
- }
358
-
359
- private getNumAndSize(type: string, list: PerformanceResourceTiming[]) {
360
- const obj: any = {};
361
- const num = type + 'Num';
362
- const size = type + 'Size';
363
- const transferSize = type + 'TransferSize';
364
- const cacheRate = type + 'CacheRate';
365
- const duration = type + 'Duration';
366
- obj[num] = 0;
367
- obj[size] = 0;
368
- obj[transferSize] = 0;
369
- let totalDurationTime = 0;
370
- list.forEach((timing: PerformanceResourceTiming) => {
371
- obj[num]++;
372
- obj[size] += (timing.decodedBodySize / 1024);
373
- obj[transferSize] += (timing.transferSize / 1024);
374
- totalDurationTime += timing.duration;
375
- });
376
- obj[duration] = f(obj[num] > 0 ? totalDurationTime / obj[num] : 0);
377
-
378
- if (obj[size]) {
379
- const diff = obj[size] - obj[transferSize];
380
- obj[cacheRate] = f(diff >= 0 ? 100 * diff / obj[size] : 0);
381
- }
382
-
383
- obj[size] = f(obj[size]);
384
- obj[transferSize] = f(obj[transferSize]);
385
-
386
- return obj;
387
- }
388
-
389
- private getMetric(): {metric: ResourceMetric, hostMetric: ResourceHostMetric} | undefined {
390
- // 原来代码
391
- const {0: mainPageTiming} = performance.getEntriesByType('navigation');
392
- const resourceTimings = performance.getEntriesByType('resource');
393
-
394
- if (mainPageTiming && resourceTimings && resourceTimings.length) {
395
- this.handleTimings(resourceTimings);
396
-
397
- let metric: ResourceMetric = {
398
- ...this.getNumAndSize(ResType.JS, this.jsList),
399
- ...this.getNumAndSize(ResType.CSS, this.cssList),
400
- ...this.getNumAndSize(ResType.IMG, this.imgList),
401
- ...this.getNumAndSize(ResType.FONT, this.fontList),
402
- };
403
-
404
- // 主文档大小
405
- const pageTiming = mainPageTiming as PerformanceResourceTiming;
406
- metric.docSize = f(pageTiming.decodedBodySize / 1024);
407
- metric.docTransferSize = f(pageTiming.transferSize / 1024);
408
-
409
- metric.headerSize = f((pageTiming.transferSize - pageTiming.encodedBodySize || 0) / 1024);
410
-
411
- metric.allSize = f(metric.docSize
412
- + metric.jsSize
413
- + metric.cssSize
414
- + metric.imgSize
415
- + metric.fontSize);
416
-
417
- metric.allTransferSize = f(metric.docTransferSize
418
- + metric.jsTransferSize
419
- + metric.cssTransferSize
420
- + metric.imgTransferSize
421
- + metric.fontTransferSize);
422
-
423
- const hostMetric: ResourceHostMetric = {};
424
- for (const host of Object.keys(this.hostList)) {
425
- const timings = this.hostList[host].map(item => item.timing);
426
- hostMetric[host] = this.getNumAndSize('host', timings);
427
- }
428
-
429
- return {metric, hostMetric};
430
- }
431
- return;
432
- }
433
- }
@@ -1,75 +0,0 @@
1
- /**
2
- * @file LCP
3
- * @author kaivean
4
- */
5
-
6
- import {Module, TimingCB, TimingMetric} from '../lib/interface';
7
-
8
- export default class Timing implements Module {
9
- private cb: TimingCB;
10
- // constructor() {}
11
-
12
- listenTiming(cb: TimingCB) {
13
- if (!window.performance || !window.performance.timing) {
14
- // debug('no performance.timing api');
15
- return;
16
- }
17
- this.cb = cb;
18
- }
19
-
20
- load() {
21
- setTimeout(() => {
22
- this.cb && this.cb(this.getMetric());
23
- }, 500);
24
- }
25
-
26
- private getMetric(): TimingMetric {
27
- const metric = {} as TimingMetric;
28
- const timing: PerformanceTiming = window.performance.timing;
29
-
30
- const startTime = timing.navigationStart;
31
-
32
- // 基础信息
33
- metric.dns = timing.domainLookupEnd - timing.domainLookupStart;
34
- metric.tcp = timing.connectEnd - timing.connectStart;
35
- metric.request = timing.responseStart - timing.requestStart;
36
- metric.response = timing.responseEnd - timing.responseStart;
37
- // 这是从页面部分数据返回,浏览器开始解析doc元素到最底部的script脚本解析执行完成
38
- // 脚本里触发的异步或绑定了更靠后的事件,不再纳入范围内
39
- metric.parseHtml = timing.domInteractive - timing.domLoading;
40
- // 很多事件绑定是在domContentLoaded事件里的,所以等其结束,一般页面元素的事件绑定好了,用户可以正确交互
41
- // 当然存在在该加载事件之后绑定元素事件情况,但不再此考虑范围内
42
- metric.domReady = timing.domContentLoadedEventEnd - startTime;
43
- metric.loadEventHandle = timing.loadEventEnd - timing.loadEventStart;
44
- // 基本该做的都做完,资源也都加载完成了
45
- // 当然在onload事件处理函数里启动了异步方法,不再纳入范围内
46
- metric.load = timing.loadEventEnd - startTime;
47
-
48
- // 浏览器首次绘制与首次内容绘制, ios低版本无getEntriesByType api
49
- if (performance.getEntriesByType) {
50
- const paintEntries: PerformanceEntry[] = performance.getEntriesByType('paint');
51
- if (paintEntries && paintEntries.length) {
52
- paintEntries.forEach(({name, duration, startTime}) => {
53
- const time = Math.ceil(duration + startTime);
54
- if (name === 'first-paint') {
55
- metric.fp = time;
56
- }
57
- if (name === 'first-contentful-paint') {
58
- metric.fcp = time;
59
- }
60
- });
61
- }
62
- }
63
-
64
- // 端首次绘制与首屏
65
- if (timing.domFirstPaint) {
66
- metric.t7FirstPaint = timing.domFirstPaint - startTime;
67
- }
68
-
69
- if (timing.domFirstScreenPaint) {
70
- metric.t7FirstScreen = timing.domFirstScreenPaint - startTime;
71
- }
72
-
73
- return metric;
74
- }
75
- }
package/src/module/tti.ts DELETED
@@ -1,139 +0,0 @@
1
- import {Module, TTICB, TTIOption} from '../lib/interface';
2
-
3
- function filterExcludeLogGifs(entry: PerformanceEntry) {
4
- return entry.name.indexOf('.gif?') < 0;
5
- }
6
-
7
-
8
- export default class TTI implements Module {
9
- private lastLongTask: number = 0;
10
- private observer: PerformanceObserver | null = null;
11
- private ttiTimer: any;
12
- private stopLongTaskTimeoutId: any;
13
- private cb: TTICB;
14
- private interval: number = 5000;
15
- private filterRequest = filterExcludeLogGifs;
16
- constructor() {
17
- this.observerCallback = this.observerCallback.bind(this);
18
- this.ttiCheck = this.ttiCheck.bind(this);
19
- }
20
-
21
- check() {
22
- return window.PerformanceObserver && performance && performance.timing && performance.timing.navigationStart;
23
- }
24
-
25
- listenTTI(cb: TTICB, option?: TTIOption) {
26
- if (!this.check()) {
27
- return;
28
- }
29
- // 监听获取longtask,持续20s
30
- this.observeLongtask(20000);
31
-
32
- this.cb = cb;
33
-
34
- option = option || {};
35
- if (option.interval) {
36
- this.interval = option.interval;
37
- }
38
- if (option.filterRequest) {
39
- this.filterRequest = option.filterRequest;
40
- }
41
- }
42
-
43
- load() {
44
- if (!this.check()) {
45
- return;
46
- }
47
- // 每 5 秒检查一次是否符合TTI要求
48
- this.ttiTimer = setInterval(this.ttiCheck, this.interval);
49
- }
50
-
51
- ttiCheck() {
52
- if (!this.cb) {
53
- return;
54
- }
55
-
56
- const now = performance.now();
57
- if (now - this.lastLongTask < this.interval) {
58
- // debug('tti 没有达成条件,上次 longTask 离现在差' + (now - this.lastLongTask) + 'ms');
59
- return;
60
- }
61
-
62
- const networkSilence = this.getNetworkSilenceAt();
63
- if (networkSilence === false) {
64
- // debug('tti 没有达成条件,网络没有静默');
65
- return;
66
- }
67
- if (now - networkSilence < this.interval) {
68
- // debug('tti 没有达成条件,上次网络请求离现在差' + (now - networkSilence) + 'ms');
69
- return;
70
- }
71
-
72
- // debug('tti 达成条件');
73
-
74
- clearTimeout(this.ttiTimer);
75
-
76
- let tti = this.lastLongTask;
77
- // 没有longtask,就用dom ready时间作为tti,默认是认为dom ready已经绑定好时间,可以交换了
78
- if (!tti) {
79
- tti = performance.timing.domContentLoadedEventEnd - performance.timing.navigationStart;
80
- }
81
-
82
- // 停止 long task 获取
83
- this.stopObserveLongTask();
84
- this.cb({
85
- tti,
86
- });
87
- }
88
-
89
- observerCallback(list: PerformanceObserverEntryList) {
90
- const entries = list.getEntries();
91
- const lastEntry = entries[entries.length - 1];
92
- // debug('long task (no more than ' + lastEntry.duration + 'ms) detected');
93
- // 最新的longTask完成时间
94
- this.lastLongTask = lastEntry.startTime + lastEntry.duration;
95
- }
96
-
97
- stopObserveLongTask() {
98
- if (this.observer) {
99
- this.observer.disconnect();
100
- this.observer = null;
101
- }
102
- if (this.stopLongTaskTimeoutId) {
103
- clearTimeout(this.stopLongTaskTimeoutId);
104
- }
105
- }
106
-
107
- getNetworkSilenceAt() {
108
- // const now = performance.now();
109
- const resources = performance.getEntriesByType('resource')
110
- .filter(this.filterRequest) as PerformanceResourceTiming[];
111
-
112
- let lastResourceEnd = 0;
113
- for (const item of resources) {
114
- // 还没有responseEnd字段,说明网络请求还未结束
115
- if (!item.responseEnd) {
116
- return false;
117
- }
118
- // 取responseEnd最大值
119
- if (item.responseEnd > lastResourceEnd) {
120
- lastResourceEnd = item.responseEnd;
121
- }
122
- }
123
-
124
- return lastResourceEnd;
125
- }
126
-
127
- private observeLongtask(timeout: number) {
128
- this.stopObserveLongTask();
129
-
130
- try {
131
- this.observer = new PerformanceObserver(this.observerCallback);
132
- this.observer.observe({
133
- entryTypes: ['longtask'],
134
- });
135
- this.stopLongTaskTimeoutId = setTimeout(this.stopObserveLongTask, timeout);
136
- }
137
- catch (e) {}
138
- }
139
- }