@honeydeck/honeydeck 0.3.0 → 0.5.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.
- package/DEVELOPMENT.md +4 -1
- package/Readme.md +2 -2
- package/SPEC.md +3 -3
- package/docs/components-browser-frame.md +34 -0
- package/docs/components-keyboard.md +31 -0
- package/docs/components-list-style.md +49 -0
- package/docs/components-notes.md +36 -0
- package/docs/components-reveal-group.md +58 -0
- package/docs/components-reveal-with.md +37 -0
- package/docs/components-reveal.md +33 -0
- package/docs/components-timeline-steps.md +48 -0
- package/docs/components.md +13 -54
- package/docs/configuration.md +11 -0
- package/docs/deeper-dive.md +30 -7
- package/docs/getting-started.md +2 -2
- package/docs/navigation.md +1 -1
- package/docs/pdf-export.md +4 -2
- package/docs/presenter-mode.md +6 -3
- package/docs/skills.md +3 -3
- package/docs/slidev-migration.md +3 -0
- package/docs/steps-and-reveals.md +143 -8
- package/package.json +4 -1
- package/skills/SPEC.md +2 -2
- package/skills/honeydeck/SKILL.md +2 -2
- package/skills/slidev-migration/SKILL.md +1 -0
- package/src/SPEC.md +8 -3
- package/src/cli/SPEC.md +3 -2
- package/src/cli/pdf.ts +11 -4
- package/src/layouts/SPEC.md +1 -1
- package/src/remark/SPEC.md +102 -2
- package/src/remark/code-utils.ts +151 -0
- package/src/remark/shiki-code-blocks.ts +329 -136
- package/src/remark/step-numbering.ts +408 -103
- package/src/runtime/Deck.tsx +133 -116
- package/src/runtime/EffectiveColorModeContext.tsx +37 -0
- package/src/runtime/SPEC.md +21 -8
- package/src/runtime/SlideCanvas.tsx +19 -16
- package/src/runtime/SlideScaleContext.tsx +23 -0
- package/src/runtime/components/CodeBlock.tsx +19 -202
- package/src/runtime/components/CodeBlockCopyButton.tsx +64 -0
- package/src/runtime/components/CodeBlockShared.ts +17 -0
- package/src/runtime/components/Fade.tsx +51 -0
- package/src/runtime/components/FadeGroup.tsx +175 -0
- package/src/runtime/components/FadeWith.tsx +54 -0
- package/src/runtime/components/MagicCodeBlock.tsx +223 -0
- package/src/runtime/components/NavBar.tsx +1 -1
- package/src/runtime/components/NormalCodeBlock.tsx +128 -0
- package/src/runtime/components/Reveal.tsx +27 -27
- package/src/runtime/components/RevealGroup.tsx +143 -41
- package/src/runtime/components/RevealWith.tsx +63 -0
- package/src/runtime/components/SPEC.md +112 -7
- package/src/runtime/components/TimelineReveal.tsx +81 -0
- package/src/runtime/components/index.ts +13 -5
- package/src/runtime/components/timelineVisibility.ts +45 -0
- package/src/runtime/index.ts +9 -1
- package/src/runtime/navigation.ts +6 -4
- package/src/runtime/presentationApi.ts +449 -0
- package/src/runtime/views/PresenterCastButton.tsx +39 -0
- package/src/runtime/views/PresenterView.tsx +21 -4
- package/src/runtime/views/SPEC.md +7 -5
- package/src/theme/base.css +67 -2
- package/src/vite-plugin/SPEC.md +20 -2
- package/src/vite-plugin/index.ts +16 -2
- package/src/vite-plugin/layout-demo-crawler.ts +304 -33
- package/src/vite-plugin/splitter.ts +1 -0
- package/src/vite-plugin/virtual-modules.ts +16 -6
package/src/theme/base.css
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
* [data-honeydeck-color-mode="light"] { --honeydeck-primary: oklch(55% 0.25 145); }
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
@import "@shikijs/magic-move/style.css";
|
|
18
|
+
|
|
17
19
|
/* ── Source scanning ────────────────────────────────────────────────────────
|
|
18
20
|
Tell Tailwind to scan the built-in layouts for utility class candidates.
|
|
19
21
|
Without this, layouts live outside the Vite root (user's project dir) and
|
|
@@ -407,6 +409,59 @@
|
|
|
407
409
|
color: inherit;
|
|
408
410
|
}
|
|
409
411
|
|
|
412
|
+
.honeydeck-code-block .shiki-magic-move-container {
|
|
413
|
+
box-sizing: border-box;
|
|
414
|
+
min-height: 0;
|
|
415
|
+
margin: 0;
|
|
416
|
+
padding: 1.5em;
|
|
417
|
+
overflow: auto;
|
|
418
|
+
border-radius: 0;
|
|
419
|
+
font-family: var(--honeydeck-font-mono);
|
|
420
|
+
font-size: inherit;
|
|
421
|
+
line-height: inherit;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
.honeydeck-code-block .shiki-magic-move-item {
|
|
425
|
+
opacity: var(--honeydeck-magic-code-token-opacity, 1);
|
|
426
|
+
transition-property: color, opacity, background-color, transform;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
@keyframes honeydeck-magic-code-token-enter {
|
|
430
|
+
from {
|
|
431
|
+
opacity: 0;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
to {
|
|
435
|
+
opacity: var(--honeydeck-magic-code-token-opacity, 1);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
@keyframes honeydeck-magic-code-token-leave {
|
|
440
|
+
from {
|
|
441
|
+
opacity: var(--honeydeck-magic-code-token-opacity, 1);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
to {
|
|
445
|
+
opacity: 0;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.honeydeck-code-block .shiki-magic-move-enter-active {
|
|
450
|
+
animation-name: honeydeck-magic-code-token-enter;
|
|
451
|
+
animation-duration: var(--smm-duration, 500ms);
|
|
452
|
+
animation-timing-function: var(--smm-easing, ease);
|
|
453
|
+
animation-delay: var(--smm-stagger, 0ms);
|
|
454
|
+
animation-fill-mode: both;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
.honeydeck-code-block .shiki-magic-move-leave-active {
|
|
458
|
+
animation-name: honeydeck-magic-code-token-leave;
|
|
459
|
+
animation-duration: var(--smm-duration, 500ms);
|
|
460
|
+
animation-timing-function: var(--smm-easing, ease);
|
|
461
|
+
animation-delay: var(--smm-stagger, 0ms);
|
|
462
|
+
animation-fill-mode: both;
|
|
463
|
+
}
|
|
464
|
+
|
|
410
465
|
.honeydeck-code-copy-button {
|
|
411
466
|
position: absolute;
|
|
412
467
|
top: 0.75em;
|
|
@@ -446,12 +501,22 @@
|
|
|
446
501
|
color: var(--honeydeck-primary-foreground);
|
|
447
502
|
}
|
|
448
503
|
|
|
504
|
+
@keyframes honeydeck-code-line-highlight-enter {
|
|
505
|
+
from {
|
|
506
|
+
opacity: var(--honeydeck-code-line-dim-opacity);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
to {
|
|
510
|
+
opacity: 1;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
449
514
|
.honeydeck-code-block .line {
|
|
450
515
|
transition: opacity 150ms ease-in;
|
|
451
516
|
}
|
|
452
517
|
|
|
453
|
-
.honeydeck-code-block .line[data-
|
|
454
|
-
|
|
518
|
+
.honeydeck-code-block .line[data-highlight="1"] {
|
|
519
|
+
animation: honeydeck-code-line-highlight-enter 300ms ease-out both;
|
|
455
520
|
}
|
|
456
521
|
|
|
457
522
|
/* ── Plain code fallback (no shiki, unsupported lang) ────────────────────── */
|
package/src/vite-plugin/SPEC.md
CHANGED
|
@@ -67,6 +67,21 @@ Static files live in the project `public/` directory and are served from the web
|
|
|
67
67
|
|
|
68
68
|
React components and built-in layouts may also import image assets through Vite (`webp`, `png`, `jpg`, `jpeg`, `svg`, `gif`). The built-in `Image` layout uses this for its bundled placeholder image.
|
|
69
69
|
|
|
70
|
+
### Timeline-aware MDX Components
|
|
71
|
+
|
|
72
|
+
Honeydeck's MDX compilation assigns slide-local timeline steps to built-in timeline components.
|
|
73
|
+
|
|
74
|
+
Rules:
|
|
75
|
+
|
|
76
|
+
- `<Reveal>` adds one step to the slide timeline and receives an internal `at` prop during compilation.
|
|
77
|
+
- `<Reveal name="...">` exposes that reveal step as a same-slide target for `<RevealWith target="...">`.
|
|
78
|
+
- Reveal names must be literal non-empty strings. Dynamic `name` expressions are unsupported.
|
|
79
|
+
- Duplicate reveal names within one slide are compile errors. The same name may be reused on different slides.
|
|
80
|
+
- `<RevealWith>` does not add a step. It requires exactly one literal prop: `target="name"` or `at={n}`.
|
|
81
|
+
- `<RevealWith target="name">` may reference a named `<Reveal>` before or after it on the same slide. Missing targets are compile errors.
|
|
82
|
+
- `<RevealWith at={n}>` targets an existing 1-based step on the same slide. Non-literal, non-positive, and out-of-range values are compile errors.
|
|
83
|
+
- In development, invalid timeline component usage is surfaced with clear terminal and browser diagnostics without permanently killing the dev server. Production builds fail.
|
|
84
|
+
|
|
70
85
|
### Markdown Features
|
|
71
86
|
|
|
72
87
|
Slide MDX supports GitHub-flavored Markdown pipe tables. Pipe tables render as real HTML tables in slides. The base theme styles slide tables with compact, full-width, token-based horizontal rules, bold headers, and light horizontal cell spacing so table Markdown is presentation-ready without custom CSS.
|
|
@@ -94,6 +109,7 @@ All settings use **camelCase**. No separate config file exists. Frontmatter pars
|
|
|
94
109
|
| `pdfColorMode` | `"light" \| "dark"` | unset | Optional explicit PDF color mode; when unset, PDF falls back to pinned deck `colorMode`, then `light` |
|
|
95
110
|
| `pdfSteps` | `"final" \| "all"` | `"final"` | Whether PDF includes all steps or final state |
|
|
96
111
|
| `transition` | `boolean` | `true` | Enable crossfade transition between slides |
|
|
112
|
+
| `magicCodeDuration` | `number` | `800` | Default Magic Code animation duration in milliseconds |
|
|
97
113
|
| `layouts` | `string` | built-in `@honeydeck/honeydeck/layouts` | Layout map module path |
|
|
98
114
|
| `defaultLayout` | `string` | `"Default"` | Layout used when slide has no `layout:` |
|
|
99
115
|
| `showSlideNumbers` | `boolean` | `false` | Show the current slide number in the bottom-right corner of slides |
|
|
@@ -109,6 +125,8 @@ All settings use **camelCase**. No separate config file exists. Frontmatter pars
|
|
|
109
125
|
|
|
110
126
|
The first frontmatter block in the deck entry file is parsed as deck config. Deck-level keys are not copied into slide frontmatter. If that block also contains `layout:` plus layout-specific keys, those non-deck keys are emitted as first-slide frontmatter.
|
|
111
127
|
|
|
112
|
-
Slide-level frontmatter is a frontmatter-only block after a slide separator and applies to the following slide. Imported MDX files are normal MDX modules and cannot set deck-level properties.
|
|
128
|
+
Slide-level frontmatter is a frontmatter-only block after a slide separator and applies to the following slide. Imported MDX files are normal MDX modules and cannot set deck-level properties. `magicCodeDuration` is deck-level only; the same key in slide-level frontmatter is treated as a normal layout prop and does not configure Magic Code.
|
|
129
|
+
|
|
130
|
+
Invalid `aspectRatio`, `colorMode`, and `pdfSteps` values fall back to defaults. Invalid `pdfColorMode` is ignored as unset, allowing the pinned `colorMode` fallback. `showSlideNumbers` is enabled only by literal `true`; `transition` is enabled unless literal `false`. Invalid explicit Magic Code block `duration` values are compile errors; invalid deck-level `magicCodeDuration` falls back to the default Magic Code duration.
|
|
113
131
|
|
|
114
|
-
|
|
132
|
+
During development, changes to deck-level frontmatter invalidate the virtual config and every compiled virtual slide module, because slide compilation can depend on deck settings such as `magicCodeDuration`. Layout-related virtual modules are invalidated as before so layout map and demo previews stay current.
|
package/src/vite-plugin/index.ts
CHANGED
|
@@ -136,6 +136,20 @@ export function honeydeckPlugin(
|
|
|
136
136
|
// Use an array so more-specific subpath entries are matched before
|
|
137
137
|
// the bare 'honeydeck' entry (Vite processes array aliases in order).
|
|
138
138
|
alias: [
|
|
139
|
+
{
|
|
140
|
+
find: "@honeydeck/honeydeck/components/code-block/normal",
|
|
141
|
+
replacement: resolve(
|
|
142
|
+
__dirname,
|
|
143
|
+
"../runtime/components/NormalCodeBlock.tsx",
|
|
144
|
+
),
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
find: "@honeydeck/honeydeck/components/code-block/magic",
|
|
148
|
+
replacement: resolve(
|
|
149
|
+
__dirname,
|
|
150
|
+
"../runtime/components/MagicCodeBlock.tsx",
|
|
151
|
+
),
|
|
152
|
+
},
|
|
139
153
|
{
|
|
140
154
|
find: "@honeydeck/honeydeck/components/code-block",
|
|
141
155
|
replacement: resolve(
|
|
@@ -350,8 +364,8 @@ export function honeydeckPlugin(
|
|
|
350
364
|
mdx({
|
|
351
365
|
// remarkFrontmatter strips YAML front-matter blocks so they don't
|
|
352
366
|
// appear as raw text in the rendered output.
|
|
353
|
-
// remarkStepNumbering assigns
|
|
354
|
-
// and records vfile.data.stepCount for each slide.
|
|
367
|
+
// remarkStepNumbering assigns timeline metadata to built-in MDX
|
|
368
|
+
// timeline components and records vfile.data.stepCount for each slide.
|
|
355
369
|
remarkPlugins: [
|
|
356
370
|
remarkFrontmatter,
|
|
357
371
|
remarkGfm,
|
|
@@ -168,37 +168,18 @@ function crawlLayoutMapFile(
|
|
|
168
168
|
continue;
|
|
169
169
|
|
|
170
170
|
const layoutName = getLayoutName(property);
|
|
171
|
-
|
|
172
|
-
if (!layoutName || !localName) continue;
|
|
171
|
+
if (!layoutName) continue;
|
|
173
172
|
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
);
|
|
179
|
-
continue;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const modulePath = resolveImportedModule(
|
|
183
|
-
mapPath,
|
|
184
|
-
binding.moduleSpecifier,
|
|
185
|
-
context.packageRoot,
|
|
186
|
-
);
|
|
187
|
-
if (!modulePath) {
|
|
188
|
-
context.warnings.push(
|
|
189
|
-
`Could not resolve layout module "${binding.moduleSpecifier}" for layout "${layoutName}".`,
|
|
190
|
-
);
|
|
191
|
-
continue;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
context.watchedFiles.add(modulePath);
|
|
195
|
-
const publicModuleSpecifier = toPublicSpecifier({
|
|
196
|
-
entryPath: context.entryPath,
|
|
197
|
-
packageRoot: context.packageRoot,
|
|
173
|
+
const reference = resolveLayoutModuleReference({
|
|
174
|
+
property,
|
|
175
|
+
layoutName,
|
|
176
|
+
bindings,
|
|
198
177
|
mapPath,
|
|
199
|
-
|
|
200
|
-
originalSpecifier: binding.moduleSpecifier,
|
|
178
|
+
context,
|
|
201
179
|
});
|
|
180
|
+
if (!reference) continue;
|
|
181
|
+
|
|
182
|
+
const { modulePath, publicModuleSpecifier } = reference;
|
|
202
183
|
|
|
203
184
|
let demoMetadata: StaticDemoMetadata | undefined;
|
|
204
185
|
try {
|
|
@@ -372,13 +353,303 @@ function getLayoutName(
|
|
|
372
353
|
return null;
|
|
373
354
|
}
|
|
374
355
|
|
|
375
|
-
|
|
376
|
-
|
|
356
|
+
type LayoutModuleReference = Pick<
|
|
357
|
+
DiscoveredLayoutDemo,
|
|
358
|
+
"modulePath" | "publicModuleSpecifier"
|
|
359
|
+
>;
|
|
360
|
+
|
|
361
|
+
function resolveLayoutModuleReference({
|
|
362
|
+
property,
|
|
363
|
+
layoutName,
|
|
364
|
+
bindings,
|
|
365
|
+
mapPath,
|
|
366
|
+
context,
|
|
367
|
+
}: {
|
|
368
|
+
property: ts.PropertyAssignment | ts.ShorthandPropertyAssignment;
|
|
369
|
+
layoutName: string;
|
|
370
|
+
bindings: Map<string, ImportBinding>;
|
|
371
|
+
mapPath: string;
|
|
372
|
+
context: CrawlContext;
|
|
373
|
+
}): LayoutModuleReference | null {
|
|
374
|
+
const value = ts.isShorthandPropertyAssignment(property)
|
|
375
|
+
? property.name
|
|
376
|
+
: unwrapExpression(property.initializer);
|
|
377
|
+
|
|
378
|
+
if (ts.isIdentifier(value)) {
|
|
379
|
+
return resolveImportedLayoutReference(
|
|
380
|
+
value.text,
|
|
381
|
+
layoutName,
|
|
382
|
+
bindings,
|
|
383
|
+
mapPath,
|
|
384
|
+
context,
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (ts.isPropertyAccessExpression(value)) {
|
|
389
|
+
return resolveLayoutMapMemberReference(
|
|
390
|
+
value,
|
|
391
|
+
layoutName,
|
|
392
|
+
bindings,
|
|
393
|
+
mapPath,
|
|
394
|
+
context,
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
context.warnings.push(
|
|
399
|
+
`Layout "${layoutName}" is not backed by a static import; demo auto-discovery skipped.`,
|
|
400
|
+
);
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function resolveImportedLayoutReference(
|
|
405
|
+
localName: string,
|
|
406
|
+
layoutName: string,
|
|
407
|
+
bindings: Map<string, ImportBinding>,
|
|
408
|
+
mapPath: string,
|
|
409
|
+
context: CrawlContext,
|
|
410
|
+
): LayoutModuleReference | null {
|
|
411
|
+
const binding = bindings.get(localName);
|
|
412
|
+
if (!binding) {
|
|
413
|
+
context.warnings.push(
|
|
414
|
+
`Layout "${layoutName}" is not backed by a static import; demo auto-discovery skipped.`,
|
|
415
|
+
);
|
|
416
|
+
return null;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const importedModulePath = resolveImportedModule(
|
|
420
|
+
mapPath,
|
|
421
|
+
binding.moduleSpecifier,
|
|
422
|
+
context.packageRoot,
|
|
423
|
+
);
|
|
424
|
+
if (!importedModulePath) {
|
|
425
|
+
context.warnings.push(
|
|
426
|
+
`Could not resolve layout module "${binding.moduleSpecifier}" for layout "${layoutName}".`,
|
|
427
|
+
);
|
|
428
|
+
return null;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
context.watchedFiles.add(importedModulePath);
|
|
432
|
+
const modulePath =
|
|
433
|
+
binding.importedName === "default"
|
|
434
|
+
? importedModulePath
|
|
435
|
+
: (resolveNamedExportModulePath(
|
|
436
|
+
importedModulePath,
|
|
437
|
+
binding.importedName,
|
|
438
|
+
context.packageRoot,
|
|
439
|
+
context.watchedFiles,
|
|
440
|
+
) ?? importedModulePath);
|
|
441
|
+
context.watchedFiles.add(modulePath);
|
|
442
|
+
|
|
443
|
+
return {
|
|
444
|
+
modulePath,
|
|
445
|
+
publicModuleSpecifier: toPublicSpecifier({
|
|
446
|
+
entryPath: context.entryPath,
|
|
447
|
+
packageRoot: context.packageRoot,
|
|
448
|
+
mapPath,
|
|
449
|
+
modulePath,
|
|
450
|
+
originalSpecifier: binding.moduleSpecifier,
|
|
451
|
+
}),
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function resolveLayoutMapMemberReference(
|
|
456
|
+
memberExpression: ts.PropertyAccessExpression,
|
|
457
|
+
layoutName: string,
|
|
458
|
+
bindings: Map<string, ImportBinding>,
|
|
459
|
+
mapPath: string,
|
|
460
|
+
context: CrawlContext,
|
|
461
|
+
): LayoutModuleReference | null {
|
|
462
|
+
const mapIdentifier = unwrapExpression(memberExpression.expression);
|
|
463
|
+
if (
|
|
464
|
+
!ts.isIdentifier(mapIdentifier) ||
|
|
465
|
+
!ts.isIdentifier(memberExpression.name)
|
|
466
|
+
) {
|
|
467
|
+
context.warnings.push(
|
|
468
|
+
`Layout "${layoutName}" is not backed by a static import; demo auto-discovery skipped.`,
|
|
469
|
+
);
|
|
470
|
+
return null;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const binding = bindings.get(mapIdentifier.text);
|
|
474
|
+
if (!binding) {
|
|
475
|
+
context.warnings.push(
|
|
476
|
+
`Layout "${layoutName}" references layout map "${mapIdentifier.text}" without a static import; demo auto-discovery skipped.`,
|
|
477
|
+
);
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (binding.importedName !== "default") {
|
|
482
|
+
context.warnings.push(
|
|
483
|
+
`Layout "${layoutName}" references layout map "${mapIdentifier.text}", but only default-imported layout maps can be inspected.`,
|
|
484
|
+
);
|
|
485
|
+
return null;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const memberMapPath = resolveImportedModule(
|
|
489
|
+
mapPath,
|
|
490
|
+
binding.moduleSpecifier,
|
|
491
|
+
context.packageRoot,
|
|
492
|
+
);
|
|
493
|
+
if (!memberMapPath) {
|
|
494
|
+
context.warnings.push(
|
|
495
|
+
`Could not resolve layout map "${binding.moduleSpecifier}" for layout "${layoutName}".`,
|
|
496
|
+
);
|
|
497
|
+
return null;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const memberName = memberExpression.name.text;
|
|
501
|
+
const lookupContext = {
|
|
502
|
+
...context,
|
|
503
|
+
visitedMaps: new Set<string>(),
|
|
504
|
+
};
|
|
505
|
+
const reference = crawlLayoutMapFile(memberMapPath, lookupContext).find(
|
|
506
|
+
(demo) => demo.layoutName === memberName,
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
if (!reference) {
|
|
510
|
+
context.warnings.push(
|
|
511
|
+
`Could not statically find layout "${memberName}" in layout map "${binding.moduleSpecifier}" for layout "${layoutName}".`,
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return reference ?? null;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function resolveNamedExportModulePath(
|
|
519
|
+
modulePath: string,
|
|
520
|
+
exportedName: string,
|
|
521
|
+
packageRoot: string,
|
|
522
|
+
watchedFiles?: Set<string>,
|
|
523
|
+
visited = new Set<string>(),
|
|
377
524
|
): string | null {
|
|
378
|
-
|
|
525
|
+
const visitKey = `${modulePath}#${exportedName}`;
|
|
526
|
+
if (visited.has(visitKey)) return null;
|
|
527
|
+
visited.add(visitKey);
|
|
528
|
+
watchedFiles?.add(modulePath);
|
|
529
|
+
|
|
530
|
+
let sourceFile: ts.SourceFile;
|
|
531
|
+
try {
|
|
532
|
+
sourceFile = parseFile(modulePath);
|
|
533
|
+
} catch {
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
for (const statement of sourceFile.statements) {
|
|
538
|
+
if (!ts.isExportDeclaration(statement)) continue;
|
|
539
|
+
|
|
540
|
+
if (!statement.exportClause) {
|
|
541
|
+
if (
|
|
542
|
+
!statement.moduleSpecifier ||
|
|
543
|
+
!ts.isStringLiteral(statement.moduleSpecifier)
|
|
544
|
+
)
|
|
545
|
+
continue;
|
|
546
|
+
|
|
547
|
+
const starModulePath = resolveImportedModule(
|
|
548
|
+
modulePath,
|
|
549
|
+
statement.moduleSpecifier.text,
|
|
550
|
+
packageRoot,
|
|
551
|
+
);
|
|
552
|
+
if (!starModulePath) continue;
|
|
553
|
+
watchedFiles?.add(starModulePath);
|
|
554
|
+
const resolved = resolveNamedExportModulePath(
|
|
555
|
+
starModulePath,
|
|
556
|
+
exportedName,
|
|
557
|
+
packageRoot,
|
|
558
|
+
watchedFiles,
|
|
559
|
+
visited,
|
|
560
|
+
);
|
|
561
|
+
if (resolved) return resolved;
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (!ts.isNamedExports(statement.exportClause)) continue;
|
|
566
|
+
|
|
567
|
+
for (const element of statement.exportClause.elements) {
|
|
568
|
+
if (element.name.text !== exportedName) continue;
|
|
569
|
+
|
|
570
|
+
if (
|
|
571
|
+
statement.moduleSpecifier &&
|
|
572
|
+
ts.isStringLiteral(statement.moduleSpecifier)
|
|
573
|
+
) {
|
|
574
|
+
const importedModulePath = resolveImportedModule(
|
|
575
|
+
modulePath,
|
|
576
|
+
statement.moduleSpecifier.text,
|
|
577
|
+
packageRoot,
|
|
578
|
+
);
|
|
579
|
+
if (!importedModulePath) return null;
|
|
580
|
+
watchedFiles?.add(importedModulePath);
|
|
581
|
+
|
|
582
|
+
const importedName = element.propertyName?.text ?? element.name.text;
|
|
583
|
+
if (importedName === "default") return importedModulePath;
|
|
584
|
+
|
|
585
|
+
return (
|
|
586
|
+
resolveNamedExportModulePath(
|
|
587
|
+
importedModulePath,
|
|
588
|
+
importedName,
|
|
589
|
+
packageRoot,
|
|
590
|
+
watchedFiles,
|
|
591
|
+
visited,
|
|
592
|
+
) ?? importedModulePath
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const localName = element.propertyName?.text ?? element.name.text;
|
|
597
|
+
const binding = collectImportBindings(sourceFile).get(localName);
|
|
598
|
+
if (!binding) return modulePath;
|
|
599
|
+
|
|
600
|
+
const importedModulePath = resolveImportedModule(
|
|
601
|
+
modulePath,
|
|
602
|
+
binding.moduleSpecifier,
|
|
603
|
+
packageRoot,
|
|
604
|
+
);
|
|
605
|
+
if (!importedModulePath) return null;
|
|
606
|
+
if (binding.importedName === "default") return importedModulePath;
|
|
607
|
+
|
|
608
|
+
watchedFiles?.add(importedModulePath);
|
|
609
|
+
return (
|
|
610
|
+
resolveNamedExportModulePath(
|
|
611
|
+
importedModulePath,
|
|
612
|
+
binding.importedName,
|
|
613
|
+
packageRoot,
|
|
614
|
+
watchedFiles,
|
|
615
|
+
visited,
|
|
616
|
+
) ?? importedModulePath
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
379
620
|
|
|
380
|
-
const
|
|
381
|
-
|
|
621
|
+
for (const statement of sourceFile.statements) {
|
|
622
|
+
if (!hasExportModifier(statement)) continue;
|
|
623
|
+
if (
|
|
624
|
+
(ts.isFunctionDeclaration(statement) ||
|
|
625
|
+
ts.isClassDeclaration(statement)) &&
|
|
626
|
+
statement.name?.text === exportedName
|
|
627
|
+
) {
|
|
628
|
+
return modulePath;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
if (ts.isVariableStatement(statement)) {
|
|
632
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
633
|
+
if (
|
|
634
|
+
ts.isIdentifier(declaration.name) &&
|
|
635
|
+
declaration.name.text === exportedName
|
|
636
|
+
)
|
|
637
|
+
return modulePath;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
return null;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
function hasExportModifier(node: ts.Node): boolean {
|
|
646
|
+
return (
|
|
647
|
+
ts.canHaveModifiers(node) &&
|
|
648
|
+
(ts
|
|
649
|
+
.getModifiers(node)
|
|
650
|
+
?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) ??
|
|
651
|
+
false)
|
|
652
|
+
);
|
|
382
653
|
}
|
|
383
654
|
|
|
384
655
|
function resolveImportedModule(
|
|
@@ -349,7 +349,7 @@ export function virtualModulesPlugin(options: VirtualModulesOptions): Plugin {
|
|
|
349
349
|
// e.g. '\0virtual:honeydeck/slide/2.mdx' → index 2
|
|
350
350
|
const suffix = id.slice(RESOLVED_SLIDE_PREFIX.length); // '2.mdx'
|
|
351
351
|
const index = parseInt(suffix.replace(".mdx", ""), 10);
|
|
352
|
-
const { slides } = getResult();
|
|
352
|
+
const { deckFrontmatter, slides } = getResult();
|
|
353
353
|
const slide = slides[index];
|
|
354
354
|
|
|
355
355
|
if (!slide) {
|
|
@@ -368,7 +368,10 @@ export function virtualModulesPlugin(options: VirtualModulesOptions): Plugin {
|
|
|
368
368
|
remarkGfm,
|
|
369
369
|
remarkH1Extract,
|
|
370
370
|
remarkStepNumbering,
|
|
371
|
-
|
|
371
|
+
[
|
|
372
|
+
remarkShikiCodeBlocks,
|
|
373
|
+
{ magicCodeDuration: deckFrontmatter.magicCodeDuration },
|
|
374
|
+
],
|
|
372
375
|
],
|
|
373
376
|
jsxImportSource: "react",
|
|
374
377
|
outputFormat: "program",
|
|
@@ -529,26 +532,33 @@ export function virtualModulesPlugin(options: VirtualModulesOptions): Plugin {
|
|
|
529
532
|
splitResult = newResult; // commit new cache
|
|
530
533
|
|
|
531
534
|
const affected: ModuleNode[] = [];
|
|
535
|
+
const invalidatedIds = new Set<string>();
|
|
532
536
|
|
|
533
537
|
/** Invalidate a virtual module by its resolved ID, if it's in the graph. */
|
|
534
538
|
const invalidate = (resolvedId: string): void => {
|
|
539
|
+
if (invalidatedIds.has(resolvedId)) return;
|
|
535
540
|
const mod = ctx.server.moduleGraph.getModuleById(resolvedId);
|
|
536
541
|
if (mod) {
|
|
542
|
+
invalidatedIds.add(resolvedId);
|
|
537
543
|
ctx.server.moduleGraph.invalidateModule(mod);
|
|
538
544
|
affected.push(mod);
|
|
539
545
|
}
|
|
540
546
|
};
|
|
541
547
|
|
|
542
|
-
|
|
543
|
-
if (
|
|
548
|
+
const deckFrontmatterChanged =
|
|
544
549
|
JSON.stringify(oldResult.deckFrontmatter) !==
|
|
545
|
-
JSON.stringify(newResult.deckFrontmatter)
|
|
546
|
-
|
|
550
|
+
JSON.stringify(newResult.deckFrontmatter);
|
|
551
|
+
|
|
552
|
+
// Config changed?
|
|
553
|
+
if (deckFrontmatterChanged) {
|
|
547
554
|
invalidate(RESOLVED_CONFIG_ID);
|
|
548
555
|
invalidate(RESOLVED_LAYOUTS_ID);
|
|
549
556
|
for (let i = 0; i < layoutDemoSources.length; i++) {
|
|
550
557
|
invalidate(`${RESOLVED_LAYOUT_DEMO_PREFIX}${i}.mdx`);
|
|
551
558
|
}
|
|
559
|
+
for (let i = 0; i < newResult.slides.length; i++) {
|
|
560
|
+
invalidate(`${RESOLVED_SLIDE_PREFIX}${i}.mdx`);
|
|
561
|
+
}
|
|
552
562
|
}
|
|
553
563
|
|
|
554
564
|
// Per-slide diff — invalidate only slides that changed or were added/removed.
|