@shaper.org/core 1.0.1 → 1.0.2
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.
- package/dist/index.d.mts +26 -2
- package/dist/index.mjs +47 -2
- package/dist/runtime/error-tracker.ts +166 -0
- package/dist/runtime/vite-hook.ts +133 -0
- package/dist/runtime/vite-message.ts +61 -0
- package/package.json +8 -3
- package/src/runtime/error-tracker.ts +166 -0
- package/src/runtime/vite-hook.ts +133 -0
- package/src/runtime/vite-message.ts +61 -0
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import * as vite0 from "vite";
|
|
2
|
+
import { HmrContext, ViteDevServer } from "vite";
|
|
3
|
+
|
|
1
4
|
//#region src/iframe-post-message.d.ts
|
|
2
5
|
type PostMessageType = "server-ready" | "all-routes" | "route-change" | "route-refresh";
|
|
3
6
|
interface PostMessage {
|
|
@@ -43,6 +46,27 @@ declare class IframePostMessageClient {
|
|
|
43
46
|
sendRouteChange(route: RouteInfo): void;
|
|
44
47
|
sendRouteRefresh(route: RouteInfo): void;
|
|
45
48
|
}
|
|
46
|
-
declare const iframePostMessageClient: IframePostMessageClient;
|
|
47
49
|
//#endregion
|
|
48
|
-
|
|
50
|
+
//#region src/hmr-events-plugin.d.ts
|
|
51
|
+
declare const HMREventsPlugin: () => {
|
|
52
|
+
name: string;
|
|
53
|
+
enforce: string;
|
|
54
|
+
resolveId(id: string): "virtual:hmr-events-plugin" | undefined;
|
|
55
|
+
load(id: string): string | undefined;
|
|
56
|
+
transformIndexHtml(html: string): {
|
|
57
|
+
html: string;
|
|
58
|
+
tags: {
|
|
59
|
+
tag: string;
|
|
60
|
+
injectTo: string;
|
|
61
|
+
type: string;
|
|
62
|
+
attrs: {
|
|
63
|
+
src: string;
|
|
64
|
+
type: string;
|
|
65
|
+
};
|
|
66
|
+
}[];
|
|
67
|
+
};
|
|
68
|
+
configureServer(server: ViteDevServer): void;
|
|
69
|
+
handleHotUpdate(ctx: HmrContext): vite0.ModuleNode[];
|
|
70
|
+
};
|
|
71
|
+
//#endregion
|
|
72
|
+
export { AllRoutesMessage, HMREventsPlugin, IframePostMessageClient, PostMessage, PostMessageType, RouteChangeMessage, RouteInfo, RouteRefreshMessage, ServerReadyMessage };
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { fileURLToPath } from "url";
|
|
2
|
+
|
|
1
3
|
//#region src/iframe-post-message.ts
|
|
2
4
|
var IframePostMessageClient = class {
|
|
3
5
|
authHost;
|
|
@@ -43,7 +45,50 @@ var IframePostMessageClient = class {
|
|
|
43
45
|
this._send(message);
|
|
44
46
|
}
|
|
45
47
|
};
|
|
46
|
-
const iframePostMessageClient = new IframePostMessageClient();
|
|
47
48
|
|
|
48
49
|
//#endregion
|
|
49
|
-
|
|
50
|
+
//#region src/hmr-events-plugin.ts
|
|
51
|
+
const HMREventsPlugin = () => {
|
|
52
|
+
return {
|
|
53
|
+
name: "hmr-events-plugin",
|
|
54
|
+
enforce: "pre",
|
|
55
|
+
resolveId(id) {
|
|
56
|
+
if (id === "virtual:hmr-events-plugin") return id;
|
|
57
|
+
},
|
|
58
|
+
load(id) {
|
|
59
|
+
if (id === "virtual:hmr-events-plugin") {
|
|
60
|
+
const runtimePath = fileURLToPath(new URL("./runtime/error-tracker", import.meta.url));
|
|
61
|
+
return `import ${JSON.stringify(runtimePath)};`;
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
transformIndexHtml(html) {
|
|
65
|
+
return {
|
|
66
|
+
html,
|
|
67
|
+
tags: [{
|
|
68
|
+
tag: "script",
|
|
69
|
+
injectTo: "body",
|
|
70
|
+
type: "module",
|
|
71
|
+
attrs: {
|
|
72
|
+
src: "/@id/virtual:hmr-events-plugin",
|
|
73
|
+
type: "module"
|
|
74
|
+
}
|
|
75
|
+
}]
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
configureServer(server) {
|
|
79
|
+
server.ws.on("breeze:beforeUpdate", (event) => {});
|
|
80
|
+
server.ws.on("breeze:afterUpdate", (event) => {
|
|
81
|
+
for (let eventMessage of event.data) console.log(eventMessage.message);
|
|
82
|
+
console.log("[breeze] end update");
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
handleHotUpdate(ctx) {
|
|
86
|
+
const { file, timestamp, read, server, modules } = ctx;
|
|
87
|
+
console.log(`[breeze] hmr update: ${file}`);
|
|
88
|
+
return modules;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
//#endregion
|
|
94
|
+
export { HMREventsPlugin, IframePostMessageClient };
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { ViteHook } from "./vite-hook";
|
|
2
|
+
import { ViteMessage } from "./vite-message";
|
|
3
|
+
|
|
4
|
+
interface LogEvent {
|
|
5
|
+
type: "logEvent";
|
|
6
|
+
logLevel: string;
|
|
7
|
+
timestamp: number;
|
|
8
|
+
message: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface UnhandledrejectionEvent {
|
|
12
|
+
type: "unhandledrejectionEvent";
|
|
13
|
+
message: string;
|
|
14
|
+
timestamp: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface ErrorEvent {
|
|
18
|
+
type: "errorEvent";
|
|
19
|
+
lineno: number;
|
|
20
|
+
colno: number;
|
|
21
|
+
filename: string;
|
|
22
|
+
message: string;
|
|
23
|
+
timestamp: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type Event = LogEvent | UnhandledrejectionEvent | ErrorEvent;
|
|
27
|
+
|
|
28
|
+
class ErrorTracker {
|
|
29
|
+
eventsQueue: Event[];
|
|
30
|
+
|
|
31
|
+
public vHook: ViteHook;
|
|
32
|
+
public viteMessage: ViteMessage;
|
|
33
|
+
|
|
34
|
+
constructor() {
|
|
35
|
+
this.eventsQueue = [];
|
|
36
|
+
|
|
37
|
+
this.viteMessage = new ViteMessage();
|
|
38
|
+
|
|
39
|
+
this.vHook = new ViteHook();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
isBlankScreen() {
|
|
43
|
+
const app = document.querySelector("div#app");
|
|
44
|
+
return app ? app.childElementCount === 0 : false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
start() {
|
|
48
|
+
this.handleError();
|
|
49
|
+
this.handleUnhandledrejection();
|
|
50
|
+
this.consoleInterception();
|
|
51
|
+
|
|
52
|
+
let startTimestamp: number;
|
|
53
|
+
this.vHook.onBeforeUpdate((data, timestamp) => {
|
|
54
|
+
startTimestamp = timestamp;
|
|
55
|
+
this.viteMessage.sendBeforeUpdate(data);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
this.vHook.onError((data, timestamp) => {
|
|
59
|
+
console.debug(data);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
this.vHook.onAfterUpdate((data, timestamp) => {
|
|
63
|
+
setTimeout(() => {
|
|
64
|
+
const endTimestamp = Date.now();
|
|
65
|
+
|
|
66
|
+
let events = this.eventsQueue.filter(
|
|
67
|
+
(event) =>
|
|
68
|
+
event.timestamp >= startTimestamp &&
|
|
69
|
+
event.timestamp <= endTimestamp,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
this.viteMessage.sendAfterUpdate(events);
|
|
73
|
+
|
|
74
|
+
this.eventsQueue = [];
|
|
75
|
+
}, 5);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
consoleInterception() {
|
|
80
|
+
const originalConsole = {
|
|
81
|
+
log: console.log,
|
|
82
|
+
warn: console.warn,
|
|
83
|
+
error: console.error,
|
|
84
|
+
trace: console.trace,
|
|
85
|
+
// debug: console.debug,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
for (let logLevel of Object.keys(originalConsole)) {
|
|
89
|
+
console[logLevel] = function (...args) {
|
|
90
|
+
const logArgumentsString = args.map((arg) => {
|
|
91
|
+
if (arg) {
|
|
92
|
+
return arg.toString();
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
this.eventsQueue.push({
|
|
97
|
+
type: "logEvent",
|
|
98
|
+
logLevel,
|
|
99
|
+
timestamp: Date.now(),
|
|
100
|
+
message: logArgumentsString.join(" "),
|
|
101
|
+
});
|
|
102
|
+
originalConsole[logLevel].apply(this, args);
|
|
103
|
+
}.bind(this);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
handleUnhandledrejection() {
|
|
108
|
+
window.addEventListener("unhandledrejection", (evt) => {
|
|
109
|
+
console.debug(evt);
|
|
110
|
+
const UnhandledrejectionMessage = {
|
|
111
|
+
type: "unhandledrejectionEvent",
|
|
112
|
+
message: evt.reason.stack || evt.reason.message,
|
|
113
|
+
timestamp: Date.now(),
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
this.eventsQueue.push(UnhandledrejectionMessage);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
handleError() {
|
|
121
|
+
window.addEventListener("error", (evt) => {
|
|
122
|
+
let message;
|
|
123
|
+
if (evt.error) {
|
|
124
|
+
message = evt.error.stack || evt.error.message;
|
|
125
|
+
} else {
|
|
126
|
+
message = evt.message;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
console.debug(evt);
|
|
130
|
+
const errorMessage = {
|
|
131
|
+
type: "errorEvent",
|
|
132
|
+
lineno: evt.lineno,
|
|
133
|
+
colno: evt.colno,
|
|
134
|
+
filename: evt.filename,
|
|
135
|
+
message: message,
|
|
136
|
+
timestamp: Date.now(),
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
this.eventsQueue.push(errorMessage);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// This handle the following kind of runtime error
|
|
143
|
+
// For now it's diffcult to know what to do because those errors are not sync with the HMR lifecyle
|
|
144
|
+
// Maybe use a queue
|
|
145
|
+
// <script setup lang="ts">
|
|
146
|
+
// import { onMounted } from 'vue';
|
|
147
|
+
|
|
148
|
+
// onMounted(() => {
|
|
149
|
+
// // This will trigger the error event because it happens at runtime
|
|
150
|
+
// setTimeout(() => {
|
|
151
|
+
// undefinedFunction(); // Runtime error - WILL trigger window.error
|
|
152
|
+
// }, 0);
|
|
153
|
+
// });
|
|
154
|
+
// </script>
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const tracker = new ErrorTracker();
|
|
159
|
+
tracker.start();
|
|
160
|
+
|
|
161
|
+
// HMR listening
|
|
162
|
+
// const handleMessage = (event) => {
|
|
163
|
+
// console.log(event);
|
|
164
|
+
// };
|
|
165
|
+
|
|
166
|
+
// window.addEventListener("message", handleMessage);
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ErrorPayload,
|
|
3
|
+
FullReloadPayload,
|
|
4
|
+
PrunePayload,
|
|
5
|
+
UpdatePayload,
|
|
6
|
+
} from "vite/types/hmrPayload.js";
|
|
7
|
+
import { ViteHotContext } from "vite/types/hot.js";
|
|
8
|
+
|
|
9
|
+
type ViteUpdateCallback =
|
|
10
|
+
| ((data: UpdatePayload, timestamp: number) => void)
|
|
11
|
+
| null;
|
|
12
|
+
|
|
13
|
+
type VitePruneCallback =
|
|
14
|
+
| ((data: PrunePayload, timestamp: number) => void)
|
|
15
|
+
| null;
|
|
16
|
+
|
|
17
|
+
type VitFullReloadCallback =
|
|
18
|
+
| ((data: FullReloadPayload, timestamp: number) => void)
|
|
19
|
+
| null;
|
|
20
|
+
|
|
21
|
+
type ViteErrorCallback =
|
|
22
|
+
| ((data: ErrorPayload, timestamp: number) => void)
|
|
23
|
+
| null;
|
|
24
|
+
|
|
25
|
+
type ViteInvalidateCallback = ((data: any, timestamp: number) => void) | null;
|
|
26
|
+
|
|
27
|
+
type ViteWebSocketConnectCallback =
|
|
28
|
+
| ((data: any, timestamp: number) => void)
|
|
29
|
+
| null;
|
|
30
|
+
|
|
31
|
+
type ViteWebSocketDisconnectCallback =
|
|
32
|
+
| ((data: any, timestamp: number) => void)
|
|
33
|
+
| null;
|
|
34
|
+
|
|
35
|
+
class ViteHook {
|
|
36
|
+
private hmr: ViteHotContext | undefined;
|
|
37
|
+
constructor() {
|
|
38
|
+
this.hmr = import.meta.hot;
|
|
39
|
+
}
|
|
40
|
+
hotExists(hmr: ViteHotContext | undefined): hmr is ViteHotContext {
|
|
41
|
+
if (hmr) return true;
|
|
42
|
+
console.error("HotContext does not exists");
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
onBeforeUpdate(fn: ViteUpdateCallback = null) {
|
|
47
|
+
if (!this.hotExists(this.hmr)) return;
|
|
48
|
+
|
|
49
|
+
this.hmr.on("vite:beforeUpdate", (data: UpdatePayload) => {
|
|
50
|
+
const timestamp = Date.now();
|
|
51
|
+
console.debug("beforeUpdate");
|
|
52
|
+
fn && fn(data, timestamp);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
onAfterUpdate(fn: ViteUpdateCallback = null) {
|
|
57
|
+
if (!this.hotExists(this.hmr)) return;
|
|
58
|
+
|
|
59
|
+
this.hmr.on("vite:afterUpdate", (data: UpdatePayload) => {
|
|
60
|
+
const timestamp = Date.now();
|
|
61
|
+
console.debug("afterUpdate");
|
|
62
|
+
fn && fn(data, timestamp);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
onBeforePrune(fn: VitePruneCallback = null) {
|
|
67
|
+
if (!this.hotExists(this.hmr)) return;
|
|
68
|
+
|
|
69
|
+
this.hmr.on("vite:beforePrune", (data: PrunePayload) => {
|
|
70
|
+
const timestamp = Date.now();
|
|
71
|
+
console.debug("beforePrune");
|
|
72
|
+
|
|
73
|
+
fn && fn(data, timestamp);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
onBeforeFullReload(fn: VitFullReloadCallback = null) {
|
|
78
|
+
if (!this.hotExists(this.hmr)) return;
|
|
79
|
+
|
|
80
|
+
this.hmr.on("vite:beforeFullReload", (data: FullReloadPayload) => {
|
|
81
|
+
const timestamp = Date.now();
|
|
82
|
+
console.debug("beforeFullReload");
|
|
83
|
+
|
|
84
|
+
fn && fn(data, timestamp);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
onError(fn: ViteErrorCallback = null) {
|
|
89
|
+
if (!this.hotExists(this.hmr)) return;
|
|
90
|
+
|
|
91
|
+
this.hmr.on("vite:error", (data: ErrorPayload) => {
|
|
92
|
+
const timestamp = Date.now();
|
|
93
|
+
console.debug("error");
|
|
94
|
+
|
|
95
|
+
fn && fn(data, timestamp);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
onInvalidate(fn: ViteInvalidateCallback = null) {
|
|
100
|
+
if (!this.hotExists(this.hmr)) return;
|
|
101
|
+
|
|
102
|
+
this.hmr.on("vite:invalidate", (data: any) => {
|
|
103
|
+
const timestamp = Date.now();
|
|
104
|
+
console.debug("invalidate");
|
|
105
|
+
|
|
106
|
+
fn && fn(data, timestamp);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
onWsConnect(fn: ViteWebSocketConnectCallback = null) {
|
|
111
|
+
if (!this.hotExists(this.hmr)) return;
|
|
112
|
+
|
|
113
|
+
this.hmr.on("vite:ws:connect", (data: any) => {
|
|
114
|
+
const timestamp = Date.now();
|
|
115
|
+
console.debug("ws:connect");
|
|
116
|
+
|
|
117
|
+
fn && fn(data, timestamp);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
onWsDisconnect(fn: ViteWebSocketDisconnectCallback = null) {
|
|
122
|
+
if (!this.hotExists(this.hmr)) return;
|
|
123
|
+
|
|
124
|
+
this.hmr.on("vite:ws:disconnect", (data: any) => {
|
|
125
|
+
const timestamp = Date.now();
|
|
126
|
+
console.debug(":ws:disconnect");
|
|
127
|
+
|
|
128
|
+
fn && fn(data, timestamp);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export { ViteHook };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { ViteHotContext } from "vite/types/hot.js";
|
|
2
|
+
|
|
3
|
+
interface MessageEvent {
|
|
4
|
+
type: "custom";
|
|
5
|
+
event: string;
|
|
6
|
+
data: any;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
enum MessageEventType {
|
|
10
|
+
BEFORE_UPDATE = "breeze:beforeUpdate",
|
|
11
|
+
AFTER_UPDATE = "breeze:afterUpdate",
|
|
12
|
+
ERROR = "breeze:error",
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
class ViteMessage {
|
|
16
|
+
private server: ViteHotContext | undefined;
|
|
17
|
+
|
|
18
|
+
constructor() {
|
|
19
|
+
this.server = import.meta.hot;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
hotExists(server: ViteHotContext | undefined): server is ViteHotContext {
|
|
23
|
+
if (server) return true;
|
|
24
|
+
console.error("HotContext does not exists");
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
send(event: string, message: MessageEvent) {
|
|
29
|
+
if (!this.hotExists(this.server)) return;
|
|
30
|
+
this.server.send(event, message);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
sendBeforeUpdate(data: any) {
|
|
34
|
+
if (!this.hotExists(this.server)) return;
|
|
35
|
+
this.server.send(MessageEventType.BEFORE_UPDATE, {
|
|
36
|
+
type: "custom",
|
|
37
|
+
event: MessageEventType.BEFORE_UPDATE,
|
|
38
|
+
data: data,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
sendError(data: any) {
|
|
43
|
+
if (!this.hotExists(this.server)) return;
|
|
44
|
+
this.server.send(MessageEventType.ERROR, {
|
|
45
|
+
type: "custom",
|
|
46
|
+
event: MessageEventType.ERROR,
|
|
47
|
+
data: data,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
sendAfterUpdate(data: any) {
|
|
52
|
+
if (!this.hotExists(this.server)) return;
|
|
53
|
+
this.server.send(MessageEventType.AFTER_UPDATE, {
|
|
54
|
+
type: "custom",
|
|
55
|
+
event: MessageEventType.AFTER_UPDATE,
|
|
56
|
+
data: data,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export { ViteMessage };
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shaper.org/core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
7
7
|
"files": [
|
|
8
|
-
"dist"
|
|
8
|
+
"dist",
|
|
9
|
+
"src/runtime"
|
|
9
10
|
],
|
|
10
11
|
"exports": {
|
|
11
12
|
".": {
|
|
@@ -36,12 +37,16 @@
|
|
|
36
37
|
},
|
|
37
38
|
"dependencies": {
|
|
38
39
|
"@rolldown/pluginutils": "1.0.0-beta.50",
|
|
39
|
-
"tsup": "^8.5.1"
|
|
40
|
+
"tsup": "^8.5.1",
|
|
41
|
+
"esbuild": "^0.27.0"
|
|
40
42
|
},
|
|
41
43
|
"tsdown": {
|
|
42
44
|
"dts": true,
|
|
43
45
|
"format": [
|
|
44
46
|
"esm"
|
|
47
|
+
],
|
|
48
|
+
"copy": [
|
|
49
|
+
"src/runtime/"
|
|
45
50
|
]
|
|
46
51
|
},
|
|
47
52
|
"publishConfig": {
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { ViteHook } from "./vite-hook";
|
|
2
|
+
import { ViteMessage } from "./vite-message";
|
|
3
|
+
|
|
4
|
+
interface LogEvent {
|
|
5
|
+
type: "logEvent";
|
|
6
|
+
logLevel: string;
|
|
7
|
+
timestamp: number;
|
|
8
|
+
message: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface UnhandledrejectionEvent {
|
|
12
|
+
type: "unhandledrejectionEvent";
|
|
13
|
+
message: string;
|
|
14
|
+
timestamp: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface ErrorEvent {
|
|
18
|
+
type: "errorEvent";
|
|
19
|
+
lineno: number;
|
|
20
|
+
colno: number;
|
|
21
|
+
filename: string;
|
|
22
|
+
message: string;
|
|
23
|
+
timestamp: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type Event = LogEvent | UnhandledrejectionEvent | ErrorEvent;
|
|
27
|
+
|
|
28
|
+
class ErrorTracker {
|
|
29
|
+
eventsQueue: Event[];
|
|
30
|
+
|
|
31
|
+
public vHook: ViteHook;
|
|
32
|
+
public viteMessage: ViteMessage;
|
|
33
|
+
|
|
34
|
+
constructor() {
|
|
35
|
+
this.eventsQueue = [];
|
|
36
|
+
|
|
37
|
+
this.viteMessage = new ViteMessage();
|
|
38
|
+
|
|
39
|
+
this.vHook = new ViteHook();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
isBlankScreen() {
|
|
43
|
+
const app = document.querySelector("div#app");
|
|
44
|
+
return app ? app.childElementCount === 0 : false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
start() {
|
|
48
|
+
this.handleError();
|
|
49
|
+
this.handleUnhandledrejection();
|
|
50
|
+
this.consoleInterception();
|
|
51
|
+
|
|
52
|
+
let startTimestamp: number;
|
|
53
|
+
this.vHook.onBeforeUpdate((data, timestamp) => {
|
|
54
|
+
startTimestamp = timestamp;
|
|
55
|
+
this.viteMessage.sendBeforeUpdate(data);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
this.vHook.onError((data, timestamp) => {
|
|
59
|
+
console.debug(data);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
this.vHook.onAfterUpdate((data, timestamp) => {
|
|
63
|
+
setTimeout(() => {
|
|
64
|
+
const endTimestamp = Date.now();
|
|
65
|
+
|
|
66
|
+
let events = this.eventsQueue.filter(
|
|
67
|
+
(event) =>
|
|
68
|
+
event.timestamp >= startTimestamp &&
|
|
69
|
+
event.timestamp <= endTimestamp,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
this.viteMessage.sendAfterUpdate(events);
|
|
73
|
+
|
|
74
|
+
this.eventsQueue = [];
|
|
75
|
+
}, 5);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
consoleInterception() {
|
|
80
|
+
const originalConsole = {
|
|
81
|
+
log: console.log,
|
|
82
|
+
warn: console.warn,
|
|
83
|
+
error: console.error,
|
|
84
|
+
trace: console.trace,
|
|
85
|
+
// debug: console.debug,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
for (let logLevel of Object.keys(originalConsole)) {
|
|
89
|
+
console[logLevel] = function (...args) {
|
|
90
|
+
const logArgumentsString = args.map((arg) => {
|
|
91
|
+
if (arg) {
|
|
92
|
+
return arg.toString();
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
this.eventsQueue.push({
|
|
97
|
+
type: "logEvent",
|
|
98
|
+
logLevel,
|
|
99
|
+
timestamp: Date.now(),
|
|
100
|
+
message: logArgumentsString.join(" "),
|
|
101
|
+
});
|
|
102
|
+
originalConsole[logLevel].apply(this, args);
|
|
103
|
+
}.bind(this);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
handleUnhandledrejection() {
|
|
108
|
+
window.addEventListener("unhandledrejection", (evt) => {
|
|
109
|
+
console.debug(evt);
|
|
110
|
+
const UnhandledrejectionMessage = {
|
|
111
|
+
type: "unhandledrejectionEvent",
|
|
112
|
+
message: evt.reason.stack || evt.reason.message,
|
|
113
|
+
timestamp: Date.now(),
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
this.eventsQueue.push(UnhandledrejectionMessage);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
handleError() {
|
|
121
|
+
window.addEventListener("error", (evt) => {
|
|
122
|
+
let message;
|
|
123
|
+
if (evt.error) {
|
|
124
|
+
message = evt.error.stack || evt.error.message;
|
|
125
|
+
} else {
|
|
126
|
+
message = evt.message;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
console.debug(evt);
|
|
130
|
+
const errorMessage = {
|
|
131
|
+
type: "errorEvent",
|
|
132
|
+
lineno: evt.lineno,
|
|
133
|
+
colno: evt.colno,
|
|
134
|
+
filename: evt.filename,
|
|
135
|
+
message: message,
|
|
136
|
+
timestamp: Date.now(),
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
this.eventsQueue.push(errorMessage);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// This handle the following kind of runtime error
|
|
143
|
+
// For now it's diffcult to know what to do because those errors are not sync with the HMR lifecyle
|
|
144
|
+
// Maybe use a queue
|
|
145
|
+
// <script setup lang="ts">
|
|
146
|
+
// import { onMounted } from 'vue';
|
|
147
|
+
|
|
148
|
+
// onMounted(() => {
|
|
149
|
+
// // This will trigger the error event because it happens at runtime
|
|
150
|
+
// setTimeout(() => {
|
|
151
|
+
// undefinedFunction(); // Runtime error - WILL trigger window.error
|
|
152
|
+
// }, 0);
|
|
153
|
+
// });
|
|
154
|
+
// </script>
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const tracker = new ErrorTracker();
|
|
159
|
+
tracker.start();
|
|
160
|
+
|
|
161
|
+
// HMR listening
|
|
162
|
+
// const handleMessage = (event) => {
|
|
163
|
+
// console.log(event);
|
|
164
|
+
// };
|
|
165
|
+
|
|
166
|
+
// window.addEventListener("message", handleMessage);
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ErrorPayload,
|
|
3
|
+
FullReloadPayload,
|
|
4
|
+
PrunePayload,
|
|
5
|
+
UpdatePayload,
|
|
6
|
+
} from "vite/types/hmrPayload.js";
|
|
7
|
+
import { ViteHotContext } from "vite/types/hot.js";
|
|
8
|
+
|
|
9
|
+
type ViteUpdateCallback =
|
|
10
|
+
| ((data: UpdatePayload, timestamp: number) => void)
|
|
11
|
+
| null;
|
|
12
|
+
|
|
13
|
+
type VitePruneCallback =
|
|
14
|
+
| ((data: PrunePayload, timestamp: number) => void)
|
|
15
|
+
| null;
|
|
16
|
+
|
|
17
|
+
type VitFullReloadCallback =
|
|
18
|
+
| ((data: FullReloadPayload, timestamp: number) => void)
|
|
19
|
+
| null;
|
|
20
|
+
|
|
21
|
+
type ViteErrorCallback =
|
|
22
|
+
| ((data: ErrorPayload, timestamp: number) => void)
|
|
23
|
+
| null;
|
|
24
|
+
|
|
25
|
+
type ViteInvalidateCallback = ((data: any, timestamp: number) => void) | null;
|
|
26
|
+
|
|
27
|
+
type ViteWebSocketConnectCallback =
|
|
28
|
+
| ((data: any, timestamp: number) => void)
|
|
29
|
+
| null;
|
|
30
|
+
|
|
31
|
+
type ViteWebSocketDisconnectCallback =
|
|
32
|
+
| ((data: any, timestamp: number) => void)
|
|
33
|
+
| null;
|
|
34
|
+
|
|
35
|
+
class ViteHook {
|
|
36
|
+
private hmr: ViteHotContext | undefined;
|
|
37
|
+
constructor() {
|
|
38
|
+
this.hmr = import.meta.hot;
|
|
39
|
+
}
|
|
40
|
+
hotExists(hmr: ViteHotContext | undefined): hmr is ViteHotContext {
|
|
41
|
+
if (hmr) return true;
|
|
42
|
+
console.error("HotContext does not exists");
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
onBeforeUpdate(fn: ViteUpdateCallback = null) {
|
|
47
|
+
if (!this.hotExists(this.hmr)) return;
|
|
48
|
+
|
|
49
|
+
this.hmr.on("vite:beforeUpdate", (data: UpdatePayload) => {
|
|
50
|
+
const timestamp = Date.now();
|
|
51
|
+
console.debug("beforeUpdate");
|
|
52
|
+
fn && fn(data, timestamp);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
onAfterUpdate(fn: ViteUpdateCallback = null) {
|
|
57
|
+
if (!this.hotExists(this.hmr)) return;
|
|
58
|
+
|
|
59
|
+
this.hmr.on("vite:afterUpdate", (data: UpdatePayload) => {
|
|
60
|
+
const timestamp = Date.now();
|
|
61
|
+
console.debug("afterUpdate");
|
|
62
|
+
fn && fn(data, timestamp);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
onBeforePrune(fn: VitePruneCallback = null) {
|
|
67
|
+
if (!this.hotExists(this.hmr)) return;
|
|
68
|
+
|
|
69
|
+
this.hmr.on("vite:beforePrune", (data: PrunePayload) => {
|
|
70
|
+
const timestamp = Date.now();
|
|
71
|
+
console.debug("beforePrune");
|
|
72
|
+
|
|
73
|
+
fn && fn(data, timestamp);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
onBeforeFullReload(fn: VitFullReloadCallback = null) {
|
|
78
|
+
if (!this.hotExists(this.hmr)) return;
|
|
79
|
+
|
|
80
|
+
this.hmr.on("vite:beforeFullReload", (data: FullReloadPayload) => {
|
|
81
|
+
const timestamp = Date.now();
|
|
82
|
+
console.debug("beforeFullReload");
|
|
83
|
+
|
|
84
|
+
fn && fn(data, timestamp);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
onError(fn: ViteErrorCallback = null) {
|
|
89
|
+
if (!this.hotExists(this.hmr)) return;
|
|
90
|
+
|
|
91
|
+
this.hmr.on("vite:error", (data: ErrorPayload) => {
|
|
92
|
+
const timestamp = Date.now();
|
|
93
|
+
console.debug("error");
|
|
94
|
+
|
|
95
|
+
fn && fn(data, timestamp);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
onInvalidate(fn: ViteInvalidateCallback = null) {
|
|
100
|
+
if (!this.hotExists(this.hmr)) return;
|
|
101
|
+
|
|
102
|
+
this.hmr.on("vite:invalidate", (data: any) => {
|
|
103
|
+
const timestamp = Date.now();
|
|
104
|
+
console.debug("invalidate");
|
|
105
|
+
|
|
106
|
+
fn && fn(data, timestamp);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
onWsConnect(fn: ViteWebSocketConnectCallback = null) {
|
|
111
|
+
if (!this.hotExists(this.hmr)) return;
|
|
112
|
+
|
|
113
|
+
this.hmr.on("vite:ws:connect", (data: any) => {
|
|
114
|
+
const timestamp = Date.now();
|
|
115
|
+
console.debug("ws:connect");
|
|
116
|
+
|
|
117
|
+
fn && fn(data, timestamp);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
onWsDisconnect(fn: ViteWebSocketDisconnectCallback = null) {
|
|
122
|
+
if (!this.hotExists(this.hmr)) return;
|
|
123
|
+
|
|
124
|
+
this.hmr.on("vite:ws:disconnect", (data: any) => {
|
|
125
|
+
const timestamp = Date.now();
|
|
126
|
+
console.debug(":ws:disconnect");
|
|
127
|
+
|
|
128
|
+
fn && fn(data, timestamp);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export { ViteHook };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { ViteHotContext } from "vite/types/hot.js";
|
|
2
|
+
|
|
3
|
+
interface MessageEvent {
|
|
4
|
+
type: "custom";
|
|
5
|
+
event: string;
|
|
6
|
+
data: any;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
enum MessageEventType {
|
|
10
|
+
BEFORE_UPDATE = "breeze:beforeUpdate",
|
|
11
|
+
AFTER_UPDATE = "breeze:afterUpdate",
|
|
12
|
+
ERROR = "breeze:error",
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
class ViteMessage {
|
|
16
|
+
private server: ViteHotContext | undefined;
|
|
17
|
+
|
|
18
|
+
constructor() {
|
|
19
|
+
this.server = import.meta.hot;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
hotExists(server: ViteHotContext | undefined): server is ViteHotContext {
|
|
23
|
+
if (server) return true;
|
|
24
|
+
console.error("HotContext does not exists");
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
send(event: string, message: MessageEvent) {
|
|
29
|
+
if (!this.hotExists(this.server)) return;
|
|
30
|
+
this.server.send(event, message);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
sendBeforeUpdate(data: any) {
|
|
34
|
+
if (!this.hotExists(this.server)) return;
|
|
35
|
+
this.server.send(MessageEventType.BEFORE_UPDATE, {
|
|
36
|
+
type: "custom",
|
|
37
|
+
event: MessageEventType.BEFORE_UPDATE,
|
|
38
|
+
data: data,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
sendError(data: any) {
|
|
43
|
+
if (!this.hotExists(this.server)) return;
|
|
44
|
+
this.server.send(MessageEventType.ERROR, {
|
|
45
|
+
type: "custom",
|
|
46
|
+
event: MessageEventType.ERROR,
|
|
47
|
+
data: data,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
sendAfterUpdate(data: any) {
|
|
52
|
+
if (!this.hotExists(this.server)) return;
|
|
53
|
+
this.server.send(MessageEventType.AFTER_UPDATE, {
|
|
54
|
+
type: "custom",
|
|
55
|
+
event: MessageEventType.AFTER_UPDATE,
|
|
56
|
+
data: data,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export { ViteMessage };
|