@stencil/dev-server 5.0.0-alpha.6 → 5.0.0-alpha.8

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.
Files changed (2) hide show
  1. package/dist/index.mjs +127 -0
  2. package/package.json +2 -2
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">&lt;${component.tagName}&gt;</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.
@@ -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.6",
3
+ "version": "5.0.0-alpha.8",
4
4
  "description": "Development server for Stencil with DOM-based HMR",
5
5
  "keywords": [
6
6
  "dev server",
@@ -46,7 +46,7 @@
46
46
  "typescript": ">4.0.0 <7.0.0",
47
47
  "vitest": "^4.1.7",
48
48
  "vitest-environment-stencil": "^1.12.1",
49
- "@stencil/core": "5.0.0-alpha.6"
49
+ "@stencil/core": "5.0.0-alpha.8"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "@stencil/core": "^5.0.0-0"