-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathindex.js
127 lines (111 loc) · 4.06 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
var onPerformance = require('on-performance')
var scheduler = require('nanoscheduler')()
var assert = require('assert')
module.exports = ChooHooks
function ChooHooks (emitter) {
if (!(this instanceof ChooHooks)) return new ChooHooks(emitter)
assert.equal(typeof emitter, 'object')
this.hasWindow = typeof window !== 'undefined'
this.hasIdleCallback = this.hasWindow && window.requestIdleCallback
this.hasPerformance = this.hasWindow &&
window.performance &&
window.performance.getEntriesByName
this.emitter = emitter
this.listeners = {}
this.buffer = {
render: {},
events: {}
}
}
ChooHooks.prototype.on = function (name, handler) {
this.listeners[name] = handler
}
ChooHooks.prototype.start = function () {
var self = this
if (this.hasPerformance) {
window.performance.onresourcetimingbufferfull = function () {
var listener = self.listeners['resource-timing-buffer-full']
if (listener) listener()
}
}
// TODO also handle log events
onPerformance(function (timing) {
if (!timing) return
if (timing.entryType !== 'measure') return
var eventName = timing.name
if (/choo\.morph/.test(eventName)) {
self.buffer.render.morph = timing
} else if (/choo\.route/.test(eventName)) {
self.buffer.render.route = timing
} else if (/choo\.render/.test(eventName)) {
self.buffer.render.render = timing
} else if (/choo\.emit/.test(eventName) && !/log:/.test(eventName)) {
var eventListener = self.listeners['event']
if (eventListener) {
var timingName = eventName.match(/choo\.emit\('(.*)'\)/)[1]
if (timingName === 'render' || timingName === 'DOMContentLoaded') return
var traceId = eventName.match(/\[(\d+)\]/)[1]
var data = self.buffer.events[traceId]
self.buffer.events[traceId] = null
eventListener(timingName, data, timing)
}
}
var rBuf = self.buffer.render
if (rBuf.render && rBuf.route && rBuf.morph) {
var renderListener = self.listeners['render']
if (!renderListener) return
var timings = {}
while (self.buffer.render.length) {
var _timing = self.buffer.render.pop()
var name = _timing.name
if (/choo\.render/.test(name)) timings.render = _timing
else if (/choo\.morph/.test(name)) timings.morph = _timing
else timings.route = _timing
}
rBuf.render = rBuf.route = rBuf.morph = void 0
renderListener(timings)
}
})
// Check if there's timings without any listeners
// and trigger the DOMContentLoaded event.
// If the timing API is not available, we handle all events here
this.emitter.on('*', function (eventName, data, uuid) {
var logLevel = /^log:(\w{4,5})/.exec(eventName)
if (!self.hasPerformance && eventName === 'render') {
// Render
var renderListener = self.listeners['render']
if (renderListener) renderListener()
} else if (eventName === 'DOMContentLoaded') {
// DOMContentLoaded
self._emitLoaded()
} else if (logLevel) {
logLevel = logLevel[1]
// Log:*
var logListener = self.listeners['log:' + logLevel]
if (logListener) {
logListener.apply(null, Array.prototype.slice.call(arguments, 0, arguments.length - 1))
}
} else if (!self.emitter.listeners(eventName).length) {
// Unhandled
var unhandledListener = self.listeners['unhandled']
if (unhandledListener) unhandledListener(eventName, data)
} else if (eventName !== 'render') {
// *
if (self.hasPerformance) self.buffer.events[uuid] = data
}
})
}
// compute and log time till interactive when DOMContentLoaded event fires
ChooHooks.prototype._emitLoaded = function () {
var self = this
scheduler.push(function clear () {
var listener = self.listeners['DOMContentLoaded']
var timing = self.hasWindow && window.performance && window.performance.timing
if (listener && timing) {
listener({
interactive: timing.domInteractive - timing.navigationStart,
loaded: timing.domContentLoadedEventEnd - timing.navigationStart
})
}
})
}