@stencil/dev-server 0.0.19-0 → 5.0.0-next.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/dist/index.mjs ADDED
@@ -0,0 +1,1277 @@
1
+ import * as path from "node:path";
2
+ import * as http from "node:http";
3
+ import * as https from "node:https";
4
+ import * as net from "node:net";
5
+ import { WebSocketServer } from "ws";
6
+ import * as fs from "node:fs";
7
+ import { inspect } from "node:util";
8
+ import * as zlib from "node:zlib";
9
+ import { fork } from "node:child_process";
10
+
11
+ //#region src/server/utils.ts
12
+ const DEV_SERVER_URL = "/~dev-server";
13
+ const DEV_MODULE_URL = "/~dev-module";
14
+ const DEV_SERVER_INIT_URL = `${DEV_SERVER_URL}-init`;
15
+ const OPEN_IN_EDITOR_URL = `${DEV_SERVER_URL}-open-in-editor`;
16
+ const VERSION = "5.0.0";
17
+ const DEFAULT_HEADERS = {
18
+ "cache-control": "no-cache, no-store, must-revalidate, max-age=0",
19
+ expires: "0",
20
+ date: "Wed, 1 Jan 2000 00:00:00 GMT",
21
+ server: `Stencil Dev Server ${VERSION}`,
22
+ "access-control-allow-origin": "*",
23
+ "access-control-expose-headers": "*"
24
+ };
25
+ function responseHeaders(headers, httpCache = false) {
26
+ const result = {
27
+ ...DEFAULT_HEADERS,
28
+ ...headers
29
+ };
30
+ if (httpCache) {
31
+ result["cache-control"] = "max-age=3600";
32
+ delete result["date"];
33
+ delete result["expires"];
34
+ }
35
+ return result;
36
+ }
37
+ function getBrowserUrl(protocol, address, port, basePath, pathname) {
38
+ address = address === "0.0.0.0" ? "localhost" : address;
39
+ const portSuffix = !port || port === 80 || port === 443 ? "" : ":" + port;
40
+ let path = basePath;
41
+ if (pathname.startsWith("/")) pathname = pathname.substring(1);
42
+ path += pathname;
43
+ protocol = protocol.replace(/:/g, "");
44
+ return `${protocol}://${address}${portSuffix}${path}`;
45
+ }
46
+ function getDevServerClientUrl(devServerConfig, host, protocol) {
47
+ let address = devServerConfig.address;
48
+ let port = devServerConfig.port;
49
+ if (host) {
50
+ address = host;
51
+ port = null;
52
+ }
53
+ return getBrowserUrl(protocol ?? devServerConfig.protocol, address, port, devServerConfig.basePath, DEV_SERVER_URL);
54
+ }
55
+ const CONTENT_TYPES = {
56
+ html: "text/html",
57
+ htm: "text/html",
58
+ css: "text/css",
59
+ js: "text/javascript",
60
+ mjs: "text/javascript",
61
+ json: "application/json",
62
+ xml: "application/xml",
63
+ svg: "image/svg+xml",
64
+ png: "image/png",
65
+ jpg: "image/jpeg",
66
+ jpeg: "image/jpeg",
67
+ gif: "image/gif",
68
+ webp: "image/webp",
69
+ ico: "image/x-icon",
70
+ woff: "font/woff",
71
+ woff2: "font/woff2",
72
+ ttf: "font/ttf",
73
+ otf: "font/otf",
74
+ eot: "application/vnd.ms-fontobject",
75
+ mp3: "audio/mpeg",
76
+ mp4: "video/mp4",
77
+ webm: "video/webm",
78
+ ogg: "audio/ogg",
79
+ wav: "audio/wav",
80
+ pdf: "application/pdf",
81
+ zip: "application/zip",
82
+ wasm: "application/wasm",
83
+ map: "application/json",
84
+ txt: "text/plain",
85
+ md: "text/markdown",
86
+ ts: "text/typescript",
87
+ tsx: "text/typescript-jsx"
88
+ };
89
+ function getContentType(filePath) {
90
+ const last = filePath.replace(/^.*[/\\]/, "").toLowerCase();
91
+ const ext = last.replace(/^.*\./, "").toLowerCase();
92
+ const hasPath = last.length < filePath.length;
93
+ return (ext.length < last.length - 1 || !hasPath) && CONTENT_TYPES[ext] || "application/octet-stream";
94
+ }
95
+ function isHtmlFile(filePath) {
96
+ const lower = filePath.toLowerCase().trim();
97
+ return lower.endsWith(".html") || lower.endsWith(".htm");
98
+ }
99
+ function isCssFile(filePath) {
100
+ return filePath.toLowerCase().trim().endsWith(".css");
101
+ }
102
+ const TXT_EXT = [
103
+ "css",
104
+ "html",
105
+ "htm",
106
+ "js",
107
+ "json",
108
+ "svg",
109
+ "xml",
110
+ "mjs",
111
+ "ts",
112
+ "tsx",
113
+ "md",
114
+ "txt"
115
+ ];
116
+ function isSimpleText(filePath) {
117
+ const ext = filePath.toLowerCase().trim().split(".").pop();
118
+ return ext ? TXT_EXT.includes(ext) : false;
119
+ }
120
+ function isExtensionLessPath(pathname) {
121
+ const parts = pathname.split("/");
122
+ return !parts[parts.length - 1].includes(".");
123
+ }
124
+ function isSsrStaticDataPath(pathname) {
125
+ const parts = pathname.split("/");
126
+ return parts[parts.length - 1].split("?")[0] === "page.state.json";
127
+ }
128
+ function getSsrStaticDataPath(req) {
129
+ const parts = req.url.href.split("/");
130
+ const fileNameParts = parts[parts.length - 1].split("?");
131
+ parts.pop();
132
+ let ssrPath = new URL(parts.join("/")).href;
133
+ if (!ssrPath.endsWith("/") && req.headers) {
134
+ if (new Headers(req.headers).get("referer")?.endsWith("/")) ssrPath += "/";
135
+ }
136
+ return {
137
+ ssrPath,
138
+ fileName: fileNameParts[0],
139
+ hasQueryString: typeof fileNameParts[1] === "string" && fileNameParts[1].length > 0
140
+ };
141
+ }
142
+ function isDevClient(pathname) {
143
+ return pathname.startsWith(DEV_SERVER_URL);
144
+ }
145
+ function isDevModule(pathname) {
146
+ return pathname.includes(DEV_MODULE_URL);
147
+ }
148
+ function isOpenInEditor(pathname) {
149
+ return pathname === OPEN_IN_EDITOR_URL;
150
+ }
151
+ function isInitialDevServerLoad(pathname) {
152
+ return pathname === DEV_SERVER_INIT_URL;
153
+ }
154
+ function isDevServerClient(pathname) {
155
+ return pathname === DEV_SERVER_URL;
156
+ }
157
+ function shouldCompress(devServerConfig, req) {
158
+ if (!devServerConfig.gzip) return false;
159
+ if (req.method !== "GET") return false;
160
+ const acceptEncoding = req.headers?.["accept-encoding"];
161
+ if (typeof acceptEncoding !== "string") return false;
162
+ return acceptEncoding.includes("gzip");
163
+ }
164
+ /**
165
+ * Normalize a file path to use forward slashes and remove redundant slashes.
166
+ */
167
+ function normalizePath(path) {
168
+ let normalized = path.replace(/\\/g, "/");
169
+ normalized = normalized.replace(/\/+/g, "/");
170
+ if (path.startsWith("\\\\")) normalized = "/" + normalized;
171
+ return normalized;
172
+ }
173
+
174
+ //#endregion
175
+ //#region src/server/context.ts
176
+ /**
177
+ * Server context factory.
178
+ * Creates the shared context object passed to request handlers.
179
+ */
180
+ function createServerContext(sys, sendMsg, devServerConfig, buildResultsResolves, compilerRequestResolves) {
181
+ const logRequest = (req, status) => {
182
+ if (devServerConfig) sendMsg({ requestLog: {
183
+ method: req.method || "?",
184
+ url: req.pathname || "?",
185
+ status
186
+ } });
187
+ };
188
+ const serve500 = (req, res, error, xSource) => {
189
+ try {
190
+ if (res.headersSent) {
191
+ res.end();
192
+ return;
193
+ }
194
+ res.writeHead(500, responseHeaders({
195
+ "content-type": "text/plain; charset=utf-8",
196
+ "x-source": xSource
197
+ }));
198
+ res.write(inspect(error));
199
+ res.end();
200
+ logRequest(req, 500);
201
+ } catch (e) {
202
+ sendMsg({ error: { message: "serve500: " + e } });
203
+ }
204
+ };
205
+ const serve404 = (req, res, xSource, content = null) => {
206
+ try {
207
+ if (res.headersSent) {
208
+ res.end();
209
+ return;
210
+ }
211
+ if (req.pathname === "/favicon.ico") {
212
+ const defaultFavicon = path.join(devServerConfig.devServerDir, "static", "favicon.ico");
213
+ const rs = fs.createReadStream(defaultFavicon);
214
+ rs.on("error", () => {
215
+ if (!res.headersSent) res.writeHead(404);
216
+ res.end();
217
+ });
218
+ res.writeHead(200, responseHeaders({
219
+ "content-type": "image/x-icon",
220
+ "x-source": `favicon: ${xSource}`
221
+ }));
222
+ rs.pipe(res);
223
+ return;
224
+ }
225
+ if (content == null) content = [
226
+ "404 File Not Found",
227
+ "Url: " + req.pathname,
228
+ "File: " + req.filePath
229
+ ].join("\n");
230
+ res.writeHead(404, responseHeaders({
231
+ "content-type": "text/plain; charset=utf-8",
232
+ "x-source": xSource
233
+ }));
234
+ res.write(content);
235
+ res.end();
236
+ logRequest(req, 404);
237
+ } catch (e) {
238
+ serve500(req, res, e, xSource);
239
+ }
240
+ };
241
+ const serve302 = (req, res, pathname = null) => {
242
+ logRequest(req, 302);
243
+ res.writeHead(302, { location: pathname || devServerConfig.basePath || "/" });
244
+ res.end();
245
+ };
246
+ const getBuildResults = () => new Promise((resolve, reject) => {
247
+ if (serverCtx.isServerListening) {
248
+ buildResultsResolves.push({
249
+ resolve,
250
+ reject
251
+ });
252
+ sendMsg({ requestBuildResults: true });
253
+ } else reject("dev server closed");
254
+ });
255
+ const getCompilerRequest = (compilerRequestPath) => new Promise((resolve, reject) => {
256
+ if (serverCtx.isServerListening) {
257
+ compilerRequestResolves.push({
258
+ path: compilerRequestPath,
259
+ resolve,
260
+ reject
261
+ });
262
+ sendMsg({ compilerRequestPath });
263
+ } else reject("dev server closed");
264
+ });
265
+ const serverCtx = {
266
+ connectorHtml: null,
267
+ dirTemplate: null,
268
+ getBuildResults,
269
+ getCompilerRequest,
270
+ isServerListening: false,
271
+ logRequest,
272
+ prerenderConfig: null,
273
+ serve302,
274
+ serve404,
275
+ serve500,
276
+ sys
277
+ };
278
+ return serverCtx;
279
+ }
280
+
281
+ //#endregion
282
+ //#region src/server/editor.ts
283
+ async function openInBrowser(opts) {
284
+ const { default: open } = await import("open");
285
+ await open(opts.url);
286
+ }
287
+ let launchEditorLoaded = false;
288
+ let launchEditor = null;
289
+ async function loadLaunchEditor() {
290
+ if (launchEditorLoaded) return;
291
+ try {
292
+ const mod = await import("launch-editor");
293
+ launchEditor = mod.default || mod;
294
+ } catch (e) {
295
+ console.warn("launch-editor package is not available. Open in editor functionality will be disabled.");
296
+ launchEditor = null;
297
+ }
298
+ launchEditorLoaded = true;
299
+ }
300
+ async function serveOpenInEditor(serverCtx, req, res) {
301
+ let status = 200;
302
+ const data = {};
303
+ try {
304
+ await parseEditorData(serverCtx.sys, req, data);
305
+ await openDataInEditor(data);
306
+ } catch (e) {
307
+ data.error = String(e);
308
+ status = 500;
309
+ }
310
+ serverCtx.logRequest(req, status);
311
+ res.writeHead(status, responseHeaders({ "content-type": "application/json; charset=utf-8" }));
312
+ res.write(JSON.stringify(data, null, 2));
313
+ res.end();
314
+ }
315
+ async function parseEditorData(sys, req, data) {
316
+ const qs = req.searchParams;
317
+ if (!qs.has("file")) {
318
+ data.error = "missing file";
319
+ return;
320
+ }
321
+ data.file = qs.get("file");
322
+ if (qs.has("line") && !isNaN(Number(qs.get("line")))) data.line = parseInt(qs.get("line"), 10);
323
+ if (typeof data.line !== "number" || data.line < 1) data.line = 1;
324
+ if (qs.has("column") && !isNaN(Number(qs.get("column")))) data.column = parseInt(qs.get("column"), 10);
325
+ if (typeof data.column !== "number" || data.column < 1) data.column = 1;
326
+ if (qs.has("editor")) data.editor = qs.get("editor");
327
+ data.exists = (await sys.stat(data.file)).isFile;
328
+ }
329
+ async function openDataInEditor(data) {
330
+ if (!data.exists || data.error) return;
331
+ await loadLaunchEditor();
332
+ if (!launchEditor) {
333
+ data.error = "launch-editor not available";
334
+ return;
335
+ }
336
+ try {
337
+ const fileSpec = `${data.file}:${data.line}:${data.column}`;
338
+ await new Promise((resolve, reject) => {
339
+ let errorCalled = false;
340
+ launchEditor(fileSpec, data.editor || process.env.EDITOR, (_fileName, errorMessage) => {
341
+ errorCalled = true;
342
+ const errMsg = errorMessage || "Unknown error";
343
+ console.error("Editor launch failed.");
344
+ console.error("The \"code\" executable was not found in your PATH.");
345
+ console.error("This usually means your editor's command-line tool isn't installed.");
346
+ console.error("Try running:");
347
+ console.error(" code --version");
348
+ console.error("If that fails, install your editor's CLI command and ensure it's in your PATH.\n");
349
+ data.error = errMsg;
350
+ reject(new Error(errMsg));
351
+ });
352
+ setTimeout(() => {
353
+ if (!errorCalled) {
354
+ data.open = fileSpec;
355
+ resolve();
356
+ }
357
+ }, 100);
358
+ });
359
+ } catch (e) {
360
+ if (!data.error) data.error = String(e);
361
+ }
362
+ }
363
+ function getEditors() {
364
+ return Promise.resolve([
365
+ {
366
+ id: "code",
367
+ name: "Visual Studio Code"
368
+ },
369
+ {
370
+ id: "cursor",
371
+ name: "Cursor"
372
+ },
373
+ {
374
+ id: "code-insiders",
375
+ name: "VS Code Insiders"
376
+ },
377
+ {
378
+ id: "webstorm",
379
+ name: "WebStorm"
380
+ },
381
+ {
382
+ id: "idea",
383
+ name: "IntelliJ IDEA"
384
+ },
385
+ {
386
+ id: "sublime",
387
+ name: "Sublime Text"
388
+ },
389
+ {
390
+ id: "atom",
391
+ name: "Atom"
392
+ },
393
+ {
394
+ id: "vim",
395
+ name: "Vim"
396
+ },
397
+ {
398
+ id: "emacs",
399
+ name: "Emacs"
400
+ }
401
+ ]);
402
+ }
403
+
404
+ //#endregion
405
+ //#region src/server/ssr.ts
406
+ /**
407
+ * SSR (Server-Side Rendering) request handling.
408
+ * Migrated from ssr-request.ts.
409
+ */
410
+ async function ssrPageRequest(devServerConfig, serverCtx, req, res) {
411
+ try {
412
+ let status = 500;
413
+ let content = "";
414
+ const { hydrateApp, srcIndexHtml, diagnostics } = await setupHydrateApp(devServerConfig, serverCtx);
415
+ if (!diagnostics.some((diagnostic) => diagnostic.level === "error")) try {
416
+ const opts = getSsrHydrateOptions(devServerConfig, serverCtx, req.url);
417
+ const ssrResults = await hydrateApp.renderToString(srcIndexHtml, opts);
418
+ diagnostics.push(...ssrResults.diagnostics);
419
+ status = ssrResults.httpStatus ?? 500;
420
+ content = ssrResults.html ?? "";
421
+ } catch (e) {
422
+ catchError(diagnostics, e);
423
+ }
424
+ if (diagnostics.some((diagnostic) => diagnostic.level === "error")) {
425
+ content = getSsrErrorContent(diagnostics);
426
+ status = 500;
427
+ }
428
+ if (devServerConfig.websocket) content = appendDevServerClientScript(devServerConfig, req, content);
429
+ serverCtx.logRequest(req, status);
430
+ res.writeHead(status, responseHeaders({
431
+ "content-type": "text/html; charset=utf-8",
432
+ "content-length": Buffer.byteLength(content, "utf8")
433
+ }));
434
+ res.write(content);
435
+ res.end();
436
+ } catch (e) {
437
+ serverCtx.serve500(req, res, e, "ssrPageRequest");
438
+ }
439
+ }
440
+ async function ssrStaticDataRequest(devServerConfig, serverCtx, req, res) {
441
+ try {
442
+ const data = {};
443
+ let httpCache = false;
444
+ const { hydrateApp, srcIndexHtml, diagnostics } = await setupHydrateApp(devServerConfig, serverCtx);
445
+ if (!diagnostics.some((diagnostic) => diagnostic.level === "error")) try {
446
+ const { ssrPath, hasQueryString } = getSsrStaticDataPath(req);
447
+ const opts = getSsrHydrateOptions(devServerConfig, serverCtx, new URL(ssrPath, req.url));
448
+ const ssrResults = await hydrateApp.renderToString(srcIndexHtml, opts);
449
+ diagnostics.push(...ssrResults.diagnostics);
450
+ ssrResults.staticData.forEach((s) => {
451
+ if (s.type === "application/json") data[s.id] = JSON.parse(s.content);
452
+ else data[s.id] = s.content;
453
+ });
454
+ data.components = ssrResults.components.map((c) => c.tag).sort();
455
+ httpCache = hasQueryString;
456
+ } catch (e) {
457
+ catchError(diagnostics, e);
458
+ }
459
+ if (diagnostics.length > 0) data.diagnostics = diagnostics;
460
+ const status = diagnostics.some((diagnostic) => diagnostic.level === "error") ? 500 : 200;
461
+ const content = JSON.stringify(data);
462
+ serverCtx.logRequest(req, status);
463
+ res.writeHead(status, responseHeaders({
464
+ "content-type": "application/json; charset=utf-8",
465
+ "content-length": Buffer.byteLength(content, "utf8")
466
+ }, httpCache && status === 200));
467
+ res.write(content);
468
+ res.end();
469
+ } catch (e) {
470
+ serverCtx.serve500(req, res, e, "ssrStaticDataRequest");
471
+ }
472
+ }
473
+ async function setupHydrateApp(devServerConfig, serverCtx) {
474
+ let srcIndexHtml = null;
475
+ let hydrateApp = null;
476
+ const buildResults = await serverCtx.getBuildResults();
477
+ const diagnostics = [];
478
+ if (serverCtx.prerenderConfig == null && isString(devServerConfig.prerenderConfig)) try {
479
+ const prerenderConfigResults = (await import("@stencil/core/compiler")).nodeRequire(devServerConfig.prerenderConfig);
480
+ diagnostics.push(...prerenderConfigResults.diagnostics);
481
+ if (prerenderConfigResults.module?.config) serverCtx.prerenderConfig = prerenderConfigResults.module.config;
482
+ } catch (e) {
483
+ catchError(diagnostics, e);
484
+ }
485
+ if (!isString(buildResults.hydrateAppFilePath)) diagnostics.push({
486
+ messageText: "Missing hydrateAppFilePath",
487
+ level: "error",
488
+ type: "ssr",
489
+ lines: []
490
+ });
491
+ else if (!isString(devServerConfig.srcIndexHtml)) diagnostics.push({
492
+ messageText: "Missing srcIndexHtml",
493
+ level: "error",
494
+ type: "ssr",
495
+ lines: []
496
+ });
497
+ else {
498
+ srcIndexHtml = await serverCtx.sys.readFile(devServerConfig.srcIndexHtml, "utf8");
499
+ if (!isString(srcIndexHtml)) diagnostics.push({
500
+ level: "error",
501
+ lines: [],
502
+ messageText: `Unable to load src index html: ${devServerConfig.srcIndexHtml}`,
503
+ type: "ssr"
504
+ });
505
+ else {
506
+ const hydrateAppFilePath = path.resolve(buildResults.hydrateAppFilePath);
507
+ try {
508
+ const hydrateModule = await import(`file://${hydrateAppFilePath}${`?t=${Date.now()}`}`);
509
+ hydrateApp = hydrateModule.default || hydrateModule;
510
+ } catch (e) {
511
+ catchError(diagnostics, e);
512
+ }
513
+ }
514
+ }
515
+ return {
516
+ hydrateApp,
517
+ srcIndexHtml,
518
+ diagnostics
519
+ };
520
+ }
521
+ function getSsrHydrateOptions(devServerConfig, serverCtx, url) {
522
+ const opts = {
523
+ url: url.href,
524
+ addModulePreloads: false,
525
+ approximateLineWidth: 120,
526
+ inlineExternalStyleSheets: false,
527
+ minifyScriptElements: false,
528
+ minifyStyleElements: false,
529
+ removeAttributeQuotes: false,
530
+ removeBooleanAttributeQuotes: false,
531
+ removeEmptyAttributes: false,
532
+ removeHtmlComments: false,
533
+ prettyHtml: true
534
+ };
535
+ const prerenderConfig = serverCtx?.prerenderConfig;
536
+ if (isFunction(prerenderConfig?.hydrateOptions)) {
537
+ const userOpts = prerenderConfig.hydrateOptions(url);
538
+ if (userOpts) Object.assign(opts, userOpts);
539
+ }
540
+ if (isFunction(serverCtx.sys.applyPrerenderGlobalPatch)) {
541
+ const orgBeforeHydrate = opts.beforeHydrate;
542
+ const applyPatch = serverCtx.sys.applyPrerenderGlobalPatch;
543
+ opts.beforeHydrate = (document) => {
544
+ const devServerHostUrl = new URL(devServerConfig.browserUrl).origin;
545
+ applyPatch({
546
+ devServerHostUrl,
547
+ window: document.defaultView
548
+ });
549
+ if (typeof orgBeforeHydrate === "function") return orgBeforeHydrate(document);
550
+ };
551
+ }
552
+ return opts;
553
+ }
554
+ function getSsrErrorContent(diagnostics) {
555
+ return `<!doctype html>
556
+ <html>
557
+ <head>
558
+ <title>SSR Error</title>
559
+ <style>
560
+ body {
561
+ font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace !important;
562
+ }
563
+ </style>
564
+ </head>
565
+ <body>
566
+ <h1>SSR Dev Error</h1>
567
+ ${diagnostics.map((diagnostic) => `
568
+ <p>
569
+ ${diagnostic.messageText}
570
+ </p>
571
+ `).join("")}
572
+ </body>
573
+ </html>`;
574
+ }
575
+ function catchError(diagnostics, err) {
576
+ const diagnostic = {
577
+ level: "error",
578
+ type: "runtime",
579
+ messageText: "",
580
+ lines: []
581
+ };
582
+ if (err instanceof Error) {
583
+ diagnostic.messageText = err.message;
584
+ if (err.stack) diagnostic.messageText += "\n" + err.stack;
585
+ } else diagnostic.messageText = String(err);
586
+ diagnostics.push(diagnostic);
587
+ }
588
+ function isString(val) {
589
+ return typeof val === "string";
590
+ }
591
+ function isFunction(val) {
592
+ return typeof val === "function";
593
+ }
594
+
595
+ //#endregion
596
+ //#region src/server/handlers.ts
597
+ /**
598
+ * Request handlers.
599
+ * Consolidated from request-handler.ts, serve-file.ts, serve-dev-client.ts,
600
+ * serve-dev-node-module.ts, and serve-directory-index.ts.
601
+ */
602
+ function createRequestHandler(devServerConfig, serverCtx) {
603
+ let userRequestHandler = null;
604
+ let userHandlerLoaded = false;
605
+ return async function(incomingReq, res) {
606
+ if (!userHandlerLoaded && typeof devServerConfig.requestListenerPath === "string") {
607
+ userHandlerLoaded = true;
608
+ try {
609
+ const userModule = await import(devServerConfig.requestListenerPath);
610
+ userRequestHandler = userModule.default || userModule;
611
+ } catch (e) {
612
+ console.error("Failed to load user request handler:", e);
613
+ }
614
+ }
615
+ async function defaultHandler() {
616
+ try {
617
+ const req = normalizeHttpRequest(devServerConfig, incomingReq);
618
+ if (!req.url) return serverCtx.serve302(req, res);
619
+ if (devServerConfig.pingRoute !== null && req.pathname === devServerConfig.pingRoute) {
620
+ try {
621
+ if (!(await serverCtx.getBuildResults()).hasSuccessfulBuild) return serverCtx.serve500(req, res, "Build not successful", "build error");
622
+ res.writeHead(200, "OK");
623
+ res.write("OK");
624
+ res.end();
625
+ } catch {
626
+ serverCtx.serve500(req, res, "Error getting build results", "ping error");
627
+ }
628
+ return;
629
+ }
630
+ if (isDevClient(req.pathname) && devServerConfig.websocket) return serveDevClient(devServerConfig, serverCtx, req, res);
631
+ if (isDevModule(req.pathname)) return serveDevNodeModule(serverCtx, req, res);
632
+ if (!isValidUrlBasePath(devServerConfig.basePath, req.url)) return serverCtx.serve404(req, res, "invalid basePath", `404 File Not Found, base path: ${devServerConfig.basePath}`);
633
+ if (devServerConfig.ssr) {
634
+ if (isExtensionLessPath(req.url.pathname)) return ssrPageRequest(devServerConfig, serverCtx, req, res);
635
+ if (isSsrStaticDataPath(req.url.pathname)) return ssrStaticDataRequest(devServerConfig, serverCtx, req, res);
636
+ }
637
+ req.stats = await serverCtx.sys.stat(req.filePath);
638
+ if (req.stats.isFile) return serveFile(devServerConfig, serverCtx, req, res);
639
+ if (req.stats.isDirectory) return serveDirectoryIndex(devServerConfig, serverCtx, req, res);
640
+ const xSource = ["notfound"];
641
+ const validHistoryApi = isValidHistoryApi(devServerConfig, req);
642
+ xSource.push(`validHistoryApi: ${validHistoryApi}`);
643
+ if (validHistoryApi) try {
644
+ const indexFilePath = path.join(devServerConfig.root, devServerConfig.historyApiFallback.index);
645
+ xSource.push(`indexFilePath: ${indexFilePath}`);
646
+ req.stats = await serverCtx.sys.stat(indexFilePath);
647
+ if (req.stats.isFile) {
648
+ req.filePath = indexFilePath;
649
+ return serveFile(devServerConfig, serverCtx, req, res);
650
+ }
651
+ } catch (e) {
652
+ xSource.push(`notfound error: ${e}`);
653
+ }
654
+ return serverCtx.serve404(req, res, xSource.join(", "));
655
+ } catch (e) {
656
+ const errorReq = {
657
+ method: (incomingReq.method || "GET").toUpperCase(),
658
+ acceptHeader: "",
659
+ url: null,
660
+ searchParams: null
661
+ };
662
+ return serverCtx.serve500(errorReq, res, e, "not found error");
663
+ }
664
+ }
665
+ if (typeof userRequestHandler === "function") await userRequestHandler(incomingReq, res, defaultHandler);
666
+ else await defaultHandler();
667
+ };
668
+ }
669
+ function normalizeHttpRequest(devServerConfig, incomingReq) {
670
+ const req = {
671
+ method: (incomingReq.method || "GET").toUpperCase(),
672
+ headers: incomingReq.headers,
673
+ acceptHeader: incomingReq.headers && typeof incomingReq.headers.accept === "string" && incomingReq.headers.accept || "",
674
+ host: incomingReq.headers && typeof incomingReq.headers.host === "string" && incomingReq.headers.host || void 0,
675
+ url: null,
676
+ searchParams: null
677
+ };
678
+ if ((incomingReq.url || "").trim() || null) {
679
+ if (req.host) req.url = new URL(incomingReq.url, `http://${req.host}`);
680
+ else req.url = new URL(incomingReq.url, "http://dev.stenciljs.com");
681
+ req.searchParams = req.url.searchParams;
682
+ }
683
+ if (req.url) {
684
+ req.pathname = req.url.pathname.replace(/\\/g, "/").split("/").map((part) => decodeURIComponent(part)).join("/");
685
+ if (req.pathname.length > 0 && !isDevClient(req.pathname)) req.pathname = "/" + req.pathname.substring(devServerConfig.basePath.length);
686
+ req.filePath = normalizePath(path.normalize(path.join(devServerConfig.root, path.relative("/", req.pathname))));
687
+ }
688
+ return req;
689
+ }
690
+ function isValidUrlBasePath(basePath, url) {
691
+ let pathname = url.pathname;
692
+ if (!pathname.endsWith("/")) pathname += "/";
693
+ if (!basePath.endsWith("/")) basePath += "/";
694
+ return pathname.startsWith(basePath);
695
+ }
696
+ function isValidHistoryApi(devServerConfig, req) {
697
+ if (!devServerConfig.historyApiFallback) return false;
698
+ if (req.method !== "GET") return false;
699
+ if (!req.acceptHeader.includes("text/html")) return false;
700
+ if (!devServerConfig.historyApiFallback.disableDotRule && req.pathname?.includes(".")) return false;
701
+ return true;
702
+ }
703
+ const urlVersionIds = /* @__PURE__ */ new Map();
704
+ async function serveFile(devServerConfig, serverCtx, req, res) {
705
+ try {
706
+ if (isSimpleText(req.filePath)) {
707
+ let content = await serverCtx.sys.readFile(req.filePath, "utf8");
708
+ if (devServerConfig.websocket && isHtmlFile(req.filePath) && !isDevServerClient(req.pathname)) content = appendDevServerClientScript(devServerConfig, req, content);
709
+ else if (isCssFile(req.filePath)) content = updateStyleUrls(req.url, content);
710
+ if (shouldCompress(devServerConfig, req)) {
711
+ res.writeHead(200, responseHeaders({
712
+ "content-type": getContentType(req.filePath) + "; charset=utf-8",
713
+ "content-encoding": "gzip",
714
+ vary: "Accept-Encoding"
715
+ }));
716
+ zlib.gzip(content, { level: 9 }, (_, data) => {
717
+ res.end(data);
718
+ });
719
+ } else {
720
+ res.writeHead(200, responseHeaders({
721
+ "content-type": getContentType(req.filePath) + "; charset=utf-8",
722
+ "content-length": Buffer.byteLength(content, "utf8")
723
+ }));
724
+ res.write(content);
725
+ res.end();
726
+ }
727
+ } else {
728
+ const readStream = fs.createReadStream(req.filePath);
729
+ readStream.on("error", (err) => {
730
+ if (!res.headersSent) serverCtx.serve500(req, res, err, "serveFile");
731
+ else res.end();
732
+ });
733
+ res.writeHead(200, responseHeaders({
734
+ "content-type": getContentType(req.filePath),
735
+ "content-length": req.stats.size
736
+ }));
737
+ readStream.pipe(res);
738
+ }
739
+ serverCtx.logRequest(req, 200);
740
+ } catch (e) {
741
+ serverCtx.serve500(req, res, e, "serveFile");
742
+ }
743
+ }
744
+ function updateStyleUrls(url, oldCss) {
745
+ const versionId = url.searchParams.get("s-hmr");
746
+ const hmrUrls = url.searchParams.get("s-hmr-urls");
747
+ if (versionId && hmrUrls) hmrUrls.split(",").forEach((hmrUrl) => {
748
+ urlVersionIds.set(hmrUrl, versionId);
749
+ });
750
+ const reg = /url\((['"]?)(.*)\1\)/gi;
751
+ let result;
752
+ let newCss = oldCss;
753
+ while ((result = reg.exec(oldCss)) !== null) {
754
+ const oldUrl = result[2];
755
+ const parsedUrl = new URL(oldUrl, url);
756
+ const fileName = path.basename(parsedUrl.pathname);
757
+ const cachedVersionId = urlVersionIds.get(fileName);
758
+ if (!cachedVersionId) continue;
759
+ parsedUrl.searchParams.set("s-hmr", cachedVersionId);
760
+ newCss = newCss.replace(oldUrl, parsedUrl.pathname);
761
+ }
762
+ return newCss;
763
+ }
764
+ function appendDevServerClientScript(devServerConfig, req, content) {
765
+ return appendDevServerClientIframe(content, `<iframe title="Stencil Dev Server Connector ${VERSION} &#9889;" src="${getDevServerClientUrl(devServerConfig, req.headers?.["x-forwarded-host"] ?? req.host, req.headers?.["x-forwarded-proto"])}" style="display:block;width:0;height:0;border:0;visibility:hidden" aria-hidden="true"></iframe>`);
766
+ }
767
+ function appendDevServerClientIframe(content, iframe) {
768
+ if (content.includes("</body>")) return content.replace("</body>", `${iframe}</body>`);
769
+ if (content.includes("</html>")) return content.replace("</html>", `${iframe}</html>`);
770
+ return `${content}${iframe}`;
771
+ }
772
+ async function serveDevClient(devServerConfig, serverCtx, req, res) {
773
+ try {
774
+ if (isOpenInEditor(req.pathname)) return serveOpenInEditor(serverCtx, req, res);
775
+ if (isDevServerClient(req.pathname)) return serveDevClientScript(devServerConfig, serverCtx, req, res);
776
+ if (isInitialDevServerLoad(req.pathname)) req.filePath = path.join(devServerConfig.devServerDir, "templates", "initial-load.html");
777
+ else {
778
+ const subPath = req.pathname.replace(DEV_SERVER_URL + "/", "");
779
+ if (subPath.startsWith("client/")) req.filePath = path.join(devServerConfig.devServerDir, subPath);
780
+ else req.filePath = path.join(devServerConfig.devServerDir, "static", subPath);
781
+ }
782
+ try {
783
+ req.stats = await serverCtx.sys.stat(req.filePath);
784
+ if (req.stats.isFile) return serveFile(devServerConfig, serverCtx, req, res);
785
+ return serverCtx.serve404(req, res, "serveDevClient not file");
786
+ } catch (e) {
787
+ return serverCtx.serve404(req, res, `serveDevClient stats error ${e}`);
788
+ }
789
+ } catch (e) {
790
+ return serverCtx.serve500(req, res, e, "serveDevClient");
791
+ }
792
+ }
793
+ async function serveDevClientScript(devServerConfig, serverCtx, req, res) {
794
+ try {
795
+ if (serverCtx.connectorHtml == null) {
796
+ const filePath = path.join(devServerConfig.devServerDir, "connector.html");
797
+ serverCtx.connectorHtml = serverCtx.sys.readFileSync(filePath, "utf8");
798
+ if (typeof serverCtx.connectorHtml !== "string") return serverCtx.serve404(req, res, "serveDevClientScript");
799
+ const devClientConfig = {
800
+ basePath: devServerConfig.basePath,
801
+ editors: await getEditors(),
802
+ reloadStrategy: devServerConfig.reloadStrategy
803
+ };
804
+ serverCtx.connectorHtml = serverCtx.connectorHtml.replace("window.__DEV_CLIENT_CONFIG__", JSON.stringify(devClientConfig));
805
+ }
806
+ res.writeHead(200, responseHeaders({ "content-type": "text/html; charset=utf-8" }));
807
+ res.write(serverCtx.connectorHtml);
808
+ res.end();
809
+ } catch (e) {
810
+ return serverCtx.serve500(req, res, e, "serveDevClientScript");
811
+ }
812
+ }
813
+ async function serveDevNodeModule(serverCtx, req, res) {
814
+ try {
815
+ const results = await serverCtx.getCompilerRequest(req.pathname);
816
+ const headers = {
817
+ "content-type": "application/javascript; charset=utf-8",
818
+ "content-length": Buffer.byteLength(results.content, "utf8"),
819
+ "x-dev-node-module-id": results.nodeModuleId,
820
+ "x-dev-node-module-version": results.nodeModuleVersion,
821
+ "x-dev-node-module-resolved-path": results.nodeResolvedPath,
822
+ "x-dev-node-module-cache-path": results.cachePath,
823
+ "x-dev-node-module-cache-hit": results.cacheHit
824
+ };
825
+ res.writeHead(results.status, responseHeaders(headers));
826
+ res.write(results.content);
827
+ res.end();
828
+ } catch (e) {
829
+ serverCtx.serve500(req, res, e, "serveDevNodeModule");
830
+ }
831
+ }
832
+ async function serveDirectoryIndex(devServerConfig, serverCtx, req, res) {
833
+ const indexFilePath = path.join(req.filePath, "index.html");
834
+ req.stats = await serverCtx.sys.stat(indexFilePath);
835
+ if (req.stats.isFile) {
836
+ req.filePath = indexFilePath;
837
+ return serveFile(devServerConfig, serverCtx, req, res);
838
+ }
839
+ if (!req.pathname.endsWith("/")) return serverCtx.serve302(req, res, req.pathname + "/");
840
+ try {
841
+ const dirFilePaths = await serverCtx.sys.readDir(req.filePath);
842
+ try {
843
+ if (serverCtx.dirTemplate == null) {
844
+ const dirTemplatePath = path.join(devServerConfig.devServerDir, "templates", "directory-index.html");
845
+ serverCtx.dirTemplate = serverCtx.sys.readFileSync(dirTemplatePath);
846
+ }
847
+ const files = await getDirectoryFiles(serverCtx.sys, req.url, dirFilePaths);
848
+ const templateHtml = serverCtx.dirTemplate.replace("{{title}}", req.pathname).replace("{{nav}}", getDirectoryNav(req.pathname)).replace("{{files}}", files);
849
+ serverCtx.logRequest(req, 200);
850
+ res.writeHead(200, responseHeaders({
851
+ "content-type": "text/html; charset=utf-8",
852
+ "x-directory-index": req.pathname
853
+ }));
854
+ res.write(templateHtml);
855
+ res.end();
856
+ } catch (e) {
857
+ return serverCtx.serve500(req, res, e, "serveDirectoryIndex");
858
+ }
859
+ } catch {
860
+ return serverCtx.serve404(req, res, "serveDirectoryIndex");
861
+ }
862
+ }
863
+ async function getDirectoryFiles(sys, baseUrl, dirItemNames) {
864
+ const items = await getDirectoryItems(sys, baseUrl, dirItemNames);
865
+ if (baseUrl.pathname !== "/") items.unshift({
866
+ isDirectory: true,
867
+ pathname: "../",
868
+ name: ".."
869
+ });
870
+ return items.map((item) => {
871
+ return `
872
+ <li class="${item.isDirectory ? "directory" : "file"}">
873
+ <a href="${item.pathname}">
874
+ <span class="icon"></span>
875
+ <span>${item.name}</span>
876
+ </a>
877
+ </li>`;
878
+ }).join("");
879
+ }
880
+ async function getDirectoryItems(sys, baseUrl, dirFilePaths) {
881
+ return await Promise.all(dirFilePaths.map(async (dirFilePath) => {
882
+ const fileName = path.basename(dirFilePath);
883
+ const url = new URL(fileName, baseUrl);
884
+ const stats = await sys.stat(dirFilePath);
885
+ return {
886
+ name: fileName,
887
+ pathname: url.pathname,
888
+ isDirectory: stats.isDirectory
889
+ };
890
+ }));
891
+ }
892
+ function getDirectoryNav(pathName) {
893
+ const dirs = pathName.split("/");
894
+ dirs.pop();
895
+ let url = "";
896
+ return dirs.map((dir, index) => {
897
+ url += dir + "/";
898
+ return `<a href="${url}">${index === 0 ? "~" : dir}</a>`;
899
+ }).join("<span>/</span>") + "<span>/</span>";
900
+ }
901
+
902
+ //#endregion
903
+ //#region src/server/server.ts
904
+ /**
905
+ * HTTP and WebSocket server.
906
+ * Consolidated from server-process.ts, server-http.ts, and server-web-socket.ts.
907
+ * Uses native Node 22+ WebSocket instead of the 'ws' package.
908
+ */
909
+ function createHttpServer(devServerConfig, serverCtx) {
910
+ const reqHandler = createRequestHandler(devServerConfig, serverCtx);
911
+ const credentials = devServerConfig.https;
912
+ return credentials ? https.createServer(credentials, reqHandler) : http.createServer(reqHandler);
913
+ }
914
+ async function findClosestOpenPort(host, port, strictPort = false) {
915
+ if (!await isPortTaken(host, port)) return port;
916
+ if (strictPort) throw new Error(`Port ${port} is already in use. Please specify a different port or set strictPort to false.`);
917
+ async function findNext(portToCheck) {
918
+ if (!await isPortTaken(host, portToCheck)) return portToCheck;
919
+ return findNext(portToCheck + 1);
920
+ }
921
+ return findNext(port + 1);
922
+ }
923
+ function isPortTaken(host, port) {
924
+ return new Promise((resolve, reject) => {
925
+ const tester = net.createServer().once("error", () => {
926
+ resolve(true);
927
+ }).once("listening", () => {
928
+ tester.once("close", () => resolve(false)).close();
929
+ }).on("error", (err) => {
930
+ reject(err);
931
+ }).listen(port, host);
932
+ });
933
+ }
934
+ function createWebSocket(httpServer, onMessageFromClient) {
935
+ const wsServer = new WebSocketServer({ server: httpServer });
936
+ wsServer.on("connection", (rawWs) => {
937
+ const ws = rawWs;
938
+ ws.isAlive = true;
939
+ ws.on("message", (data) => {
940
+ try {
941
+ onMessageFromClient(JSON.parse(data.toString()));
942
+ } catch (e) {
943
+ console.error("WebSocket message parse error:", e);
944
+ }
945
+ });
946
+ ws.on("pong", () => {
947
+ ws.isAlive = true;
948
+ });
949
+ ws.on("error", (err) => {
950
+ console.error("WebSocket error:", err);
951
+ });
952
+ });
953
+ const pingInterval = setInterval(() => {
954
+ wsServer.clients.forEach((ws) => {
955
+ const devWs = ws;
956
+ if (!devWs.isAlive) return devWs.close(1e3);
957
+ devWs.isAlive = false;
958
+ devWs.ping();
959
+ });
960
+ }, 1e4);
961
+ return {
962
+ sendToBrowser: (msg) => {
963
+ if (msg && wsServer && wsServer.clients) {
964
+ const data = JSON.stringify(msg);
965
+ wsServer.clients.forEach((ws) => {
966
+ if (ws.readyState === ws.OPEN) ws.send(data);
967
+ });
968
+ }
969
+ },
970
+ close: () => {
971
+ return new Promise((resolve, reject) => {
972
+ clearInterval(pingInterval);
973
+ wsServer.clients.forEach((ws) => {
974
+ ws.close(1e3);
975
+ });
976
+ wsServer.close((err) => {
977
+ if (err) reject(err);
978
+ else resolve();
979
+ });
980
+ });
981
+ }
982
+ };
983
+ }
984
+ function initServerProcess(sendMsg) {
985
+ let server = null;
986
+ let webSocket = null;
987
+ let serverCtx = null;
988
+ const buildResultsResolves = [];
989
+ const compilerRequestResolves = [];
990
+ const createNodeSys = async () => {
991
+ const { createNodeSys: createSys } = await import("@stencil/core/sys/node");
992
+ return createSys({ process });
993
+ };
994
+ const startServer = async (msg) => {
995
+ const devServerConfig = msg.startServer;
996
+ devServerConfig.port = await findClosestOpenPort(devServerConfig.address, devServerConfig.port, devServerConfig.strictPort);
997
+ devServerConfig.browserUrl = getBrowserUrl(devServerConfig.protocol, devServerConfig.address, devServerConfig.port, devServerConfig.basePath, "/");
998
+ devServerConfig.root = normalizePath(devServerConfig.root);
999
+ serverCtx = createServerContext(await createNodeSys(), sendMsg, devServerConfig, buildResultsResolves, compilerRequestResolves);
1000
+ server = createHttpServer(devServerConfig, serverCtx);
1001
+ webSocket = devServerConfig.websocket ? createWebSocket(server, sendMsg) : null;
1002
+ server.listen(devServerConfig.port, devServerConfig.address);
1003
+ serverCtx.isServerListening = true;
1004
+ if (devServerConfig.openBrowser) openInBrowser({ url: getBrowserUrl(devServerConfig.protocol, devServerConfig.address, devServerConfig.port, devServerConfig.basePath, devServerConfig.initialLoadUrl || DEV_SERVER_INIT_URL) });
1005
+ sendMsg({ serverStarted: devServerConfig });
1006
+ };
1007
+ const closeServer = () => {
1008
+ const promises = [];
1009
+ buildResultsResolves.forEach((r) => r.reject("dev server closed"));
1010
+ buildResultsResolves.length = 0;
1011
+ compilerRequestResolves.forEach((r) => r.reject("dev server closed"));
1012
+ compilerRequestResolves.length = 0;
1013
+ if (serverCtx?.sys) promises.push(serverCtx.sys.destroy());
1014
+ if (webSocket) {
1015
+ promises.push(webSocket.close());
1016
+ webSocket = null;
1017
+ }
1018
+ if (server) promises.push(new Promise((resolve) => {
1019
+ server.close((err) => {
1020
+ if (err) console.error(`close error: ${err}`);
1021
+ resolve();
1022
+ });
1023
+ }));
1024
+ Promise.all(promises).finally(() => {
1025
+ sendMsg({ serverClosed: true });
1026
+ });
1027
+ };
1028
+ const receiveMessageFromMain = (msg) => {
1029
+ try {
1030
+ if (msg) {
1031
+ if (msg.startServer) startServer(msg);
1032
+ else if (msg.closeServer) closeServer();
1033
+ else if (msg.compilerRequestResults) for (let i = compilerRequestResolves.length - 1; i >= 0; i--) {
1034
+ const r = compilerRequestResolves[i];
1035
+ if (r.path === msg.compilerRequestResults.path) {
1036
+ r.resolve(msg.compilerRequestResults);
1037
+ compilerRequestResolves.splice(i, 1);
1038
+ }
1039
+ }
1040
+ else if (serverCtx) {
1041
+ if (msg.buildResults && !msg.isActivelyBuilding) {
1042
+ buildResultsResolves.forEach((r) => r.resolve(msg.buildResults));
1043
+ buildResultsResolves.length = 0;
1044
+ }
1045
+ if (webSocket) webSocket.sendToBrowser(msg);
1046
+ }
1047
+ }
1048
+ } catch (e) {
1049
+ let stack = null;
1050
+ if (e instanceof Error) stack = e.stack ?? null;
1051
+ sendMsg({ error: {
1052
+ message: String(e),
1053
+ stack
1054
+ } });
1055
+ }
1056
+ };
1057
+ return receiveMessageFromMain;
1058
+ }
1059
+
1060
+ //#endregion
1061
+ //#region src/server/worker-main.ts
1062
+ /**
1063
+ * Worker process proxy for dev server.
1064
+ * Forks a child process to run the HTTP and WebSocket server in isolation.
1065
+ */
1066
+ /**
1067
+ * Initialize the dev server in a forked worker process.
1068
+ * This provides process isolation so that server crashes don't affect the main compiler.
1069
+ *
1070
+ * @param sendToMain - Callback to send messages from worker to main process
1071
+ * @returns Function to send messages from main to worker process
1072
+ */
1073
+ function initServerProcessWorkerProxy(sendToMain) {
1074
+ let serverProcess = fork(path.join(import.meta.dirname, "worker-thread.js"), [], {
1075
+ execArgv: process.execArgv.filter((v) => !/^--(debug|inspect)/.test(v)),
1076
+ env: process.env,
1077
+ cwd: process.cwd(),
1078
+ stdio: [
1079
+ "pipe",
1080
+ "pipe",
1081
+ "pipe",
1082
+ "ipc"
1083
+ ]
1084
+ });
1085
+ /**
1086
+ * Send a message from main to the worker process
1087
+ */
1088
+ const receiveFromMain = (msg) => {
1089
+ if (serverProcess && serverProcess.connected) serverProcess.send(msg);
1090
+ else if (msg.closeServer) sendToMain({ serverClosed: true });
1091
+ };
1092
+ serverProcess.on("message", (msg) => {
1093
+ if (msg.serverClosed && serverProcess) {
1094
+ serverProcess.kill("SIGINT");
1095
+ serverProcess = null;
1096
+ }
1097
+ sendToMain(msg);
1098
+ });
1099
+ serverProcess.stdout?.on("data", (data) => {
1100
+ console.log(`dev server: ${data}`);
1101
+ });
1102
+ serverProcess.stderr?.on("data", (data) => {
1103
+ sendToMain({ error: {
1104
+ message: "stderr: " + data.toString(),
1105
+ type: "stderr",
1106
+ stack: null
1107
+ } });
1108
+ });
1109
+ serverProcess.on("error", (error) => {
1110
+ sendToMain({ error: {
1111
+ message: error.message,
1112
+ type: "worker-error",
1113
+ stack: error.stack || null
1114
+ } });
1115
+ });
1116
+ serverProcess.on("exit", (code) => {
1117
+ if (code !== 0 && code !== null) sendToMain({ error: {
1118
+ message: `Worker process exited with code ${code}`,
1119
+ type: "worker-exit",
1120
+ stack: null
1121
+ } });
1122
+ if (serverProcess) {
1123
+ serverProcess = null;
1124
+ sendToMain({ serverClosed: true });
1125
+ }
1126
+ });
1127
+ return receiveFromMain;
1128
+ }
1129
+
1130
+ //#endregion
1131
+ //#region src/server/index.ts
1132
+ /**
1133
+ * @stencil/dev-server
1134
+ *
1135
+ * A modern development server for Stencil with DOM-based HMR.
1136
+ * Designed for lazy-loading component architectures where module graphs
1137
+ * are discovered at runtime from the DOM.
1138
+ */
1139
+ /**
1140
+ * Start the Stencil development server.
1141
+ *
1142
+ * @param stencilDevServerConfig - Configuration for the dev server
1143
+ * @param logger - Logger instance for output
1144
+ * @param watcher - Optional compiler watcher for build events
1145
+ * @returns Promise resolving to the DevServer instance
1146
+ */
1147
+ function start(stencilDevServerConfig, logger, watcher) {
1148
+ return new Promise(async (resolve, reject) => {
1149
+ try {
1150
+ const devServerConfig = {
1151
+ devServerDir: import.meta.dirname,
1152
+ ...stencilDevServerConfig
1153
+ };
1154
+ if (!path.isAbsolute(devServerConfig.root)) devServerConfig.root = path.join(process.cwd(), devServerConfig.root);
1155
+ let initServerProcessFn;
1156
+ if (stencilDevServerConfig.worker === true || stencilDevServerConfig.worker === void 0) initServerProcessFn = initServerProcessWorkerProxy;
1157
+ else initServerProcessFn = initServerProcess;
1158
+ startServer(devServerConfig, logger, watcher, initServerProcessFn, resolve, reject);
1159
+ } catch (e) {
1160
+ reject(e);
1161
+ }
1162
+ });
1163
+ }
1164
+ function startServer(devServerConfig, logger, watcher, initServerProcessFn, resolve, reject) {
1165
+ const timespan = logger.createTimeSpan("starting dev server", true);
1166
+ const startupTimeout = logger.getLevel() !== "debug" || devServerConfig.startupTimeout !== 0 ? setTimeout(() => {
1167
+ reject("dev server startup timeout");
1168
+ }, devServerConfig.startupTimeout ?? 15e3) : null;
1169
+ let isActivelyBuilding = false;
1170
+ let lastBuildResults = null;
1171
+ let devServer = null;
1172
+ let removeWatcher = null;
1173
+ let closeResolve = null;
1174
+ let hasStarted = false;
1175
+ let browserUrl = "";
1176
+ let sendToServer = null;
1177
+ const closePromise = new Promise((res) => {
1178
+ closeResolve = res;
1179
+ });
1180
+ const close = async () => {
1181
+ if (startupTimeout) clearTimeout(startupTimeout);
1182
+ isActivelyBuilding = false;
1183
+ if (removeWatcher) removeWatcher();
1184
+ if (devServer) devServer = null;
1185
+ if (sendToServer) {
1186
+ sendToServer({ closeServer: true });
1187
+ sendToServer = null;
1188
+ }
1189
+ return closePromise;
1190
+ };
1191
+ const emit = (eventName, data) => {
1192
+ if (sendToServer) {
1193
+ if (eventName === "buildFinish") {
1194
+ isActivelyBuilding = false;
1195
+ lastBuildResults = { ...data };
1196
+ sendToServer({
1197
+ buildResults: { ...lastBuildResults },
1198
+ isActivelyBuilding
1199
+ });
1200
+ } else if (eventName === "buildLog") sendToServer({ buildLog: { ...data } });
1201
+ else if (eventName === "buildStart") isActivelyBuilding = true;
1202
+ }
1203
+ };
1204
+ const serverStarted = (msg) => {
1205
+ hasStarted = true;
1206
+ if (startupTimeout) clearTimeout(startupTimeout);
1207
+ devServerConfig = msg.serverStarted;
1208
+ devServer = {
1209
+ address: devServerConfig.address,
1210
+ basePath: devServerConfig.basePath,
1211
+ browserUrl: devServerConfig.browserUrl,
1212
+ protocol: devServerConfig.protocol,
1213
+ port: devServerConfig.port,
1214
+ root: devServerConfig.root,
1215
+ emit,
1216
+ close
1217
+ };
1218
+ browserUrl = devServerConfig.browserUrl;
1219
+ timespan.finish(`dev server started: ${browserUrl}`);
1220
+ resolve(devServer);
1221
+ };
1222
+ const requestLog = (msg) => {
1223
+ if (devServerConfig.logRequests && msg.requestLog) if (msg.requestLog.status >= 500) logger.info(logger.red(`${msg.requestLog.method} ${msg.requestLog.url} (${msg.requestLog.status})`));
1224
+ else if (msg.requestLog.status >= 400) logger.info(logger.dim(logger.red(`${msg.requestLog.method} ${msg.requestLog.url} (${msg.requestLog.status})`)));
1225
+ else if (msg.requestLog.status >= 300) logger.info(logger.dim(logger.magenta(`${msg.requestLog.method} ${msg.requestLog.url} (${msg.requestLog.status})`)));
1226
+ else logger.info(logger.dim(`${logger.cyan(msg.requestLog.method)} ${msg.requestLog.url}`));
1227
+ };
1228
+ const serverError = async (msg) => {
1229
+ if (msg.error) if (hasStarted) logger.error(msg.error.message + " " + msg.error.stack);
1230
+ else {
1231
+ await close();
1232
+ reject(msg.error.message);
1233
+ }
1234
+ };
1235
+ const requestBuildResults = () => {
1236
+ if (sendToServer) if (lastBuildResults != null) {
1237
+ const msg = {
1238
+ buildResults: { ...lastBuildResults },
1239
+ isActivelyBuilding
1240
+ };
1241
+ delete msg.buildResults.hmr;
1242
+ sendToServer(msg);
1243
+ } else sendToServer({ isActivelyBuilding: true });
1244
+ };
1245
+ const compilerRequest = async (compilerRequestPath) => {
1246
+ if (watcher?.request && sendToServer) {
1247
+ const compilerRequestResults = await watcher.request({ path: compilerRequestPath });
1248
+ sendToServer({ compilerRequestResults });
1249
+ }
1250
+ };
1251
+ const receiveFromServer = (msg) => {
1252
+ try {
1253
+ if (msg.serverStarted) serverStarted(msg);
1254
+ else if (msg.serverClosed) {
1255
+ logger.debug(`dev server closed: ${browserUrl}`);
1256
+ closeResolve?.();
1257
+ } else if (msg.requestBuildResults) requestBuildResults();
1258
+ else if (msg.compilerRequestPath) compilerRequest(msg.compilerRequestPath);
1259
+ else if (msg.requestLog) requestLog(msg);
1260
+ else if (msg.error) serverError(msg);
1261
+ else logger.debug(`server msg not handled: ${JSON.stringify(msg)}`);
1262
+ } catch (e) {
1263
+ logger.error("receiveFromServer: " + e);
1264
+ }
1265
+ };
1266
+ try {
1267
+ if (watcher) removeWatcher = watcher.on(emit);
1268
+ sendToServer = initServerProcessFn(receiveFromServer);
1269
+ sendToServer({ startServer: devServerConfig });
1270
+ } catch (e) {
1271
+ close();
1272
+ reject(e);
1273
+ }
1274
+ }
1275
+
1276
+ //#endregion
1277
+ export { initServerProcess, start };