diff --git a/documentation/docs/20-core-concepts/10-routing.md b/documentation/docs/20-core-concepts/10-routing.md index 63f28d22609a..9326d5eae0c2 100644 --- a/documentation/docs/20-core-concepts/10-routing.md +++ b/documentation/docs/20-core-concepts/10-routing.md @@ -19,20 +19,20 @@ Each route directory contains one or more _route files_, which can be identified A `+page.svelte` component defines a page of your app. By default, pages are rendered both on the server ([SSR](glossary#ssr)) for the initial request and in the browser ([CSR](glossary#csr)) for subsequent navigation. ```svelte -/// file: src/routes/+page.svelte +

Hello and welcome to my site!

About my site ``` ```svelte -/// file: src/routes/about/+page.svelte +

About this site

TODO...

Home ``` ```svelte -/// file: src/routes/blog/[slug]/+page.svelte + @@ -188,7 +188,7 @@ Layouts can be _nested_. Suppose we don't just have a single `/settings` page, b We can create a layout that only applies to pages below `/settings` (while inheriting the root layout with the top-level nav): ```svelte -/// file: src/routes/settings/+layout.svelte + @@ -341,7 +341,7 @@ export async function load({ parent }) { ``` ```svelte -/// file: src/routes/abc/+page.svelte + diff --git a/documentation/docs/30-advanced/65-snapshots.md b/documentation/docs/30-advanced/65-snapshots.md index fff0a2e372a0..2ea91be8b357 100644 --- a/documentation/docs/30-advanced/65-snapshots.md +++ b/documentation/docs/30-advanced/65-snapshots.md @@ -9,7 +9,7 @@ For example, if the user fills out a form but clicks a link before submitting, t To do this, export a `snapshot` object with `capture` and `restore` methods from a `+page.svelte` or `+layout.svelte`: ```svelte -/// file: +page.svelte + -
%sveltekit.body%
+ +
%sveltekit.body%
diff --git a/sites/kit.svelte.dev/src/constants.js b/sites/kit.svelte.dev/src/constants.js new file mode 100644 index 000000000000..a50a135a6641 --- /dev/null +++ b/sites/kit.svelte.dev/src/constants.js @@ -0,0 +1,6 @@ +export const CONTENT_BASE = '../../documentation'; + +/** All the paths are relative to the project root when being run on server or built */ +export const CONTENT_BASE_PATHS = { + DOCS: `${CONTENT_BASE}/docs` +}; diff --git a/sites/kit.svelte.dev/src/lib/docs/Contents.svelte b/sites/kit.svelte.dev/src/lib/docs/Contents.svelte deleted file mode 100644 index 4597cf0e67a6..000000000000 --- a/sites/kit.svelte.dev/src/lib/docs/Contents.svelte +++ /dev/null @@ -1,158 +0,0 @@ - - - - - diff --git a/sites/kit.svelte.dev/src/lib/docs/client/Tooltip.svelte b/sites/kit.svelte.dev/src/lib/docs/client/Tooltip.svelte deleted file mode 100644 index 56ecc6f5383a..000000000000 --- a/sites/kit.svelte.dev/src/lib/docs/client/Tooltip.svelte +++ /dev/null @@ -1,67 +0,0 @@ - - - -
-
- {@html html} -
-
- - diff --git a/sites/kit.svelte.dev/src/lib/docs/client/hovers.js b/sites/kit.svelte.dev/src/lib/docs/client/hovers.js deleted file mode 100644 index f0a079f2a557..000000000000 --- a/sites/kit.svelte.dev/src/lib/docs/client/hovers.js +++ /dev/null @@ -1,60 +0,0 @@ -import { onMount } from 'svelte'; -import Tooltip from './Tooltip.svelte'; - -export function setup() { - onMount(() => { - let tooltip; - let timeout; - - function over(event) { - if (event.target.tagName === 'DATA-LSP') { - clearTimeout(timeout); - - if (!tooltip) { - tooltip = new Tooltip({ - target: document.body - }); - - tooltip.$on('mouseenter', () => { - clearTimeout(timeout); - }); - - tooltip.$on('mouseleave', () => { - clearTimeout(timeout); - tooltip.$destroy(); - tooltip = null; - }); - } - - const rect = event.target.getBoundingClientRect(); - const html = event.target.getAttribute('lsp'); - - const x = (rect.left + rect.right) / 2 + window.scrollX; - const y = rect.top + window.scrollY; - - tooltip.$set({ - html, - x, - y - }); - } - } - - function out(event) { - if (event.target.tagName === 'DATA-LSP') { - timeout = setTimeout(() => { - tooltip.$destroy(); - tooltip = null; - }, 200); - } - } - - window.addEventListener('mouseover', over); - window.addEventListener('mouseout', out); - - return () => { - window.removeEventListener('mouseover', over); - window.removeEventListener('mouseout', out); - }; - }); -} diff --git a/sites/kit.svelte.dev/src/lib/docs/server/index.js b/sites/kit.svelte.dev/src/lib/docs/server/index.js deleted file mode 100644 index cb6f23129aea..000000000000 --- a/sites/kit.svelte.dev/src/lib/docs/server/index.js +++ /dev/null @@ -1,628 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import { renderCodeToHTML, runTwoSlash, createShikiHighlighter } from 'shiki-twoslash'; -import PrismJS from 'prismjs'; -import 'prismjs/components/prism-bash.js'; -import 'prismjs/components/prism-diff.js'; -import 'prismjs/components/prism-typescript.js'; -import 'prism-svelte'; -import { escape, extract_frontmatter, transform } from './markdown.js'; -import { modules } from './type-info.js'; -import { replace_placeholders } from './render.js'; -import { parse_route_id } from '../../../../../../packages/kit/src/utils/routing.js'; -import ts from 'typescript'; -import MagicString from 'magic-string'; -import { fileURLToPath } from 'url'; -import { createHash } from 'crypto'; - -const snippet_cache = fileURLToPath(new URL('../../../../.snippets', import.meta.url)); -if (!fs.existsSync(snippet_cache)) { - fs.mkdirSync(snippet_cache, { recursive: true }); -} - -const languages = { - bash: 'bash', - env: 'bash', - html: 'markup', - svelte: 'svelte', - js: 'javascript', - css: 'css', - diff: 'diff', - ts: 'typescript', - '': '' -}; - -const base = '../../documentation'; - -const type_regex = new RegExp( - `(import\\('@sveltejs\\/kit'\\)\\.)?\\b(${modules - .flatMap((module) => module.types) - .map((type) => type.name) - .join('|')})\\b`, - 'g' -); - -const type_links = new Map(); - -const slugs = { - '@sveltejs/kit': 'public-types' -}; - -modules.forEach((module) => { - const slug = slugs[module.name] || slugify(module.name); - - module.types.forEach((type) => { - const link = `/docs/types#${slug}-${slugify(type.name)}`; - type_links.set(type.name, link); - }); -}); - -/** @param {string} html */ -function replace_blank_lines(html) { - // preserve blank lines in output (maybe there's a more correct way to do this?) - return html.replaceAll(/
( )?<\/div>/g, '
\n
'); -} - -function dynamic_extensions(text) { - if (text === 'svelte.config.js') return text; - - return text.replace(/^(\S+)\.(js|ts)$/, (match, $1) => { - if (match.endsWith('.d.ts')) return match; - if ($1 === 'svelte.config') return match; - return `${$1}.js.ts`; - }); -} - -/** - * @param {string} file - */ -export async function read_file(file) { - const match = /\d{2}-(.+)\.md/.exec(path.basename(file)); - if (!match) return null; - - const markdown = replace_placeholders(fs.readFileSync(`${base}/${file}`, 'utf-8')); - - const highlighter = await createShikiHighlighter({ theme: 'css-variables' }); - - const { metadata, body } = extract_frontmatter(markdown); - - const { content, sections } = parse({ - file, - body: generate_ts_from_js(body), - code: (source, language, current) => { - const hash = createHash('sha256'); - - hash.update(source + language + current); - const digest = hash.digest().toString('base64').replace(/\//g, '-'); - - if (fs.existsSync(`${snippet_cache}/${digest}.html`)) { - return fs.readFileSync(`${snippet_cache}/${digest}.html`, 'utf-8'); - } - - /** @type {Record} */ - const options = {}; - - let html = ''; - - source = source - .replace(/^\/\/\/ (.+?): (.+)\n/gm, (match, key, value) => { - options[key] = value; - return ''; - }) - .replace(/^([\-\+])?((?: )+)/gm, (match, prefix = '', spaces) => { - if (prefix && language !== 'diff') return match; - - // for no good reason at all, marked replaces tabs with spaces - let tabs = ''; - for (let i = 0; i < spaces.length; i += 4) { - tabs += ' '; - } - - return prefix + tabs; - }) - .replace(/\*\\\//g, '*/'); - - let version_class = ''; - if (language === 'generated-ts' || language === 'generated-svelte') { - language = language.replace('generated-', ''); - version_class = 'ts-version'; - } else if (language === 'original-js' || language === 'original-svelte') { - language = language.replace('original-', ''); - version_class = 'js-version'; - } - - if (language === 'dts' || language === 'yaml') { - html = renderCodeToHTML( - source, - language === 'dts' ? 'ts' : language, - { twoslash: false }, - { themeName: 'css-variables' }, - highlighter - ); - html = replace_blank_lines(html); - } else if (language === 'js' || language === 'ts') { - try { - const injected = []; - if ( - source.includes('$app/') || - source.includes('$service-worker') || - source.includes('@sveltejs/kit/') - ) { - injected.push( - `// @filename: ambient-kit.d.ts`, - `/// ` - ); - } - - if (source.includes('$env/')) { - // TODO we're hardcoding static env vars that are used in code examples - // in the types, which isn't... totally ideal, but will do for now - injected.push( - `declare module '$env/dynamic/private' { export const env: Record }`, - `declare module '$env/dynamic/public' { export const env: Record }`, - `declare module '$env/static/private' { export const API_KEY: string }`, - `declare module '$env/static/public' { export const PUBLIC_BASE_URL: string }` - ); - } - - if (source.includes('./$types') && !source.includes('@filename: $types.d.ts')) { - const params = parse_route_id(options.file || `+page.${language}`) - .params.map((param) => `${param.name}: string`) - .join(', '); - - injected.push( - `// @filename: $types.d.ts`, - `import type * as Kit from '@sveltejs/kit';`, - `export type PageLoad = Kit.Load<{${params}}>;`, - `export type PageServerLoad = Kit.ServerLoad<{${params}}>;`, - `export type LayoutLoad = Kit.Load<{${params}}>;`, - `export type LayoutServerLoad = Kit.ServerLoad<{${params}}>;`, - `export type RequestHandler = Kit.RequestHandler<{${params}}>;`, - `export type Action = Kit.Action<{${params}}>;`, - `export type Actions = Kit.Actions<{${params}}>;`, - `export type EntryGenerator = () => Promise> | Array<{${params}}>;` - ); - } - - // special case — we need to make allowances for code snippets coming - // from e.g. ambient.d.ts - if (file.endsWith('30-modules.md')) { - injected.push('// @errors: 7006 7031'); - } - - if (file.endsWith('10-configuration.md')) { - injected.push('// @errors: 2307'); - } - - // another special case - if (source.includes('$lib/types')) { - injected.push(`declare module '$lib/types' { export interface User {} }`); - } - - if (injected.length) { - const injected_str = injected.join('\n'); - if (source.includes('// @filename:')) { - source = source.replace('// @filename:', `${injected_str}\n\n// @filename:`); - } else { - source = source.replace( - /^(?!\/\/ @)/m, - `${injected_str}\n\n// @filename: index.${language}\n// ---cut---\n` - ); - } - } - - const twoslash = runTwoSlash(source, language, { - defaultCompilerOptions: { - allowJs: true, - checkJs: true, - target: ts.ScriptTarget.ES2022 - } - }); - - html = renderCodeToHTML( - twoslash.code, - 'ts', - { twoslash: true }, - { themeName: 'css-variables' }, - highlighter, - twoslash - ); - } catch (e) { - console.error(`Error compiling snippet in ${file}`); - console.error(e.code); - throw e; - } - - // we need to be able to inject the LSP attributes as HTML, not text, so we - // turn < into &lt; - html = html.replace( - /]*)>(\w+)<\/data-lsp>/g, - (match, lsp, attrs, name) => { - if (!lsp) return name; - return `${name}`; - } - ); - - html = replace_blank_lines(html); - } else if (language === 'diff') { - const lines = source.split('\n').map((content) => { - let type = null; - if (/^[\+\-]/.test(content)) { - type = content[0] === '+' ? 'inserted' : 'deleted'; - content = content.slice(1); - } - - return { - type, - content: escape(content) - }; - }); - - html = `
${lines
-					.map((line) => {
-						if (line.type) return `${line.content}\n`;
-						return line.content + '\n';
-					})
-					.join('')}
`; - } else { - const plang = languages[language]; - - const highlighted = plang - ? PrismJS.highlight(source, PrismJS.languages[plang], language) - : source.replace(/[&<>]/g, (c) => ({ '&': '&', '<': '<', '>': '>' }[c])); - - html = `
${highlighted}
`; - } - - if (options.file) { - html = `
${options.file}${html}
`; - } - - if (version_class) { - html = html.replace(/class=('|")/, `class=$1${version_class} `); - } - - type_regex.lastIndex = 0; - - html = html - .replace(type_regex, (match, prefix, name) => { - if (options.link === 'false' || name === current) { - // we don't want e.g. RequestHandler to link to RequestHandler - return match; - } - - const link = `${name}`; - return `${prefix || ''}${link}`; - }) - .replace( - /^(\s+)([\s\S]+?)<\/span>\n/gm, - (match, intro_whitespace, content) => { - // we use some CSS trickery to make comments break onto multiple lines while preserving indentation - const lines = (intro_whitespace + content).split('\n'); - return lines - .map((line) => { - const match = /^(\s*)(.*)/.exec(line); - const indent = (match[1] ?? '').replace(/\t/g, ' ').length; - - return `${ - line ?? '' - }`; - }) - .join(''); - } - ) - .replace(/\/\*…\*\//g, '…'); - - fs.writeFileSync(`${snippet_cache}/${digest}.html`, html); - return html; - }, - codespan: (text) => { - return ( - '' + - dynamic_extensions(text).replace(type_regex, (match, prefix, name) => { - const link = `${name}`; - return `${prefix || ''}${link}`; - }) + - '' - ); - } - }); - - return { - file, - slug: match[1], - title: metadata.title, - content, - sections - }; -} - -/** - * @param {{ - * file: string; - * body: string; - * code: (source: string, language: string, current: string) => string; - * codespan: (source: string) => string; - * }} opts - */ -function parse({ file, body, code, codespan }) { - const headings = []; - - /** @type {import('./types').Section[]} */ - const sections = []; - - /** @type {import('./types').Section} */ - let section; - - // this is a bit hacky, but it allows us to prevent type declarations - // from linking to themselves - let current = ''; - - /** @type {string} */ - const content = transform(body, { - /** - * @param {string} html - * @param {number} level - */ - heading(html, level) { - const title = html - .replace(/<\/?code>/g, '') - .replace(/"/g, '"') - .replace(/</g, '<') - .replace(/>/g, '>'); - - current = title; - - const normalized = slugify(title); - - headings[level - 1] = normalized; - headings.length = level; - - const slug = headings.filter(Boolean).join('-'); - - if (level === 2) { - section = { - title, - slug, - sections: [] - }; - - sections.push(section); - } else if (level === 3) { - (section?.sections ?? sections).push({ - title, - slug - }); - } else { - throw new Error(`Unexpected in ${file}`); - } - - return `${dynamic_extensions(html)} - `; - }, - code: (source, language) => code(source, language, current), - codespan - }); - - return { - sections, - content - }; -} - -/** @param {string} title */ -export function slugify(title) { - return title - .toLowerCase() - .replace(/</g, '') - .replace(/>/g, '') - .replace(/[^a-z0-9-$]/g, '-') - .replace(/-{2,}/g, '-') - .replace(/^-/, '') - .replace(/-$/, ''); -} - -/** - * Appends a JS->TS / Svelte->Svelte-TS code block after each JS/Svelte code block. - * The language is `generated-js`/`generated-svelte` which can be used to detect this in later steps. - * @param {string} markdown - */ -export function generate_ts_from_js(markdown) { - return markdown - .replaceAll(/```js\n([\s\S]+?)\n```/g, (match, code) => { - if (!code.includes('/// file:')) { - // No named file -> assume that the code is not meant to be shown in two versions - return match; - } - if (code.includes('/// file: svelte.config.js')) { - // svelte.config.js has no TS equivalent - return match; - } - - const ts = convert_to_ts(code); - - if (!ts) { - // No changes -> don't show TS version - return match; - } - - return match.replace('js', 'original-js') + '\n```generated-ts\n' + ts + '\n```'; - }) - .replaceAll(/```svelte\n([\s\S]+?)\n```/g, (match, code) => { - if (!code.includes('/// file:')) { - // No named file -> assume that the code is not meant to be shown in two versions - return match; - } - - // Assumption: no context="module" blocks - const script = code.match(/`) + - '\n```' - ); - }); -} - -/** - * Transforms a JS code block into a TS code block by turning JSDoc into type annotations. - * Due to pragmatism only the cases currently used in the docs are implemented. - * @param {string} js_code - * @param {string} [indent] - * @param {string} [offset] - * */ -function convert_to_ts(js_code, indent = '', offset = '') { - js_code = js_code - .replaceAll('// @filename: index.js', '// @filename: index.ts') - .replace(/(\/\/\/ .+?\.)js/, '$1ts') - // *\/ appears in some JsDoc comments in d.ts files due to the JSDoc-in-JSDoc problem - .replace(/\*\\\//g, '*/'); - - const ast = ts.createSourceFile( - 'filename.ts', - js_code, - ts.ScriptTarget.Latest, - true, - ts.ScriptKind.TS - ); - const code = new MagicString(js_code); - const imports = new Map(); - - function walk(node) { - // @ts-ignore - if (node.jsDoc) { - // @ts-ignore - for (const comment of node.jsDoc) { - let modified = false; - - for (const tag of comment.tags ?? []) { - if (ts.isJSDocTypeTag(tag)) { - const [name, generics] = get_type_info(tag); - - if (ts.isFunctionDeclaration(node)) { - const is_export = - ts.canHaveModifiers(node) && - ts - .getModifiers(node) - ?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) - ? 'export ' - : ''; - const is_async = - ts.canHaveModifiers(node) && - ts - .getModifiers(node) - ?.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword); - code.overwrite( - node.getStart(), - node.name.getEnd(), - `${is_export ? 'export ' : ''}const ${node.name.getText()} = (${ - is_async ? 'async ' : '' - }` - ); - code.appendLeft(node.body.getStart(), '=> '); - const type = generics !== undefined ? `${name}${generics}` : name; - code.appendLeft(node.body.getEnd(), `) satisfies ${type};`); - - modified = true; - } else if ( - ts.isVariableStatement(node) && - node.declarationList.declarations.length === 1 - ) { - const variable_statement = node.declarationList.declarations[0]; - - if (variable_statement.name.getText() === 'actions') { - code.appendLeft(variable_statement.getEnd(), ` satisfies ${name}`); - } else { - code.appendLeft(variable_statement.name.getEnd(), `: ${name}`); - } - - modified = true; - } else { - throw new Error('Unhandled @type JsDoc->TS conversion: ' + js_code); - } - } else if (ts.isJSDocParameterTag(tag) && ts.isFunctionDeclaration(node)) { - if (node.parameters.length !== 1) { - throw new Error( - 'Unhandled @type JsDoc->TS conversion; needs more params logic: ' + node.getText() - ); - } - const [name] = get_type_info(tag); - code.appendLeft(node.parameters[0].getEnd(), `: ${name}`); - - modified = true; - } - } - - if (modified) { - code.overwrite(comment.getStart(), comment.getEnd(), ''); - } - } - } - - ts.forEachChild(node, walk); - } - - walk(ast); - - if (imports.size) { - const import_statements = Array.from(imports.entries()) - .map(([from, names]) => { - return `${indent}import type { ${Array.from(names).join(', ')} } from '${from}';`; - }) - .join('\n'); - const idxOfLastImport = [...ast.statements] - .reverse() - .find((statement) => ts.isImportDeclaration(statement)) - ?.getEnd(); - const insertion_point = Math.max( - idxOfLastImport ? idxOfLastImport + 1 : 0, - js_code.includes('---cut---') - ? js_code.indexOf('\n', js_code.indexOf('---cut---')) + 1 - : js_code.includes('/// file:') - ? js_code.indexOf('\n', js_code.indexOf('/// file:')) + 1 - : 0 - ); - code.appendLeft(insertion_point, offset + import_statements + '\n'); - } - - const transformed = code.toString(); - return transformed === js_code ? undefined : transformed.replace(/\n\s*\n\s*\n/g, '\n\n'); - - /** @param {ts.JSDocTypeTag | ts.JSDocParameterTag} tag */ - function get_type_info(tag) { - const type_text = tag.typeExpression.getText(); - let name = type_text.slice(1, -1); // remove { } - - const import_match = /import\('(.+?)'\)\.(\w+)(<{?[\n\* \w:;,]+}?>)?/.exec(type_text); - if (import_match) { - const [, from, _name, generics] = import_match; - name = _name; - const existing = imports.get(from); - if (existing) { - existing.add(name); - } else { - imports.set(from, new Set([name])); - } - if (generics !== undefined) { - return [ - name, - generics - .replaceAll('*', '') // get rid of JSDoc asterisks - .replace(' }>', '}>') // unindent closing brace - ]; - } - } - return [name]; - } -} diff --git a/sites/kit.svelte.dev/src/lib/docs/server/index.spec.js b/sites/kit.svelte.dev/src/lib/docs/server/index.spec.js deleted file mode 100644 index a5f12d630eea..000000000000 --- a/sites/kit.svelte.dev/src/lib/docs/server/index.spec.js +++ /dev/null @@ -1,228 +0,0 @@ -import { assert, test } from 'vitest'; -import { generate_ts_from_js } from './index.js'; - -/** - * @param {string} input - * @param {string} expected - */ -function test_ts_transformation(input, expected) { - const output = generate_ts_from_js(input); - assert.equal(output, expected); -} - -test('Creates TS from JS', () => { - test_ts_transformation( - ` -### sub heading - -etc -\`\`\`js -// @errors: 2461 -/// file: src/routes/what-is-my-user-agent/+server.js -import { json } from '@sveltejs/kit'; - -/** @type {import('./$types').RequestHandler} */ -export function GET(event) { - // log all headers - console.log(...event.request.headers); - - return json({ - // retrieve a specific header - userAgent: event.request.headers.get('user-agent') - }); -} -\`\`\` - -etc etc -`, - ` -### sub heading - -etc -\`\`\`original-js -// @errors: 2461 -/// file: src/routes/what-is-my-user-agent/+server.js -import { json } from '@sveltejs/kit'; - -/** @type {import('./$types').RequestHandler} */ -export function GET(event) { - // log all headers - console.log(...event.request.headers); - - return json({ - // retrieve a specific header - userAgent: event.request.headers.get('user-agent') - }); -} -\`\`\` -\`\`\`generated-ts -// @errors: 2461 -/// file: src/routes/what-is-my-user-agent/+server.ts -import { json } from '@sveltejs/kit'; -import type { RequestHandler } from './$types'; - -export const GET = ((event) => { - // log all headers - console.log(...event.request.headers); - - return json({ - // retrieve a specific header - userAgent: event.request.headers.get('user-agent') - }); -}) satisfies RequestHandler; -\`\`\` - -etc etc -` - ); -}); - -test('Creates Svelte-TS from Svelte-JS', () => { - test_ts_transformation( - ` -### sub heading - -etc -\`\`\`svelte -/// file: src/routes/+page.svelte - - -

{data.title}

-
{@html data.content}
-\`\`\` - -etc etc -`, - ` -### sub heading - -etc -\`\`\`original-svelte -/// file: src/routes/+page.svelte - - -

{data.title}

-
{@html data.content}
-\`\`\` -\`\`\`generated-svelte -/// file: src/routes/+page.svelte - - -

{data.title}

-
{@html data.content}
-\`\`\` - -etc etc -` - ); -}); - -test('Leaves JS file as-is if no transformation needed', () => { - test_ts_transformation( - ` -\`\`\`js -/// file: src/routes/+server.js -let foo = true; -\`\`\` - -etc etc -`, - ` -\`\`\`js -/// file: src/routes/+server.js -let foo = true; -\`\`\` - -etc etc -` - ); -}); - -test('Leaves Svelte file as-is if no transformation needed', () => { - test_ts_transformation( - ` -\`\`\`svelte -/// file: src/routes/+page.svelte - - -

{foo}

-\`\`\` - -etc etc -`, - ` -\`\`\`svelte -/// file: src/routes/+page.svelte - - -

{foo}

-\`\`\` - -etc etc -` - ); -}); - -test('Leaves JS file as-is if no named file', () => { - test_ts_transformation( - ` -\`\`\`js -/** @type {boolean} */ -let foo = true; -\`\`\` - -etc etc -`, - ` -\`\`\`js -/** @type {boolean} */ -let foo = true; -\`\`\` - -etc etc -` - ); -}); - -test('Leaves Svelte file as-is if no named file', () => { - test_ts_transformation( - ` -\`\`\`svelte - - -

{foo}

-\`\`\` - -etc etc -`, - ` -\`\`\`svelte - - -

{foo}

-\`\`\` - -etc etc -` - ); -}); diff --git a/sites/kit.svelte.dev/src/lib/docs/server/markdown.js b/sites/kit.svelte.dev/src/lib/docs/server/markdown.js deleted file mode 100644 index 9482233c3663..000000000000 --- a/sites/kit.svelte.dev/src/lib/docs/server/markdown.js +++ /dev/null @@ -1,187 +0,0 @@ -import { marked } from 'marked'; - -const escapeTest = /[&<>"']/; -const escapeReplace = /[&<>"']/g; -const escapeTestNoEncode = /[<>"']|&(?!#?\w+;)/; -const escapeReplaceNoEncode = /[<>"']|&(?!#?\w+;)/g; -const escapeReplacements = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' -}; -const getEscapeReplacement = (ch) => escapeReplacements[ch]; - -/** - * @param {string} html - * @param {boolean} [encode] - */ -export function escape(html, encode = false) { - if (encode) { - if (escapeTest.test(html)) { - return html.replace(escapeReplace, getEscapeReplacement); - } - } else { - if (escapeTestNoEncode.test(html)) { - return html.replace(escapeReplaceNoEncode, getEscapeReplacement); - } - } - - return html; -} - -/** @type {Partial} */ -const default_renderer = { - code(code, infostring, escaped) { - const lang = (infostring || '').match(/\S*/)[0]; - - code = code.replace(/\n$/, '') + '\n'; - - if (!lang) { - return '
' + (escaped ? code : escape(code, true)) + '
\n'; - } - - return ( - '
' +
-			(escaped ? code : escape(code, true)) +
-			'
\n' - ); - }, - - blockquote(quote) { - return '
\n' + quote + '
\n'; - }, - - html(html) { - return html; - }, - - heading(text, level) { - return '' + text + '\n'; - }, - - hr() { - return '
\n'; - }, - - list(body, ordered, start) { - const type = ordered ? 'ol' : 'ul', - startatt = ordered && start !== 1 ? ' start="' + start + '"' : ''; - return '<' + type + startatt + '>\n' + body + '\n'; - }, - - listitem(text) { - return '
  • ' + text + '
  • \n'; - }, - - checkbox(checked) { - return ' '; - }, - - paragraph(text) { - return '

    ' + text + '

    \n'; - }, - - table(header, body) { - if (body) body = '' + body + ''; - - return '\n' + '\n' + header + '\n' + body + '
    \n'; - }, - - tablerow(content) { - return '\n' + content + '\n'; - }, - - tablecell(content, flags) { - const type = flags.header ? 'th' : 'td'; - const tag = flags.align ? '<' + type + ' align="' + flags.align + '">' : '<' + type + '>'; - return tag + content + '\n'; - }, - - // span level renderer - strong(text) { - return '' + text + ''; - }, - - em(text) { - return '' + text + ''; - }, - - codespan(text) { - return '' + text + ''; - }, - - br() { - return '
    '; - }, - - del(text) { - return '' + text + ''; - }, - - link(href, title, text) { - if (href === null) { - return text; - } - let out = ''; - return out; - }, - - image(href, title, text) { - if (href === null) { - return text; - } - - let out = '' + text + '} renderer - */ -export function transform(markdown, renderer = {}) { - marked.use({ - renderer: { - // we have to jump through these hoops because of marked's API design choices — - // options are global, and merged in confusing ways. You can't do e.g. - // `new Marked(options).parse(markdown)` - ...default_renderer, - ...renderer - } - }); - - return marked(markdown); -} - -/** @param {string} markdown */ -export function extract_frontmatter(markdown) { - const match = /---\r?\n([\s\S]+?)\r?\n---/.exec(markdown); - const frontmatter = match[1]; - const body = markdown.slice(match[0].length); - - /** @type {Record} */ - const metadata = {}; - frontmatter.split('\n').forEach((pair) => { - const i = pair.indexOf(':'); - metadata[pair.slice(0, i).trim()] = pair.slice(i + 1).trim(); - }); - - return { metadata, body }; -} diff --git a/sites/kit.svelte.dev/src/lib/docs/server/render.js b/sites/kit.svelte.dev/src/lib/docs/server/render.js deleted file mode 100644 index cfe1c80bbfec..000000000000 --- a/sites/kit.svelte.dev/src/lib/docs/server/render.js +++ /dev/null @@ -1,136 +0,0 @@ -import { modules } from './type-info.js'; - -/** @param {string} content */ -export function replace_placeholders(content) { - return content - .replace(/> EXPANDED_TYPES: (.+?)#(.+)$/gm, (_, name, id) => { - const module = modules.find((module) => module.name === name); - if (!module) throw new Error(`Could not find module ${name}`); - - const type = module.types.find((t) => t.name === id); - - return ( - type.comment + - type.children - .map((child) => { - let section = `### ${child.name}`; - - if (child.bullets) { - section += `\n\n
    \n\n${child.bullets.join( - '\n' - )}\n\n
    `; - } - - section += `\n\n${child.comment}`; - - if (child.children) { - section += `\n\n
    \n\n${child.children - .map(stringify) - .join('\n')}\n\n
    `; - } - - return section; - }) - .join('\n\n') - ); - }) - .replace(/> TYPES: (.+?)(?:#(.+))?$/gm, (_, name, id) => { - const module = modules.find((module) => module.name === name); - if (!module) throw new Error(`Could not find module ${name}`); - - if (id) { - const type = module.types.find((t) => t.name === id); - - return ( - `
    ${fence(type.snippet)}` + - type.children.map(stringify).join('\n\n') + - `
    ` - ); - } - - return `${module.comment}\n\n${module.types - .map((t) => { - let children = t.children.map(stringify).join('\n\n'); - if (t.name === 'Config' || t.name === 'KitConfig') { - // special case — we want these to be on a separate page - children = - '
    \n\nSee the [configuration reference](/docs/configuration) for details.
    '; - } - - const markdown = `
    ${fence(t.snippet)}` + children + `
    `; - return `### ${t.name}\n\n${t.comment}\n\n${markdown}\n\n`; - }) - .join('')}`; - }) - .replace('> MODULES', () => { - return modules - .map((module) => { - if (module.exports.length === 0 && !module.exempt) return ''; - if (module.name === 'Private types') return; - - let import_block = ''; - - if (module.exports.length > 0) { - // deduplication is necessary for now, because of `error()` overload - const exports = Array.from(new Set(module.exports.map((x) => x.name))); - - let declaration = `import { ${exports.join(', ')} } from '${module.name}';`; - if (declaration.length > 80) { - declaration = `import {\n\t${exports.join(',\n\t')}\n} from '${module.name}';`; - } - - import_block = fence(declaration, 'js'); - } - - return `## ${module.name}\n\n${import_block}\n\n${module.comment}\n\n${module.exports - .map((type) => { - const markdown = - `
    ${fence(type.snippet)}` + - type.children.map(stringify).join('\n\n') + - `
    `; - return `### ${type.name}\n\n${type.comment}\n\n${markdown}`; - }) - .join('\n\n')}`; - }) - .join('\n\n'); - }); -} - -/** - * @param {string} code - * @param {string} lang - */ -function fence(code, lang = 'dts') { - return '\n\n```' + lang + '\n' + code + '\n```\n\n'; -} - -/** - * @param {import('./types').Type} member - */ -function stringify(member) { - const bullet_block = - member.bullets.length > 0 - ? `\n\n
    \n\n${member.bullets.join('\n')}
    ` - : ''; - - const child_block = - member.children.length > 0 - ? `\n\n
    ${member.children - .map(stringify) - .join('\n')}
    ` - : ''; - - return ( - `
    ${fence(member.snippet)}` + - `
    \n\n` + - bullet_block + - '\n\n' + - member.comment - .replace(/\/\/\/ type: (.+)/g, '/** @type {$1} */') - .replace(/^( )+/gm, (match, spaces) => { - return '\t'.repeat(match.length / 2); - }) + - child_block + - '\n
    ' - ); -} diff --git a/sites/kit.svelte.dev/src/lib/docs/server/types.d.ts b/sites/kit.svelte.dev/src/lib/docs/server/types.d.ts deleted file mode 100644 index c627e1b68ba7..000000000000 --- a/sites/kit.svelte.dev/src/lib/docs/server/types.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -export interface Section { - title: string; - slug: string; - sections?: Section[]; -} - -export interface Type { - name: string; - comment: string; - snippet: string; - bullets: string[]; - children: Type[]; -} diff --git a/sites/kit.svelte.dev/src/lib/server/docs/index.js b/sites/kit.svelte.dev/src/lib/server/docs/index.js new file mode 100644 index 000000000000..f2d7e80597fb --- /dev/null +++ b/sites/kit.svelte.dev/src/lib/server/docs/index.js @@ -0,0 +1,129 @@ +import { base as app_base } from '$app/paths'; +import { modules } from '$lib/generated/type-info.js'; +import { + escape, + extractFrontmatter, + markedTransform, + normalizeSlugify, + removeMarkdown, + replaceExportTypePlaceholders +} from '@sveltejs/site-kit/markdown'; +import { readFile, readdir } from 'node:fs/promises'; +import { CONTENT_BASE_PATHS } from '../../../constants.js'; +import { render_content } from '../renderer'; + +/** + * @param {import('./types').DocsData} docs_data + * @param {string} slug + */ +export async function get_parsed_docs(docs_data, slug) { + for (const { pages } of docs_data) { + for (const page of pages) { + if (page.slug === slug) { + return { + ...page, + content: await render_content(page.file, page.content) + }; + } + } + } +} + +/** @return {Promise} */ +export async function get_docs_data(base = CONTENT_BASE_PATHS.DOCS) { + /** @type {import('./types').DocsData} */ + const docs_data = []; + + for (const category_dir of await readdir(base)) { + const match = /\d{2}-(.+)/.exec(category_dir); + if (!match) continue; + + const category_slug = match[1]; + + // Read the meta.json + const { title: category_title, draft = 'false' } = JSON.parse( + await readFile(`${base}/${category_dir}/meta.json`, 'utf-8') + ); + + if (draft === 'true') continue; + + /** @type {import('./types').Category} */ + const category = { + title: category_title, + slug: category_slug, + pages: [] + }; + + for (const page_md of (await readdir(`${base}/${category_dir}`)).filter( + (filename) => filename !== 'meta.json' + )) { + const match = /\d{2}-(.+)/.exec(page_md); + if (!match) continue; + + const page_slug = match[1].replace('.md', ''); + + const page_data = extractFrontmatter( + await readFile(`${base}/${category_dir}/${page_md}`, 'utf-8') + ); + + if (page_data.metadata.draft === 'true') continue; + + const page_title = page_data.metadata.title; + const page_content = page_data.body; + + category.pages.push({ + title: page_title, + slug: page_slug, + content: page_content, + category: category_title, + sections: get_sections(page_content), + path: `${app_base}/docs/${page_slug}`, + file: `${category_dir}/${page_md}` + }); + } + + docs_data.push(category); + } + + return docs_data; +} + +/** @param {import('./types').DocsData} docs_data */ +export function get_docs_list(docs_data) { + return docs_data.map((category) => ({ + title: category.title, + pages: category.pages.map((page) => ({ + title: page.title, + path: page.path + })) + })); +} + +/** @param {string} markdown */ +function get_sections(markdown) { + const headingRegex = /^##\s+(.*)$/gm; + /** @type {import('./types').Section[]} */ + const secondLevelHeadings = []; + let match; + + const placeholders_rendered = replaceExportTypePlaceholders(markdown, modules); + + while ((match = headingRegex.exec(placeholders_rendered)) !== null) { + const unTYPED = match[1].startsWith('[TYPE]:') ? match[1].replace('[TYPE]: ', '') : match[1]; + + secondLevelHeadings.push({ + title: removeMarkdown( + escape(markedTransform(unTYPED, { paragraph: (txt) => txt })) + .replace(/<\/?code>/g, '') + .replace(/'/g, "'") + .replace(/"/g, '"') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/<(\/)?(em|b|strong|code)>/g, '') + ), + slug: normalizeSlugify(unTYPED) + }); + } + + return secondLevelHeadings; +} diff --git a/sites/kit.svelte.dev/src/lib/server/docs/types.d.ts b/sites/kit.svelte.dev/src/lib/server/docs/types.d.ts new file mode 100644 index 000000000000..6b984d72eb27 --- /dev/null +++ b/sites/kit.svelte.dev/src/lib/server/docs/types.d.ts @@ -0,0 +1,24 @@ +export type DocsData = Category[]; + +export interface Section { + title: string; + slug: string; + /** Currently, we are only going with 2 level headings, so this will be undefined. In future, we may want to support 3 levels, in which case this will be a list of sections */ + sections?: Section[]; +} + +export type Category = { + title: string; + slug: string; + pages: Page[]; +}; + +export type Page = { + title: string; + slug: string; + file: string; + path: string; + category: string; + content: string; + sections: Section[]; +}; diff --git a/sites/kit.svelte.dev/src/lib/server/renderer.js b/sites/kit.svelte.dev/src/lib/server/renderer.js new file mode 100644 index 000000000000..31ce2713e24c --- /dev/null +++ b/sites/kit.svelte.dev/src/lib/server/renderer.js @@ -0,0 +1,81 @@ +import { modules } from '$lib/generated/type-info.js'; +import { renderContentMarkdown, slugify } from '@sveltejs/site-kit/markdown'; +import { parse_route_id } from '../../../../../packages/kit/src/utils/routing.js'; + +/** + * @param {string} filename + * @param {string} body + */ +export const render_content = (filename, body) => + renderContentMarkdown(filename, body, { + cacheCodeSnippets: true, + modules, + + resolveTypeLinks: (module_name, type_name) => { + if (module_name === '@sveltejs/kit') module_name = 'Public Types'; + + return { + page: `/docs/types`, + slug: `${slugify(module_name)}-${slugify(type_name)}` + }; + }, + + twoslashBanner: (filename, source, language, options) => { + const injected = []; + + if ( + source.includes('$app/') || + source.includes('$service-worker') || + source.includes('@sveltejs/kit/') + ) { + injected.push(`// @filename: ambient-kit.d.ts`, `/// `); + } + + if (source.includes('$env/')) { + // TODO we're hardcoding static env vars that are used in code examples + // in the types, which isn't... totally ideal, but will do for now + injected.push( + `declare module '$env/dynamic/private' { export const env: Record }`, + `declare module '$env/dynamic/public' { export const env: Record }`, + `declare module '$env/static/private' { export const API_KEY: string }`, + `declare module '$env/static/public' { export const PUBLIC_BASE_URL: string }` + ); + } + + if (source.includes('./$types') && !source.includes('@filename: $types.d.ts')) { + const params = parse_route_id(options.file || `+page.${language}`) + .params.map((param) => `${param.name}: string`) + .join(', '); + + injected.push( + `// @filename: $types.d.ts`, + `import type * as Kit from '@sveltejs/kit';`, + `export type PageLoad = Kit.Load<{${params}}>;`, + `export type PageServerLoad = Kit.ServerLoad<{${params}}>;`, + `export type LayoutLoad = Kit.Load<{${params}}>;`, + `export type LayoutServerLoad = Kit.ServerLoad<{${params}}>;`, + `export type RequestHandler = Kit.RequestHandler<{${params}}>;`, + `export type Action = Kit.Action<{${params}}>;`, + `export type Actions = Kit.Actions<{${params}}>;`, + `export type EntryGenerator = () => Promise> | Array<{${params}}>;` + ); + } + + // special case — we need to make allowances for code snippets coming + // from e.g. ambient.d.ts + if (filename.endsWith('30-modules.md')) { + injected.push('// @errors: 7006 7031'); + } + + if (filename.endsWith('10-configuration.md')) { + injected.push('// @errors: 2307'); + } + + // another special case + if (source.includes('$lib/types')) { + injected.push(`declare module '$lib/types' { export interface User {} }`); + } + + return injected.join('\n'); + } + }); diff --git a/sites/kit.svelte.dev/src/routes/+layout.js b/sites/kit.svelte.dev/src/routes/+layout.js deleted file mode 100644 index 189f71e2e1b3..000000000000 --- a/sites/kit.svelte.dev/src/routes/+layout.js +++ /dev/null @@ -1 +0,0 @@ -export const prerender = true; diff --git a/sites/kit.svelte.dev/src/routes/+layout.server.js b/sites/kit.svelte.dev/src/routes/+layout.server.js new file mode 100644 index 000000000000..625e73326af7 --- /dev/null +++ b/sites/kit.svelte.dev/src/routes/+layout.server.js @@ -0,0 +1,23 @@ +export const prerender = true; + +export const load = async ({ url, fetch }) => { + const nav_list = await fetch('/nav.json').then((r) => r.json()); + + return { + nav_title: get_nav_title(url), + nav_links: nav_list + }; +}; + +/** @param {URL} url */ +function get_nav_title(url) { + const list = new Map([[/^docs/, 'Docs']]); + + for (const [regex, title] of list) { + if (regex.test(url.pathname.replace(/^\/(.+)/, '$1'))) { + return title; + } + } + + return ''; +} diff --git a/sites/kit.svelte.dev/src/routes/+layout.svelte b/sites/kit.svelte.dev/src/routes/+layout.svelte index 7e2131f3a82e..94c2bd70d117 100644 --- a/sites/kit.svelte.dev/src/routes/+layout.svelte +++ b/sites/kit.svelte.dev/src/routes/+layout.svelte @@ -1,55 +1,51 @@ - - - + export let data; + - - +
    {#if browser} @@ -74,11 +70,6 @@ } } - li { - display: flex; - align-items: center; - } - :global(.examples-container, .repl-outer, .tutorial-outer) { height: calc(100vh - var(--sk-nav-height)) !important; } diff --git a/sites/kit.svelte.dev/src/routes/+page.svelte b/sites/kit.svelte.dev/src/routes/+page.svelte index 7f297b1c8ca1..4d2963c65641 100644 --- a/sites/kit.svelte.dev/src/routes/+page.svelte +++ b/sites/kit.svelte.dev/src/routes/+page.svelte @@ -1,13 +1,14 @@
    + {#if category} +

    {category}

    + {/if}
    - -
    - -
    - +
    @@ -31,14 +33,16 @@ padding: var(--sk-page-padding-top) var(--sk-page-padding-side); } - .toc-container { - background: var(--sk-back-3); + .category { + font: 700 var(--sk-text-s) var(--sk-font); + text-transform: uppercase; + letter-spacing: 0.12em; + margin: 0 0 0.5em; + color: var(--sk-text-3); } - .ts-toggle { - width: 100%; - border-top: 1px solid var(--sk-back-4); - background-color: var(--sk-back-3); + .toc-container { + background: var(--sk-back-3); } @media (min-width: 832px) { @@ -67,15 +71,6 @@ .page { padding-left: calc(var(--sidebar-width) + var(--sk-page-padding-side)); } - - .ts-toggle { - position: fixed; - width: var(--sidebar-width); - bottom: var(--sk-banner-bottom-height); - z-index: 1; - margin-right: 0; - border-right: 1px solid var(--sk-back-5); - } } @media (min-width: 1200px) { diff --git a/sites/kit.svelte.dev/src/routes/docs/+page.js b/sites/kit.svelte.dev/src/routes/docs/+page.js index b5521ff494c6..ba2ed3ac9607 100644 --- a/sites/kit.svelte.dev/src/routes/docs/+page.js +++ b/sites/kit.svelte.dev/src/routes/docs/+page.js @@ -1,7 +1,5 @@ import { redirect } from '@sveltejs/kit'; -import { base } from '$app/paths'; -/** @type {import('./$types').Load} */ export function load() { throw redirect(307, `./docs/introduction`); } diff --git a/sites/kit.svelte.dev/src/routes/docs/[slug]/+page.server.js b/sites/kit.svelte.dev/src/routes/docs/[slug]/+page.server.js index f32014245600..2d06cbb987ee 100644 --- a/sites/kit.svelte.dev/src/routes/docs/[slug]/+page.server.js +++ b/sites/kit.svelte.dev/src/routes/docs/[slug]/+page.server.js @@ -1,22 +1,12 @@ -import fs from 'fs'; -import { read_file } from '$lib/docs/server'; +import { get_docs_data, get_parsed_docs } from '$lib/server/docs/index.js'; import { error } from '@sveltejs/kit'; -const base = '../../documentation/docs'; +export const prerender = true; -/** @type {import('./$types').PageServerLoad} */ export async function load({ params }) { - for (const subdir of fs.readdirSync(base)) { - if (!fs.statSync(`${base}/${subdir}`).isDirectory()) continue; + const processed_page = await get_parsed_docs(await get_docs_data(), params.slug); - for (const file of fs.readdirSync(`${base}/${subdir}`)) { - if (file.slice(3, -3) === params.slug) { - return { - page: await read_file(`docs/${subdir}/${file}`) - }; - } - } - } + if (!processed_page) throw error(404); - throw error(404); + return { page: processed_page }; } diff --git a/sites/kit.svelte.dev/src/routes/docs/[slug]/+page.svelte b/sites/kit.svelte.dev/src/routes/docs/[slug]/+page.svelte index 0a0720893626..cb7d44a1c164 100644 --- a/sites/kit.svelte.dev/src/routes/docs/[slug]/+page.svelte +++ b/sites/kit.svelte.dev/src/routes/docs/[slug]/+page.svelte @@ -1,10 +1,10 @@ @@ -23,13 +48,15 @@ -
    +

    {data.page.title}

    Edit this page on GitHub + +
    {@html data.page.content}
    @@ -51,8 +78,6 @@
    - - diff --git a/sites/kit.svelte.dev/src/routes/faq/+page.js b/sites/kit.svelte.dev/src/routes/faq/+page.js new file mode 100644 index 000000000000..aa05ba684c44 --- /dev/null +++ b/sites/kit.svelte.dev/src/routes/faq/+page.js @@ -0,0 +1,5 @@ +import { redirect } from '@sveltejs/kit'; + +export const load = ({}) => { + throw redirect(301, '/docs/faq'); +}; diff --git a/sites/kit.svelte.dev/src/routes/faq/+page.server.js b/sites/kit.svelte.dev/src/routes/faq/+page.server.js deleted file mode 100644 index a0d26e883b10..000000000000 --- a/sites/kit.svelte.dev/src/routes/faq/+page.server.js +++ /dev/null @@ -1,13 +0,0 @@ -import fs from 'fs'; -import { read_file } from '$lib/docs/server'; - -export async function load() { - const sections = []; - - for (const file of fs.readdirSync(`../../documentation/faq`)) { - const section = await read_file(`faq/${file}`); - if (section) sections.push(section); - } - - return { sections }; -} diff --git a/sites/kit.svelte.dev/src/routes/faq/+page.svelte b/sites/kit.svelte.dev/src/routes/faq/+page.svelte deleted file mode 100644 index 4666c5870203..000000000000 --- a/sites/kit.svelte.dev/src/routes/faq/+page.svelte +++ /dev/null @@ -1,49 +0,0 @@ - - - - FAQ • SvelteKit - - - - - - -
    -

    Frequently Asked Questions

    -
    -
    - {#each data.sections as faq} - - {/each} -
    -
    -
    - - diff --git a/sites/kit.svelte.dev/src/routes/home/Deployment.svelte b/sites/kit.svelte.dev/src/routes/home/Deployment.svelte index 37e3fd7c2d4e..ff6dec0ce38c 100644 --- a/sites/kit.svelte.dev/src/routes/home/Deployment.svelte +++ b/sites/kit.svelte.dev/src/routes/home/Deployment.svelte @@ -1,16 +1,18 @@
    diff --git a/sites/kit.svelte.dev/src/routes/home/Intro.svelte b/sites/kit.svelte.dev/src/routes/home/Intro.svelte index c8b816394d62..d3a93005596c 100644 --- a/sites/kit.svelte.dev/src/routes/home/Intro.svelte +++ b/sites/kit.svelte.dev/src/routes/home/Intro.svelte @@ -1,36 +1,34 @@ -
    -
    -
    -

    fast

    -

    - Powered by Svelte and - Vite, speed is baked into - every crevice: fast setup, fast dev, fast builds, fast page loads, fast navigation. Did we - mention it's fast? -

    -
    + +
    +

    fast

    +

    + Powered by Svelte and + Vite, speed is baked into + every crevice: fast setup, fast dev, fast builds, fast page loads, fast navigation. Did we + mention it's fast? +

    +
    -
    -

    fun

    -

    - No more wasted days figuring out bundler configuration, routing, SSR, CSP, TypeScript, - deployment settings and all the other boring stuff. Code with joy. -

    -
    +
    +

    fun

    +

    + No more wasted days figuring out bundler configuration, routing, SSR, CSP, TypeScript, + deployment settings and all the other boring stuff. Code with joy. +

    +
    -
    -

    flexible

    -

    - SPA? MPA? SSR? SSG? Check. SvelteKit gives you the tools to succeed whatever it is you're - building. And it runs wherever JavaScript does. -

    -
    +
    +

    flexible

    +

    + SPA? MPA? SSR? SSG? Check. SvelteKit gives you the tools to succeed whatever it is you're + building. And it runs wherever JavaScript does. +

    -
    + diff --git a/sites/kit.svelte.dev/src/routes/nav.json/+server.js b/sites/kit.svelte.dev/src/routes/nav.json/+server.js new file mode 100644 index 000000000000..38a0f66124dc --- /dev/null +++ b/sites/kit.svelte.dev/src/routes/nav.json/+server.js @@ -0,0 +1,28 @@ +import { get_docs_data, get_docs_list } from '$lib/server/docs/index.js'; +import { json } from '@sveltejs/kit'; + +export const prerender = true; + +export const GET = async () => { + return json(await get_nav_list()); +}; + +/** + * @returns {Promise} + */ +async function get_nav_list() { + const docs_list = get_docs_list(await get_docs_data()); + const processed_docs_list = docs_list.map(({ title, pages }) => ({ + title, + sections: pages.map(({ title, path }) => ({ title, path })) + })); + + return [ + { + title: 'Docs', + prefix: 'docs', + pathname: '/docs/introduction', + sections: processed_docs_list + } + ]; +} diff --git a/sites/kit.svelte.dev/src/routes/search/+page.server.js b/sites/kit.svelte.dev/src/routes/search/+page.server.js index c7cd83244f20..10451707bfed 100644 --- a/sites/kit.svelte.dev/src/routes/search/+page.server.js +++ b/sites/kit.svelte.dev/src/routes/search/+page.server.js @@ -1,6 +1,5 @@ import { init, inited, search } from '@sveltejs/site-kit/search'; -/** @type {import('./$types').PageServerLoad} */ export async function load({ url, fetch }) { if (!inited) { const res = await fetch('/content.json'); diff --git a/sites/kit.svelte.dev/src/routes/search/+page.svelte b/sites/kit.svelte.dev/src/routes/search/+page.svelte index bad2c3c75eae..e38998282f24 100644 --- a/sites/kit.svelte.dev/src/routes/search/+page.svelte +++ b/sites/kit.svelte.dev/src/routes/search/+page.svelte @@ -1,7 +1,6 @@ diff --git a/sites/kit.svelte.dev/svelte.config.js b/sites/kit.svelte.dev/svelte.config.js index 50de0b3d5417..46575d85e0fd 100644 --- a/sites/kit.svelte.dev/svelte.config.js +++ b/sites/kit.svelte.dev/svelte.config.js @@ -10,14 +10,6 @@ const config = { paths: { relative: true } - }, - - vitePlugin: { - experimental: { - inspector: { - holdMode: true - } - } } }; diff --git a/sites/kit.svelte.dev/tsconfig.json b/sites/kit.svelte.dev/tsconfig.json index aec59b8b3396..db9370bd28e1 100644 --- a/sites/kit.svelte.dev/tsconfig.json +++ b/sites/kit.svelte.dev/tsconfig.json @@ -1,9 +1,10 @@ { + "extends": "./.svelte-kit/tsconfig.json", "compilerOptions": { "allowJs": true, "checkJs": true, "allowSyntheticDefaultImports": true, - "moduleResolution": "bundler" - }, - "extends": "./.svelte-kit/tsconfig.json" + "moduleResolution": "bundler", + "outDir": "build" + } } diff --git a/sites/kit.svelte.dev/vite.config.js b/sites/kit.svelte.dev/vite.config.js index b55a5947a7a3..53404d28e2ac 100644 --- a/sites/kit.svelte.dev/vite.config.js +++ b/sites/kit.svelte.dev/vite.config.js @@ -1,4 +1,6 @@ import { sveltekit } from '@sveltejs/kit/vite'; +import browserslist from 'browserslist'; +import { browserslistToTargets } from 'lightningcss'; import * as path from 'path'; import { imagetools } from 'vite-imagetools'; @@ -7,11 +9,11 @@ const fallback = { '.heif': 'jpg', '.avif': 'png', '.jpeg': 'jpg', - '.jpg': 'jpg', - '.png': 'png', + '.jpg': 'jpg', + '.png': 'png', '.tiff': 'jpg', '.webp': 'png', - '.gif': 'gif' + '.gif': 'gif' }; /** @type {import('vite').UserConfig} */ @@ -20,6 +22,16 @@ const config = { logLevel: 'info', + css: { + transformer: 'lightningcss', + lightningcss: { + targets: browserslistToTargets(browserslist(['>0.2%', 'not dead'])) + } + }, + build: { + cssMinify: 'lightningcss' + }, + plugins: [ imagetools({ defaultDirectives: (url) => {