Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 60 additions & 50 deletions docs/src/lib/markdown/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -87,7 +96,6 @@ function getMetadata(
*/
export async function loadExamplesFromMarkdown(
markdownContent: string,
catalog: ComponentCatalog | null,
allExamples: Record<string, () => Promise<any>>,
allSources: Record<string, () => Promise<any>>,
defaultComponent?: string,
Expand Down Expand Up @@ -118,65 +126,67 @@ export async function loadExamplesFromMarkdown(

const examples: Examples = {};

// Collect all load promises
const loadPromises: Promise<void>[] = [];

// 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;
}
5 changes: 4 additions & 1 deletion docs/src/lib/types.ts
Original file line number Diff line number Diff line change
@@ -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<string, Record<string, { component: Component; source: string }>>;
export type Examples = Record<string, Record<string, LoadedExample>>;
2 changes: 1 addition & 1 deletion docs/src/routes/docs/+layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 17 additions & 1 deletion docs/src/routes/docs/components/[name]/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions docs/src/routes/docs/components/[name]/+layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <Example name="..." /> usage
Expand Down
25 changes: 24 additions & 1 deletion docs/src/routes/docs/components/[name]/[example]/+page.ts
Original file line number Diff line number Diff line change
@@ -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
}
Expand Down
4 changes: 2 additions & 2 deletions docs/src/routes/docs/utils/[name]/+layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <Example name="..." /> usage
Expand Down
Loading