kss-modern 1.2.0-beta.1 → 1.2.0-beta.3

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 (23) hide show
  1. package/dist/node/lib/index.mjs +157 -68
  2. package/dist/node/lib/pug/worker.d.mts +12 -0
  3. package/dist/node/lib/pug/worker.d.ts +12 -0
  4. package/dist/node/lib/pug/worker.mjs +6 -4
  5. package/dist/node/shared/{kss-modern.C3thzyxO.mjs → kss-modern.o1Fp758z.mjs} +96 -10
  6. package/dist/styleguide-assets/.vite/manifest.json +36 -23
  7. package/dist/styleguide-assets/{animate-BwJZPSbF.js → animate-DRGZltJ3.js} +1223 -1237
  8. package/dist/styleguide-assets/assets/worker-CejjK-QF.js +152 -0
  9. package/dist/styleguide-assets/{axe-BUK9a7nX.js → axe-B09yVm_c.js} +103 -105
  10. package/dist/styleguide-assets/{browser-bCxY1z1G.js → browser-BgMZpraI.js} +8 -8
  11. package/dist/styleguide-assets/{clipboard-BqN2XQ0C.js → clipboard-0X6xnPVo.js} +1 -1
  12. package/dist/styleguide-assets/{code-highlight-ClkzHTGA.js → code-highlight-BygtMiLu.js} +1 -1
  13. package/dist/styleguide-assets/fullpage-CzaJFxfS.js +177 -0
  14. package/dist/styleguide-assets/{html-validator-Cv_-JCAq.js → html-validator-CeH3wXbU.js} +23 -32
  15. package/dist/styleguide-assets/{icons-DICmq5xt.js → icons-Do1H6FzJ.js} +1 -1
  16. package/dist/styleguide-assets/kss-modern-DW2ZdZvd.css +3 -0
  17. package/dist/styleguide-assets/{preview-8L8aaFj4.js → preview-ChQyjhUb.js} +5 -5
  18. package/dist/styleguide-assets/rolldown-runtime-B1bRi_D7.js +27 -0
  19. package/dist/styleguide-assets/shared-hGcWlnRM.js +15 -0
  20. package/package.json +12 -9
  21. package/dist/styleguide-assets/assets/worker-N4ARB4CK.js +0 -12
  22. package/dist/styleguide-assets/fullpage-CVmYa-FG.js +0 -127
  23. package/dist/styleguide-assets/kss-modern-CjPjhHw0.css +0 -3
@@ -1,23 +1,32 @@
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';
9
+ import htmlLang from '@shikijs/langs/html';
10
+ import markdownLang from '@shikijs/langs/markdown';
11
+ import scssLang from '@shikijs/langs/scss';
12
+ import shellscriptLang from '@shikijs/langs/shellscript';
13
+ import typescriptLang from '@shikijs/langs/typescript';
8
14
  import { fromAsyncCodeToHtml } from '@shikijs/markdown-it/async';
15
+ import auroraX from '@shikijs/themes/aurora-x';
16
+ import githubLightDefault from '@shikijs/themes/github-light-default';
9
17
  import MarkdownItAsync from 'markdown-it-async';
10
- import { codeToHtml } from 'shiki';
18
+ import { createHighlighterCore } from 'shiki/core';
19
+ import { createJavaScriptRegexEngine } from 'shiki/engine/javascript';
11
20
  import os from 'node:os';
12
21
  import { Worker } from 'node:worker_threads';
13
22
  import fs$1 from 'node:fs/promises';
14
23
  import { objectEntries } from '@antfu/utils';
15
24
  import { readFileSync } from 'node:fs';
16
25
  import chokidar from 'chokidar';
17
- import '@biomejs/js-api';
18
26
  import 'pug';
19
27
  import 'consola';
20
28
  import 'consola/utils';
29
+ import 'pug-load';
21
30
 
22
31
  const HEX_COLOR_RE = /^#[0-9A-F]{6}$/i;
23
32
  function isValidHexColor(hexCode) {
@@ -349,6 +358,20 @@ function markdownItComponent(md, options) {
349
358
  }
350
359
 
351
360
  let md;
361
+ let highlighter;
362
+ let loadedLanguages;
363
+ async function highlightCodeToHtml(code, options) {
364
+ if (!highlighter) {
365
+ highlighter = await createHighlighterCore({
366
+ themes: [githubLightDefault, auroraX],
367
+ langs: [htmlLang, scssLang, typescriptLang, shellscriptLang, markdownLang],
368
+ engine: createJavaScriptRegexEngine()
369
+ });
370
+ loadedLanguages = new Set(highlighter.getLoadedLanguages());
371
+ }
372
+ const lang = loadedLanguages.has(options.lang) ? options.lang : "text";
373
+ return highlighter.codeToHtml(code, { ...options, lang });
374
+ }
352
375
  function shiftHeadingLevels(markdownContent, rootHeadingLevel) {
353
376
  const getHasHeadingLevel = (level) => new RegExp(`^#{${level}} `, "m").test(markdownContent);
354
377
  const shiftDown = (shiftAmount) => {
@@ -379,7 +402,7 @@ async function parseMarkdown(data) {
379
402
  });
380
403
  md.use(
381
404
  fromAsyncCodeToHtml(
382
- codeToHtml,
405
+ highlightCodeToHtml,
383
406
  {
384
407
  themes: {
385
408
  light: "github-light-default",
@@ -998,45 +1021,116 @@ class PugDependencyGraph {
998
1021
  }
999
1022
  }
1000
1023
 
1001
- function renderPugErrorOverlay(error) {
1024
+ function renderPugErrorOverlay(error, lastHtml = "") {
1002
1025
  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();
1026
+ const message = escape(error.message).replace(/\r?\n/g, "&#10;");
1027
+ const fileAttr = error.file ? ` error-file="${escape(error.file)}"` : "";
1028
+ const emptyAttr = lastHtml ? "" : " data-empty";
1029
+ const overlay = `<${PUG_ERROR_OVERLAY_TAG} error-id="${id}"${fileAttr} error-message="${message}"${emptyAttr}></${PUG_ERROR_OVERLAY_TAG}>`;
1030
+ return `${lastHtml}${overlay}`.trim();
1020
1031
  }
1021
1032
 
1022
- const MAX_POOL_SIZE = os.cpus().length;
1033
+ const MAX_POOL_SIZE = Math.max(2, os.cpus().length - 1);
1023
1034
  const INLINE_THRESHOLD = 2;
1024
1035
  const cache = new PugCompileCache();
1025
1036
  const graph = new PugDependencyGraph();
1026
1037
  function getPugDependencyGraph() {
1027
1038
  return graph;
1028
1039
  }
1040
+ let lastCompileTimings = { read: 0, parse: 0, render: 0, a11y: 0, sections: 0 };
1041
+ function accumulateTimings(timings) {
1042
+ lastCompileTimings.read += timings.read;
1043
+ lastCompileTimings.parse += timings.parse;
1044
+ lastCompileTimings.render += timings.render;
1045
+ lastCompileTimings.a11y += timings.a11y;
1046
+ lastCompileTimings.sections += 1;
1047
+ }
1048
+ function getLastCompileTimings() {
1049
+ return lastCompileTimings;
1050
+ }
1029
1051
  const __filename$1 = fileURLToPath(import.meta.url);
1030
1052
  const __dirname$1 = path.dirname(__filename$1);
1031
1053
  const workerFilePath = __dirname$1.includes("dist/") ? "./pug/worker.mjs" : "./worker.ts";
1032
1054
  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 = [];
1055
+ const slots = Array.from({ length: MAX_POOL_SIZE }, () => ({ tail: Promise.resolve(), active: 0 }));
1056
+ const allWorkers = /* @__PURE__ */ new Set();
1057
+ function slotIndexFor(id) {
1058
+ let hash = 2166136261;
1059
+ for (let i = 0; i < id.length; i++) {
1060
+ hash ^= id.charCodeAt(i);
1061
+ hash = Math.imul(hash, 16777619);
1062
+ }
1063
+ return (hash >>> 0) % MAX_POOL_SIZE;
1064
+ }
1065
+ function spawnWorker(index) {
1066
+ const worker = new Worker(workerFilePathResolved, { name: `pug-worker-${index}` });
1067
+ allWorkers.add(worker);
1068
+ return worker;
1069
+ }
1070
+ function runOnWorker(worker, input) {
1071
+ return new Promise((resolve, reject) => {
1072
+ function cleanup() {
1073
+ worker.off("message", onMessage);
1074
+ worker.off("error", onError);
1075
+ }
1076
+ function onMessage(result) {
1077
+ cleanup();
1078
+ resolve(result);
1079
+ }
1080
+ function onError(error) {
1081
+ cleanup();
1082
+ reject(error);
1083
+ }
1084
+ worker.once("message", onMessage);
1085
+ worker.once("error", onError);
1086
+ worker.postMessage(input);
1087
+ });
1088
+ }
1089
+ function runPinned(id, input) {
1090
+ const slot = slots[slotIndexFor(id)];
1091
+ slot.active++;
1092
+ const run = slot.tail.then(async () => {
1093
+ if (!slot.worker)
1094
+ slot.worker = spawnWorker(slots.indexOf(slot));
1095
+ const worker = slot.worker;
1096
+ worker.ref();
1097
+ try {
1098
+ return await runOnWorker(worker, input);
1099
+ } catch (error) {
1100
+ allWorkers.delete(worker);
1101
+ void worker.terminate().catch(() => {
1102
+ });
1103
+ if (slot.worker === worker)
1104
+ slot.worker = void 0;
1105
+ throw error;
1106
+ } finally {
1107
+ slot.active--;
1108
+ if (slot.active === 0)
1109
+ slot.worker?.unref();
1110
+ }
1111
+ });
1112
+ slot.tail = run.then(() => {
1113
+ }, () => {
1114
+ });
1115
+ return run;
1116
+ }
1117
+ function getPugPoolInfo() {
1118
+ return { cpus: os.cpus().length, maxPoolSize: MAX_POOL_SIZE, workers: allWorkers.size };
1119
+ }
1120
+ async function terminatePugWorkers() {
1121
+ const workers = [...allWorkers];
1122
+ allWorkers.clear();
1123
+ for (const slot of slots) {
1124
+ slot.worker = void 0;
1125
+ slot.tail = Promise.resolve();
1126
+ slot.active = 0;
1127
+ }
1128
+ await Promise.all(workers.map((worker) => worker.terminate()));
1037
1129
  }
1038
1130
  const signals = ["SIGINT", "SIGTERM", "SIGQUIT"];
1039
- signals.forEach((signal) => process.on(signal, async () => await terminateAllWorkers()));
1131
+ signals.forEach((signal) => process.on(signal, () => {
1132
+ void terminatePugWorkers();
1133
+ }));
1040
1134
  async function storeResult(id, markupSource, html, dependencies) {
1041
1135
  graph.setDependencies(id, dependencies);
1042
1136
  const depSignatures = await computeDepSignatures(dependencies);
@@ -1046,14 +1140,16 @@ function recordFailure(mode, repository, errors, pugError) {
1046
1140
  logger.error(`Pug markup failed to compile for section "${pugError.id}": ${pugError.message}`);
1047
1141
  errors.push(pugError);
1048
1142
  if (mode === "development") {
1049
- repository.set(pugError.id, { markup: renderPugErrorOverlay(pugError) });
1143
+ const lastHtml = cache.get(pugError.id)?.compiledHtml ?? "";
1144
+ repository.set(pugError.id, { markup: renderPugErrorOverlay(pugError, lastHtml) });
1050
1145
  }
1051
1146
  }
1052
1147
  async function compileInline(mode, contentDir, repository, id, errors) {
1053
1148
  const markupSource = repository.get(id).markup;
1054
1149
  try {
1055
- const { html, dependencies } = await compileMarkup(contentDir, mode, markupSource, id);
1150
+ const { html, dependencies, timings } = await compileMarkup(contentDir, mode, markupSource, id);
1056
1151
  repository.set(id, { markup: html });
1152
+ accumulateTimings(timings);
1057
1153
  await storeResult(id, markupSource, html, dependencies);
1058
1154
  } catch (error) {
1059
1155
  const message = error instanceof Error ? error.message : String(error);
@@ -1062,39 +1158,16 @@ async function compileInline(mode, contentDir, repository, id, errors) {
1062
1158
  }
1063
1159
  }
1064
1160
  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
1161
  const tasks = ids.map(async (id) => {
1091
1162
  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);
1163
+ let result;
1164
+ try {
1165
+ result = await runPinned(id, { id, mode, html: markupSource, contentDir });
1166
+ } catch (error) {
1167
+ const message = error instanceof Error ? error.message : String(error);
1168
+ recordFailure(mode, repository, errors, { id, message });
1169
+ return;
1170
+ }
1098
1171
  if ("error" in result) {
1099
1172
  recordFailure(mode, repository, errors, {
1100
1173
  id,
@@ -1104,10 +1177,10 @@ async function compileViaWorkerPool(mode, contentDir, repository, ids, errors) {
1104
1177
  return;
1105
1178
  }
1106
1179
  repository.set(id, { markup: result.html });
1180
+ accumulateTimings(result.timings);
1107
1181
  await storeResult(id, markupSource, result.html, result.dependencies);
1108
1182
  });
1109
1183
  await Promise.all(tasks);
1110
- await terminateAllWorkers();
1111
1184
  }
1112
1185
  async function compileIds(mode, contentDir, repository, ids) {
1113
1186
  const clonedRepository = new Map(repository);
@@ -1129,6 +1202,7 @@ async function compileIds(mode, contentDir, repository, ids) {
1129
1202
  if (misses.length === 0)
1130
1203
  return clonedRepository;
1131
1204
  const errors = [];
1205
+ lastCompileTimings = { read: 0, parse: 0, render: 0, a11y: 0, sections: 0 };
1132
1206
  const useInline = mode === "development" && misses.length <= INLINE_THRESHOLD;
1133
1207
  if (useInline) {
1134
1208
  await Promise.all(misses.map((id) => compileInline(mode, contentDir, clonedRepository, id, errors)));
@@ -1189,7 +1263,7 @@ async function generateFullPageFile(data) {
1189
1263
  <meta name="theme-color" media="(prefers-color-scheme: light)" content="${data.theme.light}">
1190
1264
  <meta name="theme-color" media="(prefers-color-scheme: dark)" content="${data.theme.dark}">
1191
1265
  ` : `<meta name="theme-color" content="${data.theme}">`}
1192
- <script type="module" src="/styleguide-assets/fullpage-CVmYa-FG.js"><\/script>
1266
+ <script type="module" src="/styleguide-assets/fullpage-CzaJFxfS.js"><\/script>
1193
1267
  ${computedStyleTags}
1194
1268
  </head>
1195
1269
  <body${data.page.bodyclass ? ` class="${data.page.bodyclass}"` : ""}>
@@ -1511,6 +1585,7 @@ ${html ?? ""}
1511
1585
  `;
1512
1586
  }
1513
1587
  function getMainContentRegular(section, config) {
1588
+ const sourceCode = stripPugErrorOverlay(section.markup);
1514
1589
  const openInEditorPaths = {};
1515
1590
  if (config.mode === "development" && config.launchInEditor) {
1516
1591
  const computedRootPath = config.launchInEditor && typeof config.launchInEditor === "object" && "rootDir" in config.launchInEditor ? config.launchInEditor.rootDir : process.cwd();
@@ -1612,7 +1687,7 @@ function getMainContentRegular(section, config) {
1612
1687
  <button
1613
1688
  class="inline-flex items-center gap-1.5 p-4 cursor-pointer active:scale-90 transition hover:text-styleguide-highlight duration-200"
1614
1689
  type="button"
1615
- data-clipboard-value="${encodeURIComponent(section.markup)}"
1690
+ data-clipboard-value="${encodeURIComponent(sourceCode)}"
1616
1691
  data-clipboard-uri-encoded="true"
1617
1692
  >
1618
1693
  <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 +1730,7 @@ function getMainContentRegular(section, config) {
1655
1730
  <div
1656
1731
  id="code-fullpage-${sectionSanitizeId(section.id)}"
1657
1732
  class="overflow-x-auto w-full code-highlight"
1658
- data-source-code="${encodeURIComponent(section.markup)}"
1733
+ data-source-code="${encodeURIComponent(sourceCode)}"
1659
1734
  data-source-lang="html"
1660
1735
  ></div>
1661
1736
  </div>
@@ -1992,7 +2067,7 @@ async function generatePreviewFile(data) {
1992
2067
  <meta name="theme-color" media="(prefers-color-scheme: light)" content="${data.theme.light}">
1993
2068
  <meta name="theme-color" media="(prefers-color-scheme: dark)" content="${data.theme.dark}">
1994
2069
  ` : `<meta name="theme-color" content="${data.theme}">`}
1995
- <link rel="stylesheet" type="text/css" href="/styleguide-assets/kss-modern-CjPjhHw0.css">
2070
+ <link rel="stylesheet" type="text/css" href="/styleguide-assets/kss-modern-DW2ZdZvd.css">
1996
2071
  <script>
1997
2072
  //#region client/lib/iframe-resize.ts
1998
2073
  var e = [
@@ -2145,7 +2220,7 @@ document.addEventListener("DOMContentLoaded", () => {
2145
2220
  //#endregion
2146
2221
  ;
2147
2222
  <\/script>
2148
- <script type="module" src="/styleguide-assets/preview-8L8aaFj4.js"><\/script>
2223
+ <script type="module" src="/styleguide-assets/preview-ChQyjhUb.js"><\/script>
2149
2224
  <link rel="preload" href="/styleguide-assets/fonts/geist-mono-latin-400-normal.woff2" as="font" type="font/woff2" crossorigin="anonymous">
2150
2225
  <link rel="preload" href="/styleguide-assets/fonts/geist-mono-latin-600-normal.woff2" as="font" type="font/woff2" crossorigin="anonymous">
2151
2226
  <link rel="preload" href="/styleguide-assets/fonts/geist-mono-latin-300-normal.woff2" as="font" type="font/woff2" crossorigin="anonymous">
@@ -2568,17 +2643,21 @@ async function rebuildSections(config, context, changedSectionIds) {
2568
2643
  }
2569
2644
  if (subset.size === 0)
2570
2645
  return;
2646
+ const startedAt = performance.now();
2647
+ const workersBefore = getPugPoolInfo().workers;
2571
2648
  const recompiled = await compilePugMarkupIncremental(config.mode, config.contentDir, subset, subset.keys());
2572
2649
  for (const [id, entry] of recompiled) {
2573
2650
  context.compiledRepository.set(id, { markup: entry.markup });
2574
2651
  reindexInsertMarkupConsumer(context, id);
2575
2652
  }
2653
+ const afterCompile = performance.now();
2576
2654
  const resolved = resolveInsertMarkupForSections(context.compiledRepository, context.sectionsById, affected);
2577
2655
  for (const [id, markup] of resolved) {
2578
2656
  const node = context.nodeById.get(id);
2579
2657
  if (node)
2580
2658
  node.markup = markup;
2581
2659
  }
2660
+ const afterResolve = performance.now();
2582
2661
  const writeTasks = [];
2583
2662
  const previewsToWrite = /* @__PURE__ */ new Set();
2584
2663
  for (const id of affected) {
@@ -2596,6 +2675,16 @@ async function rebuildSections(config, context, changedSectionIds) {
2596
2675
  writeTasks.push(writePreviewFile(config, context, firstLevelIndex, secondLevelIndex));
2597
2676
  }
2598
2677
  await Promise.all(writeTasks);
2678
+ const afterWrite = performance.now();
2679
+ if (config.mode === "development") {
2680
+ const ms = (value) => `${value.toFixed(1)}ms`;
2681
+ const pool = getPugPoolInfo();
2682
+ const spawned = pool.workers - workersBefore;
2683
+ const t = getLastCompileTimings();
2684
+ logger.info(
2685
+ `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)`
2686
+ );
2687
+ }
2599
2688
  }
2600
2689
  async function watchStyleguide(config, onChange, onError) {
2601
2690
  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 };