@abreen/tada 1.17.0 → 1.18.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.
Files changed (107) hide show
  1. package/README.md +41 -7
  2. package/bin/tada.ts +23 -9
  3. package/bin/validators.ts +34 -1
  4. package/build/branding.ts +41 -0
  5. package/build/build-manifest.ts +10 -5
  6. package/build/{watch/compiler-types.d.ts → build-types.d.ts} +8 -5
  7. package/build/build-validation.ts +176 -0
  8. package/build/bundle.ts +75 -65
  9. package/build/columns-plugin.ts +27 -1
  10. package/build/config-loader.ts +3 -3
  11. package/build/custom-fonts.ts +8 -5
  12. package/build/deflist-id-plugin.ts +2 -2
  13. package/build/generate-favicon.ts +7 -6
  14. package/build/generate-fonts.ts +8 -15
  15. package/build/generate-katex-assets.ts +19 -17
  16. package/build/generate-web-app-manifest.ts +3 -11
  17. package/build/lodash-template.ts +14 -0
  18. package/build/output-publication.ts +238 -0
  19. package/build/pagefind.ts +49 -21
  20. package/build/pdf-text.ts +41 -1
  21. package/build/pipeline.ts +95 -101
  22. package/build/site-assets.ts +46 -0
  23. package/build/site-build.ts +267 -0
  24. package/build/source-model.ts +223 -229
  25. package/build/source-records.ts +69 -81
  26. package/build/template-globals.ts +3 -0
  27. package/build/templates.ts +12 -8
  28. package/build/toc-plugin.ts +21 -21
  29. package/build/types.d.ts +38 -38
  30. package/build/util.ts +0 -4
  31. package/build/utils/code.ts +51 -117
  32. package/build/utils/final-html.ts +171 -47
  33. package/build/utils/jdi-runner/LiterateRunner.java +9 -1
  34. package/build/utils/literate-java.ts +11 -7
  35. package/build/utils/markdown-partials.ts +4 -3
  36. package/build/utils/markdown.ts +6 -17
  37. package/build/utils/paths.ts +3 -0
  38. package/build/utils/plain-text.ts +18 -0
  39. package/build/utils/render.ts +197 -154
  40. package/build/utils/shiki-highlighter.ts +11 -0
  41. package/build/utils/source-template.ts +2 -2
  42. package/build/utils/trace-core.ts +8 -83
  43. package/build/utils/trace.ts +38 -95
  44. package/build/validate-config-links.ts +39 -6
  45. package/build/watch/build-incremental.ts +46 -103
  46. package/build/watch/compiler.ts +158 -37
  47. package/build/watch/engine.ts +207 -162
  48. package/build/watch/index.ts +9 -2
  49. package/build/watch/planner.ts +88 -144
  50. package/build/watch/runtime.ts +21 -34
  51. package/build/watch/types.d.ts +32 -64
  52. package/package.json +4 -5
  53. package/schema/site.schema.json +18 -2
  54. package/src/_content.scss +447 -0
  55. package/src/_fonts.scss +90 -0
  56. package/src/_layout.scss +50 -35
  57. package/src/appearance-picker/{style.scss → _index.scss} +1 -1
  58. package/src/code/{style.scss → _index.scss} +61 -0
  59. package/src/header/{_base.scss → _index.scss} +98 -6
  60. package/src/index.ts +1 -17
  61. package/src/navigate/{style.scss → _index.scss} +20 -0
  62. package/src/navigate/eligible.ts +0 -40
  63. package/src/navigate/index.ts +50 -5
  64. package/src/navigate/lifecycle.ts +19 -2
  65. package/src/navigate/runtime.ts +20 -4
  66. package/src/page-update/index.ts +33 -20
  67. package/src/print/{style.scss → _index.scss} +3 -1
  68. package/src/search/index.ts +30 -2
  69. package/src/slides/{style.scss → _index.scss} +5 -0
  70. package/src/style.scss +24 -536
  71. package/src/timezone/index.ts +29 -3
  72. package/src/timezone/time-format.ts +4 -0
  73. package/src/toc/index.ts +6 -5
  74. package/src/trace/index.ts +11 -1
  75. package/templates/_author.html +2 -2
  76. package/templates/_download.html +1 -1
  77. package/templates/_footer.html +1 -1
  78. package/templates/_heading.html +3 -3
  79. package/templates/_nav.html +2 -2
  80. package/templates/_page-bottom.html +6 -0
  81. package/templates/_top.html +18 -14
  82. package/templates/code.html +22 -21
  83. package/templates/default.html +12 -11
  84. package/templates/literate.html +6 -5
  85. package/build/copy.ts +0 -82
  86. package/build/generate-content-assets.ts +0 -121
  87. package/build/utils/content-files.ts +0 -170
  88. package/build/watch/assets.ts +0 -103
  89. package/build/watch/build-full.ts +0 -97
  90. package/build/watch/build-helpers.ts +0 -66
  91. package/build/watch/build-result.ts +0 -19
  92. package/build/watch/fs-commit.ts +0 -153
  93. package/build/watch/mutations.ts +0 -73
  94. package/build/watch/snapshot.ts +0 -201
  95. package/build/watch/validation.ts +0 -50
  96. package/src/code.scss +0 -60
  97. package/src/critical.scss +0 -5
  98. package/src/header/style.scss +0 -91
  99. /package/src/{literate.scss → _literate.scss} +0 -0
  100. /package/src/{material-symbols.scss → _material-symbols.scss} +0 -0
  101. /package/src/anchor/{style.scss → _index.scss} +0 -0
  102. /package/src/page-update/{style.scss → _index.scss} +0 -0
  103. /package/src/question/{style.scss → _index.scss} +0 -0
  104. /package/src/search/{style.scss → _index.scss} +0 -0
  105. /package/src/timezone/{style.scss → _index.scss} +0 -0
  106. /package/src/toc/{style.scss → _index.scss} +0 -0
  107. /package/src/trace/{style.scss → _index.scss} +0 -0
@@ -1,8 +1,7 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import _ from 'lodash';
3
+ import { compileTemplate } from '../lodash-template';
4
4
  import type MarkdownIt from 'markdown-it';
5
- import { stripHtml } from 'string-strip-html';
6
5
  import { makeLogger } from '../log';
7
6
  import { B } from '../colors';
8
7
  import createTemplateGlobals from '../template-globals';
@@ -28,9 +27,11 @@ import {
28
27
  import { extensionIsMarkdown } from './file-types';
29
28
  import { createTraceHelpers } from './trace';
30
29
  import { stripHtmlComments } from './html-comments';
30
+ import { htmlToPlainText } from './plain-text';
31
31
  import { finalizeHtmlPage } from './final-html';
32
32
  import {
33
33
  createApplyBasePath,
34
+ getProjectDir,
34
35
  normalizeOutputPath,
35
36
  toPosix,
36
37
  toUrlPath,
@@ -41,7 +42,7 @@ import { parseFrontMatterAndContent } from './front-matter';
41
42
  import { createMarkdown } from './markdown';
42
43
  import { generateTocHtml, generateCodeTocHtml } from '../toc-plugin';
43
44
  import {
44
- parseLiterateJava,
45
+ extractLiterateJavaCode,
45
46
  hasMainMethod,
46
47
  deriveClassName,
47
48
  compileJavaSource,
@@ -55,7 +56,6 @@ import type {
55
56
  RenderLiterateJavaOptions,
56
57
  RenderCopiedContentOptions,
57
58
  RenderDependencyCollector,
58
- TraceToolAvailability,
59
59
  } from '../types';
60
60
 
61
61
  const log = makeLogger(import.meta.url);
@@ -66,9 +66,18 @@ export { stripHtmlComments } from './html-comments';
66
66
 
67
67
  const REQUIRED_FRONT_MATTER_FIELDS = ['title'];
68
68
 
69
- function isWatchMode(assetFiles: string[]): boolean {
70
- return assetFiles.some(f => f.includes('watch-reload-client'));
71
- }
69
+ // Page variables that renderers assign themselves. Front matter that sets one
70
+ // would be overwritten or, for `template`, select a nonexistent template.
71
+ const RESERVED_FRONT_MATTER_KEYS: readonly string[] = [
72
+ 'template',
73
+ 'titleHtml',
74
+ 'descriptionHtml',
75
+ 'tocHtml',
76
+ 'tocItems',
77
+ 'codeFilePath',
78
+ 'downloadName',
79
+ 'filePath',
80
+ ];
72
81
 
73
82
  function renderInlineField(
74
83
  md: MarkdownIt,
@@ -81,7 +90,7 @@ function renderInlineField(
81
90
  }
82
91
  const html = md.renderInline(raw);
83
92
  vars[`${field}Html`] = html;
84
- vars[field] = stripHtml(html).result;
93
+ vars[field] = htmlToPlainText(html);
85
94
  }
86
95
 
87
96
  interface TemplateParametersInput {
@@ -120,6 +129,19 @@ function resolveAuthor(
120
129
  pageVariables.author = authorEntry;
121
130
  }
122
131
 
132
+ function rejectReservedFrontMatterKeys(
133
+ pageVariables: Record<string, unknown>,
134
+ filePath: string,
135
+ ): void {
136
+ const key = RESERVED_FRONT_MATTER_KEYS.find(k =>
137
+ Object.hasOwn(pageVariables, k),
138
+ );
139
+ if (key) {
140
+ const displayPath = toPosix(path.relative(getProjectDir(), filePath));
141
+ throw new Error(`${displayPath}: front matter key "${key}" is reserved`);
142
+ }
143
+ }
144
+
123
145
  function validateFrontMatter(
124
146
  pageVariables: Record<string, unknown>,
125
147
  filePath: string,
@@ -166,10 +188,17 @@ function renderSiteBanner(siteVariables: SiteVariables): string {
166
188
  return createMarkdown(siteVariables).render(siteVariables.banner);
167
189
  }
168
190
 
191
+ // `<meta charset>` is the first element in `<head>` so browsers find it within
192
+ // the first 1024 bytes. Tags injected into the head go right after it.
193
+ const CHARSET_META_PATTERN = /<meta charset="UTF-8"\s*\/?>/;
194
+
195
+ function insertAfterCharsetMeta(html: string, tags: string): string {
196
+ return html.replace(CHARSET_META_PATTERN, charsetMeta => charsetMeta + tags);
197
+ }
198
+
169
199
  export function injectAssetTags(
170
200
  html: string,
171
201
  assetFiles: string[],
172
- distDir: string,
173
202
  siteVariables: SiteVariables,
174
203
  ): string {
175
204
  const jsAssets = assetFiles.filter(f => f.endsWith('.js'));
@@ -181,15 +210,8 @@ export function injectAssetTags(
181
210
  `<script defer src="${normalizeOutputPath('/' + asset)}"></script>`,
182
211
  )
183
212
  .join('');
184
- const criticalAssets = cssAssets.filter(f => f.includes('critical.bundle.'));
185
- const asyncAssets = cssAssets.filter(f => !f.includes('critical.bundle.'));
186
- const criticalTags = criticalAssets
187
- .map(asset => {
188
- const css = fs.readFileSync(path.join(distDir, asset), 'utf-8');
189
- return `<style>${css}</style>`;
190
- })
191
- .join('');
192
- const asyncLinkTags = asyncAssets
213
+ // Render-blocking on purpose: pages never paint without the full stylesheet
214
+ const stylesheetTags = cssAssets
193
215
  .map(
194
216
  asset =>
195
217
  `<link href="${normalizeOutputPath('/' + asset)}" rel="stylesheet">`,
@@ -197,11 +219,6 @@ export function injectAssetTags(
197
219
  .join('');
198
220
 
199
221
  const fontPreloadTags = getDefaultFontPreloadFiles(siteVariables)
200
- .filter(
201
- preload =>
202
- preload.source === 'public' ||
203
- fs.existsSync(path.join(distDir, preload.filePath)),
204
- )
205
222
  .map(preload => {
206
223
  const urlPath =
207
224
  preload.source === 'public'
@@ -211,31 +228,27 @@ export function injectAssetTags(
211
228
  })
212
229
  .join('');
213
230
 
214
- return html
215
- .replace(
216
- '<head>',
217
- `<head>${fontPreloadTags}${criticalTags}${asyncLinkTags}`,
218
- )
219
- .replace('</head>', `${scriptTags}</head>`);
231
+ return insertAfterCharsetMeta(
232
+ html,
233
+ `${fontPreloadTags}${stylesheetTags}`,
234
+ ).replace('</head>', `${scriptTags}</head>`);
220
235
  }
221
236
 
222
237
  export function injectKatexStylesheet(html: string): string {
223
238
  const tag = `<link href="/katex/katex.min.css" rel="stylesheet">`;
224
- return html.replace('<head>', `<head>${tag}`);
239
+ return insertAfterCharsetMeta(html, tag);
225
240
  }
226
241
 
227
242
  export function preparePageTemplateHtml({
228
243
  templateHtml,
229
244
  assetFiles,
230
- distDir,
231
245
  siteVariables,
232
246
  }: {
233
247
  templateHtml: string;
234
248
  assetFiles: string[];
235
- distDir: string;
236
249
  siteVariables: SiteVariables;
237
250
  }): string {
238
- let html = injectAssetTags(templateHtml, assetFiles, distDir, siteVariables);
251
+ let html = injectAssetTags(templateHtml, assetFiles, siteVariables);
239
252
  if (templateHtml.includes('class="katex"')) {
240
253
  html = injectKatexStylesheet(html);
241
254
  }
@@ -249,13 +262,14 @@ function toContentAssetPath(contentDir: string, filePath: string): string {
249
262
  export function renderPlainTextPageAsset({
250
263
  filePath,
251
264
  contentDir,
252
- distDir,
253
265
  siteVariables,
254
266
  validInternalTargets,
255
267
  assetFiles,
268
+ isWatchMode,
256
269
  literateJavaOutputPaths,
270
+ generatedPageTargets,
271
+ codePageSourceTargets,
257
272
  dependencyCollector,
258
- cachedTraceSourceDir,
259
273
  traceCache,
260
274
  traceToolAvailability,
261
275
  }: RenderPlainTextOptions): Asset[] {
@@ -264,34 +278,21 @@ export function renderPlainTextPageAsset({
264
278
  const sourceUrlPath = normalizeOutputPath(`/${subPath}.html`);
265
279
 
266
280
  log.info`Rendering page ${B`${subPath + ext}`}`;
267
- const watchMode = isWatchMode(assetFiles);
268
- const { content, pageVariables, tocItems, alertIds } = renderPlainTextContent(
281
+ const { content, pageVariables, tocItems } = renderPlainTextContent(
269
282
  filePath,
270
283
  subPath,
271
284
  sourceUrlPath,
272
285
  siteVariables,
273
286
  validInternalTargets,
274
- watchMode,
275
- {
276
- dependencyCollector,
277
- traceCache,
278
- contentDir,
279
- distDir,
280
- cachedTraceSourceDir,
281
- traceToolAvailability,
282
- },
287
+ isWatchMode,
288
+ { dependencyCollector, traceCache, contentDir, traceToolAvailability },
283
289
  );
284
290
 
285
- validateFrontMatter(pageVariables, filePath);
286
-
287
- if (!pageVariables.template) {
288
- pageVariables.template = 'default';
289
- }
291
+ pageVariables.template = 'default';
290
292
 
291
293
  if (pageVariables.toc && tocItems) {
292
294
  pageVariables.tocHtml = generateTocHtml(
293
295
  tocItems as Parameters<typeof generateTocHtml>[0],
294
- alertIds,
295
296
  );
296
297
  }
297
298
 
@@ -300,7 +301,7 @@ export function renderPlainTextPageAsset({
300
301
  siteVariables,
301
302
  content,
302
303
  subPath,
303
- isWatchMode: watchMode,
304
+ isWatchMode,
304
305
  bannerHtml: renderSiteBanner(siteVariables),
305
306
  });
306
307
 
@@ -310,16 +311,13 @@ export function renderPlainTextPageAsset({
310
311
  ) as string;
311
312
  const finalized = finalizeHtmlPage({
312
313
  filePath,
313
- html: preparePageTemplateHtml({
314
- templateHtml,
315
- assetFiles,
316
- distDir,
317
- siteVariables,
318
- }),
314
+ html: preparePageTemplateHtml({ templateHtml, assetFiles, siteVariables }),
319
315
  siteVariables,
320
316
  sourceUrlPath,
321
317
  validInternalTargets,
322
318
  literateJavaOutputPaths,
319
+ generatedPageTargets,
320
+ codePageSourceTargets,
323
321
  dependencyCollector,
324
322
  });
325
323
 
@@ -335,11 +333,13 @@ export function renderPlainTextPageAsset({
335
333
  export function renderCodePageAsset({
336
334
  filePath,
337
335
  contentDir,
338
- distDir,
339
336
  siteVariables,
340
337
  assetFiles,
338
+ isWatchMode,
341
339
  validInternalTargets,
342
340
  literateJavaOutputPaths,
341
+ generatedPageTargets,
342
+ codePageSourceTargets,
343
343
  dependencyCollector,
344
344
  }: RenderCodePageOptions): Asset[] {
345
345
  const { dir, name, ext } = path.parse(filePath);
@@ -382,23 +382,20 @@ export function renderCodePageAsset({
382
382
  siteVariables,
383
383
  content,
384
384
  subPath,
385
- isWatchMode: isWatchMode(assetFiles),
385
+ isWatchMode,
386
386
  bannerHtml: renderSiteBanner(siteVariables),
387
387
  });
388
388
 
389
389
  const templateHtml = render('code.html', templateParameters) as string;
390
390
  const finalized = finalizeHtmlPage({
391
391
  filePath,
392
- html: preparePageTemplateHtml({
393
- templateHtml,
394
- assetFiles,
395
- distDir,
396
- siteVariables,
397
- }),
392
+ html: preparePageTemplateHtml({ templateHtml, assetFiles, siteVariables }),
398
393
  siteVariables,
399
394
  sourceUrlPath,
400
395
  validInternalTargets,
401
396
  literateJavaOutputPaths,
397
+ generatedPageTargets,
398
+ codePageSourceTargets,
402
399
  dependencyCollector,
403
400
  });
404
401
 
@@ -442,58 +439,42 @@ export function renderCopiedContentAsset({
442
439
  return [{ assetPath: relPath, content: templated }];
443
440
  }
444
441
 
445
- /** Parses the file, renders using template, returns HTML & params used to generate page */
446
- function renderPlainTextContent(
447
- filePath: string,
448
- subPath: string,
449
- sourceUrlPath: string,
450
- siteVariables: SiteVariables,
451
- validInternalTargets: Set<string>,
452
- isWatchMode: boolean,
453
- {
454
- dependencyCollector,
455
- traceCache,
456
- contentDir,
457
- distDir,
458
- cachedTraceSourceDir,
459
- traceToolAvailability,
460
- }: {
461
- dependencyCollector?: RenderDependencyCollector;
462
- traceCache?: Map<
463
- string,
464
- {
465
- manifestUrl: string;
466
- artifactId: string;
467
- highlightedSources: { file: string; highlightedSource: string }[];
468
- totalSteps: number;
469
- sourceMtims: Record<string, number>;
470
- }
471
- >;
472
- contentDir?: string;
473
- distDir?: string;
474
- cachedTraceSourceDir?: string;
475
- traceToolAvailability?: TraceToolAvailability;
476
- } = {},
477
- ): {
478
- content: string | null;
479
- pageVariables: Record<string, unknown>;
480
- tocItems: unknown[] | null;
481
- alertIds: string[];
482
- } {
483
- const applyBasePath = createApplyBasePath(siteVariables);
442
+ interface PreparePageVariablesInput {
443
+ rawPageVariables: Record<string, unknown>;
444
+ filePath: string;
445
+ subPath: string;
446
+ sourceUrlPath: string;
447
+ siteVariables: SiteVariables;
448
+ validInternalTargets: ReadonlySet<string>;
449
+ isWatchMode: boolean;
450
+ /** Whether this renderer supports `slides: true` (Markdown pages only) */
451
+ allowSlides: boolean;
452
+ dependencyCollector?: RenderDependencyCollector;
453
+ }
484
454
 
485
- const ext = path.extname(filePath);
486
- const raw = fs.readFileSync(filePath, 'utf-8');
487
- const { pageVariables, content } = parseFrontMatterAndContent(raw, ext);
488
- if (pageVariables.slides === true && !extensionIsMarkdown(ext)) {
455
+ /**
456
+ * Turns parsed front matter into page variables. Shared by every renderer
457
+ * whose source has front matter, so each one applies the same rules:
458
+ * Lodash processing of string values, inline Markdown for the title and
459
+ * description, required fields, author lookup, and parent link validation.
460
+ */
461
+ function preparePageVariables({
462
+ rawPageVariables,
463
+ filePath,
464
+ subPath,
465
+ sourceUrlPath,
466
+ siteVariables,
467
+ validInternalTargets,
468
+ isWatchMode,
469
+ allowSlides,
470
+ dependencyCollector,
471
+ }: PreparePageVariablesInput): Record<string, unknown> {
472
+ rejectReservedFrontMatterKeys(rawPageVariables, filePath);
473
+ if (rawPageVariables.slides === true && !allowSlides) {
489
474
  throw new Error(
490
475
  `${filePath}: slides mode is only supported on Markdown pages`,
491
476
  );
492
477
  }
493
- const frontMatterMd = createMarkdown(siteVariables, {
494
- filePath,
495
- slides: pageVariables.slides === true,
496
- });
497
478
 
498
479
  // Handle substitutions inside front matter using siteVariables
499
480
  const siteOnlyParams = createTemplateParameters({
@@ -503,21 +484,26 @@ function renderPlainTextContent(
503
484
  subPath,
504
485
  isWatchMode,
505
486
  });
506
- const pageVariablesProcessed: Record<string, unknown> = Object.fromEntries(
507
- Object.entries(pageVariables).map(([k, v]) => [
487
+ const pageVariables: Record<string, unknown> = Object.fromEntries(
488
+ Object.entries(rawPageVariables).map(([k, v]) => [
508
489
  k,
509
- typeof v === 'string' ? _.template(v)(siteOnlyParams) : v,
490
+ typeof v === 'string' ? compileTemplate(v)(siteOnlyParams) : v,
510
491
  ]),
511
492
  );
512
493
 
513
494
  // Render title and description as inline Markdown
514
- renderInlineField(frontMatterMd, pageVariablesProcessed, 'title');
515
- renderInlineField(frontMatterMd, pageVariablesProcessed, 'description');
495
+ const frontMatterMd = createMarkdown(siteVariables, {
496
+ filePath,
497
+ slides: pageVariables.slides === true,
498
+ });
499
+ renderInlineField(frontMatterMd, pageVariables, 'title');
500
+ renderInlineField(frontMatterMd, pageVariables, 'description');
516
501
 
517
- resolveAuthor(pageVariablesProcessed, filePath, dependencyCollector);
502
+ validateFrontMatter(pageVariables, filePath);
503
+ resolveAuthor(pageVariables, filePath, dependencyCollector);
518
504
 
519
505
  const parentError = validateParentLink(
520
- pageVariablesProcessed.parent,
506
+ pageVariables.parent,
521
507
  filePath,
522
508
  validInternalTargets,
523
509
  sourceUrlPath,
@@ -526,40 +512,84 @@ function renderPlainTextContent(
526
512
  throw new Error(parentError);
527
513
  }
528
514
  const resolvedParentTarget = resolveParentLinkTarget(
529
- pageVariablesProcessed.parent,
515
+ pageVariables.parent,
530
516
  sourceUrlPath,
531
517
  );
532
518
  if (resolvedParentTarget) {
533
519
  dependencyCollector?.internalTargets?.add(resolvedParentTarget);
534
520
  }
535
521
 
522
+ return pageVariables;
523
+ }
524
+
525
+ /** Parses the file, renders using template, returns HTML & params used to generate page */
526
+ function renderPlainTextContent(
527
+ filePath: string,
528
+ subPath: string,
529
+ sourceUrlPath: string,
530
+ siteVariables: SiteVariables,
531
+ validInternalTargets: ReadonlySet<string>,
532
+ isWatchMode: boolean,
533
+ {
534
+ dependencyCollector,
535
+ traceCache,
536
+ contentDir,
537
+ traceToolAvailability,
538
+ }: Pick<
539
+ RenderPlainTextOptions,
540
+ | 'dependencyCollector'
541
+ | 'traceCache'
542
+ | 'contentDir'
543
+ | 'traceToolAvailability'
544
+ >,
545
+ ): {
546
+ content: string | null;
547
+ pageVariables: Record<string, unknown>;
548
+ tocItems: unknown[] | null;
549
+ } {
550
+ const applyBasePath = createApplyBasePath(siteVariables);
551
+
552
+ const ext = path.extname(filePath);
553
+ const raw = fs.readFileSync(filePath, 'utf-8');
554
+ const { pageVariables: rawPageVariables, content } =
555
+ parseFrontMatterAndContent(raw, ext);
556
+ const pageVariables = preparePageVariables({
557
+ rawPageVariables,
558
+ filePath,
559
+ subPath,
560
+ sourceUrlPath,
561
+ siteVariables,
562
+ validInternalTargets,
563
+ isWatchMode,
564
+ allowSlides: extensionIsMarkdown(ext),
565
+ dependencyCollector,
566
+ });
567
+
536
568
  const strippedContent = stripHtmlComments(content);
537
569
 
538
570
  const params = createTemplateParameters({
539
- pageVariables: pageVariablesProcessed,
571
+ pageVariables,
540
572
  siteVariables,
541
573
  content: strippedContent,
542
574
  subPath,
543
575
  isWatchMode,
544
576
  });
545
577
 
546
- if (traceCache && contentDir && distDir) {
578
+ if (traceCache) {
547
579
  const helpers = createTraceHelpers({
548
580
  filePath,
549
581
  contentDir,
550
- distDir,
551
582
  applyBasePath,
552
583
  cache: traceCache,
553
584
  toolAvailability: traceToolAvailability,
554
585
  dependencyCollector,
555
- cachedTraceSourceDir,
556
586
  });
557
587
  params.renderTrace = helpers.renderTrace;
558
588
  }
559
589
 
560
590
  let html: string;
561
591
  try {
562
- html = _.template(strippedContent)(params);
592
+ html = compileTemplate(strippedContent)(params);
563
593
  } catch (err: unknown) {
564
594
  throw new Error(
565
595
  `${filePath}: Lodash template error in page or template: ${(err as Error).message}`,
@@ -568,7 +598,6 @@ function renderPlainTextContent(
568
598
  }
569
599
 
570
600
  let tocItems: unknown[] | null = null;
571
- let alertIds: string[] = [];
572
601
  if (extensionIsMarkdown(ext)) {
573
602
  const md = createMarkdown(siteVariables, {
574
603
  filePath,
@@ -576,29 +605,29 @@ function renderPlainTextContent(
576
605
  templateParams: params,
577
606
  dependencyCollector,
578
607
  });
579
- const env: Record<string, unknown> = { alertIds: [] as string[] };
608
+ const env: Record<string, unknown> = {};
580
609
  html = md.render(html!, env);
581
610
  tocItems = (env.tocItems as unknown[] | undefined) || null;
582
- alertIds = env.alertIds as string[];
583
611
  }
584
612
 
585
613
  return {
586
614
  content: html,
587
615
  pageVariables: params.page as Record<string, unknown>,
588
616
  tocItems,
589
- alertIds,
590
617
  };
591
618
  }
592
619
 
593
620
  export function renderLiterateJavaPageAsset({
594
621
  filePath,
595
622
  contentDir,
596
- distDir,
597
623
  siteVariables,
598
624
  assetFiles,
625
+ isWatchMode,
599
626
  skipExecution,
600
627
  validInternalTargets,
601
628
  literateJavaOutputPaths,
629
+ generatedPageTargets,
630
+ codePageSourceTargets,
602
631
  dependencyCollector,
603
632
  }: RenderLiterateJavaOptions): Asset[] {
604
633
  const { dir, name } = path.parse(filePath);
@@ -607,16 +636,38 @@ export function renderLiterateJavaPageAsset({
607
636
 
608
637
  log.info`Rendering literate Java page ${B`${name}`}`;
609
638
 
639
+ const sourceUrlPath = `/${subPath}.java.html`;
610
640
  const raw = fs.readFileSync(filePath, 'utf-8');
611
- const {
641
+ const { pageVariables: rawPageVariables, content } =
642
+ parseFrontMatterAndContent(raw, '.md');
643
+ const pageVariables = preparePageVariables({
644
+ rawPageVariables,
645
+ filePath,
646
+ subPath,
647
+ sourceUrlPath,
648
+ siteVariables,
649
+ validInternalTargets,
650
+ isWatchMode,
651
+ allowSlides: false,
652
+ dependencyCollector,
653
+ });
654
+ const params = createTemplateParameters({
612
655
  pageVariables,
656
+ siteVariables,
613
657
  content,
614
- javaSource,
615
- codeBlocks,
616
- visibleBlockIndices,
617
- } = parseLiterateJava(raw, siteVariables);
618
-
619
- validateFrontMatter(pageVariables, filePath);
658
+ subPath,
659
+ isWatchMode,
660
+ });
661
+ const md = createMarkdown(siteVariables, {
662
+ filePath,
663
+ templateParams: params,
664
+ preserveHtmlComments: true,
665
+ dependencyCollector,
666
+ });
667
+ const env: Record<string, unknown> = {};
668
+ const tokens = md.parse(compileTemplate(content)(params), env);
669
+ const { javaSource, codeBlocks, visibleBlockIndices } =
670
+ extractLiterateJavaCode(tokens);
620
671
 
621
672
  const stdin =
622
673
  typeof pageVariables.stdin === 'string' ? pageVariables.stdin : undefined;
@@ -649,8 +700,6 @@ export function renderLiterateJavaPageAsset({
649
700
 
650
701
  // Render full markdown with a custom fence rule that replaces fences
651
702
  // with Shiki-highlighted code blocks and optional JDI output columns
652
- const sourceUrlPath = `/${subPath}.java.html`;
653
- const md = createMarkdown(siteVariables, { filePath });
654
703
  let fenceIndex = 0;
655
704
  const defaultFence = md.renderer.rules.fence!;
656
705
 
@@ -698,8 +747,9 @@ export function renderLiterateJavaPageAsset({
698
747
  return codeHtml;
699
748
  };
700
749
 
701
- const env: Record<string, unknown> = { alertIds: [] as string[] };
702
- const contentHtml = md.render(stripHtmlComments(content), env);
750
+ const contentHtml = stripHtmlComments(
751
+ md.renderer.render(tokens, md.options, env),
752
+ );
703
753
 
704
754
  // Build page variables
705
755
  const javaFileName = `${className}.java`;
@@ -707,7 +757,6 @@ export function renderLiterateJavaPageAsset({
707
757
  `/${toUrlPath(path.relative(contentDir, path.join(dir, javaFileName)))}`,
708
758
  );
709
759
 
710
- renderInlineField(md, pageVariables, 'title');
711
760
  pageVariables.template = 'literate';
712
761
  pageVariables.codeFilePath = codeFilePath;
713
762
  pageVariables.downloadName = javaFileName;
@@ -715,34 +764,28 @@ export function renderLiterateJavaPageAsset({
715
764
  if (pageVariables.toc && env.tocItems) {
716
765
  pageVariables.tocHtml = generateTocHtml(
717
766
  env.tocItems as Parameters<typeof generateTocHtml>[0],
718
- env.alertIds as string[],
719
767
  );
720
768
  }
721
769
 
722
- resolveAuthor(pageVariables, filePath, dependencyCollector);
723
-
724
770
  const templateParameters = createTemplateParameters({
725
771
  pageVariables,
726
772
  siteVariables,
727
773
  content: contentHtml,
728
774
  subPath,
729
- isWatchMode: isWatchMode(assetFiles),
775
+ isWatchMode,
730
776
  bannerHtml: renderSiteBanner(siteVariables),
731
777
  });
732
778
 
733
779
  const templateHtml = render('literate.html', templateParameters) as string;
734
780
  const finalized = finalizeHtmlPage({
735
781
  filePath,
736
- html: preparePageTemplateHtml({
737
- templateHtml,
738
- assetFiles,
739
- distDir,
740
- siteVariables,
741
- }),
782
+ html: preparePageTemplateHtml({ templateHtml, assetFiles, siteVariables }),
742
783
  siteVariables,
743
784
  sourceUrlPath,
744
785
  validInternalTargets,
745
786
  literateJavaOutputPaths,
787
+ generatedPageTargets,
788
+ codePageSourceTargets,
746
789
  dependencyCollector,
747
790
  });
748
791
 
@@ -78,3 +78,14 @@ export function highlightCode(code: string, lang: string): string {
78
78
  defaultColor: false,
79
79
  });
80
80
  }
81
+
82
+ export function highlightCodeToHast(
83
+ code: string,
84
+ lang: string,
85
+ ): ReturnType<HighlighterGeneric<BundledLanguage, BundledTheme>['codeToHast']> {
86
+ return getHighlighter().codeToHast(code, {
87
+ lang,
88
+ themes: SHIKI_THEMES,
89
+ defaultColor: false,
90
+ });
91
+ }
@@ -1,4 +1,4 @@
1
- import _ from 'lodash';
1
+ import { compileTemplate } from '../lodash-template';
2
2
  import type { SiteVariables } from '../types';
3
3
 
4
4
  /**
@@ -17,7 +17,7 @@ export function applySourceTemplate(
17
17
  const params = { vars: siteVariables.vars || {}, site: siteVariables };
18
18
 
19
19
  try {
20
- return _.template(source)(params);
20
+ return compileTemplate(source)(params);
21
21
  } catch (err: unknown) {
22
22
  throw new Error(
23
23
  `${filePath}: Lodash template error in source code: ${(err as Error).message}`,