kss-modern 1.2.0-beta.1 → 1.2.0-beta.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.
@@ -1,10 +1,11 @@
1
1
  import path from 'node:path';
2
+ import { performance } from 'node:perf_hooks';
2
3
  import process from 'node:process';
3
4
  import { fileURLToPath } from 'node:url';
4
5
  import fs from 'fs-extra';
5
6
  import { glob } from 'tinyglobby';
6
- import { l as logger, s as sanitizeSpecialCharacters, I as INSERT_VITE_PUG_TAG_RE, P as PUG_SRC_RE, c as compileMarkup, p as pugErrorFile, a as sectionSanitizeId, e as ensureStartingSlash, g as generateId, r as replaceWrapperContent } from '../shared/kss-modern.C3thzyxO.mjs';
7
- export { b as createLogger } from '../shared/kss-modern.C3thzyxO.mjs';
7
+ import { l as logger, s as sanitizeSpecialCharacters, I as INSERT_VITE_PUG_TAG_RE, P as PUG_SRC_RE, a as PUG_ERROR_OVERLAY_TAG, c as compileMarkup, p as pugErrorFile, b as sectionSanitizeId, d as stripPugErrorOverlay, e as ensureStartingSlash, g as generateId, r as replaceWrapperContent } from '../shared/kss-modern.o1Fp758z.mjs';
8
+ export { f as createLogger } from '../shared/kss-modern.o1Fp758z.mjs';
8
9
  import { fromAsyncCodeToHtml } from '@shikijs/markdown-it/async';
9
10
  import MarkdownItAsync from 'markdown-it-async';
10
11
  import { codeToHtml } from 'shiki';
@@ -14,10 +15,10 @@ import fs$1 from 'node:fs/promises';
14
15
  import { objectEntries } from '@antfu/utils';
15
16
  import { readFileSync } from 'node:fs';
16
17
  import chokidar from 'chokidar';
17
- import '@biomejs/js-api';
18
18
  import 'pug';
19
19
  import 'consola';
20
20
  import 'consola/utils';
21
+ import 'pug-load';
21
22
 
22
23
  const HEX_COLOR_RE = /^#[0-9A-F]{6}$/i;
23
24
  function isValidHexColor(hexCode) {
@@ -998,45 +999,116 @@ class PugDependencyGraph {
998
999
  }
999
1000
  }
1000
1001
 
1001
- function renderPugErrorOverlay(error) {
1002
+ function renderPugErrorOverlay(error, lastHtml = "") {
1002
1003
  const id = escape(error.id);
1003
- const file = error.file ? escape(error.file) : "";
1004
- const message = escape(error.message);
1005
- return `
1006
- <div
1007
- role="alert"
1008
- style="box-sizing:border-box;width:100%;margin:0;padding:16px 18px;border:1px solid #f43f5e59;border-radius:12px;background:#1f1f23;font-family:ui-sans-serif,system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;color:#e4e4e7;line-height:1.5;text-align:left;"
1009
- >
1010
- <div style="display:flex;align-items:center;gap:8px;margin-bottom:12px;">
1011
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="#f43f5e" style="width:18px;height:18px;flex:none;" aria-hidden="true">
1012
- <path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495ZM10 5a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 10 5Zm0 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z" clip-rule="evenodd" />
1013
- </svg>
1014
- <span style="font-weight:600;font-size:12px;letter-spacing:.04em;text-transform:uppercase;color:#fb7185;">Pug compile error</span>
1015
- </div>
1016
- <h1 style="margin:0;font-size:16px;font-weight:600;color:#fafafa;">Section <code style="font-family:ui-monospace,SFMono-Regular,Menlo,monospace;background:#f43f5e1a;border-radius:5px;padding:1px 7px;color:#fda4af;">${id}</code> failed to compile</h1>
1017
- ${file ? `<p style="margin:8px 0 0;font-size:13px;color:#a1a1aa;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;word-break:break-all;">${file}</p>` : ""}
1018
- <pre style="margin:12px 0 0;padding:12px 14px;background:#111113;border:1px solid #2a2a30;border-radius:8px;overflow:auto;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.55;color:#f4f4f5;white-space:pre;"><code>${message}</code></pre>
1019
- </div>`.trim();
1004
+ const message = escape(error.message).replace(/\r?\n/g, "&#10;");
1005
+ const fileAttr = error.file ? ` error-file="${escape(error.file)}"` : "";
1006
+ const emptyAttr = lastHtml ? "" : " data-empty";
1007
+ const overlay = `<${PUG_ERROR_OVERLAY_TAG} error-id="${id}"${fileAttr} error-message="${message}"${emptyAttr}></${PUG_ERROR_OVERLAY_TAG}>`;
1008
+ return `${lastHtml}${overlay}`.trim();
1020
1009
  }
1021
1010
 
1022
- const MAX_POOL_SIZE = os.cpus().length;
1011
+ const MAX_POOL_SIZE = Math.max(2, os.cpus().length - 1);
1023
1012
  const INLINE_THRESHOLD = 2;
1024
1013
  const cache = new PugCompileCache();
1025
1014
  const graph = new PugDependencyGraph();
1026
1015
  function getPugDependencyGraph() {
1027
1016
  return graph;
1028
1017
  }
1018
+ let lastCompileTimings = { read: 0, parse: 0, render: 0, a11y: 0, sections: 0 };
1019
+ function accumulateTimings(timings) {
1020
+ lastCompileTimings.read += timings.read;
1021
+ lastCompileTimings.parse += timings.parse;
1022
+ lastCompileTimings.render += timings.render;
1023
+ lastCompileTimings.a11y += timings.a11y;
1024
+ lastCompileTimings.sections += 1;
1025
+ }
1026
+ function getLastCompileTimings() {
1027
+ return lastCompileTimings;
1028
+ }
1029
1029
  const __filename$1 = fileURLToPath(import.meta.url);
1030
1030
  const __dirname$1 = path.dirname(__filename$1);
1031
1031
  const workerFilePath = __dirname$1.includes("dist/") ? "./pug/worker.mjs" : "./worker.ts";
1032
1032
  const workerFilePathResolved = path.resolve(__dirname$1, workerFilePath);
1033
- let workerPool = [];
1034
- async function terminateAllWorkers() {
1035
- await Promise.all(workerPool.map((worker) => worker.terminate()));
1036
- workerPool = [];
1033
+ const slots = Array.from({ length: MAX_POOL_SIZE }, () => ({ tail: Promise.resolve(), active: 0 }));
1034
+ const allWorkers = /* @__PURE__ */ new Set();
1035
+ function slotIndexFor(id) {
1036
+ let hash = 2166136261;
1037
+ for (let i = 0; i < id.length; i++) {
1038
+ hash ^= id.charCodeAt(i);
1039
+ hash = Math.imul(hash, 16777619);
1040
+ }
1041
+ return (hash >>> 0) % MAX_POOL_SIZE;
1042
+ }
1043
+ function spawnWorker(index) {
1044
+ const worker = new Worker(workerFilePathResolved, { name: `pug-worker-${index}` });
1045
+ allWorkers.add(worker);
1046
+ return worker;
1047
+ }
1048
+ function runOnWorker(worker, input) {
1049
+ return new Promise((resolve, reject) => {
1050
+ function cleanup() {
1051
+ worker.off("message", onMessage);
1052
+ worker.off("error", onError);
1053
+ }
1054
+ function onMessage(result) {
1055
+ cleanup();
1056
+ resolve(result);
1057
+ }
1058
+ function onError(error) {
1059
+ cleanup();
1060
+ reject(error);
1061
+ }
1062
+ worker.once("message", onMessage);
1063
+ worker.once("error", onError);
1064
+ worker.postMessage(input);
1065
+ });
1066
+ }
1067
+ function runPinned(id, input) {
1068
+ const slot = slots[slotIndexFor(id)];
1069
+ slot.active++;
1070
+ const run = slot.tail.then(async () => {
1071
+ if (!slot.worker)
1072
+ slot.worker = spawnWorker(slots.indexOf(slot));
1073
+ const worker = slot.worker;
1074
+ worker.ref();
1075
+ try {
1076
+ return await runOnWorker(worker, input);
1077
+ } catch (error) {
1078
+ allWorkers.delete(worker);
1079
+ void worker.terminate().catch(() => {
1080
+ });
1081
+ if (slot.worker === worker)
1082
+ slot.worker = void 0;
1083
+ throw error;
1084
+ } finally {
1085
+ slot.active--;
1086
+ if (slot.active === 0)
1087
+ slot.worker?.unref();
1088
+ }
1089
+ });
1090
+ slot.tail = run.then(() => {
1091
+ }, () => {
1092
+ });
1093
+ return run;
1094
+ }
1095
+ function getPugPoolInfo() {
1096
+ return { cpus: os.cpus().length, maxPoolSize: MAX_POOL_SIZE, workers: allWorkers.size };
1097
+ }
1098
+ async function terminatePugWorkers() {
1099
+ const workers = [...allWorkers];
1100
+ allWorkers.clear();
1101
+ for (const slot of slots) {
1102
+ slot.worker = void 0;
1103
+ slot.tail = Promise.resolve();
1104
+ slot.active = 0;
1105
+ }
1106
+ await Promise.all(workers.map((worker) => worker.terminate()));
1037
1107
  }
1038
1108
  const signals = ["SIGINT", "SIGTERM", "SIGQUIT"];
1039
- signals.forEach((signal) => process.on(signal, async () => await terminateAllWorkers()));
1109
+ signals.forEach((signal) => process.on(signal, () => {
1110
+ void terminatePugWorkers();
1111
+ }));
1040
1112
  async function storeResult(id, markupSource, html, dependencies) {
1041
1113
  graph.setDependencies(id, dependencies);
1042
1114
  const depSignatures = await computeDepSignatures(dependencies);
@@ -1046,14 +1118,16 @@ function recordFailure(mode, repository, errors, pugError) {
1046
1118
  logger.error(`Pug markup failed to compile for section "${pugError.id}": ${pugError.message}`);
1047
1119
  errors.push(pugError);
1048
1120
  if (mode === "development") {
1049
- repository.set(pugError.id, { markup: renderPugErrorOverlay(pugError) });
1121
+ const lastHtml = cache.get(pugError.id)?.compiledHtml ?? "";
1122
+ repository.set(pugError.id, { markup: renderPugErrorOverlay(pugError, lastHtml) });
1050
1123
  }
1051
1124
  }
1052
1125
  async function compileInline(mode, contentDir, repository, id, errors) {
1053
1126
  const markupSource = repository.get(id).markup;
1054
1127
  try {
1055
- const { html, dependencies } = await compileMarkup(contentDir, mode, markupSource, id);
1128
+ const { html, dependencies, timings } = await compileMarkup(contentDir, mode, markupSource, id);
1056
1129
  repository.set(id, { markup: html });
1130
+ accumulateTimings(timings);
1057
1131
  await storeResult(id, markupSource, html, dependencies);
1058
1132
  } catch (error) {
1059
1133
  const message = error instanceof Error ? error.message : String(error);
@@ -1062,39 +1136,16 @@ async function compileInline(mode, contentDir, repository, id, errors) {
1062
1136
  }
1063
1137
  }
1064
1138
  async function compileViaWorkerPool(mode, contentDir, repository, ids, errors) {
1065
- const poolSize = Math.min(ids.length, MAX_POOL_SIZE);
1066
- const waiters = [];
1067
- const freeWorkers = [];
1068
- function releaseWorker(worker) {
1069
- const waiter = waiters.shift();
1070
- if (waiter) {
1071
- waiter(worker);
1072
- } else {
1073
- freeWorkers.push(worker);
1074
- }
1075
- }
1076
- function acquireWorker() {
1077
- const worker = freeWorkers.pop();
1078
- if (worker) {
1079
- return Promise.resolve(worker);
1080
- }
1081
- return new Promise((resolve) => waiters.push(resolve));
1082
- }
1083
- workerPool = Array.from({ length: poolSize }, (_, index) => {
1084
- const worker = new Worker(workerFilePathResolved, {
1085
- name: `pug-worker-${index}`
1086
- });
1087
- freeWorkers.push(worker);
1088
- return worker;
1089
- });
1090
1139
  const tasks = ids.map(async (id) => {
1091
1140
  const markupSource = repository.get(id).markup;
1092
- const worker = await acquireWorker();
1093
- const result = await new Promise((resolve) => {
1094
- worker.once("message", resolve);
1095
- worker.postMessage({ id, mode, html: markupSource, contentDir });
1096
- });
1097
- releaseWorker(worker);
1141
+ let result;
1142
+ try {
1143
+ result = await runPinned(id, { id, mode, html: markupSource, contentDir });
1144
+ } catch (error) {
1145
+ const message = error instanceof Error ? error.message : String(error);
1146
+ recordFailure(mode, repository, errors, { id, message });
1147
+ return;
1148
+ }
1098
1149
  if ("error" in result) {
1099
1150
  recordFailure(mode, repository, errors, {
1100
1151
  id,
@@ -1104,10 +1155,10 @@ async function compileViaWorkerPool(mode, contentDir, repository, ids, errors) {
1104
1155
  return;
1105
1156
  }
1106
1157
  repository.set(id, { markup: result.html });
1158
+ accumulateTimings(result.timings);
1107
1159
  await storeResult(id, markupSource, result.html, result.dependencies);
1108
1160
  });
1109
1161
  await Promise.all(tasks);
1110
- await terminateAllWorkers();
1111
1162
  }
1112
1163
  async function compileIds(mode, contentDir, repository, ids) {
1113
1164
  const clonedRepository = new Map(repository);
@@ -1129,6 +1180,7 @@ async function compileIds(mode, contentDir, repository, ids) {
1129
1180
  if (misses.length === 0)
1130
1181
  return clonedRepository;
1131
1182
  const errors = [];
1183
+ lastCompileTimings = { read: 0, parse: 0, render: 0, a11y: 0, sections: 0 };
1132
1184
  const useInline = mode === "development" && misses.length <= INLINE_THRESHOLD;
1133
1185
  if (useInline) {
1134
1186
  await Promise.all(misses.map((id) => compileInline(mode, contentDir, clonedRepository, id, errors)));
@@ -1189,7 +1241,7 @@ async function generateFullPageFile(data) {
1189
1241
  <meta name="theme-color" media="(prefers-color-scheme: light)" content="${data.theme.light}">
1190
1242
  <meta name="theme-color" media="(prefers-color-scheme: dark)" content="${data.theme.dark}">
1191
1243
  ` : `<meta name="theme-color" content="${data.theme}">`}
1192
- <script type="module" src="/styleguide-assets/fullpage-CVmYa-FG.js"><\/script>
1244
+ <script type="module" src="/styleguide-assets/fullpage-Bm_FdpPi.js"><\/script>
1193
1245
  ${computedStyleTags}
1194
1246
  </head>
1195
1247
  <body${data.page.bodyclass ? ` class="${data.page.bodyclass}"` : ""}>
@@ -1511,6 +1563,7 @@ ${html ?? ""}
1511
1563
  `;
1512
1564
  }
1513
1565
  function getMainContentRegular(section, config) {
1566
+ const sourceCode = stripPugErrorOverlay(section.markup);
1514
1567
  const openInEditorPaths = {};
1515
1568
  if (config.mode === "development" && config.launchInEditor) {
1516
1569
  const computedRootPath = config.launchInEditor && typeof config.launchInEditor === "object" && "rootDir" in config.launchInEditor ? config.launchInEditor.rootDir : process.cwd();
@@ -1612,7 +1665,7 @@ function getMainContentRegular(section, config) {
1612
1665
  <button
1613
1666
  class="inline-flex items-center gap-1.5 p-4 cursor-pointer active:scale-90 transition hover:text-styleguide-highlight duration-200"
1614
1667
  type="button"
1615
- data-clipboard-value="${encodeURIComponent(section.markup)}"
1668
+ data-clipboard-value="${encodeURIComponent(sourceCode)}"
1616
1669
  data-clipboard-uri-encoded="true"
1617
1670
  >
1618
1671
  <svg class="h-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
@@ -1655,7 +1708,7 @@ function getMainContentRegular(section, config) {
1655
1708
  <div
1656
1709
  id="code-fullpage-${sectionSanitizeId(section.id)}"
1657
1710
  class="overflow-x-auto w-full code-highlight"
1658
- data-source-code="${encodeURIComponent(section.markup)}"
1711
+ data-source-code="${encodeURIComponent(sourceCode)}"
1659
1712
  data-source-lang="html"
1660
1713
  ></div>
1661
1714
  </div>
@@ -1992,7 +2045,7 @@ async function generatePreviewFile(data) {
1992
2045
  <meta name="theme-color" media="(prefers-color-scheme: light)" content="${data.theme.light}">
1993
2046
  <meta name="theme-color" media="(prefers-color-scheme: dark)" content="${data.theme.dark}">
1994
2047
  ` : `<meta name="theme-color" content="${data.theme}">`}
1995
- <link rel="stylesheet" type="text/css" href="/styleguide-assets/kss-modern-CjPjhHw0.css">
2048
+ <link rel="stylesheet" type="text/css" href="/styleguide-assets/kss-modern-DW2ZdZvd.css">
1996
2049
  <script>
1997
2050
  //#region client/lib/iframe-resize.ts
1998
2051
  var e = [
@@ -2145,7 +2198,7 @@ document.addEventListener("DOMContentLoaded", () => {
2145
2198
  //#endregion
2146
2199
  ;
2147
2200
  <\/script>
2148
- <script type="module" src="/styleguide-assets/preview-8L8aaFj4.js"><\/script>
2201
+ <script type="module" src="/styleguide-assets/preview-w33XyCLZ.js"><\/script>
2149
2202
  <link rel="preload" href="/styleguide-assets/fonts/geist-mono-latin-400-normal.woff2" as="font" type="font/woff2" crossorigin="anonymous">
2150
2203
  <link rel="preload" href="/styleguide-assets/fonts/geist-mono-latin-600-normal.woff2" as="font" type="font/woff2" crossorigin="anonymous">
2151
2204
  <link rel="preload" href="/styleguide-assets/fonts/geist-mono-latin-300-normal.woff2" as="font" type="font/woff2" crossorigin="anonymous">
@@ -2568,17 +2621,21 @@ async function rebuildSections(config, context, changedSectionIds) {
2568
2621
  }
2569
2622
  if (subset.size === 0)
2570
2623
  return;
2624
+ const startedAt = performance.now();
2625
+ const workersBefore = getPugPoolInfo().workers;
2571
2626
  const recompiled = await compilePugMarkupIncremental(config.mode, config.contentDir, subset, subset.keys());
2572
2627
  for (const [id, entry] of recompiled) {
2573
2628
  context.compiledRepository.set(id, { markup: entry.markup });
2574
2629
  reindexInsertMarkupConsumer(context, id);
2575
2630
  }
2631
+ const afterCompile = performance.now();
2576
2632
  const resolved = resolveInsertMarkupForSections(context.compiledRepository, context.sectionsById, affected);
2577
2633
  for (const [id, markup] of resolved) {
2578
2634
  const node = context.nodeById.get(id);
2579
2635
  if (node)
2580
2636
  node.markup = markup;
2581
2637
  }
2638
+ const afterResolve = performance.now();
2582
2639
  const writeTasks = [];
2583
2640
  const previewsToWrite = /* @__PURE__ */ new Set();
2584
2641
  for (const id of affected) {
@@ -2596,6 +2653,16 @@ async function rebuildSections(config, context, changedSectionIds) {
2596
2653
  writeTasks.push(writePreviewFile(config, context, firstLevelIndex, secondLevelIndex));
2597
2654
  }
2598
2655
  await Promise.all(writeTasks);
2656
+ const afterWrite = performance.now();
2657
+ if (config.mode === "development") {
2658
+ const ms = (value) => `${value.toFixed(1)}ms`;
2659
+ const pool = getPugPoolInfo();
2660
+ const spawned = pool.workers - workersBefore;
2661
+ const t = getLastCompileTimings();
2662
+ logger.info(
2663
+ `Incremental rebuild: ${affected.size} section(s), ${writeTasks.length} file(s) in ${ms(afterWrite - startedAt)} (compile ${ms(afterCompile - startedAt)}, insert-markup ${ms(afterResolve - afterCompile)}, write ${ms(afterWrite - afterResolve)}) [cpus=${pool.cpus}, pool=${pool.maxPoolSize}, workers=${pool.workers}${spawned > 0 ? `, +${spawned} cold-spawned` : ""}] compile breakdown over ${t.sections} section(s): read ${ms(t.read)}, parse ${ms(t.parse)}, render ${ms(t.render)}, a11y ${ms(t.a11y)} (summed per-section; read=disk I/O, parse=lex/parse/codegen)`
2664
+ );
2665
+ }
2599
2666
  }
2600
2667
  async function watchStyleguide(config, onChange, onError) {
2601
2668
  const initialBuild = await buildAll(config);
@@ -1,6 +1,17 @@
1
1
  import { StyleguideConfiguration } from '../index.mjs';
2
2
 
3
3
  type Mode = StyleguideConfiguration['mode'];
4
+ /** Where a section's compile time went, summed over the entry file and any <insert-vite-pug> tags. */
5
+ interface CompileTimings {
6
+ /** Reading the entry file + all includes/extends off disk (file I/O only). */
7
+ read: number;
8
+ /** pug lex + parse + codegen (CPU; excludes the include reads). */
9
+ parse: number;
10
+ /** Executing the compiled template function (pure render). */
11
+ render: number;
12
+ /** `fixAccessibilityIssues` post-processing. */
13
+ a11y: number;
14
+ }
4
15
 
5
16
  interface PugWorkerInput {
6
17
  id: string;
@@ -12,6 +23,7 @@ interface PugWorkerSuccess {
12
23
  id: string;
13
24
  html: string;
14
25
  dependencies: string[];
26
+ timings: CompileTimings;
15
27
  }
16
28
  interface PugWorkerError {
17
29
  id: string;
@@ -1,6 +1,17 @@
1
1
  import { StyleguideConfiguration } from '../index.js';
2
2
 
3
3
  type Mode = StyleguideConfiguration['mode'];
4
+ /** Where a section's compile time went, summed over the entry file and any <insert-vite-pug> tags. */
5
+ interface CompileTimings {
6
+ /** Reading the entry file + all includes/extends off disk (file I/O only). */
7
+ read: number;
8
+ /** pug lex + parse + codegen (CPU; excludes the include reads). */
9
+ parse: number;
10
+ /** Executing the compiled template function (pure render). */
11
+ render: number;
12
+ /** `fixAccessibilityIssues` post-processing. */
13
+ a11y: number;
14
+ }
4
15
 
5
16
  interface PugWorkerInput {
6
17
  id: string;
@@ -12,6 +23,7 @@ interface PugWorkerSuccess {
12
23
  id: string;
13
24
  html: string;
14
25
  dependencies: string[];
26
+ timings: CompileTimings;
15
27
  }
16
28
  interface PugWorkerError {
17
29
  id: string;
@@ -1,11 +1,13 @@
1
1
  import { parentPort } from 'node:worker_threads';
2
- import { c as compileMarkup, p as pugErrorFile } from '../../shared/kss-modern.C3thzyxO.mjs';
2
+ import { c as compileMarkup, p as pugErrorFile } from '../../shared/kss-modern.o1Fp758z.mjs';
3
+ import 'node:fs';
3
4
  import 'node:fs/promises';
4
5
  import 'node:path';
5
- import '@biomejs/js-api';
6
+ import 'node:perf_hooks';
6
7
  import 'pug';
7
8
  import 'consola';
8
9
  import 'consola/utils';
10
+ import 'pug-load';
9
11
 
10
12
  if (!parentPort) {
11
13
  throw new Error("This file must be run as a worker thread");
@@ -13,8 +15,8 @@ if (!parentPort) {
13
15
  parentPort.on("message", async (data) => {
14
16
  const { id, mode, html, contentDir } = data;
15
17
  try {
16
- const { html: compiled, dependencies } = await compileMarkup(contentDir, mode, html, id);
17
- parentPort.postMessage({ id, html: compiled, dependencies });
18
+ const { html: compiled, dependencies, timings } = await compileMarkup(contentDir, mode, html, id);
19
+ parentPort.postMessage({ id, html: compiled, dependencies, timings });
18
20
  } catch (error) {
19
21
  const message = error instanceof Error ? error.message : String(error);
20
22
  parentPort.postMessage({ id, error: message, file: pugErrorFile(error) });
@@ -1,9 +1,11 @@
1
+ import { statSync, readFileSync } from 'node:fs';
1
2
  import fs from 'node:fs/promises';
2
3
  import path from 'node:path';
3
- import { Biome, Distribution } from '@biomejs/js-api';
4
+ import { performance } from 'node:perf_hooks';
4
5
  import pug from 'pug';
5
6
  import { createConsola } from 'consola';
6
7
  import { colors } from 'consola/utils';
8
+ import pugLoad from 'pug-load';
7
9
 
8
10
  const sectionSanitizeId = (id2) => id2.toLowerCase().replaceAll(".", "-");
9
11
 
@@ -25,6 +27,11 @@ const logger = createLogger();
25
27
  const INSERT_VITE_PUG_TAG_RE = /<insert-vite-pug src="(.+?)".*(?:[\n\r\u2028\u2029]\s*)?(modifierClass="(.+?)")? *><\/insert-vite-pug>/g;
26
28
  const PUG_SRC_RE = /src="(.+?)"/;
27
29
  const PUG_MODIFIER_CLASS_RE = /modifierClass="(.+?)"/;
30
+ const PUG_ERROR_OVERLAY_TAG = "pug-error-overlay";
31
+ const PUG_ERROR_OVERLAY_RE = new RegExp(`<${PUG_ERROR_OVERLAY_TAG}\\b[^>]*></${PUG_ERROR_OVERLAY_TAG}>`, "g");
32
+ function stripPugErrorOverlay(html) {
33
+ return html.replace(PUG_ERROR_OVERLAY_RE, "");
34
+ }
28
35
  function fixAccessibilityIssues(html) {
29
36
  let parsedMarkup = html;
30
37
  const omitValue = [
@@ -80,9 +87,46 @@ function generateId() {
80
87
  return value;
81
88
  }
82
89
 
90
+ const parseCache = /* @__PURE__ */ new Map();
91
+ function signatureOf(filename) {
92
+ try {
93
+ const stat = statSync(filename);
94
+ return `${stat.mtimeMs}:${stat.size}`;
95
+ } catch {
96
+ return null;
97
+ }
98
+ }
99
+ let installed = false;
100
+ const originalLoadString = pugLoad.string;
101
+ function installPugParseCache() {
102
+ if (installed)
103
+ return;
104
+ installed = true;
105
+ pugLoad.string = function cachedLoadString(src, options) {
106
+ const filename = options?.filename;
107
+ if (filename && typeof options.lex === "function" && typeof options.parse === "function") {
108
+ const sig = signatureOf(filename);
109
+ if (sig !== null) {
110
+ let parsed;
111
+ const cached = parseCache.get(filename);
112
+ if (cached && cached.sig === sig) {
113
+ parsed = cached.ast;
114
+ } else {
115
+ parsed = options.parse(options.lex(src, options), options);
116
+ parseCache.set(filename, { sig, ast: parsed });
117
+ }
118
+ return pugLoad(parsed, options);
119
+ }
120
+ }
121
+ return originalLoadString(src, options);
122
+ };
123
+ }
124
+
125
+ installPugParseCache();
83
126
  let biomePromise;
84
127
  function getBiome() {
85
128
  biomePromise ??= (async () => {
129
+ const { Biome, Distribution } = await import('@biomejs/js-api');
86
130
  const biome = await Biome.create({ distribution: Distribution.NODE });
87
131
  const { projectKey } = biome.openProject(".");
88
132
  biome.applyConfiguration(projectKey, {
@@ -118,7 +162,7 @@ async function biomeFormat(content) {
118
162
  }
119
163
  }
120
164
  function createUseId(sectionId) {
121
- const namespace = sectionSanitizeId(sectionId);
165
+ const namespace = `id-${sectionSanitizeId(sectionId)}`;
122
166
  let autoCounter = 0;
123
167
  return (key) => {
124
168
  if (key === void 0) {
@@ -132,24 +176,50 @@ function pugErrorFile(error) {
132
176
  const candidate = error;
133
177
  return candidate?.path ?? candidate?.filename;
134
178
  }
179
+ let includeReadMs = 0;
180
+ function timedRead(filePath) {
181
+ const t0 = performance.now();
182
+ const content = readFileSync(filePath, "utf8");
183
+ includeReadMs += performance.now() - t0;
184
+ return content;
185
+ }
135
186
  function compilePugFile(pugFilePath, mode, sectionId, extraLocals = {}) {
136
- const pugFn = pug.compileFile(pugFilePath, {
187
+ const tRead0 = performance.now();
188
+ const source = readFileSync(pugFilePath, "utf8");
189
+ let readMs = performance.now() - tRead0;
190
+ const includeReadBefore = includeReadMs;
191
+ const tParse0 = performance.now();
192
+ const pugFn = pug.compile(source, {
193
+ filename: pugFilePath,
137
194
  // define doctype to avoid self-closing tags on wrong places
138
195
  doctype: "html",
139
196
  // pretty output in dev keeps the "Show code" view readable without Biome
140
- pretty: mode === "development"
197
+ pretty: mode === "development",
198
+ // pug defaults this to true, which wraps the generated function in per-line try/catch +
199
+ // line-mapping — measurably bloating codegen and `new Function`. We surface compile errors via
200
+ // our own overlay, so the precise pug line in a runtime stack isn't worth the cost.
201
+ compileDebug: false,
202
+ plugins: [{ read: timedRead }]
141
203
  });
204
+ const includeReads = includeReadMs - includeReadBefore;
205
+ const parseMs = performance.now() - tParse0 - includeReads;
206
+ readMs += includeReads;
142
207
  const dependencies = [path.resolve(pugFilePath), ...pugFn.dependencies.map((dep) => path.resolve(dep))];
208
+ const tRender0 = performance.now();
143
209
  const html = pugFn({ ...extraLocals, useId: createUseId(sectionId) });
144
- return { html, dependencies };
210
+ const renderMs = performance.now() - tRender0;
211
+ return { html, dependencies, readMs, parseMs, renderMs };
145
212
  }
146
213
  async function expandVitePugTags(contentDir, mode, html, sectionId) {
147
214
  const vitePugTags = html.match(INSERT_VITE_PUG_TAG_RE);
148
215
  if (!vitePugTags) {
149
- return { html, dependencies: [] };
216
+ return { html, dependencies: [], timings: { read: 0, parse: 0, render: 0, a11y: 0 } };
150
217
  }
151
218
  let markupOutput = html;
152
219
  const dependencies = [];
220
+ let read = 0;
221
+ let parse = 0;
222
+ let render = 0;
153
223
  await Promise.all(vitePugTags.map(async (vitePugTag) => {
154
224
  const pugSourcePath = vitePugTag.match(PUG_SRC_RE)?.[1];
155
225
  if (!pugSourcePath) {
@@ -162,35 +232,51 @@ async function expandVitePugTags(contentDir, mode, html, sectionId) {
162
232
  const pugModifierClass = vitePugTag.match(PUG_MODIFIER_CLASS_RE);
163
233
  const pugLocals = pugModifierClass && pugModifierClass[1] ? { modifierClass: pugModifierClass[1] } : {};
164
234
  const pugFilePath = path.join(contentDir, pugSourcePath);
165
- const { html: pugOutput, dependencies: pugDependencies } = compilePugFile(pugFilePath, mode, sectionId, pugLocals);
235
+ const { html: pugOutput, dependencies: pugDependencies, readMs, parseMs, renderMs } = compilePugFile(pugFilePath, mode, sectionId, pugLocals);
166
236
  dependencies.push(...pugDependencies);
237
+ read += readMs;
238
+ parse += parseMs;
239
+ render += renderMs;
167
240
  markupOutput = markupOutput.replaceAll(vitePugTag, pugOutput);
168
241
  }));
169
- return { html: markupOutput, dependencies };
242
+ return { html: markupOutput, dependencies, timings: { read, parse, render, a11y: 0 } };
170
243
  }
171
244
  async function compileMarkup(contentDir, mode, html, sectionId) {
172
245
  let result = html;
173
246
  const dependencies = [];
247
+ let read = 0;
248
+ let parse = 0;
249
+ let render = 0;
174
250
  const trimmed = html.trim();
175
251
  const isBarePath = !trimmed.includes("<") && !trimmed.includes("\n");
176
252
  if (isBarePath && trimmed.endsWith(".html")) {
177
253
  const htmlFilePath = path.join(contentDir, trimmed);
254
+ const t0 = performance.now();
178
255
  result = await fs.readFile(htmlFilePath, "utf-8");
256
+ read += performance.now() - t0;
179
257
  dependencies.push(path.resolve(htmlFilePath));
180
258
  } else if (isBarePath && trimmed.endsWith(".pug")) {
181
259
  const pugFilePath = path.join(contentDir, trimmed);
182
260
  const compiled = compilePugFile(pugFilePath, mode, sectionId);
183
261
  result = compiled.html;
184
262
  dependencies.push(...compiled.dependencies);
263
+ read += compiled.readMs;
264
+ parse += compiled.parseMs;
265
+ render += compiled.renderMs;
185
266
  }
186
267
  const expanded = await expandVitePugTags(contentDir, mode, result, sectionId);
187
268
  result = expanded.html;
188
269
  dependencies.push(...expanded.dependencies);
270
+ read += expanded.timings.read;
271
+ parse += expanded.timings.parse;
272
+ render += expanded.timings.render;
189
273
  if (mode === "production") {
190
274
  result = await biomeFormat(result);
191
275
  }
276
+ const tA11y = performance.now();
192
277
  result = fixAccessibilityIssues(result);
193
- return { html: result, dependencies: [...new Set(dependencies)] };
278
+ const a11y = performance.now() - tA11y;
279
+ return { html: result, dependencies: [...new Set(dependencies)], timings: { read, parse, render, a11y } };
194
280
  }
195
281
 
196
- export { INSERT_VITE_PUG_TAG_RE as I, PUG_SRC_RE as P, sectionSanitizeId as a, createLogger as b, compileMarkup as c, ensureStartingSlash as e, generateId as g, logger as l, pugErrorFile as p, replaceWrapperContent as r, sanitizeSpecialCharacters as s };
282
+ export { INSERT_VITE_PUG_TAG_RE as I, PUG_SRC_RE as P, PUG_ERROR_OVERLAY_TAG as a, sectionSanitizeId as b, compileMarkup as c, stripPugErrorOverlay as d, ensureStartingSlash as e, createLogger as f, generateId as g, logger as l, pugErrorFile as p, replaceWrapperContent as r, sanitizeSpecialCharacters as s };