diff --git a/apps/svelte.dev/content/blog/2024-12-01-advent-of-svelte.md b/apps/svelte.dev/content/blog/2024-12-01-advent-of-svelte.md index 5a81ef26a5..f569196f7e 100644 --- a/apps/svelte.dev/content/blog/2024-12-01-advent-of-svelte.md +++ b/apps/svelte.dev/content/blog/2024-12-01-advent-of-svelte.md @@ -99,9 +99,13 @@ As of today, you can also return things that _aren't_ built in to the language, - [docs](/docs/kit/hooks#Universal-hooks-transport) - [demo](https://stackblitz.com/edit/sveltejs-kit-template-default-b5zbxomg?file=src%2Fhooks.js) -## Day 13 +## Day 13: rise of the robots -Coming soon! +For those of you using LLMs to help you write code — via Cursor or Copilot or Claude or Bolt or v0 or some other interface — we now publish the documentation in a selection of robot-friendly `llms.txt` files. This is experimental and will evolve over time, but by way of example here's a [snake game](/playground/0de3c1c1a31d47bdbb7c4aa3477a6b46) built by Sonnet 3.5 with no additional prompting. + +Thanks to [Didier Catz](https://x.com/didiercatz) and [Stanislav Khromov](https://bsky.app/profile/khromov.se) for building this! + +- [docs](/docs/llms) ## Day 14 diff --git a/apps/svelte.dev/package.json b/apps/svelte.dev/package.json index 62625303a4..c158effcd5 100644 --- a/apps/svelte.dev/package.json +++ b/apps/svelte.dev/package.json @@ -67,6 +67,7 @@ "lightningcss": "^1.25.1", "magic-string": "^0.30.11", "marked": "^14.1.2", + "minimatch": "^10.0.1", "prettier": "^3.3.2", "prettier-plugin-svelte": "^3.2.4", "satori": "^0.10.13", diff --git a/apps/svelte.dev/src/lib/server/llms.ts b/apps/svelte.dev/src/lib/server/llms.ts new file mode 100644 index 0000000000..3008cff916 --- /dev/null +++ b/apps/svelte.dev/src/lib/server/llms.ts @@ -0,0 +1,134 @@ +import { minimatch } from 'minimatch'; +import { dev } from '$app/environment'; +import { index } from './content'; + +interface GenerateLlmContentOptions { + ignore?: string[]; + minimize?: Partial; + sections: Section[]; +} + +interface MinimizeOptions { + remove_legacy: boolean; + remove_note_blocks: boolean; + remove_details_blocks: boolean; + remove_playground_links: boolean; + remove_prettier_ignore: boolean; + normalize_whitespace: boolean; +} + +interface Section { + slug: string; + title: string; +} + +const defaults: MinimizeOptions = { + remove_legacy: false, + remove_note_blocks: false, + remove_details_blocks: false, + remove_playground_links: false, + remove_prettier_ignore: false, + normalize_whitespace: false +}; + +export function generate_llm_content(options: GenerateLlmContentOptions): string { + let content = ''; + + for (const section of options.sections) { + if (options.sections.length > 1) { + content += `# Start of ${section.title} documentation\n\n`; + } + + for (const [path, document] of Object.entries(index)) { + if (!path.startsWith(`docs/${section.slug}`)) continue; + + if (options.ignore?.some((pattern) => minimatch(path, pattern))) { + if (dev) console.log(`❌ Ignored by pattern: ${path}`); + continue; + } + + const doc_content = options.minimize + ? minimize_content(document.body, options.minimize) + : document.body; + if (doc_content.trim() === '') continue; + + content += `\n# ${document.metadata.title}\n\n`; + content += doc_content; + content += '\n'; + } + } + + return content; +} + +export const sections: Section[] = [ + { slug: 'svelte', title: 'Svelte' }, + { slug: 'kit', title: 'SvelteKit' }, + { slug: 'cli', title: 'the Svelte CLI' } +]; + +export function get_documentation_title(section: Section): string { + return `This is the developer documentation for ${section.title}.`; +} + +function minimize_content(content: string, options?: Partial): string { + // Merge with defaults, but only for properties that are defined + const settings: MinimizeOptions = { ...defaults, ...options }; + + let minimized = content; + + if (settings.remove_legacy) { + minimized = remove_quote_blocks(minimized, 'LEGACY'); + } + + if (settings.remove_note_blocks) { + minimized = remove_quote_blocks(minimized, 'NOTE'); + } + + if (settings.remove_details_blocks) { + minimized = remove_quote_blocks(minimized, 'DETAILS'); + } + + if (settings.remove_playground_links) { + // Replace playground URLs with /[link] but keep the original link text + minimized = minimized.replace(/\[([^\]]+)\]\(\/playground[^)]+\)/g, '[$1](/REMOVED)'); + } + + if (settings.remove_prettier_ignore) { + minimized = minimized + .split('\n') + .filter((line) => line.trim() !== '') + .join('\n'); + } + + if (settings.normalize_whitespace) { + minimized = minimized.replace(/\s+/g, ' '); + } + + minimized = minimized.trim(); + + return minimized; +} + +function remove_quote_blocks(content: string, blockType: string): string { + return content + .split('\n') + .reduce((acc: string[], line: string, index: number, lines: string[]) => { + // If we find a block (with or without additional text), skip it and all subsequent blockquote lines + if (line.trim().startsWith(`> [!${blockType}]`)) { + // Skip all subsequent lines that are part of the blockquote + let i = index; + while (i < lines.length && (lines[i].startsWith('>') || lines[i].trim() === '')) { + i++; + } + // Update the index to skip all these lines + index = i - 1; + return acc; + } + + // Only add the line if it's not being skipped + acc.push(line); + return acc; + }, []) + .join('\n'); +} diff --git a/apps/svelte.dev/src/routes/docs/+page.svelte b/apps/svelte.dev/src/routes/docs/+page.svelte index 06929a50e5..6691bf3986 100644 --- a/apps/svelte.dev/src/routes/docs/+page.svelte +++ b/apps/svelte.dev/src/routes/docs/+page.svelte @@ -90,6 +90,14 @@

+ +

I'm a Large Language Model (LLM)

+

+ If you're an artificial intelligence, or trying to teach one how to use Svelte, we offer the + documentation in plaintext format. Beep boop. +

+
+

Help! I'm stuck

diff --git a/apps/svelte.dev/src/routes/docs/[...path]/llms.txt/+server.ts b/apps/svelte.dev/src/routes/docs/[...path]/llms.txt/+server.ts new file mode 100644 index 0000000000..d757e502e0 --- /dev/null +++ b/apps/svelte.dev/src/routes/docs/[...path]/llms.txt/+server.ts @@ -0,0 +1,29 @@ +import { error } from '@sveltejs/kit'; +import { generate_llm_content, get_documentation_title, sections } from '$lib/server/llms'; + +export const prerender = true; + +export function entries() { + return sections.map((section) => ({ path: section.slug })); +} + +export function GET({ params }) { + const pkg = params.path; + + const section = sections.find((s) => s.slug === pkg); + + if (!section) { + error(404, 'Not Found'); + } + + const prefix = `${get_documentation_title(section)}`; + const content = `${prefix}\n\n${generate_llm_content({ sections: [section] })}`; + + return new Response(content, { + status: 200, + headers: { + 'Content-Type': 'text/plain; charset=utf-8', + 'Cache-Control': 'public, max-age=3600' + } + }); +} diff --git a/apps/svelte.dev/src/routes/docs/llms/+page.svelte b/apps/svelte.dev/src/routes/docs/llms/+page.svelte new file mode 100644 index 0000000000..2d07e49cf2 --- /dev/null +++ b/apps/svelte.dev/src/routes/docs/llms/+page.svelte @@ -0,0 +1,46 @@ + + +

+ +

Docs for LLMs

+ +

+ We support the llms.txt convention for making documentation + available to large language models and the applications that make use of them. +

+ +

Currently, we have the following root-level files...

+ +
    +
  • /llms.txt — a listing of the available files
  • +
  • + /llms-full.txt — complete documentation for Svelte, SvelteKit and + the CLI +
  • +
  • + /llms-small.txt — compressed documentation for use with smaller + context windows +
  • +
+ +

...and package-level documentation:

+ + + +
+ + diff --git a/apps/svelte.dev/src/routes/llms-full.txt/+server.ts b/apps/svelte.dev/src/routes/llms-full.txt/+server.ts new file mode 100644 index 0000000000..18f2518797 --- /dev/null +++ b/apps/svelte.dev/src/routes/llms-full.txt/+server.ts @@ -0,0 +1,15 @@ +import { generate_llm_content, sections } from '$lib/server/llms'; + +export const prerender = true; + +export function GET() { + const content = `This is the full developer documentation for Svelte and SvelteKit.\n\n${generate_llm_content({ sections })}`; + + return new Response(content, { + status: 200, + headers: { + 'Content-Type': 'text/plain; charset=utf-8', + 'Cache-Control': 'public, max-age=3600' + } + }); +} diff --git a/apps/svelte.dev/src/routes/llms-small.txt/+server.ts b/apps/svelte.dev/src/routes/llms-small.txt/+server.ts new file mode 100644 index 0000000000..63eb51f77e --- /dev/null +++ b/apps/svelte.dev/src/routes/llms-small.txt/+server.ts @@ -0,0 +1,47 @@ +import { generate_llm_content, sections } from '$lib/server/llms'; + +export function GET() { + const main_content = generate_llm_content({ + sections, + ignore: [ + // Svelte ignores + 'docs/svelte/legacy/**/*', + 'docs/svelte/misc/custom-elements', + 'docs/svelte/misc/v4-migration-guide', + 'docs/svelte/misc/v5-migration-guide', + 'docs/svelte/misc/faq', + 'docs/svelte/reference/compiler-errors', + 'docs/svelte/reference/compiler-warnings', + 'docs/svelte/reference/runtime-errors', + 'docs/svelte/reference/runtime-warnings', + 'docs/svelte/reference/svelte-legacy', + '**/xx-*', + + // SvelteKit ignores + 'docs/kit/advanced/packaging', + 'docs/kit/appendix/**/*', + 'docs/kit/best-practices/performance', + 'docs/kit/build-and-deploy/*adapter-*', + 'docs/kit/build-and-deploy/writing-adapters' + ], + minimize: { + remove_legacy: true, + remove_note_blocks: true, + remove_details_blocks: true, + remove_playground_links: true, + remove_prettier_ignore: true, + normalize_whitespace: true + } + }); + const content = `This is the abridged developer documentation for Svelte and SvelteKit.\n\n${main_content}`; + + return new Response(content, { + status: 200, + headers: { + 'Content-Type': 'text/plain; charset=utf-8', + 'Cache-Control': 'public, max-age=3600' + } + }); +} + +export const prerender = true; diff --git a/apps/svelte.dev/src/routes/llms.txt/+server.ts b/apps/svelte.dev/src/routes/llms.txt/+server.ts new file mode 100644 index 0000000000..9a961557c2 --- /dev/null +++ b/apps/svelte.dev/src/routes/llms.txt/+server.ts @@ -0,0 +1,22 @@ +import { get_documentation_title, sections } from '$lib/server/llms'; +import template from './template.md?raw'; + +const DOMAIN = `https://svelte.dev`; + +export const prerender = true; + +export function GET() { + const package_docs = sections.map( + (section) => + `- [${section.title} documentation](${DOMAIN}/docs/${section.slug}/llms.txt): ${get_documentation_title(section)}` + ); + + const content = template.replace('%PACKAGE_DOCS%', package_docs.join('\n')); + + return new Response(content, { + headers: { + 'Content-Type': 'text/plain; charset=utf-8', + 'Cache-Control': 'public, max-age=3600' + } + }); +} diff --git a/apps/svelte.dev/src/routes/llms.txt/template.md b/apps/svelte.dev/src/routes/llms.txt/template.md new file mode 100644 index 0000000000..aa4b32e1ac --- /dev/null +++ b/apps/svelte.dev/src/routes/llms.txt/template.md @@ -0,0 +1,19 @@ +# Svelte Documentation for LLMs + +> Svelte is a UI framework that uses a compiler to let you write breathtakingly concise components that do minimal work in the browser, using languages you already know — HTML, CSS and JavaScript. + +## Documentation Sets + +- [Abridged documentation](https://svelte.dev/llms-small.txt): A minimal version of the Svelte and SvelteKit documentation, with examples and non-essential content removed +- [Complete documentation](https://svelte.dev/llms-full.txt): The complete Svelte and SvelteKit documentation including all examples and additional content + +## Individual Package Documentation + +%PACKAGE_DOCS% + +## Notes + +- The abridged documentation excludes legacy compatibility notes, detailed examples, and supplementary information +- The complete documentation includes all content from the official documentation +- Package-specific documentation files contain only the content relevant to that package +- The content is automatically generated from the same source as the official documentation diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e6aaa965d7..8b52930155 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -162,6 +162,9 @@ importers: marked: specifier: ^14.1.2 version: 14.1.2 + minimatch: + specifier: ^10.0.1 + version: 10.0.1 prettier: specifier: ^3.3.2 version: 3.3.2 @@ -2558,6 +2561,10 @@ packages: engines: {node: '>=10.0.0'} hasBin: true + minimatch@10.0.1: + resolution: {integrity: sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==} + engines: {node: 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -5520,6 +5527,10 @@ snapshots: mime@3.0.0: {} + minimatch@10.0.1: + dependencies: + brace-expansion: 2.0.1 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.11