@ywal123456/jskim 0.3.1 → 0.5.0
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/README.md +27 -11
- package/bin/jskim.js +32 -29
- package/docs/configuration.md +26 -3
- package/docs/create-jskim.md +3 -3
- package/package.json +1 -1
- package/scripts/build.js +14 -2
- package/scripts/commands/build-command.js +113 -3
- package/scripts/commands/dev-command.js +10 -1
- package/scripts/commands/help-text.js +23 -11
- package/scripts/commands/serve-command.js +20 -4
- package/scripts/commands/serve-errors.js +30 -4
- package/scripts/commands/watch-command.js +1 -1
- package/scripts/dev.js +16 -2
- package/scripts/lib/apply-serve-cli-overrides.js +96 -0
- package/scripts/lib/assert-output-dirs-compatible.js +88 -0
- package/scripts/lib/classify-reload.js +131 -0
- package/scripts/lib/create-live-reload.js +422 -32
- package/scripts/lib/create-project-watcher.js +3 -1
- package/scripts/lib/create-watch-runtime.js +109 -16
- package/scripts/lib/open-browser.js +102 -0
- package/scripts/lib/parse-cli-args.js +216 -0
- package/scripts/lib/select-project-name.js +64 -0
- package/scripts/lib/stylesheet-reload.js +58 -0
- package/scripts/serve.js +21 -2
- package/scripts/watch.js +13 -2
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const {
|
|
4
|
+
isSameOriginStylesheetHref,
|
|
5
|
+
appendCacheBustParam,
|
|
6
|
+
} = require('./stylesheet-reload');
|
|
7
|
+
|
|
3
8
|
const LIVE_RELOAD_PATH = '/_jskim/live-reload';
|
|
4
9
|
const HEARTBEAT_MS = 20000;
|
|
10
|
+
const CSS_LOAD_TIMEOUT_MS = 8000;
|
|
11
|
+
const OVERLAY_HOST_ID = '__jskim_error_overlay__';
|
|
5
12
|
|
|
6
13
|
/**
|
|
7
14
|
* SSE ベースのライブリロード管理です。
|
|
@@ -15,6 +22,10 @@ function createLiveReload({ projectName, enabled = true }) {
|
|
|
15
22
|
const clients = new Set();
|
|
16
23
|
let heartbeatTimer = null;
|
|
17
24
|
let closed = false;
|
|
25
|
+
/** @type {string|null} */
|
|
26
|
+
let configError = null;
|
|
27
|
+
/** @type {string|null} */
|
|
28
|
+
let buildError = null;
|
|
18
29
|
|
|
19
30
|
function ensureHeartbeat() {
|
|
20
31
|
if (!enabled || heartbeatTimer || closed) {
|
|
@@ -46,6 +57,32 @@ function createLiveReload({ projectName, enabled = true }) {
|
|
|
46
57
|
}
|
|
47
58
|
}
|
|
48
59
|
|
|
60
|
+
function writeEvent(res, eventName, data) {
|
|
61
|
+
const payload = `event: ${eventName}\ndata: ${JSON.stringify(data)}\n\n`;
|
|
62
|
+
res.write(payload);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function broadcast(eventName, data) {
|
|
66
|
+
if (!enabled || closed) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
for (const res of [...clients]) {
|
|
70
|
+
try {
|
|
71
|
+
writeEvent(res, eventName, data);
|
|
72
|
+
} catch {
|
|
73
|
+
removeClient(res);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function getActiveErrorMessage() {
|
|
79
|
+
return configError || buildError;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function hasConfigError() {
|
|
83
|
+
return Boolean(configError);
|
|
84
|
+
}
|
|
85
|
+
|
|
49
86
|
/**
|
|
50
87
|
* 内部 SSE リクエストを処理します。
|
|
51
88
|
* @returns {boolean} 処理したら true
|
|
@@ -80,7 +117,6 @@ function createLiveReload({ projectName, enabled = true }) {
|
|
|
80
117
|
res.setHeader('Content-Type', 'text/event-stream; charset=utf-8');
|
|
81
118
|
res.setHeader('Cache-Control', 'no-store');
|
|
82
119
|
res.setHeader('Connection', 'keep-alive');
|
|
83
|
-
// nginx 等のバッファリング回避(ローカルでも無害)
|
|
84
120
|
res.setHeader('X-Accel-Buffering', 'no');
|
|
85
121
|
|
|
86
122
|
if (method === 'HEAD') {
|
|
@@ -92,6 +128,18 @@ function createLiveReload({ projectName, enabled = true }) {
|
|
|
92
128
|
clients.add(res);
|
|
93
129
|
ensureHeartbeat();
|
|
94
130
|
|
|
131
|
+
const active = getActiveErrorMessage();
|
|
132
|
+
if (active) {
|
|
133
|
+
try {
|
|
134
|
+
writeEvent(res, 'error', {
|
|
135
|
+
project: projectName,
|
|
136
|
+
message: active,
|
|
137
|
+
});
|
|
138
|
+
} catch {
|
|
139
|
+
removeClient(res);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
95
143
|
const onClose = () => {
|
|
96
144
|
removeClient(res);
|
|
97
145
|
};
|
|
@@ -103,30 +151,13 @@ function createLiveReload({ projectName, enabled = true }) {
|
|
|
103
151
|
}
|
|
104
152
|
|
|
105
153
|
function getClientScript() {
|
|
106
|
-
return
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
" source.addEventListener('reload', function () {",
|
|
112
|
-
' window.location.reload();',
|
|
113
|
-
' });',
|
|
114
|
-
' source.onerror = function () {',
|
|
115
|
-
" console.info('[JSKim] ライブリロード接続を再試行しています…');",
|
|
116
|
-
' };',
|
|
117
|
-
' } catch (err) {',
|
|
118
|
-
" console.warn('[JSKim] ライブリロードを開始できませんでした。');",
|
|
119
|
-
' }',
|
|
120
|
-
'})();',
|
|
121
|
-
'</script>',
|
|
122
|
-
].join('');
|
|
154
|
+
return buildClientScript({
|
|
155
|
+
liveReloadPath: LIVE_RELOAD_PATH,
|
|
156
|
+
overlayHostId: OVERLAY_HOST_ID,
|
|
157
|
+
cssLoadTimeoutMs: CSS_LOAD_TIMEOUT_MS,
|
|
158
|
+
});
|
|
123
159
|
}
|
|
124
160
|
|
|
125
|
-
/**
|
|
126
|
-
* HTML 文字列に client script を注入します(メモリ上のみ)。
|
|
127
|
-
* @param {string} html
|
|
128
|
-
* @returns {string}
|
|
129
|
-
*/
|
|
130
161
|
function injectHtml(html) {
|
|
131
162
|
if (!enabled || typeof html !== 'string') {
|
|
132
163
|
return html;
|
|
@@ -146,22 +177,113 @@ function createLiveReload({ projectName, enabled = true }) {
|
|
|
146
177
|
return html + script;
|
|
147
178
|
}
|
|
148
179
|
|
|
149
|
-
function
|
|
180
|
+
function broadcastConfigError(message) {
|
|
181
|
+
if (!enabled || closed) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
configError = normalizeErrorMessage(message);
|
|
185
|
+
broadcast('error', {
|
|
186
|
+
project: projectName,
|
|
187
|
+
message: configError,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function broadcastBuildError(message) {
|
|
192
|
+
if (!enabled || closed) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
buildError = normalizeErrorMessage(message);
|
|
196
|
+
// 未解決の config error がある間は config を優先表示する
|
|
197
|
+
if (!configError) {
|
|
198
|
+
broadcast('error', {
|
|
199
|
+
project: projectName,
|
|
200
|
+
message: buildError,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* 後方互換: source を指定して error を送る。
|
|
207
|
+
* @param {unknown} message
|
|
208
|
+
* @param {'build'|'config'} [source='build']
|
|
209
|
+
*/
|
|
210
|
+
function broadcastError(message, source = 'build') {
|
|
211
|
+
if (source === 'config') {
|
|
212
|
+
broadcastConfigError(message);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
broadcastBuildError(message);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function clearConfigError() {
|
|
219
|
+
configError = null;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function clearBuildError() {
|
|
223
|
+
buildError = null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function clearErrorState() {
|
|
227
|
+
configError = null;
|
|
228
|
+
buildError = null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function broadcastClearError() {
|
|
150
232
|
if (!enabled || closed) {
|
|
151
233
|
return;
|
|
152
234
|
}
|
|
235
|
+
clearErrorState();
|
|
236
|
+
broadcast('clear-error', {
|
|
237
|
+
project: projectName,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
153
240
|
|
|
154
|
-
|
|
241
|
+
function broadcastCssReload() {
|
|
242
|
+
if (!enabled || closed) {
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
// 未解決の config エラー中は source 成功で overlay を消さない
|
|
246
|
+
if (configError) {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
clearBuildError();
|
|
250
|
+
broadcast('clear-error', {
|
|
251
|
+
project: projectName,
|
|
252
|
+
});
|
|
253
|
+
broadcast('css', {
|
|
155
254
|
project: projectName,
|
|
156
|
-
})
|
|
255
|
+
});
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
157
258
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
259
|
+
function broadcastReload() {
|
|
260
|
+
if (!enabled || closed) {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
// reconnect で stale error が復帰しないよう、reload 前に server 状態を消す
|
|
264
|
+
clearErrorState();
|
|
265
|
+
broadcast('reload', {
|
|
266
|
+
project: projectName,
|
|
267
|
+
});
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* source rebuild 成功時専用。config error が残っていれば何もしない。
|
|
273
|
+
* @param {'css'|'reload'} kind
|
|
274
|
+
* @returns {boolean}
|
|
275
|
+
*/
|
|
276
|
+
function notifySourceBuildSuccess(kind) {
|
|
277
|
+
if (!enabled || closed) {
|
|
278
|
+
return false;
|
|
164
279
|
}
|
|
280
|
+
if (configError) {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
if (kind === 'css') {
|
|
284
|
+
return broadcastCssReload();
|
|
285
|
+
}
|
|
286
|
+
return broadcastReload();
|
|
165
287
|
}
|
|
166
288
|
|
|
167
289
|
function close() {
|
|
@@ -169,6 +291,7 @@ function createLiveReload({ projectName, enabled = true }) {
|
|
|
169
291
|
return;
|
|
170
292
|
}
|
|
171
293
|
closed = true;
|
|
294
|
+
clearErrorState();
|
|
172
295
|
|
|
173
296
|
if (heartbeatTimer) {
|
|
174
297
|
clearInterval(heartbeatTimer);
|
|
@@ -187,15 +310,282 @@ function createLiveReload({ projectName, enabled = true }) {
|
|
|
187
310
|
handleRequest,
|
|
188
311
|
injectHtml,
|
|
189
312
|
broadcastReload,
|
|
313
|
+
broadcastError,
|
|
314
|
+
broadcastConfigError,
|
|
315
|
+
broadcastBuildError,
|
|
316
|
+
broadcastClearError,
|
|
317
|
+
broadcastCssReload,
|
|
318
|
+
notifySourceBuildSuccess,
|
|
319
|
+
clearConfigError,
|
|
320
|
+
clearBuildError,
|
|
321
|
+
clearErrorState,
|
|
322
|
+
hasConfigError,
|
|
190
323
|
getClientScript,
|
|
191
324
|
close,
|
|
192
325
|
get clientCount() {
|
|
193
326
|
return clients.size;
|
|
194
327
|
},
|
|
328
|
+
get lastErrorMessage() {
|
|
329
|
+
return getActiveErrorMessage();
|
|
330
|
+
},
|
|
331
|
+
get configErrorMessage() {
|
|
332
|
+
return configError;
|
|
333
|
+
},
|
|
334
|
+
get buildErrorMessage() {
|
|
335
|
+
return buildError;
|
|
336
|
+
},
|
|
195
337
|
};
|
|
196
338
|
}
|
|
197
339
|
|
|
340
|
+
function normalizeErrorMessage(message) {
|
|
341
|
+
if (message == null) {
|
|
342
|
+
return String(message);
|
|
343
|
+
}
|
|
344
|
+
if (typeof message === 'string') {
|
|
345
|
+
return message;
|
|
346
|
+
}
|
|
347
|
+
if (typeof message === 'object' && typeof message.message === 'string') {
|
|
348
|
+
return message.message;
|
|
349
|
+
}
|
|
350
|
+
return String(message);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* browser に注入する runtime script を組み立てます。
|
|
355
|
+
* stylesheet 判定は stylesheet-reload.js の関数本体を埋め込み、実装 drift を防ぎます。
|
|
356
|
+
*
|
|
357
|
+
* @param {object} options
|
|
358
|
+
* @returns {string}
|
|
359
|
+
*/
|
|
360
|
+
function buildClientScript(options) {
|
|
361
|
+
const liveReloadPath = JSON.stringify(options.liveReloadPath);
|
|
362
|
+
const overlayHostId = JSON.stringify(options.overlayHostId);
|
|
363
|
+
const cssLoadTimeoutMs = Number(options.cssLoadTimeoutMs) || CSS_LOAD_TIMEOUT_MS;
|
|
364
|
+
|
|
365
|
+
return [
|
|
366
|
+
'<script>',
|
|
367
|
+
'(function () {',
|
|
368
|
+
' var LIVE_RELOAD_PATH = ' + liveReloadPath + ';',
|
|
369
|
+
' var OVERLAY_HOST_ID = ' + overlayHostId + ';',
|
|
370
|
+
' var CSS_LOAD_TIMEOUT_MS = ' + cssLoadTimeoutMs + ';',
|
|
371
|
+
' var overlayHost = null;',
|
|
372
|
+
' var fallbackReloadScheduled = false;',
|
|
373
|
+
' var isSameOriginStylesheetHref = ' +
|
|
374
|
+
isSameOriginStylesheetHref.toString() +
|
|
375
|
+
';',
|
|
376
|
+
' var appendCacheBustParam = ' + appendCacheBustParam.toString() + ';',
|
|
377
|
+
'',
|
|
378
|
+
' function scheduleFullReload() {',
|
|
379
|
+
' if (fallbackReloadScheduled) {',
|
|
380
|
+
' return;',
|
|
381
|
+
' }',
|
|
382
|
+
' fallbackReloadScheduled = true;',
|
|
383
|
+
' window.location.reload();',
|
|
384
|
+
' }',
|
|
385
|
+
'',
|
|
386
|
+
' function ensureOverlayHost() {',
|
|
387
|
+
' var existing = document.getElementById(OVERLAY_HOST_ID);',
|
|
388
|
+
' if (existing) {',
|
|
389
|
+
' overlayHost = existing;',
|
|
390
|
+
' return existing;',
|
|
391
|
+
' }',
|
|
392
|
+
' var host = document.createElement("div");',
|
|
393
|
+
' host.id = OVERLAY_HOST_ID;',
|
|
394
|
+
' host.setAttribute("data-jskim-overlay", "true");',
|
|
395
|
+
' host.style.all = "initial";',
|
|
396
|
+
' host.style.position = "fixed";',
|
|
397
|
+
' host.style.inset = "0";',
|
|
398
|
+
' host.style.zIndex = "2147483646";',
|
|
399
|
+
' host.style.pointerEvents = "none";',
|
|
400
|
+
' document.documentElement.appendChild(host);',
|
|
401
|
+
' overlayHost = host;',
|
|
402
|
+
' return host;',
|
|
403
|
+
' }',
|
|
404
|
+
'',
|
|
405
|
+
' function showErrorOverlay(message) {',
|
|
406
|
+
' var host = ensureOverlayHost();',
|
|
407
|
+
' var shadow = host.shadowRoot || host.attachShadow({ mode: "open" });',
|
|
408
|
+
' while (shadow.firstChild) {',
|
|
409
|
+
' shadow.removeChild(shadow.firstChild);',
|
|
410
|
+
' }',
|
|
411
|
+
' var style = document.createElement("style");',
|
|
412
|
+
' style.textContent = [',
|
|
413
|
+
' ":host { all: initial; }",',
|
|
414
|
+
' ".wrap { pointer-events: auto; position: fixed; inset: 0; display: flex; align-items: flex-start; justify-content: center; padding: 24px 16px; box-sizing: border-box; background: rgba(20, 20, 20, 0.55); font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }",',
|
|
415
|
+
' ".panel { width: min(920px, 100%); max-height: min(80vh, 720px); display: flex; flex-direction: column; background: #111; color: #f5f5f5; border: 1px solid #444; box-shadow: 0 12px 40px rgba(0,0,0,0.45); }",',
|
|
416
|
+
' ".head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 14px; border-bottom: 1px solid #333; background: #1a1a1a; }",',
|
|
417
|
+
' ".title { font-size: 14px; font-weight: 700; letter-spacing: 0.02em; }",',
|
|
418
|
+
' ".close { pointer-events: auto; border: 1px solid #666; background: #222; color: #fff; padding: 4px 10px; cursor: pointer; font: inherit; }",',
|
|
419
|
+
' ".body { overflow: auto; padding: 14px; margin: 0; white-space: pre-wrap; word-break: break-word; font-size: 12px; line-height: 1.5; }"',
|
|
420
|
+
' ].join("");',
|
|
421
|
+
' var wrap = document.createElement("div");',
|
|
422
|
+
' wrap.className = "wrap";',
|
|
423
|
+
' var panel = document.createElement("div");',
|
|
424
|
+
' panel.className = "panel";',
|
|
425
|
+
' var head = document.createElement("div");',
|
|
426
|
+
' head.className = "head";',
|
|
427
|
+
' var title = document.createElement("div");',
|
|
428
|
+
' title.className = "title";',
|
|
429
|
+
' title.textContent = "JSKim dev error";',
|
|
430
|
+
' var closeBtn = document.createElement("button");',
|
|
431
|
+
' closeBtn.type = "button";',
|
|
432
|
+
' closeBtn.className = "close";',
|
|
433
|
+
' closeBtn.textContent = "Close";',
|
|
434
|
+
' closeBtn.addEventListener("click", function () {',
|
|
435
|
+
' hideErrorOverlay();',
|
|
436
|
+
' });',
|
|
437
|
+
' var body = document.createElement("pre");',
|
|
438
|
+
' body.className = "body";',
|
|
439
|
+
' body.textContent = String(message == null ? "" : message);',
|
|
440
|
+
' head.appendChild(title);',
|
|
441
|
+
' head.appendChild(closeBtn);',
|
|
442
|
+
' panel.appendChild(head);',
|
|
443
|
+
' panel.appendChild(body);',
|
|
444
|
+
' wrap.appendChild(panel);',
|
|
445
|
+
' shadow.appendChild(style);',
|
|
446
|
+
' shadow.appendChild(wrap);',
|
|
447
|
+
' host.style.display = "block";',
|
|
448
|
+
' host.style.pointerEvents = "none";',
|
|
449
|
+
' }',
|
|
450
|
+
'',
|
|
451
|
+
' function hideErrorOverlay() {',
|
|
452
|
+
' var host = overlayHost || document.getElementById(OVERLAY_HOST_ID);',
|
|
453
|
+
' if (!host) {',
|
|
454
|
+
' return;',
|
|
455
|
+
' }',
|
|
456
|
+
' if (host.shadowRoot) {',
|
|
457
|
+
' while (host.shadowRoot.firstChild) {',
|
|
458
|
+
' host.shadowRoot.removeChild(host.shadowRoot.firstChild);',
|
|
459
|
+
' }',
|
|
460
|
+
' }',
|
|
461
|
+
' host.style.display = "none";',
|
|
462
|
+
' }',
|
|
463
|
+
'',
|
|
464
|
+
' function isSameOriginHref(href) {',
|
|
465
|
+
' return isSameOriginStylesheetHref(href, window.location.href);',
|
|
466
|
+
' }',
|
|
467
|
+
'',
|
|
468
|
+
' function withCacheBust(href, token) {',
|
|
469
|
+
' return appendCacheBustParam(href, window.location.href, token);',
|
|
470
|
+
' }',
|
|
471
|
+
'',
|
|
472
|
+
' function reloadStylesheets() {',
|
|
473
|
+
' var links = Array.prototype.slice.call(',
|
|
474
|
+
' document.querySelectorAll(\'link[rel~="stylesheet"]\')',
|
|
475
|
+
' );',
|
|
476
|
+
' var targets = [];',
|
|
477
|
+
' for (var i = 0; i < links.length; i += 1) {',
|
|
478
|
+
' var link = links[i];',
|
|
479
|
+
' var href = link.getAttribute("href") || link.href;',
|
|
480
|
+
' if (isSameOriginHref(href)) {',
|
|
481
|
+
' targets.push(link);',
|
|
482
|
+
' }',
|
|
483
|
+
' }',
|
|
484
|
+
' if (targets.length === 0) {',
|
|
485
|
+
' scheduleFullReload();',
|
|
486
|
+
' return;',
|
|
487
|
+
' }',
|
|
488
|
+
'',
|
|
489
|
+
' var token = String(Date.now());',
|
|
490
|
+
' var remaining = targets.length;',
|
|
491
|
+
' var failed = false;',
|
|
492
|
+
'',
|
|
493
|
+
' function doneOne(ok) {',
|
|
494
|
+
' if (failed) {',
|
|
495
|
+
' return;',
|
|
496
|
+
' }',
|
|
497
|
+
' if (!ok) {',
|
|
498
|
+
' failed = true;',
|
|
499
|
+
' scheduleFullReload();',
|
|
500
|
+
' return;',
|
|
501
|
+
' }',
|
|
502
|
+
' remaining -= 1;',
|
|
503
|
+
' if (remaining <= 0) {',
|
|
504
|
+
' fallbackReloadScheduled = false;',
|
|
505
|
+
' }',
|
|
506
|
+
' }',
|
|
507
|
+
'',
|
|
508
|
+
' for (var j = 0; j < targets.length; j += 1) {',
|
|
509
|
+
' (function (oldLink) {',
|
|
510
|
+
' var nextHref = withCacheBust(oldLink.getAttribute("href") || oldLink.href, token);',
|
|
511
|
+
' if (!nextHref) {',
|
|
512
|
+
' doneOne(false);',
|
|
513
|
+
' return;',
|
|
514
|
+
' }',
|
|
515
|
+
' var clone = oldLink.cloneNode(true);',
|
|
516
|
+
' clone.setAttribute("href", nextHref);',
|
|
517
|
+
' var settled = false;',
|
|
518
|
+
' var timer = setTimeout(function () {',
|
|
519
|
+
' if (settled) {',
|
|
520
|
+
' return;',
|
|
521
|
+
' }',
|
|
522
|
+
' settled = true;',
|
|
523
|
+
' doneOne(false);',
|
|
524
|
+
' }, CSS_LOAD_TIMEOUT_MS);',
|
|
525
|
+
' function finish(ok) {',
|
|
526
|
+
' if (settled) {',
|
|
527
|
+
' return;',
|
|
528
|
+
' }',
|
|
529
|
+
' settled = true;',
|
|
530
|
+
' clearTimeout(timer);',
|
|
531
|
+
' if (ok) {',
|
|
532
|
+
' if (oldLink.parentNode) {',
|
|
533
|
+
' oldLink.parentNode.removeChild(oldLink);',
|
|
534
|
+
' }',
|
|
535
|
+
' }',
|
|
536
|
+
' doneOne(ok);',
|
|
537
|
+
' }',
|
|
538
|
+
' clone.addEventListener("load", function () { finish(true); });',
|
|
539
|
+
' clone.addEventListener("error", function () { finish(false); });',
|
|
540
|
+
' if (oldLink.parentNode) {',
|
|
541
|
+
' oldLink.parentNode.insertBefore(clone, oldLink.nextSibling);',
|
|
542
|
+
' } else {',
|
|
543
|
+
' finish(false);',
|
|
544
|
+
' }',
|
|
545
|
+
' })(targets[j]);',
|
|
546
|
+
' }',
|
|
547
|
+
' }',
|
|
548
|
+
'',
|
|
549
|
+
' try {',
|
|
550
|
+
' var source = new EventSource(LIVE_RELOAD_PATH);',
|
|
551
|
+
' source.addEventListener("reload", function () {',
|
|
552
|
+
' window.location.reload();',
|
|
553
|
+
' });',
|
|
554
|
+
' source.addEventListener("error", function (event) {',
|
|
555
|
+
' if (event && typeof event.data === "string" && event.data) {',
|
|
556
|
+
' try {',
|
|
557
|
+
' var payload = JSON.parse(event.data);',
|
|
558
|
+
' showErrorOverlay(payload.message || "Build error");',
|
|
559
|
+
' } catch (err) {',
|
|
560
|
+
' showErrorOverlay("Build error");',
|
|
561
|
+
' }',
|
|
562
|
+
' return;',
|
|
563
|
+
' }',
|
|
564
|
+
' console.info("[JSKim] ライブリロード接続を再試行しています…");',
|
|
565
|
+
' });',
|
|
566
|
+
' source.addEventListener("clear-error", function () {',
|
|
567
|
+
' hideErrorOverlay();',
|
|
568
|
+
' });',
|
|
569
|
+
' source.addEventListener("css", function () {',
|
|
570
|
+
' try {',
|
|
571
|
+
' reloadStylesheets();',
|
|
572
|
+
' } catch (err) {',
|
|
573
|
+
' scheduleFullReload();',
|
|
574
|
+
' }',
|
|
575
|
+
' });',
|
|
576
|
+
' } catch (err) {',
|
|
577
|
+
' console.warn("[JSKim] ライブリロードを開始できませんでした。");',
|
|
578
|
+
' }',
|
|
579
|
+
'})();',
|
|
580
|
+
'</script>',
|
|
581
|
+
].join('\n');
|
|
582
|
+
}
|
|
583
|
+
|
|
198
584
|
module.exports = {
|
|
199
585
|
createLiveReload,
|
|
200
586
|
LIVE_RELOAD_PATH,
|
|
587
|
+
OVERLAY_HOST_ID,
|
|
588
|
+
CSS_LOAD_TIMEOUT_MS,
|
|
589
|
+
buildClientScript,
|
|
590
|
+
normalizeErrorMessage,
|
|
201
591
|
};
|
|
@@ -114,9 +114,11 @@ function createProjectWatcher(project, options = {}) {
|
|
|
114
114
|
return;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
const absolutePath = path.resolve(filePath);
|
|
117
118
|
pendingEvents.push({
|
|
118
119
|
event: eventName,
|
|
119
|
-
file: toDisplayPath(
|
|
120
|
+
file: toDisplayPath(absolutePath, workspaceRoot),
|
|
121
|
+
absolutePath,
|
|
120
122
|
});
|
|
121
123
|
|
|
122
124
|
if (debounceTimer) {
|