From cf32fa24975bc2d25d740f4f2047b0d65a8b4251 Mon Sep 17 00:00:00 2001 From: Puru Vijay <47742487+PuruVJ@users.noreply.github.com> Date: Thu, 22 Jun 2023 03:42:58 +0530 Subject: [PATCH 01/54] fix(site): update tutorial links (#8780) Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com> --- documentation/docs/05-misc/01-faq.md | 2 +- packages/svelte/scripts/generate-version.js | 11 ++++++++++- .../svelte.dev/src/lib/server/markdown/renderer.js | 2 +- sites/svelte.dev/src/routes/+layout.svelte | 6 +++++- sites/svelte.dev/src/routes/_components/Hero.svelte | 5 +++-- sites/svelte.dev/src/routes/nav.json/+server.js | 13 ------------- 6 files changed, 20 insertions(+), 19 deletions(-) diff --git a/documentation/docs/05-misc/01-faq.md b/documentation/docs/05-misc/01-faq.md index b8359ff17e..dd55458647 100644 --- a/documentation/docs/05-misc/01-faq.md +++ b/documentation/docs/05-misc/01-faq.md @@ -4,7 +4,7 @@ title: Frequently asked questions ## I'm new to Svelte. Where should I start? -We think the best way to get started is playing through the interactive [tutorial](/tutorial). Each step there is mainly focused on one specific aspect and is easy to follow. You'll be editing and running real Svelte components right in your browser. +We think the best way to get started is playing through the interactive [tutorial](https://learn.svelte.dev/). Each step there is mainly focused on one specific aspect and is easy to follow. You'll be editing and running real Svelte components right in your browser. Five to ten minutes should be enough to get you up and running. An hour and a half should get you through the entire tutorial. diff --git a/packages/svelte/scripts/generate-version.js b/packages/svelte/scripts/generate-version.js index 351ead1eca..9e38352be7 100644 --- a/packages/svelte/scripts/generate-version.js +++ b/packages/svelte/scripts/generate-version.js @@ -4,5 +4,14 @@ const pkg = JSON.parse(fs.readFileSync('package.json', 'utf-8')); fs.writeFileSync( './src/shared/version.js', - `// generated during release, do not modify\n\n/** @type {string} */\nexport const VERSION = '${pkg.version}';\n` + `// generated during release, do not modify + +/** + * The current version, as set in package.json. + * + * https://svelte.dev/docs/svelte-compiler#svelte-version + * @type {string} + */ +export const VERSION = '${pkg.version}'; +` ); diff --git a/sites/svelte.dev/src/lib/server/markdown/renderer.js b/sites/svelte.dev/src/lib/server/markdown/renderer.js index d342534b9d..2fdce267da 100644 --- a/sites/svelte.dev/src/lib/server/markdown/renderer.js +++ b/sites/svelte.dev/src/lib/server/markdown/renderer.js @@ -921,7 +921,7 @@ function syntax_highlight({ source, filename, language, highlighter, twoslashBan }; }); - html = `
${lines
+		html = `
${lines
 			.map((line) => {
 				if (line.type) return `${line.content}\n`;
 				return line.content + '\n';
diff --git a/sites/svelte.dev/src/routes/+layout.svelte b/sites/svelte.dev/src/routes/+layout.svelte
index 415a7e19c1..ab15ba8182 100644
--- a/sites/svelte.dev/src/routes/+layout.svelte
+++ b/sites/svelte.dev/src/routes/+layout.svelte
@@ -2,7 +2,7 @@
 	import { browser } from '$app/environment';
 	import { page } from '$app/stores';
 	import { Icon, Shell } from '@sveltejs/site-kit/components';
-	import { Nav } from '@sveltejs/site-kit/nav';
+	import { Nav, Separator } from '@sveltejs/site-kit/nav';
 	import { Search, SearchBox } from '@sveltejs/site-kit/search';
 	import '@sveltejs/site-kit/styles/index.css';
 
@@ -35,8 +35,12 @@
 			
 
 			
+				Tutorial
+
 				SvelteKit
 
+				
+
 				
 					Discord
 					
diff --git a/sites/svelte.dev/src/routes/_components/Hero.svelte b/sites/svelte.dev/src/routes/_components/Hero.svelte
index 663be50cfa..7370b7ac4a 100644
--- a/sites/svelte.dev/src/routes/_components/Hero.svelte
+++ b/sites/svelte.dev/src/routes/_components/Hero.svelte
@@ -17,7 +17,7 @@
 		
 		
@@ -86,11 +86,12 @@
 	.cta {
 		display: inline-flex;
 		align-items: center;
-		gap: 0.5rem;
+		gap: 0.1rem;
 		background: var(--sk-theme-1);
 		padding: 0.35em 0.8em;
 		font-size: var(--sk-text-s);
 		letter-spacing: 0.05em;
+		font-weight: 600;
 		white-space: nowrap;
 		border-radius: var(--sk-border-radius);
 		box-shadow: 0px 6px 14px rgba(0, 0, 0, 0.08);
diff --git a/sites/svelte.dev/src/routes/nav.json/+server.js b/sites/svelte.dev/src/routes/nav.json/+server.js
index fec735a105..3ba9192ffb 100644
--- a/sites/svelte.dev/src/routes/nav.json/+server.js
+++ b/sites/svelte.dev/src/routes/nav.json/+server.js
@@ -1,7 +1,6 @@
 import { get_blog_data, get_blog_list } from '$lib/server/blog/index.js';
 import { get_docs_data, get_docs_list } from '$lib/server/docs/index.js';
 import { get_examples_data, get_examples_list } from '$lib/server/examples/index.js';
-import { get_tutorial_data, get_tutorial_list } from '$lib/server/tutorial/index.js';
 import { json } from '@sveltejs/kit';
 
 export const prerender = true;
@@ -33,12 +32,6 @@ async function get_nav_list() {
 		}
 	];
 
-	const tutorial_list = get_tutorial_list(get_tutorial_data());
-	const processed_tutorial_list = tutorial_list.map(({ title, tutorials }) => ({
-		title,
-		sections: tutorials.map(({ title, slug }) => ({ title, path: '/tutorial/' + slug }))
-	}));
-
 	const examples_list = get_examples_list(get_examples_data());
 	const processed_examples_list = examples_list
 		.map(({ title, examples }) => ({
@@ -48,12 +41,6 @@ async function get_nav_list() {
 		.filter(({ title }) => title !== 'Embeds');
 
 	return [
-		{
-			title: 'Tutorial',
-			prefix: 'tutorial',
-			pathname: '/tutorial',
-			sections: processed_tutorial_list
-		},
 		{
 			title: 'Docs',
 			prefix: 'docs',

From 22cc4756e0864d6506ab4399917e17e9e08bd2c0 Mon Sep 17 00:00:00 2001
From: Puru Vijay <47742487+PuruVJ@users.noreply.github.com>
Date: Thu, 22 Jun 2023 06:39:20 +0530
Subject: [PATCH 02/54] feat(site): site-kit renderer (#8757)

* Push

* Weirdass fix

* Unified renderer

* Move a bit

* Accomodate to new renderer

* update site-kit

* bump site-kit

* Fix script

* Bump site-kit

---------

Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com>
---
 .../2017-09-06-the-zen-of-just-writing-css.md |   7 +-
 .../docs/03-runtime/02-svelte-store.md        |   1 +
 pnpm-lock.yaml                                |  22 +-
 sites/svelte.dev/package.json                 |   2 +-
 sites/svelte.dev/scripts/type-gen/index.js    |   4 +-
 sites/svelte.dev/src/lib/generated/types.d.ts |  16 -
 sites/svelte.dev/src/lib/server/blog/index.js |  23 +-
 sites/svelte.dev/src/lib/server/docs/index.js |  23 +-
 .../src/lib/server/examples/index.js          |  12 +-
 .../src/lib/server/markdown/index.js          | 247 -----
 .../src/lib/server/markdown/renderer.js       | 962 ------------------
 sites/svelte.dev/src/lib/server/renderer.js   |  52 +
 .../src/lib/server/tutorial/index.js          |  27 +-
 sites/svelte.dev/src/lib/utils/Tooltip.svelte |  69 --
 sites/svelte.dev/src/lib/utils/hovers.js      |  60 --
 .../src/routes/blog/[slug]/+page.svelte       |   4 +-
 .../src/routes/content.json/content.server.js |  14 +-
 .../src/routes/docs/[slug]/+page.svelte       |   5 +-
 18 files changed, 134 insertions(+), 1416 deletions(-)
 delete mode 100644 sites/svelte.dev/src/lib/generated/types.d.ts
 delete mode 100644 sites/svelte.dev/src/lib/server/markdown/index.js
 delete mode 100644 sites/svelte.dev/src/lib/server/markdown/renderer.js
 create mode 100644 sites/svelte.dev/src/lib/server/renderer.js
 delete mode 100644 sites/svelte.dev/src/lib/utils/Tooltip.svelte
 delete mode 100644 sites/svelte.dev/src/lib/utils/hovers.js

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 @@ From c808ebf2c4dd9b8831595ee73e3caed9d6c5eac9 Mon Sep 17 00:00:00 2001 From: Puru Vijay Date: Thu, 22 Jun 2023 06:47:30 +0530 Subject: [PATCH 03/54] fix: Flash of inAccurate coloR Theme (FART) --- sites/svelte.dev/src/app.html | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sites/svelte.dev/src/app.html b/sites/svelte.dev/src/app.html index ba38bf1692..99ce1df112 100644 --- a/sites/svelte.dev/src/app.html +++ b/sites/svelte.dev/src/app.html @@ -30,6 +30,16 @@ %sveltekit.head% + +
    %sveltekit.body%
    From 3c8a07136a1a9faa2ed7a39e16bec3f517d4059d Mon Sep 17 00:00:00 2001 From: Puru Vijay <47742487+PuruVJ@users.noreply.github.com> Date: Thu, 22 Jun 2023 08:42:09 +0530 Subject: [PATCH 04/54] fix(site): renderer new link format (#8787) * adapt * Uncomment * Push * Bump site-kit --- .../docs/02-template-syntax/07-special-elements.md | 2 +- documentation/docs/03-runtime/02-svelte-store.md | 4 ++-- .../docs/04-compiler-and-api/01-svelte-compiler.md | 4 ++-- pnpm-lock.yaml | 8 ++++---- sites/svelte.dev/package.json | 2 +- sites/svelte.dev/src/lib/server/renderer.js | 2 +- sites/svelte.dev/src/routes/docs/+page.svelte | 8 ++++---- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/documentation/docs/02-template-syntax/07-special-elements.md b/documentation/docs/02-template-syntax/07-special-elements.md index 5484fa3d2c..73419e5763 100644 --- a/documentation/docs/02-template-syntax/07-special-elements.md +++ b/documentation/docs/02-template-syntax/07-special-elements.md @@ -328,7 +328,7 @@ The `` element provides a place to specify per-component compile ## `` -The `` element allows you to place content in a [named slot](/docs/special-elements#slot-name-name) without wrapping it in a container DOM element. This keeps the flow layout of your document intact. +The `` element allows you to place content in a [named slot](/docs/special-elements#slot-slot-name-name) without wrapping it in a container DOM element. This keeps the flow layout of your document intact. ```svelte diff --git a/documentation/docs/03-runtime/02-svelte-store.md b/documentation/docs/03-runtime/02-svelte-store.md index 3d9acc689f..3643d8d2fa 100644 --- a/documentation/docs/03-runtime/02-svelte-store.md +++ b/documentation/docs/03-runtime/02-svelte-store.md @@ -4,9 +4,9 @@ title: 'svelte/store' The `svelte/store` module exports functions for creating [readable](/docs/svelte-store#readable), [writable](/docs/svelte-store#writable) and [derived](/docs/svelte-store#derived) stores. -Keep in mind that you don't _have_ to use these functions to enjoy the [reactive `$store` syntax](/docs/svelte-components#4-prefix-stores-with-$-to-access-their-values) in your components. Any object that correctly implements `.subscribe`, unsubscribe, and (optionally) `.set` is a valid store, and will work both with the special syntax, and with Svelte's built-in [`derived` stores](/docs/svelte-store#derived). +Keep in mind that you don't _have_ to use these functions to enjoy the [reactive `$store` syntax](/docs/svelte-components#script-4-prefix-stores-with-$-to-access-their-values) in your components. Any object that correctly implements `.subscribe`, unsubscribe, and (optionally) `.set` is a valid store, and will work both with the special syntax, and with Svelte's built-in [`derived` stores](/docs/svelte-store#derived). -This makes it possible to wrap almost any other reactive state handling library for use in Svelte. Read more about the [store contract](/docs/svelte-components#4-prefix-stores-with-$-to-access-their-values) to see what a correct implementation looks like. +This makes it possible to wrap almost any other reactive state handling library for use in Svelte. Read more about the [store contract](/docs/svelte-components#script-4-prefix-stores-with-$-to-access-their-values) to see what a correct implementation looks like. ## `writable` diff --git a/documentation/docs/04-compiler-and-api/01-svelte-compiler.md b/documentation/docs/04-compiler-and-api/01-svelte-compiler.md index d08c96c10a..f1365e55ec 100644 --- a/documentation/docs/04-compiler-and-api/01-svelte-compiler.md +++ b/documentation/docs/04-compiler-and-api/01-svelte-compiler.md @@ -29,7 +29,7 @@ const result = compile(source, { }); ``` -Refer to [CompileOptions](#type-compileoptions) for all the available options. +Refer to [CompileOptions](#types-compileoptions) for all the available options. The returned `result` object contains the code for your component, along with useful bits of metadata. @@ -47,7 +47,7 @@ import { compile } from 'svelte/compiler'; const { js, css, ast, warnings, vars, stats } = compile(source); ``` -Refer to [CompileResult](#type-compileresult) for a full description of the compile result. +Refer to [CompileResult](#types-compileresult) for a full description of the compile result. ## parse diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b1291d3c41..72aa17c5e6 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.11 - version: 6.0.0-next.11(@sveltejs/kit@1.20.4)(svelte@packages+svelte) + specifier: 6.0.0-next.14 + version: 6.0.0-next.14(@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,8 +1822,8 @@ packages: svelte-local-storage-store: 0.4.0(svelte@packages+svelte) dev: false - /@sveltejs/site-kit@6.0.0-next.11(@sveltejs/kit@1.20.4)(svelte@packages+svelte): - resolution: {integrity: sha512-v7K02vXgjotwvCbFEKHUcNK8/DOOVJ6KAGd9R7g1D93uVkAlcdn06D7ki8x17CskP5sl9FQIGV/RUuxDA459FA==} + /@sveltejs/site-kit@6.0.0-next.14(@sveltejs/kit@1.20.4)(svelte@packages+svelte): + resolution: {integrity: sha512-KAQlX47fAL1LC1OCC/mQFDmSZyfX7zEK6/B1BEF6i265SN1OBwE1ZG4yrlQa5EaUVz8MIyjcvLqbikiqAWesTA==} peerDependencies: '@sveltejs/kit': ^1.0.0 svelte: ^3.54.0 || ^4.0.0-next.1 || ^4.0.0 diff --git a/sites/svelte.dev/package.json b/sites/svelte.dev/package.json index 66014a2455..3c17e9fb3b 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.11", + "@sveltejs/site-kit": "6.0.0-next.14", "@sveltejs/vite-plugin-svelte": "^2.4.1", "@types/marked": "^5.0.0", "@types/node": "^20.3.1", diff --git a/sites/svelte.dev/src/lib/server/renderer.js b/sites/svelte.dev/src/lib/server/renderer.js index 91df473cc7..aa3c0446fc 100644 --- a/sites/svelte.dev/src/lib/server/renderer.js +++ b/sites/svelte.dev/src/lib/server/renderer.js @@ -14,7 +14,7 @@ export const render_content = (filename, body) => resolveTypeLinks: (module_name, type_name) => { return { page: `/docs/${slugify(module_name)}`, - slug: `type-${slugify(type_name)}` + slug: `types-${slugify(type_name)}` }; }, diff --git a/sites/svelte.dev/src/routes/docs/+page.svelte b/sites/svelte.dev/src/routes/docs/+page.svelte index 8cfc6dc65b..39c72c9851 100644 --- a/sites/svelte.dev/src/routes/docs/+page.svelte +++ b/sites/svelte.dev/src/routes/docs/+page.svelte @@ -145,12 +145,12 @@ // component-format- [/component-format-(script|style|script-context-module)$/i, 'svelte-components#$1'], - [/component-format-(?:script)(?:-?(.*))$/i, 'svelte-components#$1'], + [/component-format-(script)(?:-?(.*))$/i, 'svelte-components#$1-$2'], // template-syntax [/template-syntax-((?:element|component)-directives)-?(.*)/i, '$1#$2'], [/template-syntax-slot$/i, 'special-elements#slot'], - [/template-syntax-(?:slot)-?(.*)/i, 'special-elements#$1'], + [/template-syntax-(slot)-?(.*)/i, 'special-elements#$1-$2'], [/template-syntax-(if|each|await|key)$/i, 'logic-blocks#$1'], [/template-syntax-(const|debug|html)$/i, 'special-tags#$1'], [/template-syntax-(tags|attributes-and-props|text-expressions|comments)$/i, 'basic-markup#$1'], @@ -196,7 +196,7 @@ return new_ids; } - function getURlToRedirectTo() { + function get_url_to_redirect_to() { const hash = $page.url.hash.replace(/^#/i, ''); if (!hash) return '/docs/introduction'; @@ -211,6 +211,6 @@ onMount(() => { console.log(get_old_new_ids_map()); - goto(getURlToRedirectTo(), { replaceState: true }); + goto(get_url_to_redirect_to(), { replaceState: true }); }); From b582baa5701c5585f227f6c1fe534efea0247cc1 Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Thu, 22 Jun 2023 00:57:15 -0700 Subject: [PATCH 05/54] docs: list minimum versions for r-p-s and svelte-loader (#8788) --- documentation/docs/05-misc/04-v4-migration-guide.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/documentation/docs/05-misc/04-v4-migration-guide.md b/documentation/docs/05-misc/04-v4-migration-guide.md index 5a0dc18b5a..52c238d8ec 100644 --- a/documentation/docs/05-misc/04-v4-migration-guide.md +++ b/documentation/docs/05-misc/04-v4-migration-guide.md @@ -11,7 +11,8 @@ If you're a library author, consider whether to only support Svelte 4 or if it's - Upgrade to Node 16 or higher. Earlier versions are no longer supported. ([#8566](https://github.com/sveltejs/svelte/issues/8566)) - If you are using SvelteKit, upgrade to 1.20.4 or newer ([sveltejs/kit#10172](https://github.com/sveltejs/kit/pull/10172)) - If you are using Vite without SvelteKit, upgrade to `vite-plugin-svelte` 2.4.1 or newer ([#8516](https://github.com/sveltejs/svelte/issues/8516)) -- If you are using webpack, upgrade to webpack 5 or higher. Earlier versions are no longer supported. ([#8515](https://github.com/sveltejs/svelte/issues/8515)) +- If you are using webpack, upgrade to webpack 5 or higher and `svelte-loader` 3.1.8 or higher. Earlier versions are no longer supported. ([#8515](https://github.com/sveltejs/svelte/issues/8515), [198dbcf](https://github.com/sveltejs/svelte/commit/198dbcf)) +- If you are using Rollup, upgrade to `rollup-plugin-svelte` 7.1.5 or higher ([198dbcf](https://github.com/sveltejs/svelte/commit/198dbcf)) - If you are using TypeScript, upgrade to TypeScript 5 or higher. Lower versions might still work, but no guarantees are made about that. ([#8488](https://github.com/sveltejs/svelte/issues/8488)) ## Browser conditions for bundlers From f76a75b97dea12c2a3c7650b1dbec66daf4291e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jimmy=20W=C3=A4rting?= Date: Thu, 22 Jun 2023 03:10:48 -0500 Subject: [PATCH 06/54] chore: remove btoa fallback (#8783) Since we're targeting NodeJS >16 we don't need any Buffer fallback as it's now built in globally from that version onwards. --- packages/svelte/src/compiler/utils/mapped_code.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/svelte/src/compiler/utils/mapped_code.js b/packages/svelte/src/compiler/utils/mapped_code.js index c56dbac222..0987b85ada 100644 --- a/packages/svelte/src/compiler/utils/mapped_code.js +++ b/packages/svelte/src/compiler/utils/mapped_code.js @@ -266,9 +266,6 @@ export function combine_sourcemaps(filename, sourcemap_list) { if (!map.sources.length) map.sources = [filename]; return map; } -// browser vs node.js -const b64enc = typeof btoa == 'function' ? btoa : (b) => Buffer.from(b).toString('base64'); -const b64dec = typeof atob == 'function' ? atob : (a) => Buffer.from(a, 'base64').toString(); /** * @param {string} filename @@ -295,7 +292,7 @@ export function apply_preprocessor_sourcemap(filename, svelte_map, preprocessor_ toUrl: { enumerable: false, value: function toUrl() { - return 'data:application/json;charset=utf-8;base64,' + b64enc(this.toString()); + return 'data:application/json;charset=utf-8;base64,' + btoa(this.toString()); } } }); @@ -339,7 +336,7 @@ export function parse_attached_sourcemap(processed, tag_name) { // ignore attached sourcemap return ''; } - processed.map = b64dec(map_data); // use attached sourcemap + processed.map = atob(map_data); // use attached sourcemap return ''; // remove from processed.code } // sourceMappingURL is path or URL From 03942162f8722a2630478901e219c414e3d1c7b7 Mon Sep 17 00:00:00 2001 From: gtmnayan <50981692+gtm-nayan@users.noreply.github.com> Date: Thu, 22 Jun 2023 14:26:23 +0545 Subject: [PATCH 07/54] chore: deindent cjs output for compiler (#8785) makes package size a tiny bit smaller --- .changeset/smart-kangaroos-tell.md | 5 +++++ packages/svelte/rollup.config.js | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 .changeset/smart-kangaroos-tell.md diff --git a/.changeset/smart-kangaroos-tell.md b/.changeset/smart-kangaroos-tell.md new file mode 100644 index 0000000000..e561335b8a --- /dev/null +++ b/.changeset/smart-kangaroos-tell.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: deindent cjs output for compiler diff --git a/packages/svelte/rollup.config.js b/packages/svelte/rollup.config.js index 816068927a..fc1b5763a6 100644 --- a/packages/svelte/rollup.config.js +++ b/packages/svelte/rollup.config.js @@ -42,7 +42,8 @@ export default [ file: 'compiler.cjs', format: 'umd', name: 'svelte', - sourcemap: false + sourcemap: false, + indent: false }, external: [] } From 5702142d9e5cd5708d361195dc64bd10744900e0 Mon Sep 17 00:00:00 2001 From: gtmnayan <50981692+gtm-nayan@users.noreply.github.com> Date: Thu, 22 Jun 2023 14:33:23 +0545 Subject: [PATCH 08/54] feat: attach Svelte major version info to window global (#8761) Can be opt out by setting discloseVersion to false --------- Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com> --- .changeset/yellow-squids-explain.md | 5 +++++ packages/playground/src/App.svelte | 4 ++++ packages/svelte/package.json | 3 +++ packages/svelte/scripts/generate-version.js | 1 + packages/svelte/src/compiler/compile/index.js | 7 ++++++- .../svelte/src/compiler/compile/render_dom/index.js | 12 ++++++++++++ packages/svelte/src/compiler/interfaces.d.ts | 6 ++++++ .../src/runtime/internal/disclose-version/index.js | 5 +++++ packages/svelte/src/shared/version.js | 1 + packages/svelte/test/helpers.js | 1 + packages/svelte/test/js/js-output.test.js | 7 ++++++- packages/svelte/test/runtime-browser/browser.test.js | 11 +++-------- 12 files changed, 53 insertions(+), 10 deletions(-) create mode 100644 .changeset/yellow-squids-explain.md create mode 100644 packages/svelte/src/runtime/internal/disclose-version/index.js diff --git a/.changeset/yellow-squids-explain.md b/.changeset/yellow-squids-explain.md new file mode 100644 index 0000000000..6872674188 --- /dev/null +++ b/.changeset/yellow-squids-explain.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +feat: add version info to `window`. You can opt out by setting `discloseVersion` to `false` in the compiler options diff --git a/packages/playground/src/App.svelte b/packages/playground/src/App.svelte index ab87de6d96..b3c16eec67 100644 --- a/packages/playground/src/App.svelte +++ b/packages/playground/src/App.svelte @@ -1,3 +1,7 @@ + +
    Hello world!
    \ No newline at end of file diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 10df08e9f1..b778be709c 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -56,6 +56,9 @@ "types": "./types/index.d.ts", "import": "./src/runtime/store/index.js" }, + "./internal/disclose-version": { + "import": "./src/runtime/internal/disclose-version/index.js" + }, "./transition": { "types": "./types/index.d.ts", "import": "./src/runtime/transition/index.js" diff --git a/packages/svelte/scripts/generate-version.js b/packages/svelte/scripts/generate-version.js index 9e38352be7..3b92484610 100644 --- a/packages/svelte/scripts/generate-version.js +++ b/packages/svelte/scripts/generate-version.js @@ -13,5 +13,6 @@ fs.writeFileSync( * @type {string} */ export const VERSION = '${pkg.version}'; +export const PUBLIC_VERSION = '${pkg.version.split('.')[0]}'; ` ); diff --git a/packages/svelte/src/compiler/compile/index.js b/packages/svelte/src/compiler/compile/index.js index a298af4586..4271ee6c27 100644 --- a/packages/svelte/src/compiler/compile/index.js +++ b/packages/svelte/src/compiler/compile/index.js @@ -30,7 +30,8 @@ const valid_options = [ 'loopGuardTimeout', 'preserveComments', 'preserveWhitespace', - 'cssHash' + 'cssHash', + 'discloseVersion' ]; const valid_css_values = [true, false, 'injected', 'external', 'none']; const regex_valid_identifier = /^[a-zA-Z_$][a-zA-Z_$0-9]*$/; @@ -112,6 +113,10 @@ function validate_options(options, warnings) { throw new Error(`Invalid namespace '${namespace}'`); } } + + if (options.discloseVersion == undefined) { + options.discloseVersion = true; + } } /** diff --git a/packages/svelte/src/compiler/compile/render_dom/index.js b/packages/svelte/src/compiler/compile/render_dom/index.js index 7e640ac64a..9a11013fc9 100644 --- a/packages/svelte/src/compiler/compile/render_dom/index.js +++ b/packages/svelte/src/compiler/compile/render_dom/index.js @@ -604,5 +604,17 @@ export default function dom(component, options) { ); } } + + if (options.discloseVersion === true) { + component.imports.unshift({ + type: 'ImportDeclaration', + specifiers: [], + source: { + type: 'Literal', + value: `${options.sveltePath ?? 'svelte'}/internal/disclose-version` + } + }); + } + return { js: flatten(body), css }; } diff --git a/packages/svelte/src/compiler/interfaces.d.ts b/packages/svelte/src/compiler/interfaces.d.ts index f99d641755..64be9698f5 100644 --- a/packages/svelte/src/compiler/interfaces.d.ts +++ b/packages/svelte/src/compiler/interfaces.d.ts @@ -344,6 +344,12 @@ export interface CompileOptions { * @default false */ preserveWhitespace?: boolean; + /** + * If `true`, exposes the Svelte major version on the global `window` object in the browser. + * + * @default true + */ + discloseVersion?: boolean; } export interface ParserOptions { diff --git a/packages/svelte/src/runtime/internal/disclose-version/index.js b/packages/svelte/src/runtime/internal/disclose-version/index.js new file mode 100644 index 0000000000..7cda592dca --- /dev/null +++ b/packages/svelte/src/runtime/internal/disclose-version/index.js @@ -0,0 +1,5 @@ +import { PUBLIC_VERSION } from '../../../shared/version.js'; + +if (typeof window !== 'undefined') + // @ts-ignore + (window.__svelte || (window.__svelte = { v: new Set() })).v.add(PUBLIC_VERSION); diff --git a/packages/svelte/src/shared/version.js b/packages/svelte/src/shared/version.js index 79089bf0ae..2ccd9837c7 100644 --- a/packages/svelte/src/shared/version.js +++ b/packages/svelte/src/shared/version.js @@ -7,3 +7,4 @@ * @type {string} */ export const VERSION = '4.0.0-next.2'; +export const PUBLIC_VERSION = '4'; diff --git a/packages/svelte/test/helpers.js b/packages/svelte/test/helpers.js index ddc6a2818b..596086f986 100644 --- a/packages/svelte/test/helpers.js +++ b/packages/svelte/test/helpers.js @@ -147,6 +147,7 @@ export function create_loader(compileOptions, cwd) { // any imported Svelte components as well. A few edge cases aren't handled but also // currently unused in the tests, for example `export * from`and live bindings. let transformed = compiled.js.code + .replace(/^import ['"]([^'"]+)['"]/gm, 'await __import("$1")') .replace( /^import \* as (\w+) from ['"]([^'"]+)['"];?/gm, 'const $1 = await __import("$2");' diff --git a/packages/svelte/test/js/js-output.test.js b/packages/svelte/test/js/js-output.test.js index 31ffe9e5eb..95409390af 100644 --- a/packages/svelte/test/js/js-output.test.js +++ b/packages/svelte/test/js/js-output.test.js @@ -33,7 +33,12 @@ describe('js-output', () => { let actual; try { - const options = Object.assign({}, config.options || {}); + const options = Object.assign( + { + discloseVersion: false + }, + config.options || {} + ); actual = svelte .compile(input, options) diff --git a/packages/svelte/test/runtime-browser/browser.test.js b/packages/svelte/test/runtime-browser/browser.test.js index 09eb43cf0b..00ecaa6522 100644 --- a/packages/svelte/test/runtime-browser/browser.test.js +++ b/packages/svelte/test/runtime-browser/browser.test.js @@ -6,8 +6,7 @@ import * as svelte from 'svelte/compiler'; import { afterAll, assert, beforeAll, describe, it } from 'vitest'; import { pretty_print_browser_assertion, try_load_config } from '../helpers.js'; -const internal = path.resolve('src/runtime/internal/index.js'); -const index = path.resolve('src/runtime/index.js'); +const assert_file = path.resolve(__dirname, 'assert.js'); /** @type {import('@playwright/test').Browser} */ let browser; @@ -62,9 +61,7 @@ async function run_browser_test(dir) { alias: { __MAIN_DOT_SVELTE__: path.resolve(__dirname, 'samples', dir, 'main.svelte'), __CONFIG__: path.resolve(__dirname, 'samples', dir, '_config.js'), - 'assert.js': path.resolve(__dirname, 'assert.js'), - 'svelte/internal': internal, - svelte: index + 'assert.js': assert_file }, plugins: [ { @@ -169,9 +166,7 @@ async function run_custom_elements_test(dir) { entryPoints: [`${cwd}/test.js`], write: false, alias: { - 'assert.js': path.resolve(__dirname, 'assert.js'), - 'svelte/internal': internal, - svelte: index + 'assert.js': assert_file }, plugins: [ { From ef1b98f9d9c11c4eee97bce2441997c117e0c560 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Thu, 22 Jun 2023 11:59:28 +0200 Subject: [PATCH 09/54] fix: account for sourcemap in meta info (#8778) We need to use a different method for getting the meta info because `locate` is used to help construct the source map that references the preprocessed Svelte file. If we would now add source maps to that `locate` function it would go the the original source directly which means skipping potentially intermediate source maps which we would need in other situations. Sadly we can't map the character offset because for that we would need to the original source contents which we don't have in this context. fixes #8360 closes #8362 --- .changeset/silly-ladybugs-marry.md | 5 +++ .prettierrc | 2 +- packages/svelte/package.json | 3 +- .../svelte/src/compiler/compile/Component.js | 32 +++++++++++++++++- .../compiler/compile/render_dom/Renderer.js | 14 +++++++- .../render_dom/wrappers/Element/index.js | 4 ++- .../_config.js | 33 +++++++++++++++++++ .../main.svelte | 5 +++ pnpm-lock.yaml | 3 ++ 9 files changed, 96 insertions(+), 5 deletions(-) create mode 100644 .changeset/silly-ladybugs-marry.md create mode 100644 packages/svelte/test/runtime/samples/element-source-location-preprocessed/_config.js create mode 100644 packages/svelte/test/runtime/samples/element-source-location-preprocessed/main.svelte diff --git a/.changeset/silly-ladybugs-marry.md b/.changeset/silly-ladybugs-marry.md new file mode 100644 index 0000000000..10cf4b0888 --- /dev/null +++ b/.changeset/silly-ladybugs-marry.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: account for preprocessor source maps when calculating meta info diff --git a/.prettierrc b/.prettierrc index c187505941..0ea7a0b1e9 100644 --- a/.prettierrc +++ b/.prettierrc @@ -12,7 +12,7 @@ } }, { - "files": ["README.md", "packages/*/README.md"], + "files": ["README.md", "packages/*/README.md", "**/package.json"], "options": { "useTabs": false, "tabWidth": 2 diff --git a/packages/svelte/package.json b/packages/svelte/package.json index b778be709c..6b0dbc3bdb 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -103,6 +103,7 @@ "dependencies": { "@ampproject/remapping": "^2.2.1", "@jridgewell/sourcemap-codec": "^1.4.15", + "@jridgewell/trace-mapping": "^0.3.18", "acorn": "^8.8.2", "aria-query": "^5.2.1", "axobject-query": "^3.2.1", @@ -135,4 +136,4 @@ "typescript": "^5.0.4", "vitest": "^0.31.1" } -} \ No newline at end of file +} diff --git a/packages/svelte/src/compiler/compile/Component.js b/packages/svelte/src/compiler/compile/Component.js index 92c7fcaf46..03d2c2fad3 100644 --- a/packages/svelte/src/compiler/compile/Component.js +++ b/packages/svelte/src/compiler/compile/Component.js @@ -1,3 +1,4 @@ +import { TraceMap, originalPositionFor } from '@jridgewell/trace-mapping'; import { walk } from 'estree-walker'; import { getLocator } from 'locate-character'; import { reserved, is_valid } from '../utils/names.js'; @@ -142,9 +143,20 @@ export default class Component { /** @type {string} */ file; - /** @type {(c: number) => { line: number; column: number }} */ + /** + * Use this for stack traces. It is 1-based and acts on pre-processed sources. + * Use `meta_locate` for metadata on DOM elements. + * @type {(c: number) => { line: number; column: number }} + */ locate; + /** + * Use this for metadata on DOM elements. It is 1-based and acts on sources that have not been pre-processed. + * Use `locate` for source mappings. + * @type {(c: number) => { line: number; column: number }} + */ + meta_locate; + /** @type {import('./nodes/Element.js').default[]} */ elements = []; @@ -199,7 +211,25 @@ export default class Component { .replace(process.cwd(), '') .replace(regex_leading_directory_separator, '') : compile_options.filename); + + // line numbers in stack trace frames are 1-based. source maps are 0-based this.locate = getLocator(this.source, { offsetLine: 1 }); + /** @type {TraceMap | null | undefined} initialise lazy because only used in dev mode */ + let tracer; + this.meta_locate = (c) => { + /** @type {{ line: number, column: number }} */ + let location = this.locate(c); + if (tracer === undefined) { + // @ts-expect-error - fix the type of CompileOptions.sourcemap + tracer = compile_options.sourcemap ? new TraceMap(compile_options.sourcemap) : null; + } + if (tracer) { + // originalPositionFor returns 1-based lines like locator + location = originalPositionFor(tracer, location); + } + return location; + }; + // styles this.stylesheet = new Stylesheet({ source, diff --git a/packages/svelte/src/compiler/compile/render_dom/Renderer.js b/packages/svelte/src/compiler/compile/render_dom/Renderer.js index 23f9ab5fba..0a7cd11ea6 100644 --- a/packages/svelte/src/compiler/compile/render_dom/Renderer.js +++ b/packages/svelte/src/compiler/compile/render_dom/Renderer.js @@ -62,9 +62,20 @@ export default class Renderer { /** @type {import('estree').Identifier} */ file_var; - /** @type {(c: number) => { line: number; column: number }} */ + /** + * Use this for stack traces. It is 1-based and acts on pre-processed sources. + * Use `meta_locate` for metadata on DOM elements. + * @type {(c: number) => { line: number; column: number }} + */ locate; + /** + * Use this for metadata on DOM elements. It is 1-based and acts on sources that have not been pre-processed. + * Use `locate` for source mappings. + * @type {(c: number) => { line: number; column: number }} + */ + meta_locate; + /** * @param {import('../Component.js').default} component * @param {import('../../interfaces.js').CompileOptions} options @@ -73,6 +84,7 @@ export default class Renderer { this.component = component; this.options = options; this.locate = component.locate; // TODO messy + this.meta_locate = component.meta_locate; // TODO messy this.file_var = options.dev && this.component.get_unique_name('file'); component.vars .filter((v) => !v.hoistable || (v.export_name && !v.module)) diff --git a/packages/svelte/src/compiler/compile/render_dom/wrappers/Element/index.js b/packages/svelte/src/compiler/compile/render_dom/wrappers/Element/index.js index eca5f09fc5..5f8def5f29 100644 --- a/packages/svelte/src/compiler/compile/render_dom/wrappers/Element/index.js +++ b/packages/svelte/src/compiler/compile/render_dom/wrappers/Element/index.js @@ -585,9 +585,11 @@ export default class ElementWrapper extends Wrapper { ); } if (renderer.options.dev) { - const loc = renderer.locate(this.node.start); + const loc = renderer.meta_locate(this.node.start); block.chunks.hydrate.push( b`@add_location(${this.var}, ${renderer.file_var}, ${loc.line - 1}, ${loc.column}, ${ + // TODO this.node.start isn't correct if there's a source map. But since we don't know how the + // original source file looked, there's not much we can do. this.node.start });` ); diff --git a/packages/svelte/test/runtime/samples/element-source-location-preprocessed/_config.js b/packages/svelte/test/runtime/samples/element-source-location-preprocessed/_config.js new file mode 100644 index 0000000000..d132db0141 --- /dev/null +++ b/packages/svelte/test/runtime/samples/element-source-location-preprocessed/_config.js @@ -0,0 +1,33 @@ +import MagicString from 'magic-string'; +import * as path from 'node:path'; + +// fake preprocessor by doing transforms on the source +const str = new MagicString( + ` + +

    {foo}

    +`.replace(/\r\n/g, '\n') +); +str.remove(8, 26); // remove line type Foo = ... +str.remove(55, 56); // remove whitespace before

    + +export default { + compileOptions: { + dev: true, + sourcemap: str.generateMap({ hires: true }) + }, + + test({ assert, target }) { + const h1 = target.querySelector('h1'); + + assert.deepEqual(h1.__svelte_meta.loc, { + file: path.relative(process.cwd(), path.resolve(__dirname, 'main.svelte')), + line: 5, // line 4 in main.svelte, but that's the preprocessed code, the original code is above in the fake preprocessor + column: 1, // line 0 in main.svelte, but that's the preprocessed code, the original code is above in the fake preprocessor + char: 38 // TODO this is wrong but we can't backtrace it due to limitations, see add_location function usage comment for more info + }); + } +}; diff --git a/packages/svelte/test/runtime/samples/element-source-location-preprocessed/main.svelte b/packages/svelte/test/runtime/samples/element-source-location-preprocessed/main.svelte new file mode 100644 index 0000000000..fd631ff11c --- /dev/null +++ b/packages/svelte/test/runtime/samples/element-source-location-preprocessed/main.svelte @@ -0,0 +1,5 @@ + + +

    {foo}

    diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 72aa17c5e6..6e9df559e1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,6 +56,9 @@ importers: '@jridgewell/sourcemap-codec': specifier: ^1.4.15 version: 1.4.15 + '@jridgewell/trace-mapping': + specifier: ^0.3.18 + version: 0.3.18 acorn: specifier: ^8.8.2 version: 8.8.2 From 82cc48390c2b495d8db8141b511f58b979442979 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 22 Jun 2023 13:34:50 +0200 Subject: [PATCH 10/54] Version Packages (next) (#8772) Co-authored-by: github-actions[bot] --- .changeset/pre.json | 7 ++++++- packages/svelte/CHANGELOG.md | 16 +++++++++++++++- packages/svelte/package.json | 2 +- packages/svelte/src/shared/version.js | 2 +- 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index a80f2220f5..a9b22b6d8e 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -8,11 +8,16 @@ }, "changesets": [ "beige-boxes-rhyme", + "eighty-tigers-rhyme", "fair-geese-repeat", "gentle-pumas-chew", "green-sheep-learn", "mighty-suns-occur", + "odd-wasps-smoke", + "silly-ladybugs-marry", + "smart-kangaroos-tell", "stale-cougars-wink", - "tame-peaches-destroy" + "tame-peaches-destroy", + "yellow-squids-explain" ] } diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 8b353d2b79..eb227dd1da 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,19 @@ # svelte +## 4.0.0-next.3 + +### Patch Changes + +- feat: smaller minified output for destructor chunks ([#8763](https://github.com/sveltejs/svelte/pull/8763)) + +- breaking: use `CustomEvent` constructor instead of deprecated `createEvent` method ([#8775](https://github.com/sveltejs/svelte/pull/8775)) + +- fix: account for preprocessor source maps when calculating meta info ([#8778](https://github.com/sveltejs/svelte/pull/8778)) + +- chore: deindent cjs output for compiler ([#8785](https://github.com/sveltejs/svelte/pull/8785)) + +- feat: add version info to `window`. You can opt out by setting `discloseVersion` to `false` in the compiler options ([#8761](https://github.com/sveltejs/svelte/pull/8761)) + ## 4.0.0-next.2 ### Patch Changes @@ -69,7 +83,7 @@ ## 3.59.2 -* Fix escaping `