feat(site-2): Fully remove api.svelte.dev (#8432)

* Localize the examples dependents

* Localize FAQ

* Try to fix examples API

* Remove examples api

* Prerender, use vercel adapter, disable PUT action for now

* Don't call get_examples_data on top

* Move repl PUT to its own routew

* Don't prerender REPL page

* Change console warn

* Prerender tutorial the smart way

* Use getContext in root

* work around some weird content encoding glitch

* Try the pre-generate route

* Apply the suggestions

* Fix embed examples

* Remove comment

* remove commented-out code

* Update sites/svelte.dev/src/routes/_components/Demo.svelte

Co-authored-by: Rich Harris <richard.a.harris@gmail.com>

* remove unused code

* add note to self, remove ts-ignore

* move adapter-vercel to devDependencies

* remove some unused deps

* huh we need flexsearch after all? weird

* we need sourcemap-codec as well — what the hell. are these deps missing from a dep?

---------

Co-authored-by: Rich Harris <git@rich-harris.dev>
Co-authored-by: Rich Harris <richard.a.harris@gmail.com>
pull/8453/head
Puru Vijay 2 years ago committed by GitHub
parent 9793b41817
commit 5976de8f82
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -13,7 +13,5 @@ GITHUB_CLIENT_SECRET=
SUPABASE_URL=https://kpaaohfbmxvespqoqdzp.supabase.co SUPABASE_URL=https://kpaaohfbmxvespqoqdzp.supabase.co
SUPABASE_KEY= SUPABASE_KEY=
PUBLIC_API_BASE="https://api.svelte.dev"
# client-side # client-side
VITE_MAPBOX_ACCESS_TOKEN= VITE_MAPBOX_ACCESS_TOKEN=

@ -9,3 +9,5 @@
/src/routes/_components/Supporters/contributors.js /src/routes/_components/Supporters/contributors.js
/src/routes/_components/Supporters/donors.jpg /src/routes/_components/Supporters/donors.jpg
/src/routes/_components/Supporters/donors.js /src/routes/_components/Supporters/donors.js
.vercel
examples-data.js

File diff suppressed because it is too large Load Diff

@ -1,57 +1,56 @@
{ {
"name": "svelte.dev", "name": "svelte.dev",
"version": "1.0.0", "version": "1.0.0",
"description": "Docs and examples for Svelte", "description": "Docs and examples for Svelte",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "node scripts/update.js && vite dev", "dev": "node scripts/generate_examples.js && node scripts/update.js && vite dev",
"build": "node scripts/update.js && vite build", "build": "node scripts/generate_examples.js && node scripts/update.js && vite build",
"update": "node scripts/update.js --force=true", "update": "node scripts/update.js --force=true",
"preview": "vite preview", "preview": "vite preview",
"start": "node build", "start": "node build",
"check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json", "check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
"format": "npm run check:format -- --write", "format": "npm run check:format -- --write",
"check:format": "prettier --check . --ignore-path .gitignore --plugin-search-dir=.", "check:format": "prettier --check . --ignore-path .gitignore --plugin-search-dir=.",
"test": "uvu -r ts-node/register src/lib/server/markdown" "test": "uvu -r ts-node/register src/lib/server/markdown"
}, },
"dependencies": { "dependencies": {
"@supabase/supabase-js": "^2.11.0", "@supabase/supabase-js": "^2.11.0",
"@sveltejs/repl": "^0.2.0", "@sveltejs/repl": "^0.2.0",
"cookie": "^0.5.0", "cookie": "^0.5.0",
"devalue": "^4.3.0", "devalue": "^4.3.0",
"do-not-zip": "^1.0.0", "do-not-zip": "^1.0.0",
"flexsearch": "^0.7.31", "flexsearch": "^0.7.31",
"flru": "^1.0.2", "flru": "^1.0.2",
"sourcemap-codec": "^1.4.8", "sourcemap-codec": "^1.4.8"
"svelte-local-storage-store": "^0.4.0" },
}, "devDependencies": {
"devDependencies": { "@resvg/resvg-js": "^2.4.1",
"@resvg/resvg-js": "^2.4.1", "@sveltejs/adapter-vercel": "^2.4.1",
"@sveltejs/adapter-auto": "^2.0.0", "@sveltejs/kit": "^1.12.0",
"@sveltejs/kit": "^1.12.0", "@sveltejs/site-kit": "^3.3.6",
"@sveltejs/site-kit": "^3.3.6", "@sveltejs/vite-plugin-svelte": "^2.0.3",
"@sveltejs/vite-plugin-svelte": "^2.0.3", "@types/marked": "^4.0.8",
"@types/marked": "^4.0.8", "@types/prismjs": "^1.26.0",
"@types/prismjs": "^1.26.0", "degit": "^2.8.4",
"degit": "^2.8.4", "dotenv": "^16.0.3",
"dotenv": "^16.0.3", "jimp": "^0.22.7",
"jimp": "^0.22.7", "marked": "^4.2.12",
"marked": "^4.2.12", "node-fetch": "^3.3.1",
"node-fetch": "^3.3.1", "prettier": "^2.8.4",
"prettier": "^2.8.4", "prettier-plugin-svelte": "^2.9.0",
"prettier-plugin-svelte": "^2.9.0", "prism-svelte": "^0.5.0",
"prism-svelte": "^0.5.0", "prismjs": "^1.29.0",
"prismjs": "^1.29.0", "satori": "^0.4.3",
"satori": "^0.4.3", "satori-html": "^0.3.2",
"satori-html": "^0.3.2", "shelljs": "^0.8.5",
"shelljs": "^0.8.5", "shiki": "^0.14.1",
"shiki": "^0.14.1", "shiki-twoslash": "^3.1.1",
"shiki-twoslash": "^3.1.1", "svelte": "^3.57.0",
"svelte": "^3.57.0", "svelte-check": "^3.1.4",
"svelte-check": "^3.1.4", "typescript": "^5.0.2",
"typescript": "^5.0.2", "vite": "^4.2.0",
"vite": "^4.2.0", "vite-imagetools": "^4.0.18"
"vite-imagetools": "^4.0.18" }
}
} }

@ -0,0 +1,13 @@
import { get_examples_data } from '../src/lib/server/examples/get-examples.js';
import fs from 'node:fs';
const examples_data = get_examples_data(
new URL('../../../site/content/examples', import.meta.url).pathname
);
fs.mkdirSync(new URL('../src/lib/generated/', import.meta.url), { recursive: true });
fs.writeFileSync(
new URL('../src/lib/generated/examples-data.js', import.meta.url),
`export default ${JSON.stringify(examples_data)}`
);

@ -1,21 +1,21 @@
<script> <script>
import Repl from '@sveltejs/repl';
import { onMount } from 'svelte';
import { browser } from '$app/environment'; import { browser } from '$app/environment';
import { process_example } from '$lib/utils/examples'; import { process_example } from '$lib/utils/examples';
import { PUBLIC_API_BASE } from '$env/static/public'; import Repl from '@sveltejs/repl';
import { onMount } from 'svelte';
export let version = '3'; export let version = '3';
export let gist = null; export let gist = null;
export let example = null; export let example = null;
export let embedded = false; export let embedded = false;
/** @type {import('@sveltejs/repl').default} */
let repl; let repl;
let name = 'loading...'; let name = 'loading...';
let mounted = false; let mounted = false;
function load(gist, example) { async function load(gist, example) {
if (version !== 'local') { if (version !== 'local') {
fetch(`https://unpkg.com/svelte@${version}/package.json`) fetch(`https://unpkg.com/svelte@${version}/package.json`)
.then((r) => r.json()) .then((r) => r.json())
@ -58,15 +58,12 @@
repl.set({ components }); repl.set({ components });
}); });
} else if (example) { } else if (example) {
fetch(`${PUBLIC_API_BASE}/docs/svelte/examples/${example}`).then(async (response) => { const components = process_example(
if (response.ok) { (await fetch(`/examples/api/${example}.json`).then((r) => r.json())).files
const data = await response.json(); );
const components = process_example(data.files);
repl.set({
repl.set({ components,
components,
});
}
}); });
} }
} }

@ -1,18 +1,15 @@
// @ts-check // @ts-check
import fs from 'node:fs'; import fs from 'node:fs';
const base = '../../site/content/examples/'; const BASE = '../../site/content/examples/';
/** /**
* @returns {import('./types').ExamplesData} * @returns {import('./types').ExamplesData}
*/ */
export function get_examples_data() { export function get_examples_data(base = BASE) {
const examples = []; const examples = [];
for (const subdir of fs.readdirSync(base)) { for (const subdir of fs.readdirSync(base)) {
// Exclude embeds
if (subdir.endsWith('99-embeds')) continue;
const section = { const section = {
title: '', // Initialise with empty title: '', // Initialise with empty
slug: subdir.split('-').slice(1).join('-'), slug: subdir.split('-').slice(1).join('-'),
@ -42,7 +39,7 @@ export function get_examples_data() {
.readdirSync(example_base_dir) .readdirSync(example_base_dir)
.filter((file) => !file.endsWith('meta.json'))) { .filter((file) => !file.endsWith('meta.json'))) {
files.push({ files.push({
filename: file, name: file,
type: file.split('.').at(-1), type: file.split('.').at(-1),
content: fs.readFileSync(`${example_base_dir}/${file}`, 'utf-8'), content: fs.readFileSync(`${example_base_dir}/${file}`, 'utf-8'),
}); });

@ -6,8 +6,8 @@ export type ExamplesData = {
slug: string; slug: string;
files: { files: {
content: string; content: string;
type: 'svelte' | 'js'; type: string;
filename: string; name: string;
}[]; }[];
}[]; }[];
}[]; }[];

@ -0,0 +1,24 @@
// @ts-check
import fs from 'node:fs';
import { extract_frontmatter } from '../markdown';
const base = '../../site/content/faq';
/**
* @returns {import('./types').FAQData}
*/
export function get_faq_data() {
const faqs = [];
for (const file of fs.readdirSync(base)) {
const { metadata, body } = extract_frontmatter(fs.readFileSync(`${base}/${file}`, 'utf-8'));
faqs.push({
title: metadata.question, // Initialise with empty
slug: file.split('-').slice(1).join('-').replace('.md', ''),
content: body,
});
}
return faqs;
}

@ -0,0 +1,87 @@
// @ts-check
import { createShikiHighlighter } from 'shiki-twoslash';
import { transform } from '../markdown';
const languages = {
bash: 'bash',
env: 'bash',
html: 'svelte',
svelte: 'svelte',
sv: 'svelte',
js: 'javascript',
css: 'css',
diff: 'diff',
ts: 'typescript',
'': '',
};
/**
* @param {import('./types').FAQData} faq_data
*/
export async function get_parsed_faq(faq_data) {
const highlighter = await createShikiHighlighter({ theme: 'css-variables' });
return Promise.all(
faq_data.map(async ({ content, slug, title }) => {
return {
title,
slug,
content: transform(content, {
/**
* @param {string} html
*/
heading(html) {
const title = html
.replace(/<\/?code>/g, '')
.replace(/&quot;/g, '"')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>');
return title;
},
code: (source, language) => {
let html = '';
source = 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, '*/');
html = highlighter.codeToHtml(source, { lang: languages[language] });
html = html
.replace(
/^(\s+)<span class="token comment">([\s\S]+?)<\/span>\n/gm,
(match, intro_whitespace, content) => {
// we use some CSS trickery to make comments break onto multiple lines while preserving indentation
const lines = (intro_whitespace + content + '').split('\n');
return lines
.map((line) => {
const match = /^(\s*)(.*)/.exec(line);
const indent = (match?.[1] ?? '').replace(/\t/g, ' ').length;
return `<span class="token comment wrapped" style="--indent: ${indent}ch">${
line ?? ''
}</span>`;
})
.join('');
}
)
.replace(/\/\*…\*\//g, '…');
return html;
},
codespan: (text) => '<code>' + text + '</code>',
}),
};
})
);
}

@ -0,0 +1,5 @@
export type FAQData = {
title: string;
slug: string;
content: string;
}[];

@ -2,12 +2,12 @@
import fs from 'node:fs'; import fs from 'node:fs';
import { extract_frontmatter } from '../markdown/index.js'; import { extract_frontmatter } from '../markdown/index.js';
const base = '../../site/content/tutorial/'; const BASE = '../../site/content/tutorial/';
/** /**
* @returns {import('./types').TutorialData} * @returns {import('./types').TutorialData}
*/ */
export function get_tutorial_data() { export function get_tutorial_data(base = BASE) {
const tutorials = []; const tutorials = [];
for (const subdir of fs.readdirSync(base)) { for (const subdir of fs.readdirSync(base)) {

@ -2,6 +2,6 @@ import * as session from '$lib/db/session';
export async function load({ request }) { export async function load({ request }) {
return { return {
user: await session.from_cookie(request.headers.get('cookie')) user: session.from_cookie(request.headers.get('cookie')),
}; };
} }

@ -1,15 +1,19 @@
import { error, json } from '@sveltejs/kit';
import { dev } from '$app/environment'; import { dev } from '$app/environment';
import * as session from '$lib/db/session';
import { client } from '$lib/db/client'; import { client } from '$lib/db/client';
import * as gist from '$lib/db/gist'; import * as gist from '$lib/db/gist';
import { PUBLIC_API_BASE } from '$env/static/public'; import { get_example } from '$lib/server/examples';
import { get_examples_list } from '$lib/server/examples/get-examples';
import { error, json } from '@sveltejs/kit';
import examples_data from '$lib/generated/examples-data.js';
export const prerender = 'auto';
const UUID_REGEX = /^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$/; const UUID_REGEX = /^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$/;
/** @type {Set<string>} */ /** @type {Set<string>} */
let examples; let examples;
/** @param {import('$lib/server/examples/types').ExamplesData[number]['examples'][number]['files'][number][]} files */
function munge(files) { function munge(files) {
return files return files
.map((file) => { .map((file) => {
@ -18,6 +22,7 @@ function munge(files) {
let type = file.name.slice(dot + 1); let type = file.name.slice(dot + 1);
if (type === 'html') type = 'svelte'; if (type === 'html') type = 'svelte';
// @ts-expect-error what is file.source? by @PuruVJ
return { name, type, source: file.source ?? file.content ?? '' }; return { name, type, source: file.source ?? file.content ?? '' };
}) })
.sort((a, b) => { .sort((a, b) => {
@ -31,33 +36,25 @@ function munge(files) {
} }
export async function GET({ params }) { export async function GET({ params }) {
if (!examples) { // Currently, these pages(that are in examples/) are prerendered. To avoid making any FS requests,
const res = await fetch(`${PUBLIC_API_BASE}/docs/svelte/examples`); // We prerender examples pages during build time. That means, when something like `/repl/hello-world.json`
examples = new Set( // is accessed, this function won't be run at all, as it will be served from the filesystem
(await res.json())
.map((category) => category.examples)
.flat()
.map((example) => example.slug)
);
}
if (examples.has(params.id)) {
const res = await fetch(`${PUBLIC_API_BASE}/docs/svelte/examples/${params.id}`);
if (!res.ok) { examples = new Set(
return new Response(await res.json(), { get_examples_list(examples_data)
status: res.status, .map((category) => category.examples)
headers: { 'Content-Type': 'application/json' }, .flat()
}); .map((example) => example.slug)
} );
const example = await res.json(); if (examples.has(params.id)) {
const example = get_example(examples_data, params.id);
return json({ return json({
id: params.id, id: params.id,
name: example.name, name: example.title,
owner: null, owner: null,
relaxed: example.relaxed, // TODO is this right? relaxed: false, // TODO is this right? EDIT: It was example.relaxed before, which no example return to my knowledge. By @PuruVJ
components: munge(example.files), components: munge(example.files),
}); });
} }
@ -65,7 +62,16 @@ export async function GET({ params }) {
if (dev && !client) { if (dev && !client) {
// in dev with no local Supabase configured, proxy to production // in dev with no local Supabase configured, proxy to production
// this lets us at least load saved REPLs // this lets us at least load saved REPLs
return await fetch(`https://svelte.dev/repl/${params.id}.json`); const res = await fetch(`https://svelte.dev/repl/${params.id}.json`);
// returning the response directly results in a bizarre
// content encoding error, so we create a new one
return new Response(await res.text(), {
status: res.status,
headers: {
'content-type': 'application/json',
},
});
} }
if (!UUID_REGEX.test(params.id)) { if (!UUID_REGEX.test(params.id)) {
@ -83,17 +89,7 @@ export async function GET({ params }) {
name: app.name, name: app.name,
owner: app.userid, owner: app.userid,
relaxed: false, relaxed: false,
// @ts-expect-error app.files has a `source` property
components: munge(app.files), components: munge(app.files),
}); });
} }
// TODO reimplement as an action
export async function PUT({ params, request }) {
const user = await session.from_cookie(request.headers.get('cookie'));
if (!user) throw error(401, 'Unauthorized');
const body = await request.json();
await gist.update(user, params.id, body);
return new Response(undefined, { status: 204 });
}

@ -11,6 +11,6 @@ export async function load({ fetch, params, url }) {
return { return {
gist, gist,
version: url.searchParams.get('version') || '3' version: url.searchParams.get('version') || '3',
}; };
} }

@ -106,7 +106,7 @@
// ~> Any missing files are considered deleted! // ~> Any missing files are considered deleted!
const { components } = repl.toJSON(); const { components } = repl.toJSON();
const r = await fetch(`/repl/${gist.id}.json`, { const r = await fetch(`/repl/save/${gist.id}.json`, {
method: 'PUT', method: 'PUT',
credentials: 'include', credentials: 'include',
headers: { headers: {

@ -2,7 +2,6 @@
import { browser } from '$app/environment'; import { browser } from '$app/environment';
import ReplWidget from '$lib/components/ReplWidget.svelte'; import ReplWidget from '$lib/components/ReplWidget.svelte';
/** @type {import('./$types').PageData} */
export let data; export let data;
</script> </script>

@ -0,0 +1,14 @@
import * as gist from '$lib/db/gist';
import * as session from '$lib/db/session';
import { error } from '@sveltejs/kit';
// TODO reimplement as an action
export async function PUT({ params, request }) {
const user = await session.from_cookie(request.headers.get('cookie'));
if (!user) throw error(401, 'Unauthorized');
const body = await request.json();
await gist.update(user, params.id, body);
return new Response(undefined, { status: 204 });
}

@ -15,36 +15,38 @@
</svelte:head> </svelte:head>
<Shell nav_visible={$page.url.pathname !== '/repl/embed'}> <Shell nav_visible={$page.url.pathname !== '/repl/embed'}>
<Nav logo="/svelte-logo.svg"> {#if $page.url.pathname !== '/repl/embed'}
<svelte:fragment slot="nav-center"> <Nav logo="/svelte-logo.svg">
{#if $page.url.pathname !== '/search'} <svelte:fragment slot="nav-center">
<li><Search /></li> {#if $page.url.pathname !== '/search'}
{/if} <li><Search /></li>
</svelte:fragment> {/if}
</svelte:fragment>
<svelte:fragment slot="nav-right"> <svelte:fragment slot="nav-right">
<NavItem href="/tutorial">Tutorial</NavItem> <NavItem href="/tutorial">Tutorial</NavItem>
<NavItem href="/docs/introduction">Docs</NavItem> <NavItem href="/docs/introduction">Docs</NavItem>
<NavItem href="/examples">Examples</NavItem> <NavItem href="/examples">Examples</NavItem>
<NavItem href="/repl">REPL</NavItem> <NavItem href="/repl">REPL</NavItem>
<NavItem href="/blog">Blog</NavItem> <NavItem href="/blog">Blog</NavItem>
<NavItem href="/faq">FAQ</NavItem> <NavItem href="/faq">FAQ</NavItem>
<Separator /> <Separator />
<NavItem external="https://kit.svelte.dev">SvelteKit</NavItem> <NavItem external="https://kit.svelte.dev">SvelteKit</NavItem>
<NavItem external="/chat" title="Discord Chat"> <NavItem external="/chat" title="Discord Chat">
<span slot="small">Discord</span> <span slot="small">Discord</span>
<Icon name="message-square" /> <Icon name="message-square" />
</NavItem> </NavItem>
<NavItem external="https://github.com/sveltejs/svelte" title="GitHub Repo"> <NavItem external="https://github.com/sveltejs/svelte" title="GitHub Repo">
<span slot="small">GitHub</span> <span slot="small">GitHub</span>
<Icon name="github" /> <Icon name="github" />
</NavItem> </NavItem>
</svelte:fragment> </svelte:fragment>
</Nav> </Nav>
{/if}
<slot /> <slot />
</Shell> </Shell>

@ -1,11 +1,11 @@
<script> <script>
import { Blurb } from '@sveltejs/site-kit/components'; import { Blurb } from '@sveltejs/site-kit/components';
import Supporters from './_components/Supporters/index.svelte'; import Balls from './svelte-balls.png?w=640;1280;2560;3840&format=avif;webp;png&picture';
import Demo from './_components/Demo.svelte';
import Hero from './_components/Hero.svelte'; import Hero from './_components/Hero.svelte';
import Image from './_components/Image.svelte'; import Image from './_components/Image.svelte';
import Demo from './_components/Demo.svelte'; import Supporters from './_components/Supporters/index.svelte';
import WhosUsingSvelte from './_components/WhosUsingSvelte/index.svelte'; import WhosUsingSvelte from './_components/WhosUsingSvelte/index.svelte';
import Balls from './svelte-balls.png?w=640;1280;2560;3840&format=avif;webp;png&picture';
</script> </script>
<svelte:head> <svelte:head>

@ -48,10 +48,12 @@
<a href="/examples">more <span class="large-show">&nbsp;examples</span> &rarr;</a> <a href="/examples">more <span class="large-show">&nbsp;examples</span> &rarr;</a>
</div> </div>
<Example id={selected.id} /> {#if selected}
<Example id={selected?.id} />
{/if}
</div> </div>
<p class="description">{@html selected.description}</p> <p class="description">{@html selected?.description}</p>
</Section> </Section>
<style> <style>

@ -16,7 +16,7 @@
let repl; let repl;
const clone = (file) => ({ const clone = (file) => ({
name: file.filename.replace(/.\w+$/, ''), name: file.name.replace(/.\w+$/, ''),
type: file.type, type: file.type,
source: file.content, source: file.content,
}); });

@ -14,31 +14,33 @@
<ul class="examples-toc"> <ul class="examples-toc">
{#each sections as section} {#each sections as section}
<li> {#if section.title !== undefined}
<span class="section-title">{section.title}</span> <li>
<span class="section-title">{section.title}</span>
{#each section.examples as example}
<div class="row" class:active={example.slug === active_section} class:loading={isLoading}> {#each section.examples as example}
<a <div class="row" class:active={example.slug === active_section} class:loading={isLoading}>
href="/examples/{example.slug}" <a
class="row" href="/examples/{example.slug}"
class:active={example.slug === active_section} class="row"
class:loading={isLoading} class:active={example.slug === active_section}
> class:loading={isLoading}
<img >
class="thumbnail" <img
alt="{example.title} thumbnail" class="thumbnail"
src="/examples/thumbnails/{example.slug}.jpg" alt="{example.title} thumbnail"
/> src="/examples/thumbnails/{example.slug}.jpg"
/>
<span>{example.title}</span>
</a> <span>{example.title}</span>
{#if example.slug === active_section} </a>
<a bind:this={active_el} href="/repl/{example.slug}" class="repl-link">REPL</a> {#if example.slug === active_section}
{/if} <a bind:this={active_el} href="/repl/{example.slug}" class="repl-link">REPL</a>
</div> {/if}
{/each} </div>
</li> {/each}
</li>
{/if}
{/each} {/each}
</ul> </ul>

@ -0,0 +1,8 @@
// @ts-check
import examples_data from '$lib/generated/examples-data.js';
import { get_examples_list } from '$lib/server/examples/get-examples';
import { json } from '@sveltejs/kit';
export const GET = () => {
return json(get_examples_list(examples_data));
};

@ -0,0 +1,17 @@
import examples_data from '$lib/generated/examples-data.js';
import { get_example } from '$lib/server/examples';
import { get_examples_list } from '$lib/server/examples/get-examples';
import { error, json } from '@sveltejs/kit';
export const GET = ({ params }) => {
const examples = new Set(
get_examples_list(examples_data)
.map((category) => category.examples)
.flat()
.map((example) => example.slug)
);
if (!examples.has(params.slug)) throw error(404, 'Example not found');
return json(get_example(examples_data, params.slug));
};

@ -1,12 +0,0 @@
import { PUBLIC_API_BASE } from '$env/static/public';
/** @type {import('./$types').PageLoad} */
export async function load({ fetch, setHeaders }) {
const faqs = await fetch(`${PUBLIC_API_BASE}/docs/svelte/faq?content`).then((r) => r.json());
setHeaders({
'cache-control': 'public, max-age=60'
});
return { faqs };
}

@ -0,0 +1,8 @@
import { get_parsed_faq } from '$lib/server/faq';
import { get_faq_data } from '$lib/server/faq/get-faq';
export const prerender = true;
export async function load() {
return { faqs: get_parsed_faq(get_faq_data()) };
}

@ -1,7 +1,6 @@
<script> <script>
import '@sveltejs/site-kit/styles/code.css'; import '@sveltejs/site-kit/styles/code.css';
/** @type {import('./$types').PageData} */
export let data; export let data;
</script> </script>
@ -85,11 +84,7 @@
@media (min-width: 768px) { @media (min-width: 768px) {
.faqs :global(.anchor:focus), .faqs :global(.anchor:focus),
.faqs :global(h2):hover :global(.anchor), .faqs :global(:where(h2, h3, h4, h5, h6)):hover :global(.anchor) {
.faqs :global(h3):hover :global(.anchor),
.faqs :global(h4):hover :global(.anchor),
.faqs :global(h5):hover :global(.anchor),
.faqs :global(h6):hover :global(.anchor) {
opacity: 1; opacity: 1;
} }

@ -1,5 +1,5 @@
import { get_parsed_tutorial } from '$lib/server/tutorial'; import { get_parsed_tutorial } from '$lib/server/tutorial';
import { get_tutorial_data, get_tutorial_list } from '$lib/server/tutorial/get-tutorial-data'; import { get_tutorial_data, get_tutorial_list } from '$lib/server/tutorial/get-tutorial';
import { error } from '@sveltejs/kit'; import { error } from '@sveltejs/kit';
export const prerender = true; export const prerender = true;

@ -153,16 +153,6 @@
{/if} {/if}
</div> </div>
<!-- HACK to prerender -->
<p style="display: none;">
{#each data.tutorials_list as { tutorials }}
{#each tutorials as { slug }}
<!-- svelte-ignore a11y-missing-content -->
<a href="/tutorial/{slug}" />
{/each}
{/each}
</p>
<style> <style>
.tutorial-outer { .tutorial-outer {
position: relative; position: relative;

@ -1,5 +1,7 @@
// @ts-check // @ts-check
import adapter from '@sveltejs/adapter-auto'; import adapter from '@sveltejs/adapter-vercel';
import { get_examples_data, get_examples_list } from './src/lib/server/examples/get-examples.js';
import { get_tutorial_data, get_tutorial_list } from './src/lib/server/tutorial/get-tutorial.js';
/** @type {import('@sveltejs/kit').Config} */ /** @type {import('@sveltejs/kit').Config} */
export default { export default {
@ -8,6 +10,24 @@ export default {
prerender: { prerender: {
// TODO: REMOVE // TODO: REMOVE
handleMissingId: 'ignore', handleMissingId: 'ignore',
// TODO use route entries instead, once https://github.com/sveltejs/kit/pull/9571 is merged
entries: ['*', ...repl_json_entries(), ...tutorial_entries()],
}, },
}, },
}; };
function repl_json_entries() {
return get_examples_list(
get_examples_data(new URL('../../site/content/examples', import.meta.url).pathname)
).flatMap(({ examples }) =>
examples.map(({ slug }) => /** @type {(`/${string}`)} */ (`/repl/${slug}.json`))
);
}
function tutorial_entries() {
return get_tutorial_list(
get_tutorial_data(new URL('../../site/content/tutorial', import.meta.url).pathname)
).flatMap(({ tutorials }) =>
tutorials.map(({ slug }) => /** @type {(`/${string}`)} */ (`/tutorial/${slug}`))
);
}

Loading…
Cancel
Save