diff --git a/documentation/blog/2017-09-06-the-zen-of-just-writing-css.md b/documentation/blog/2017-09-06-the-zen-of-just-writing-css.md index fd74fe947d..f06a4aaac0 100644 --- a/documentation/blog/2017-09-06-the-zen-of-just-writing-css.md +++ b/documentation/blog/2017-09-06-the-zen-of-just-writing-css.md @@ -44,9 +44,10 @@ Let's see what that looks like in practice. -
- Is this what they mean by 'use the platform'? -
+ +
+ Is this what they mean by 'use the platform'? +
diff --git a/documentation/docs/03-runtime/02-svelte-store.md b/documentation/docs/03-runtime/02-svelte-store.md index 203f040fba..3d9acc689f 100644 --- a/documentation/docs/03-runtime/02-svelte-store.md +++ b/documentation/docs/03-runtime/02-svelte-store.md @@ -62,6 +62,7 @@ Note that the value of a `writable` is lost when it is destroyed, for example wh Creates a store whose value cannot be set from 'outside', the first argument is the store's initial value, and the second argument to `readable` is the same as the second argument to `writable`. ```js + // ---cut--- import { readable } from 'svelte/store'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 135647c5f8..b1291d3c41 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -182,8 +182,8 @@ importers: specifier: ^1.20.4 version: 1.20.4(svelte@packages+svelte)(vite@4.3.9) '@sveltejs/site-kit': - specifier: 6.0.0-next.8 - version: 6.0.0-next.8(@sveltejs/kit@1.20.4)(svelte@packages+svelte) + specifier: 6.0.0-next.11 + version: 6.0.0-next.11(@sveltejs/kit@1.20.4)(svelte@packages+svelte) '@sveltejs/vite-plugin-svelte': specifier: ^2.4.1 version: 2.4.1(svelte@packages+svelte)(vite@4.3.9) @@ -1822,16 +1822,16 @@ packages: svelte-local-storage-store: 0.4.0(svelte@packages+svelte) dev: false - /@sveltejs/site-kit@6.0.0-next.8(@sveltejs/kit@1.20.4)(svelte@packages+svelte): - resolution: {integrity: sha512-3sCRKQnjhxJRR1FYpBBsGRqiV1cqSYFGAi/lJb4rt1U2+cvpRxJgJUHzrQqbfB4JfWpD63FcLKYU1+P+maaX/w==} + /@sveltejs/site-kit@6.0.0-next.11(@sveltejs/kit@1.20.4)(svelte@packages+svelte): + resolution: {integrity: sha512-v7K02vXgjotwvCbFEKHUcNK8/DOOVJ6KAGd9R7g1D93uVkAlcdn06D7ki8x17CskP5sl9FQIGV/RUuxDA459FA==} peerDependencies: '@sveltejs/kit': ^1.0.0 - svelte: ^3.54.0 || ^4.0.0-next.0 || ^4.0.0 + svelte: ^3.54.0 || ^4.0.0-next.1 || ^4.0.0 dependencies: '@sveltejs/kit': 1.20.4(svelte@packages+svelte)(vite@4.3.9) esm-env: 1.0.0 svelte: link:packages/svelte - svelte-local-storage-store: 0.4.0(svelte@packages+svelte) + svelte-local-storage-store: 0.5.0(svelte@packages+svelte) dev: true /@sveltejs/vite-plugin-svelte-inspector@1.0.2(@sveltejs/vite-plugin-svelte@2.4.1)(svelte@packages+svelte)(vite@4.3.9): @@ -6157,6 +6157,16 @@ packages: svelte: ^3.48.0 dependencies: svelte: link:packages/svelte + dev: false + + /svelte-local-storage-store@0.5.0(svelte@packages+svelte): + resolution: {integrity: sha512-SEDrpapeia6fUqta+r1NvSLlJYPkZ4pBcl15EYIOSPNzy6vhpoXu8cnzUDmZxsWl7fZGAHxrVH9UyZCbyO4W+g==} + engines: {node: '>=0.14'} + peerDependencies: + svelte: ^3.48.0 || ^4.0.0 + dependencies: + svelte: link:packages/svelte + dev: true /svelte-preprocess@5.0.4(postcss@8.4.23)(sass@1.63.4)(svelte@packages+svelte)(typescript@5.1.3): resolution: {integrity: sha512-ABia2QegosxOGsVlsSBJvoWeXy1wUKSfF7SWJdTjLAbx/Y3SrVevvvbFNQqrSJw89+lNSsM58SipmZJ5SRi5iw==} diff --git a/sites/svelte.dev/package.json b/sites/svelte.dev/package.json index 24e9f3a9bf..66014a2455 100644 --- a/sites/svelte.dev/package.json +++ b/sites/svelte.dev/package.json @@ -30,7 +30,7 @@ "@resvg/resvg-js": "^2.4.1", "@sveltejs/adapter-vercel": "^3.0.1", "@sveltejs/kit": "^1.20.4", - "@sveltejs/site-kit": "6.0.0-next.8", + "@sveltejs/site-kit": "6.0.0-next.11", "@sveltejs/vite-plugin-svelte": "^2.4.1", "@types/marked": "^5.0.0", "@types/node": "^20.3.1", diff --git a/sites/svelte.dev/scripts/type-gen/index.js b/sites/svelte.dev/scripts/type-gen/index.js index bd4f364438..0959ac8309 100644 --- a/sites/svelte.dev/scripts/type-gen/index.js +++ b/sites/svelte.dev/scripts/type-gen/index.js @@ -146,7 +146,7 @@ function munge_type_element(member, depth = 1) { // @ts-ignore const doc = member.jsDoc?.[0]; - if (/private api/i.test(doc?.comment)) return; + if (/(private api|do not use)/i.test(doc?.comment)) return; /** @type {string[]} */ const children = []; @@ -304,7 +304,7 @@ fs.writeFileSync( ` /* This file is generated by running \`pnpm generate\` in the sites/svelte.dev directory — do not edit it */ -export const modules = /** @type {import('../generated/types').Modules} */ (${JSON.stringify( +export const modules = /** @type {import('@sveltejs/site-kit/markdown').Modules} */ (${JSON.stringify( modules, null, ' ' diff --git a/sites/svelte.dev/src/lib/generated/types.d.ts b/sites/svelte.dev/src/lib/generated/types.d.ts deleted file mode 100644 index 64775aafd4..0000000000 --- a/sites/svelte.dev/src/lib/generated/types.d.ts +++ /dev/null @@ -1,16 +0,0 @@ -export type Modules = { - name?: string; - comment?: string; - exempt?: boolean; - types?: Child[]; - exports?: Child[]; -}[]; - -type Child = { - name: string; - snippet: string; - comment: string; - deprecated?: string; - bullets?: string[]; - children?: Child[]; -}; diff --git a/sites/svelte.dev/src/lib/server/blog/index.js b/sites/svelte.dev/src/lib/server/blog/index.js index cd5953f2bb..23bca26233 100644 --- a/sites/svelte.dev/src/lib/server/blog/index.js +++ b/sites/svelte.dev/src/lib/server/blog/index.js @@ -1,23 +1,24 @@ // @ts-check -import { modules } from '$lib/generated/type-info.js'; +import { extractFrontmatter } from '@sveltejs/site-kit/markdown'; import fs from 'node:fs'; import { CONTENT_BASE_PATHS } from '../../../constants.js'; -import { extract_frontmatter } from '../markdown/index.js'; -import { render_markdown } from '../markdown/renderer.js'; +import { render_content } from '../renderer.js'; /** * @param {import('./types').BlogData} blog_data * @param {string} slug */ export async function get_processed_blog_post(blog_data, slug) { - const post = blog_data.find((post) => post.slug === slug); - - if (!post) return null; + for (const post of blog_data) { + if (post.slug === slug) { + return { + ...post, + content: await render_content(post.file, post.content) + }; + } + } - return { - ...post, - content: await render_markdown(post.file, post.content, { modules }) - }; + return null; } const BLOG_NAME_REGEX = /^(\d{4}-\d{2}-\d{2})-(.+)\.md$/; @@ -31,7 +32,7 @@ export function get_blog_data(base = CONTENT_BASE_PATHS.BLOG) { if (!BLOG_NAME_REGEX.test(file)) continue; const { date, date_formatted, slug } = get_date_and_slug(file); - const { metadata, body } = extract_frontmatter(fs.readFileSync(`${base}/${file}`, 'utf-8')); + const { metadata, body } = extractFrontmatter(fs.readFileSync(`${base}/${file}`, 'utf-8')); blog_posts.push({ date, diff --git a/sites/svelte.dev/src/lib/server/docs/index.js b/sites/svelte.dev/src/lib/server/docs/index.js index a958c26785..bff474ff90 100644 --- a/sites/svelte.dev/src/lib/server/docs/index.js +++ b/sites/svelte.dev/src/lib/server/docs/index.js @@ -1,15 +1,16 @@ import { base as app_base } from '$app/paths'; import { modules } from '$lib/generated/type-info.js'; -import fs from 'node:fs'; -import { CONTENT_BASE_PATHS } from '../../../constants.js'; import { escape, - extract_frontmatter, + extractFrontmatter, + markedTransform, normalizeSlugify, removeMarkdown, - transform -} from '../markdown/index.js'; -import { render_markdown } from '../markdown/renderer.js'; + replaceExportTypePlaceholders +} from '@sveltejs/site-kit/markdown'; +import fs from 'node:fs'; +import { CONTENT_BASE_PATHS } from '../../../constants.js'; +import { render_content } from '../renderer'; /** * @param {import('./types').DocsData} docs_data @@ -21,7 +22,7 @@ export async function get_parsed_docs(docs_data, slug) { if (page.slug === slug) { return { ...page, - content: await render_markdown(page.file, page.content, { modules }) + content: await render_content(page.file, page.content) }; } } @@ -62,7 +63,7 @@ export function get_docs_data(base = CONTENT_BASE_PATHS.DOCS) { const page_slug = match[1].replace('.md', ''); - const page_data = extract_frontmatter( + const page_data = extractFrontmatter( fs.readFileSync(`${base}/${category_dir}/${filename}`, 'utf-8') ); @@ -105,10 +106,12 @@ function get_sections(markdown) { const secondLevelHeadings = []; let match; - while ((match = headingRegex.exec(markdown)) !== null) { + const placeholders_rendered = replaceExportTypePlaceholders(markdown, modules); + + while ((match = headingRegex.exec(placeholders_rendered)) !== null) { secondLevelHeadings.push({ title: removeMarkdown( - escape(transform(match[1], { paragraph: (txt) => txt })) + escape(markedTransform(match[1], { paragraph: (txt) => txt })) .replace(/<\/?code>/g, '') .replace(/'/g, "'") .replace(/"/g, '"') diff --git a/sites/svelte.dev/src/lib/server/examples/index.js b/sites/svelte.dev/src/lib/server/examples/index.js index 1d4c7e5d99..df525e7881 100644 --- a/sites/svelte.dev/src/lib/server/examples/index.js +++ b/sites/svelte.dev/src/lib/server/examples/index.js @@ -6,11 +6,15 @@ import fs from 'node:fs'; * @param {string} slug */ export function get_example(examples_data, slug) { - const example = examples_data - .find((section) => section.examples.find((example) => example.slug === slug)) - ?.examples.find((example) => example.slug === slug); + for (const section of examples_data) { + for (const example of section.examples) { + if (example.slug === slug) { + return example; + } + } + } - return example; + return null; } /** diff --git a/sites/svelte.dev/src/lib/server/markdown/index.js b/sites/svelte.dev/src/lib/server/markdown/index.js deleted file mode 100644 index 0d6bbe50e1..0000000000 --- a/sites/svelte.dev/src/lib/server/markdown/index.js +++ /dev/null @@ -1,247 +0,0 @@ -import { marked } from 'marked'; - -const escapeTest = /[&<>"']/; -const escapeReplace = /[&<>"']/g; -const escapeTestNoEncode = /[<>"']|&(?!#?\w+;)/; -const escapeReplaceNoEncode = /[<>"']|&(?!#?\w+;)/g; -const escapeReplacements = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' -}; - -/** - * @param {keyof typeof escapeReplacements} ch - */ -const getEscapeReplacement = (ch) => escapeReplacements[ch]; - -export const SHIKI_LANGUAGE_MAP = { - bash: 'bash', - env: 'bash', - html: 'svelte', - svelte: 'svelte', - sv: 'svelte', - js: 'javascript', - dts: 'typescript', - css: 'css', - diff: 'diff', - ts: 'typescript', - '': '' -}; - -/** - * @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; -} - -/** @param {string} title */ -export function slugify(title) { - return title - .toLowerCase() - .replace(/'/g, '') - .replace(/</g, '') - .replace(/>/g, '') - .replace(/[^a-z0-9-$]/g, '-') - .replace(/-{2,}/g, '-') - .replace(/^-/, '') - .replace(/-$/, ''); -} - -/** @param {string} markdown */ -export function removeMarkdown(markdown) { - return markdown - .replace(/\*\*(.+?)\*\*/g, '$1') // bold - .replace(/_(.+?)_/g, '$1') // Italics - .replace(/\*(.+?)\*/g, '$1') // Italics - .replace(/`(.+?)`/g, '$1') // Inline code - .replace(/~~(.+?)~~/g, '$1') // Strikethrough - .replace(/\[(.+?)\]\(.+?\)/g, '$1') // Link - .replace(/\n/g, ' ') // New line - .replace(/ {2,}/g, ' ') - .trim(); -} - -/** @param {string} html */ -export function removeHTMLEntities(html) { - return html.replace(/&.+?;/g, ''); -} - -/** @param {string} str */ -export const normalizeSlugify = (str) => { - return slugify(removeHTMLEntities(removeMarkdown(str))).replace(/(<([^>]+)>)/gi, ''); -}; - -/** @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({ - mangle: false, - headerIds: false, - 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()] = removeQuotes(pair.slice(i + 1).trim()); - }); - - return { metadata, body }; -} - -/** @param {string} str */ -const removeQuotes = (str) => str.replace(/(^["']|["']$)/g, ''); diff --git a/sites/svelte.dev/src/lib/server/markdown/renderer.js b/sites/svelte.dev/src/lib/server/markdown/renderer.js deleted file mode 100644 index 2fdce267da..0000000000 --- a/sites/svelte.dev/src/lib/server/markdown/renderer.js +++ /dev/null @@ -1,962 +0,0 @@ -import MagicString from 'magic-string'; -import { createHash } from 'node:crypto'; -import fs from 'node:fs'; -import path from 'node:path'; -import { format } from 'prettier'; -import { createShikiHighlighter, renderCodeToHTML, runTwoSlash } from 'shiki-twoslash'; -import ts from 'typescript'; -import { - SHIKI_LANGUAGE_MAP, - escape, - normalizeSlugify, - slugify, - transform -} from '../markdown/index.js'; - -const METADATA_REGEX = /(?:|\/\/\/\s*(file|link):\s*(.*))\n/gm; - -/** - * A super markdown renderer function. Renders svelte and kit docs specific specific markdown code to html. - * - * - Syntax Highlighting -> shikiJS with `css-variables` theme. - * - TS hover snippets -> shiki-twoslash. JS and TS code snippets(other than d.ts) are run through twoslash. - * - JS -> TS conversion -> JS snippets starting with `/// file: some_file.js` are converted to TS if possible. Same for Svelte snippets starting with ``. Notice there's an additional dash(-) to the opening and closing comment tag. - * - Type links -> Type names are converted to links to the type's documentation page. - * - Snippet caching -> To avoid slowing down initial page render time, code snippets are cached in the nearest `node_modules/.snippets` folder. This is done by hashing the code snippet with SHA256 algo and storing the final rendered output in a file named the hash. - * - * ## Special syntax - * - * ### file - * - * Provided as a comment at top of a code snippet. If inside a JS code snippet, expects a triple slash comment as the first line(/// file:) - * - * ````md - * ```js - * /// file: some_file.js - * const a = 1; - * ``` - * ```` - * - * For svelte snippets, we use HTML comments, with an additional dash at the opening and end - * - * ````md - * ```svelte - * - * - * - * Hello {a} - * ``` - * ```` - * - * ### link - * - * Provided at the top. Should be under `file:` if present. - * - * This doesn't allow the imported members from `svelte/*` or `@sveltejs/kit` to be linked, as in they are not wrapped with an . - * - * ````md - * ```js - * /// file: some_file.js - * /// link: false - * import { onMount } from 'svelte'; - * - * onMount(() => { - * console.log('mounted'); - * }); - * ``` - * ```` - * - * @param {string} filename - * @param {string} body - * @param {object} options - * @param {(filename: string, content: string) => string} [options.twoslashBanner] - A function that returns a string to be prepended to the code snippet before running the code with twoslash. Helps in adding imports from svelte or sveltekit or whichever modules are being globally referenced in all or most code snippets. - * @param {import('$lib/generated/types').Modules} [options.modules] Module info generated from type-gen script. Used to create type links and type information blocks - * @param {boolean} [options.cacheCodeSnippets] Whether to cache code snippets or not. Defaults to true. - */ -export async function render_markdown( - filename, - body, - { twoslashBanner = svelte_twoslash_banner, modules = [], cacheCodeSnippets = true } = {} -) { - const highlighter = await createShikiHighlighter({ theme: 'css-variables' }); - - const { type_links, type_regex } = create_type_links(modules); - const SNIPPET_CACHE = await create_snippet_cache(cacheCodeSnippets); - - return parse({ - file: filename, - body: generate_ts_from_js(replace_export_type_placeholders(body, modules)), - code: (source, language, current) => { - const cached_snippet = SNIPPET_CACHE.get(source + language + current); - if (cached_snippet.code) return cached_snippet.code; - - /** @type {Record<'file' | 'link', string | null>} */ - const options = { file: null, link: null }; - - source = collect_options(source, options); - source = adjust_tab_indentation(source, language); - - let version_class = ''; - if (/^generated-(ts|svelte)$/.test(language)) { - language = language.replace('generated-', ''); - version_class = 'ts-version'; - } else if (/^original-(js|svelte)$/.test(language)) { - language = language.replace('original-', ''); - version_class = 'js-version'; - } - - let html = syntax_highlight({ filename, highlighter, language, source, twoslashBanner }); - - if (options.file) { - html = `
    ${options.file}${html}
    `; - } - - if (version_class) { - html = html.replace(/class=('|")/, `class=$1${version_class} `); - } - - if (type_regex) { - type_regex.lastIndex = 0; - - html = html.replace(type_regex, (match, prefix, name, pos, str) => { - const char_after = str.slice(pos + match.length, pos + match.length + 1); - - if (options.link === 'false' || name === current || /(\$|\d|\w)/.test(char_after)) { - // we don't want e.g. RequestHandler to link to RequestHandler - return match; - } - - const link = `${name}`; - return `${prefix || ''}${link}`; - }); - } - - html = indent_multiline_comments(html); - - html = html.replace(/\/\*…\*\//g, '…'); - - // Save everything locally now - SNIPPET_CACHE.save(cached_snippet?.uid, html); - - return html; - }, - codespan: (text) => { - return ( - '' + - (type_regex - ? text.replace(type_regex, (_, prefix, name) => { - const link = `${name}`; - return `${prefix || ''}${link}`; - }) - : text) + - '' - ); - } - }); -} - -/** - * @param {{ - * file: string; - * body: string; - * code: (source: string, language: string, current: string) => string; - * codespan: (source: string) => string; - * }} opts - */ -function parse({ body, code, codespan }) { - const headings = []; - - // 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, { - heading(html, level, raw) { - const title = html - .replace(/<\/?code>/g, '') - .replace(/"/g, '"') - .replace(/</g, '<') - .replace(/>/g, '>'); - - current = title; - - const normalized = normalizeSlugify(raw); - - headings[level] = normalized; - headings.length = level; - - const type_heading_match = /^\[TYPE\]:\s+(.+)/.exec(raw); - - const slug = normalizeSlugify(type_heading_match ? `type-${type_heading_match[1]}` : raw); - - return `${html - .replace(/<\/?code>/g, '') - .replace( - /^\[TYPE\]:\s+(.+)/, - '$1' - )}`; - }, - code: (source, language) => code(source, language, current), - codespan - }); - - return content; -} - -/** - * Pre-render step. Takes in all the code snippets, and replaces them with TS snippets if possible - * May replace the language labels (```js) to custom labels(```generated-ts, ```original-js, ```generated-svelte,```original-svelte) - * @param {string} markdown - */ -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) => { - METADATA_REGEX.lastIndex = 0; - - if (!METADATA_REGEX.test(code)) { - // 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; - - let count = 0; - for (const tag of comment.tags ?? []) { - if (ts.isJSDocTypeTag(tag)) { - const [name, generics] = get_type_info(tag); - - if (ts.isFunctionDeclaration(node)) { - const is_export = node.modifiers?.some( - (modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword - ) - ? 'export ' - : ''; - const is_async = node.modifiers?.some( - (modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword - ); - - const type = generics !== undefined ? `${name}<${generics}>` : name; - - code.overwrite( - node.getStart(), - node.name.getEnd(), - `${is_export ? 'export ' : ''}const ${node.name.getText()}: ${type} = (${ - is_async ? 'async ' : '' - }` - ); - - code.appendLeft(node.body.getStart(), '=> '); - code.appendLeft(node.body.getEnd(), ')'); - - 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}${generics ?? ''}`); - } - - 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 sanitised_param = tag - .getFullText() - .replace(/\s+/g, '') - .replace(/(^\*|\*$)/g, ''); - - const [, param_type] = /@param{(.+)}(.+)/.exec(sanitised_param); - - let param_count = 0; - for (const param of node.parameters) { - if (count !== param_count) { - param_count++; - continue; - } - - code.appendLeft(param.getEnd(), `:${param_type}`); - - param_count++; - } - - modified = true; - } - - count++; - } - - 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'); - } - - let transformed = format(code.toString(), { - printWidth: 100, - parser: 'typescript', - useTabs: true, - singleQuote: true - }); - - // Indent transformed's each line by 2 - transformed = transformed - .split('\n') - .map((line) => indent.repeat(1) + line) - .join('\n'); - - 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 single_line_name = format(name, { - printWidth: 1000, - parser: 'typescript', - semi: false, - singleQuote: true - }).replace('\n', ''); - - const import_match = /import\('(.+?)'\)\.(\w+)(?:<(.+)>)?$/s.exec(single_line_name); - - 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]; - } -} - -/** - * Replace module/export information placeholders in the docs. - * @param {string} content - * @param {import('$lib/generated/types').Modules} modules - */ -export function replace_export_type_placeholders(content, modules) { - const REGEXES = { - EXPANDED_TYPES: /> EXPANDED_TYPES: (.+?)#(.+)$/gm, - TYPES: /> TYPES: (.+?)(?:#(.+))?$/gm, - EXPORT_SNIPPET: /> EXPORT_SNIPPET: (.+?)#(.+)?$/gm, - MODULES: /> MODULES/, - EXPORTS: /> EXPORTS: (.+)/ - }; - - if (!modules || modules.length === 0) { - return content - .replace(REGEXES.EXPANDED_TYPES, '') - .replace(REGEXES.TYPES, '') - .replace(REGEXES.EXPORT_SNIPPET, '') - .replace(REGEXES.MODULES, '') - .replace(REGEXES.EXPORTS, ''); - } - - 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((v) => stringify(v)) - .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, 'dts')}` + - type.children.map((v) => stringify(v)).join('\n\n') + - `
    ` - ); - } - - return `${module.comment}\n\n${module.types - .map((t) => { - let children = t.children.map((val) => stringify(val, 'dts')).join('\n\n'); - - const deprecated = t.deprecated - ? `
    ${transform(t.deprecated)}
    ` - : ''; - - const markdown = `
    ${fence(t.snippet, 'dts')}` + children + `
    `; - return `### [TYPE]: ${t.name}\n\n${deprecated}\n\n${t.comment ?? ''}\n\n${markdown}\n\n`; - }) - .join('')}`; - }) - .replace(/> EXPORT_SNIPPET: (.+?)#(.+)?$/gm, (_, name, id) => { - const module = modules.find((module) => module.name === name); - if (!module) throw new Error(`Could not find module ${name} for EXPORT_SNIPPET clause`); - - if (!id) { - throw new Error(`id is required for module ${name}`); - } - - const exported = module.exports.filter((t) => t.name === id); - - return exported - .map((exportVal) => `
    ${fence(exportVal.snippet, 'dts')}
    `) - .join('\n\n'); - }) - .replace('> MODULES', () => { - return modules - .map((module) => { - if (module.exports.length === 0 && !module.exempt) 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((v) => stringify(v)).join('\n\n') + - `
    `; - return `### ${type.name}\n\n${type.comment}\n\n${markdown}`; - }) - .join('\n\n')}`; - }) - .join('\n\n'); - }) - .replace(/> EXPORTS: (.+)/, (_, name) => { - const module = modules.find((module) => module.name === name); - if (!module) throw new Error(`Could not find module ${name} for EXPORTS: clause`); - - if (module.exports.length === 0 && !module.exempt) 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 `${import_block}\n\n${module.comment}\n\n${module.exports - .map((type) => { - const markdown = - `
    ${fence(type.snippet, 'dts')}` + - type.children.map((val) => stringify(val, 'dts')).join('\n\n') + - `
    `; - return `### ${type.name}\n\n${type.comment}\n\n${markdown}`; - }) - .join('\n\n')}`; - }); -} - -/** - * @param {string} code - * @param {keyof typeof import('../markdown/index').SHIKI_LANGUAGE_MAP} lang - */ -function fence(code, lang = 'ts') { - return ( - '\n\n```' + - lang + - '\n' + - (['js', 'ts'].includes(lang) ? '// @noErrors\n' : '') + - code + - '\n```\n\n' - ); -} - -/** - * Helper function for {@link replace_export_type_placeholders}. Renders specifiv members to their markdown/html representation. - * @param {import('$lib/generated/types').Modules[number]['types'][number]} member - * @param {keyof typeof import('../markdown').SHIKI_LANGUAGE_MAP} [lang] - */ -function stringify(member, lang = 'ts') { - 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((val) => stringify(val, lang)) - .join('\n')}
    ` - : ''; - - return ( - `
    ${fence(member.snippet, lang)}` + - `
    \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
    ' - ); -} - -/** - * @type {(filename: string, source: string) => string} - */ -const svelte_twoslash_banner = (filename, source) => { - const injected = []; - - if (/(svelte)/.test(source) || filename.includes('typescript')) { - injected.push( - `// @filename: ambient.d.ts`, - `/// `, - `/// `, - `/// `, - `/// `, - `/// `, - `/// `, - `/// `, - `/// ` - ); - } - - if (filename.includes('svelte-compiler')) { - injected.push('// @esModuleInterop'); - } - - if (filename.includes('svelte.md')) { - injected.push('// @errors: 2304'); - } - - // Actions JSDoc examples are invalid. Too many errors, edge cases - if (filename.includes('svelte-action')) { - injected.push('// @noErrors'); - } - - if (filename.includes('typescript')) { - injected.push('// @errors: 2304'); - } - - // Tutorials - if (filename.startsWith('tutorial')) { - injected.push('// @noErrors'); - } - - return injected.join('\n'); -}; - -/** @param {string} start_path */ -function find_nearest_node_modules(start_path) { - if (fs.existsSync(path.join(start_path, 'node_modules'))) { - return path.resolve(start_path, 'node_modules'); - } - - const parentDir = path.dirname(start_path); - - if (start_path === parentDir) { - return null; - } - - return find_nearest_node_modules(parentDir); -} - -/** - * Utility function to work code snippet caching. - * - * @example - * - * ```js - * const SNIPPETS_CACHE = create_snippet_cache(true); - * - * const { uid, code } = SNIPPETS_CACHE.get(source); - * - * // Later to save the code to the cache - * SNIPPETS_CACHE.save(uid, processed_code); - * ``` - * - * @param {boolean} should - */ -async function create_snippet_cache(should) { - const snippet_cache = find_nearest_node_modules(import.meta.url) + '/.snippets'; - - try { - if (should) fs.mkdirSync(snippet_cache, { recursive: true }); - } catch {} - - /** - * @param {string} source - */ - function get(source) { - if (!should) return { uid: null, code: null }; - - const hash = createHash('sha256'); - hash.update(source); - const digest = hash.digest().toString('base64').replace(/\//g, '-'); - - try { - return { - uid: digest, - code: fs.readFileSync(`${snippet_cache}/${digest}.html`, 'utf-8') - }; - } catch {} - - return { uid: digest, code: null }; - } - - /** - * @param {string | null} uid - * @param {string} content - */ - function save(uid, content) { - if (!should) return; - - fs.writeFileSync(`${snippet_cache}/${uid}.html`, content); - } - - return { get, save }; -} - -/** - * @param {import('$lib/generated/types').Modules | undefined} modules - * @returns {{ type_regex: RegExp | null, type_links: Map | null }} - */ -function create_type_links(modules) { - if (!modules || modules.length === 0) return { type_regex: null, type_links: null }; - - const type_regex = new RegExp( - `(import\\('(?:svelte|@sveltejs\\/kit)'\\)\\.)?\\b(${modules - .flatMap((module) => module.types) - .map((type) => type?.name) - .join('|')})\\b`, - 'g' - ); - - const type_links = new Map(); - - for (const module of modules) { - const slug = slugify(module.name ?? ''); - - for (const type of module.types ?? []) { - const link = `/docs/${slug}#type-${slugify(type.name)}`; - type_links.set(type.name, link); - } - } - - return { type_regex, type_links }; -} - -/** - * @param {string} source - * @param {Record<'file' | 'link', string | null>} options - */ -function collect_options(source, options) { - return source.replace(METADATA_REGEX, (_, key, value) => { - options[key] = value; - return ''; - }); -} - -/** - * @param {string} source - * @param {string} language - */ -function adjust_tab_indentation(source, language) { - return source - .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, '*/'); -} - -/** - * - * @param {{ - * source: string, - * filename: string, - * language: string, - * highlighter: ReturnType - * twoslashBanner?: (filename: string, source: string) => string - * }} param0 - */ -function syntax_highlight({ source, filename, language, highlighter, twoslashBanner }) { - let html = ''; - - if (language === 'dts') { - html = renderCodeToHTML( - source, - 'ts', - { twoslash: false }, - { themeName: 'css-variables' }, - highlighter - ); - } else if (/^(js|ts)/.test(language)) { - try { - const banner = twoslashBanner?.(filename, source); - - if (banner) { - if (source.includes('// @filename:')) { - source = source.replace('// @filename:', `${banner}\n\n// @filename:`); - } else { - source = source.replace( - /^(?!\/\/ @)/m, - `${banner}\n\n// @filename: index.${language}\n` + ` // ---cut---\n` - ); - } - } - - const twoslash = runTwoSlash(source, language, { - defaultCompilerOptions: { - allowJs: true, - checkJs: true, - target: ts.ScriptTarget.ES2022, - types: ['svelte', '@sveltejs/kit'] - } - }); - - html = renderCodeToHTML( - twoslash.code, - 'ts', - { twoslash: true }, - // @ts-ignore Why shiki-twoslash requires a theme name? - {}, - highlighter, - twoslash - ); - } catch (e) { - console.error(`Error compiling snippet in ${filename}`); - 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, - (_, lsp, attrs, name) => { - if (!lsp) return name; - return `${name}`; - } - ); - - // preserve blank lines in output (maybe there's a more correct way to do this?) - html = html.replace(/
    <\/div>/g, '
    '); - } 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 highlighted = highlighter.codeToHtml(source, { - lang: SHIKI_LANGUAGE_MAP[language] - }); - - html = highlighted.replace(/
    <\/div>/g, '
    '); - } - - return html; -} - -/** - * @param {string} str - */ -function indent_multiline_comments(str) { - return str.replace( - /^(\s+)([\s\S]+?)<\/span>\n/gm, - (_, 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(''); - } - ); -} diff --git a/sites/svelte.dev/src/lib/server/renderer.js b/sites/svelte.dev/src/lib/server/renderer.js new file mode 100644 index 0000000000..91df473cc7 --- /dev/null +++ b/sites/svelte.dev/src/lib/server/renderer.js @@ -0,0 +1,52 @@ +import { modules } from '$lib/generated/type-info'; +import { renderContentMarkdown, slugify } from '@sveltejs/site-kit/markdown'; + +/** + * @param {string} filename + * @param {string} body + * @returns + */ +export const render_content = (filename, body) => + renderContentMarkdown(filename, body, { + cacheCodeSnippets: true, + modules, + + resolveTypeLinks: (module_name, type_name) => { + return { + page: `/docs/${slugify(module_name)}`, + slug: `type-${slugify(type_name)}` + }; + }, + + twoslashBanner: (filename, source) => { + const injected = []; + + if (/(svelte)/.test(source) || filename.includes('typescript')) { + injected.push(`// @filename: ambient.d.ts`, `/// `); + } + + if (filename.includes('svelte-compiler')) { + injected.push('// @esModuleInterop'); + } + + if (filename.includes('svelte.md')) { + injected.push('// @errors: 2304'); + } + + // Actions JSDoc examples are invalid. Too many errors, edge cases + if (filename.includes('svelte-action')) { + injected.push('// @noErrors'); + } + + if (filename.includes('typescript')) { + injected.push('// @errors: 2304'); + } + + // Tutorials + if (filename.startsWith('tutorial')) { + injected.push('// @noErrors'); + } + + return injected.join('\n'); + } + }); diff --git a/sites/svelte.dev/src/lib/server/tutorial/index.js b/sites/svelte.dev/src/lib/server/tutorial/index.js index 538e5052ff..e6e15db443 100644 --- a/sites/svelte.dev/src/lib/server/tutorial/index.js +++ b/sites/svelte.dev/src/lib/server/tutorial/index.js @@ -1,24 +1,25 @@ -import { modules } from '$lib/generated/type-info'; +import { extractFrontmatter } from '@sveltejs/site-kit/markdown'; import fs from 'node:fs'; import { CONTENT_BASE_PATHS } from '../../../constants.js'; -import { extract_frontmatter } from '../markdown/index.js'; -import { render_markdown } from '../markdown/renderer.js'; +import { render_content } from '../renderer.js'; /** * @param {import('./types').TutorialData} tutorial_data * @param {string} slug */ export async function get_parsed_tutorial(tutorial_data, slug) { - const tutorial = tutorial_data - .find(({ tutorials }) => tutorials.find((t) => t.slug === slug)) - ?.tutorials?.find((t) => t.slug === slug); - - if (!tutorial) return null; + for (const { tutorials } of tutorial_data) { + for (const tutorial of tutorials) { + if (tutorial.slug === slug) { + return { + ...tutorial, + content: await render_content(`tutorial/${tutorial.dir}`, tutorial.content) + }; + } + } + } - return { - ...tutorial, - content: await render_markdown(`tutorial/${tutorial.dir}`, tutorial.content, { modules }) - }; + return null; } /** @@ -49,7 +50,7 @@ export function get_tutorial_data(base = CONTENT_BASE_PATHS.TUTORIAL) { // Read the file, get frontmatter const contents = fs.readFileSync(`${tutorial_base_dir}/text.md`, 'utf-8'); - const { metadata, body } = extract_frontmatter(contents); + const { metadata, body } = extractFrontmatter(contents); // Get the contents of the apps. const completion_states_data = { initial: [], complete: [] }; diff --git a/sites/svelte.dev/src/lib/utils/Tooltip.svelte b/sites/svelte.dev/src/lib/utils/Tooltip.svelte deleted file mode 100644 index 3a9389eb04..0000000000 --- a/sites/svelte.dev/src/lib/utils/Tooltip.svelte +++ /dev/null @@ -1,69 +0,0 @@ - - - - - diff --git a/sites/svelte.dev/src/lib/utils/hovers.js b/sites/svelte.dev/src/lib/utils/hovers.js deleted file mode 100644 index f0a079f2a5..0000000000 --- a/sites/svelte.dev/src/lib/utils/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/svelte.dev/src/routes/blog/[slug]/+page.svelte b/sites/svelte.dev/src/routes/blog/[slug]/+page.svelte index 9ff121ca76..03421d89bb 100644 --- a/sites/svelte.dev/src/routes/blog/[slug]/+page.svelte +++ b/sites/svelte.dev/src/routes/blog/[slug]/+page.svelte @@ -1,10 +1,10 @@ diff --git a/sites/svelte.dev/src/routes/content.json/content.server.js b/sites/svelte.dev/src/routes/content.json/content.server.js index f16b404708..ebe2adc203 100644 --- a/sites/svelte.dev/src/routes/content.json/content.server.js +++ b/sites/svelte.dev/src/routes/content.json/content.server.js @@ -1,11 +1,11 @@ import { modules } from '$lib/generated/type-info.js'; import { - extract_frontmatter, + extractFrontmatter, + markedTransform, normalizeSlugify, removeMarkdown, - transform -} from '$lib/server/markdown/index.js'; -import { replace_export_type_placeholders } from '$lib/server/markdown/renderer.js'; + replaceExportTypePlaceholders +} from '@sveltejs/site-kit/markdown'; import fs from 'node:fs'; import path from 'node:path'; import glob from 'tiny-glob/sync.js'; @@ -33,9 +33,9 @@ export function content() { const filepath = `${base}/docs/${file}`; // const markdown = replace_placeholders(fs.readFileSync(filepath, 'utf-8')); - const markdown = replace_export_type_placeholders(fs.readFileSync(filepath, 'utf-8'), modules); + const markdown = replaceExportTypePlaceholders(fs.readFileSync(filepath, 'utf-8'), modules); - const { body, metadata } = extract_frontmatter(markdown); + const { body, metadata } = extractFrontmatter(markdown); const sections = body.trim().split(/^## /m); const intro = sections.shift().trim(); @@ -103,7 +103,7 @@ function plaintext(markdown) { /** @param {string} text */ const inline = (text) => text; - return transform(markdown, { + return markedTransform(markdown, { code: (source) => source.split('// ---cut---\n').pop(), blockquote: block, html: () => '\n', diff --git a/sites/svelte.dev/src/routes/docs/[slug]/+page.svelte b/sites/svelte.dev/src/routes/docs/[slug]/+page.svelte index d6ab43c39a..cae083211b 100644 --- a/sites/svelte.dev/src/routes/docs/[slug]/+page.svelte +++ b/sites/svelte.dev/src/routes/docs/[slug]/+page.svelte @@ -1,8 +1,7 @@