@stencil/dev-server 5.0.0-alpha.5 → 5.0.0-alpha.7
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 +129 -2
- package/package.json +8 -8
package/dist/index.mjs
CHANGED
|
@@ -510,6 +510,110 @@ function getEditors() {
|
|
|
510
510
|
]);
|
|
511
511
|
}
|
|
512
512
|
//#endregion
|
|
513
|
+
//#region src/server/dev-preview.ts
|
|
514
|
+
/**
|
|
515
|
+
* Generate a virtual HTML page for the dev server when no index.html is present.
|
|
516
|
+
*
|
|
517
|
+
* Renders all discovered components. For each component, usage markdown files
|
|
518
|
+
* (src/components/my-cmp/usage/*.md) are scanned for ```html``` code blocks, which
|
|
519
|
+
* are used as preview snippets. Falls back to a bare tag if none are found.
|
|
520
|
+
* @param buildResults The compiler build results containing component metadata and output file information.
|
|
521
|
+
* @param filterDirPath Optional directory path to filter components by (e.g. when visiting /src/my-component/).
|
|
522
|
+
* @returns A string of HTML to be served as the dev preview page.
|
|
523
|
+
*/
|
|
524
|
+
const generateDevPreview = (buildResults, filterDirPath) => {
|
|
525
|
+
const { namespace } = buildResults;
|
|
526
|
+
let { components } = buildResults;
|
|
527
|
+
if (filterDirPath) {
|
|
528
|
+
const normalized = path.normalize(filterDirPath);
|
|
529
|
+
components = components.filter((c) => path.normalize(path.dirname(c.sourceFilePath)) === normalized);
|
|
530
|
+
}
|
|
531
|
+
const title = filterDirPath ? `${namespace} / ${path.basename(filterDirPath)}` : namespace;
|
|
532
|
+
const loaderUrl = getLoaderUrl(buildResults);
|
|
533
|
+
const globalCssUrls = getGlobalCssUrls(buildResults);
|
|
534
|
+
const sections = components.length > 0 ? components.map((c) => renderComponentSection(c)) : [` <p style="color:color-mix(in oklab,CanvasText 50%,Canvas 50%)">No Stencil components found in this directory.</p>`];
|
|
535
|
+
return `<!DOCTYPE html>
|
|
536
|
+
<html lang="en">
|
|
537
|
+
<head>
|
|
538
|
+
<meta charset="UTF-8">
|
|
539
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
540
|
+
<title>${title} — Stencil Dev</title>
|
|
541
|
+
<style>
|
|
542
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
543
|
+
body { font-family: system-ui, sans-serif; margin: 0; padding: 2rem; }
|
|
544
|
+
h1 { font-size: 1rem; font-weight: 500; margin: 0 0 2rem; color: color-mix(in oklab, CanvasText 90%, Canvas 10%); }
|
|
545
|
+
.component { border: 1px solid color-mix(in oklab, CanvasText 20%, Canvas 80%); border-radius: 6px; margin-bottom: 1.5rem; overflow: hidden; }
|
|
546
|
+
.component-header { padding: 0.5rem 1rem; background: color-mix(in oklab, CanvasText 5%, Canvas 95%); font-family: monospace; font-size: 0.875rem; }
|
|
547
|
+
.component-preview { padding: 1.5rem; }
|
|
548
|
+
.component-preview + .component-preview { border-top: 1px dashed color-mix(in oklab, CanvasText 20%, Canvas 80%); }
|
|
549
|
+
</style>
|
|
550
|
+
${[loaderUrl ? ` <script type="module" src="${loaderUrl}"><\/script>` : "", ...globalCssUrls.map((url) => ` <link rel="stylesheet" href="${url}">`)].filter(Boolean).join("\n")}
|
|
551
|
+
</head>
|
|
552
|
+
<body>
|
|
553
|
+
<h1>${title}</h1>
|
|
554
|
+
${sections.join("\n")}
|
|
555
|
+
</body>
|
|
556
|
+
</html>`;
|
|
557
|
+
};
|
|
558
|
+
const renderComponentSection = (component) => {
|
|
559
|
+
const snippets = getUsageSnippets(component);
|
|
560
|
+
const previews = snippets.length > 0 ? snippets.map((html) => ` <div class="component-preview">\n ${html}\n </div>`).join("\n") : ` <div class="component-preview"><${component.tagName}></${component.tagName}></div>`;
|
|
561
|
+
return ` <div class="component">
|
|
562
|
+
<div class="component-header"><${component.tagName}></div>
|
|
563
|
+
${previews}
|
|
564
|
+
</div>`;
|
|
565
|
+
};
|
|
566
|
+
const getUsageSnippets = (component) => {
|
|
567
|
+
const usageDir = path.join(path.dirname(component.sourceFilePath), "usage");
|
|
568
|
+
try {
|
|
569
|
+
const files = fs.readdirSync(usageDir).filter((f) => f.toLowerCase().endsWith(".md"));
|
|
570
|
+
const snippets = [];
|
|
571
|
+
for (const file of files.sort()) {
|
|
572
|
+
const content = fs.readFileSync(path.join(usageDir, file), "utf-8");
|
|
573
|
+
for (const match of content.matchAll(/```html\n([\s\S]*?)```/g)) snippets.push(match[1].trim());
|
|
574
|
+
}
|
|
575
|
+
return snippets;
|
|
576
|
+
} catch {
|
|
577
|
+
return [];
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
/**
|
|
581
|
+
* Convert an absolute output file path to a server-relative URL.
|
|
582
|
+
* @param absPath The absolute file path.
|
|
583
|
+
* @param rootDir The root directory to which the path should be relative.
|
|
584
|
+
* @returns A server-relative URL.
|
|
585
|
+
*/
|
|
586
|
+
const toServePath = (absPath, rootDir) => "/" + path.relative(rootDir, absPath).split(path.sep).join("/");
|
|
587
|
+
/**
|
|
588
|
+
* Derive the loader script URL from build outputs.
|
|
589
|
+
* Prefers loader-bundle (always built in dev); falls back to standalone auto-loader if present.
|
|
590
|
+
* @param buildResults The compiler build results containing output file information.
|
|
591
|
+
* @returns A server-relative URL to a loader script, or null if not found.
|
|
592
|
+
*/
|
|
593
|
+
const getLoaderUrl = ({ outputs, fsNamespace, rootDir }) => {
|
|
594
|
+
const distLazy = outputs.find((o) => o.type === "dist-lazy");
|
|
595
|
+
if (distLazy) {
|
|
596
|
+
const file = distLazy.files.find((f) => f.endsWith(`/${fsNamespace}.js`));
|
|
597
|
+
if (file) return toServePath(file, rootDir);
|
|
598
|
+
}
|
|
599
|
+
const standalone = outputs.find((o) => o.type === "standalone");
|
|
600
|
+
if (standalone) {
|
|
601
|
+
const file = standalone.files.find((f) => /\/loader\.js$/.test(f));
|
|
602
|
+
if (file) return toServePath(file, rootDir);
|
|
603
|
+
}
|
|
604
|
+
return null;
|
|
605
|
+
};
|
|
606
|
+
/**
|
|
607
|
+
* Collect server-relative URLs for all global-style CSS outputs.
|
|
608
|
+
* @param buildResults The compiler build results containing output file information.
|
|
609
|
+
* @returns An array of server-relative URLs to global-style CSS files.
|
|
610
|
+
*/
|
|
611
|
+
const getGlobalCssUrls = ({ outputs, rootDir }) => {
|
|
612
|
+
const globalStyle = outputs.find((o) => o.type === "global-style");
|
|
613
|
+
if (!globalStyle) return [];
|
|
614
|
+
return globalStyle.files.filter((f) => f.endsWith(".css") && !f.endsWith(".css.map")).map((f) => toServePath(f, rootDir));
|
|
615
|
+
};
|
|
616
|
+
//#endregion
|
|
513
617
|
//#region src/server/ssr.ts
|
|
514
618
|
/**
|
|
515
619
|
* SSR (Server-Side Rendering) request handling.
|
|
@@ -584,7 +688,7 @@ async function setupHydrateApp(devServerConfig, serverCtx) {
|
|
|
584
688
|
const buildResults = await serverCtx.getBuildResults();
|
|
585
689
|
const diagnostics = [];
|
|
586
690
|
if (serverCtx.prerenderConfig == null && isString(devServerConfig.prerenderConfig)) try {
|
|
587
|
-
const prerenderConfigResults = (await import("@stencil/core/compiler")).nodeRequire(devServerConfig.prerenderConfig);
|
|
691
|
+
const prerenderConfigResults = await (await import("@stencil/core/compiler")).nodeRequire(devServerConfig.prerenderConfig);
|
|
588
692
|
diagnostics.push(...prerenderConfigResults.diagnostics);
|
|
589
693
|
if (prerenderConfigResults.module?.config) serverCtx.prerenderConfig = prerenderConfigResults.module.config;
|
|
590
694
|
} catch (e) {
|
|
@@ -884,7 +988,7 @@ async function serveDevClient(devServerConfig, serverCtx, req, res) {
|
|
|
884
988
|
if (isDevServerClient(req.pathname)) return serveDevClientScript(devServerConfig, serverCtx, req, res);
|
|
885
989
|
if (isInitialDevServerLoad(req.pathname)) req.filePath = path.join(devServerConfig.devServerDir, "templates", "initial-load.html");
|
|
886
990
|
else {
|
|
887
|
-
const subPath = req.pathname.replace(
|
|
991
|
+
const subPath = req.pathname.replace("/~dev-server/", "");
|
|
888
992
|
if (subPath.startsWith("client/")) req.filePath = path.join(devServerConfig.devServerDir, subPath);
|
|
889
993
|
else req.filePath = path.join(devServerConfig.devServerDir, "static", subPath);
|
|
890
994
|
}
|
|
@@ -948,6 +1052,15 @@ async function serveDirectoryIndex(devServerConfig, serverCtx, req, res) {
|
|
|
948
1052
|
if (!req.pathname.endsWith("/")) return serverCtx.serve302(req, res, req.pathname + "/");
|
|
949
1053
|
try {
|
|
950
1054
|
const dirFilePaths = await serverCtx.sys.readDir(req.filePath);
|
|
1055
|
+
const hasTsx = dirFilePaths.some((f) => f.endsWith(".tsx"));
|
|
1056
|
+
const hasHtml = dirFilePaths.some((f) => f.endsWith(".html") || f.endsWith(".htm"));
|
|
1057
|
+
if (hasTsx && !hasHtml) return serveDevPreview(devServerConfig, serverCtx, req, res, req.filePath);
|
|
1058
|
+
if (req.pathname === "/" || req.pathname === devServerConfig.basePath) {
|
|
1059
|
+
const srcPath = path.join(req.filePath, "src");
|
|
1060
|
+
try {
|
|
1061
|
+
if ((await serverCtx.sys.stat(srcPath)).isDirectory) return serverCtx.serve302(req, res, "/src/");
|
|
1062
|
+
} catch {}
|
|
1063
|
+
}
|
|
951
1064
|
try {
|
|
952
1065
|
if (serverCtx.dirTemplate == null) {
|
|
953
1066
|
const dirTemplatePath = path.join(devServerConfig.devServerDir, "templates", "directory-index.html");
|
|
@@ -969,6 +1082,20 @@ async function serveDirectoryIndex(devServerConfig, serverCtx, req, res) {
|
|
|
969
1082
|
return serverCtx.serve404(req, res, "serveDirectoryIndex");
|
|
970
1083
|
}
|
|
971
1084
|
}
|
|
1085
|
+
async function serveDevPreview(devServerConfig, serverCtx, req, res, filterDirPath) {
|
|
1086
|
+
try {
|
|
1087
|
+
const html = appendDevServerClientScript(devServerConfig, req, generateDevPreview(await serverCtx.getBuildResults(), filterDirPath));
|
|
1088
|
+
const buf = Buffer.from(html, "utf-8");
|
|
1089
|
+
serverCtx.logRequest(req, 200);
|
|
1090
|
+
res.writeHead(200, responseHeaders({
|
|
1091
|
+
"content-type": "text/html;charset=utf-8",
|
|
1092
|
+
"content-length": buf.byteLength
|
|
1093
|
+
}));
|
|
1094
|
+
res.end(buf);
|
|
1095
|
+
} catch (e) {
|
|
1096
|
+
serverCtx.serve500(req, res, e, "serveDevPreview");
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
972
1099
|
async function getDirectoryFiles(sys, baseUrl, dirItemNames) {
|
|
973
1100
|
const items = await getDirectoryItems(sys, baseUrl, dirItemNames);
|
|
974
1101
|
if (baseUrl.pathname !== "/") items.unshift({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stencil/dev-server",
|
|
3
|
-
"version": "5.0.0-alpha.
|
|
3
|
+
"version": "5.0.0-alpha.7",
|
|
4
4
|
"description": "Development server for Stencil with DOM-based HMR",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dev server",
|
|
@@ -35,18 +35,18 @@
|
|
|
35
35
|
}
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"launch-editor": "^2.
|
|
38
|
+
"launch-editor": "^2.0.0",
|
|
39
39
|
"open": "^11.0.0",
|
|
40
40
|
"ws": "^8.0.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@tsdown/css": "
|
|
43
|
+
"@tsdown/css": ">=0.21.0 <1.0.0",
|
|
44
44
|
"@types/ws": "^8.0.0",
|
|
45
|
-
"tsdown": "
|
|
46
|
-
"typescript": ">4.0.0",
|
|
47
|
-
"vitest": "^4.1.
|
|
48
|
-
"vitest-environment-stencil": "^1.
|
|
49
|
-
"@stencil/core": "5.0.0-alpha.
|
|
45
|
+
"tsdown": ">=0.21.0 <1.0.0",
|
|
46
|
+
"typescript": ">4.0.0 <7.0.0",
|
|
47
|
+
"vitest": "^4.1.7",
|
|
48
|
+
"vitest-environment-stencil": "^1.12.1",
|
|
49
|
+
"@stencil/core": "5.0.0-alpha.7"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"@stencil/core": "^5.0.0-0"
|