diff --git a/docs/src/lib/markdown/utils.ts b/docs/src/lib/markdown/utils.ts index c9c7a3127..b44dc9d2f 100644 --- a/docs/src/lib/markdown/utils.ts +++ b/docs/src/lib/markdown/utils.ts @@ -10,8 +10,14 @@ import { type Util as UtilMetadata } from 'content-collections'; -import type { Examples } from '$lib/types.js'; -import type { ComponentCatalog } from '$examples/catalog/types.js'; +import type { Examples, LoadedExample } from '$lib/types.js'; + +/** + * Helper to clean up source code by removing export statements + */ +function cleanupSourceCode(source: string): string { + return source.replace(/^.*export .*;.*$/gm, ''); +} /** * Resolve a relative or absolute path to a full Svelte component path @@ -75,9 +81,12 @@ function getMetadata( } /** - * Extract examples from markdown content and load their components + * Extract examples from markdown content and eagerly load them. + * + * Only examples explicitly referenced in the markdown are loaded - the catalog + * is NOT used here to avoid loading all examples when only a few are shown. + * * @param markdownContent - The markdown content to extract examples from - * @param catalog - Optional catalog to include examples from * @param allExamples - Glob import of all example components * @param allSources - Glob import of all example sources * @param defaultComponent - Optional default component name (from route params) @@ -87,7 +96,6 @@ function getMetadata( */ export async function loadExamplesFromMarkdown( markdownContent: string, - catalog: ComponentCatalog | null, allExamples: Record Promise>, allSources: Record Promise>, defaultComponent?: string, @@ -118,65 +126,67 @@ export async function loadExamplesFromMarkdown( const examples: Examples = {}; + // Collect all load promises + const loadPromises: Promise[] = []; + // Handle path-based examples for (const example of pageExamples) { if (example.path && currentPath) { const resolvedPath = resolveExamplePath(example.path, currentPath); - // Load component and source if (allSvelteComponents[resolvedPath] && allSvelteSources[resolvedPath]) { - try { - const component = (await allSvelteComponents[resolvedPath]()) as any; - const source = (await allSvelteSources[resolvedPath]()) as string; - - // Store path-based examples under '__path__' namespace - if (!examples['__path__']) { - examples['__path__'] = {}; - } - examples['__path__'][resolvedPath] = { - component: component.default, - source - }; - } catch (e) { - console.error(`Failed to load path-based example: ${resolvedPath}`, e); - } + loadPromises.push( + (async () => { + const [componentModule, rawSource] = await Promise.all([ + allSvelteComponents[resolvedPath](), + allSvelteSources[resolvedPath]() + ]); + + if (!examples['__path__']) { + examples['__path__'] = {}; + } + + examples['__path__'][resolvedPath] = { + component: (componentModule as any).default, + source: cleanupSourceCode(rawSource as string) + } satisfies LoadedExample; + })() + ); } } } // Handle traditional component/name-based examples - for (const path in allExamples) { - // Check if this path matches catalog examples - const catalogMatch = catalog?.examples.some( - (example) => path === `/src/examples/${type}/${catalog.component}/${example.name}.svelte` - ); - - // Check if this path matches page examples - const pageMatch = pageExamples.some( - (example) => - example.component && - example.name && - path === `/src/examples/${type}/${example.component}/${example.name}.svelte` - ); - - if (catalogMatch || pageMatch) { - const component = (await allExamples[path]()) as Component; - const source = (await allSources[path]()) as string; - const pathParts = path.split('/'); - const componentName = pathParts[pathParts.length - 2]; - const filename = pathParts[pathParts.length - 1]; - const name = filename.replace('.svelte', ''); - - // Remove `export { data };` - // TODO: Also remove blank lines left behind - const cleanupSource = source.replace(/^.*export .*;.*$/gm, ''); - - if (!examples[componentName]) { - examples[componentName] = {}; + // ONLY for examples referenced in the markdown content (not the full catalog) + // This ensures we don't load 54 examples when only 1 is shown on the page + for (const example of pageExamples) { + if (example.component && example.name) { + const examplePath = `/src/examples/${type}/${example.component}/${example.name}.svelte`; + + if (allExamples[examplePath] && allSources[examplePath]) { + loadPromises.push( + (async () => { + const [componentModule, rawSource] = await Promise.all([ + allExamples[examplePath](), + allSources[examplePath]() + ]); + + if (!examples[example.component!]) { + examples[example.component!] = {}; + } + + examples[example.component!][example.name!] = { + component: (componentModule as any).default ?? componentModule, + source: cleanupSourceCode(rawSource as string) + } satisfies LoadedExample; + })() + ); } - examples[componentName][name] = { component, source: cleanupSource }; } } + // Wait for all examples to load in parallel + await Promise.all(loadPromises); + return examples; } diff --git a/docs/src/lib/types.ts b/docs/src/lib/types.ts index 2274d621d..56b8ddc57 100644 --- a/docs/src/lib/types.ts +++ b/docs/src/lib/types.ts @@ -1,4 +1,7 @@ import type { Component } from 'svelte'; +// Loaded example with component and source +export type LoadedExample = { component: Component; source: string }; + // Examples by component name and example name -export type Examples = Record>; +export type Examples = Record>; diff --git a/docs/src/routes/docs/+layout.ts b/docs/src/routes/docs/+layout.ts index 8287dec45..78fd383f6 100644 --- a/docs/src/routes/docs/+layout.ts +++ b/docs/src/routes/docs/+layout.ts @@ -31,9 +31,9 @@ export const load = async ({ parent, url }) => { if (allMarkdown[mdPath]) { try { const markdownContent = (await allMarkdown[mdPath]()) as string; + // Eagerly load examples referenced in the markdown examples = await loadExamplesFromMarkdown( markdownContent, - null, // no catalog for standalone pages allExamples, allSources, undefined, // no default component diff --git a/docs/src/routes/docs/components/[name]/+layout.svelte b/docs/src/routes/docs/components/[name]/+layout.svelte index 2b626296f..016864921 100644 --- a/docs/src/routes/docs/components/[name]/+layout.svelte +++ b/docs/src/routes/docs/components/[name]/+layout.svelte @@ -20,9 +20,25 @@ const { metadata } = $derived(data); // Add examples to context for Example component to use + // Merges layout examples with any page-specific examples const examplesContext = { get current() { - return data.examples; + const base = data.examples ?? {}; + + // If there's an example from page data (for individual example pages), merge it in + if (page.data.example && page.params.name && page.params.example) { + const componentName = page.params.name; + const exampleName = page.params.example; + return { + ...base, + [componentName]: { + ...base[componentName], + [exampleName]: page.data.example + } + }; + } + + return base; } }; examples.set(examplesContext); diff --git a/docs/src/routes/docs/components/[name]/+layout.ts b/docs/src/routes/docs/components/[name]/+layout.ts index 203017c6e..86f0bf62a 100644 --- a/docs/src/routes/docs/components/[name]/+layout.ts +++ b/docs/src/routes/docs/components/[name]/+layout.ts @@ -22,10 +22,10 @@ export const load = async ({ params, parent }) => { const catalog: ComponentCatalog | null = catalogPath in allCatalogs ? ((await allCatalogs[catalogPath]()) as ComponentCatalog) : null; - // Load examples from markdown content and catalog + // Eagerly load examples referenced in the markdown content + // Only examples explicitly in the markdown are included (not the full catalog) const pageExamples = await loadExamplesFromMarkdown( metadata.content, - catalog, allExamples, allSources, params.name // default component for implicit usage diff --git a/docs/src/routes/docs/components/[name]/[example]/+page.ts b/docs/src/routes/docs/components/[name]/[example]/+page.ts index 9a8b48c80..f08ae7193 100644 --- a/docs/src/routes/docs/components/[name]/[example]/+page.ts +++ b/docs/src/routes/docs/components/[name]/[example]/+page.ts @@ -1,5 +1,28 @@ -export async function load() { +import type { LoadedExample } from '$lib/types.js'; + +export async function load({ params, parent }) { + const { allExamples, allSources } = await parent(); + + // Eagerly load the specific example for this page + const examplePath = `/src/examples/components/${params.name}/${params.example}.svelte`; + + let example: LoadedExample | null = null; + + if (allExamples[examplePath] && allSources[examplePath]) { + const [componentModule, rawSource] = await Promise.all([ + allExamples[examplePath](), + allSources[examplePath]() + ]); + + const component = componentModule.default ?? componentModule; + // Clean up source by removing export statements + const source = (rawSource as string).replace(/^.*export .*;.*$/gm, ''); + + example = { component, source }; + } + return { + example, meta: { tableOfContents: false } diff --git a/docs/src/routes/docs/utils/[name]/+layout.ts b/docs/src/routes/docs/utils/[name]/+layout.ts index 878ab6509..cda6ac7fa 100644 --- a/docs/src/routes/docs/utils/[name]/+layout.ts +++ b/docs/src/routes/docs/utils/[name]/+layout.ts @@ -22,10 +22,10 @@ export const load = async ({ params, parent }) => { const catalog: ComponentCatalog | null = catalogPath in allCatalogs ? ((await allCatalogs[catalogPath]()) as ComponentCatalog) : null; - // Load examples from markdown content and catalog + // Eagerly load examples referenced in the markdown content + // Only examples explicitly in the markdown are included (not the full catalog) const pageExamples = await loadExamplesFromMarkdown( metadata.content, - catalog, allExamples, allSources, params.name, // default component for implicit usage