diff --git a/apps/svelte.dev/README.md b/apps/svelte.dev/README.md index 601d924e0e..2d86130372 100644 --- a/apps/svelte.dev/README.md +++ b/apps/svelte.dev/README.md @@ -1,38 +1,25 @@ -# create-svelte +# svelte.dev -Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte). +This is the app behind [svelte.dev](https://svelte.dev), the official Svelte site. -## Creating a project +## Development -If you're seeing this, you've probably already done this step. Congrats! +### Tutorial -```bash -# create a new project in the current directory -npx sv create +The tutorial consists of two technically different parts: The Svelte tutorial and the SvelteKit tutorial. The SvelteKit tutorial uses [WebContainers](https://webcontainers.io/) under the hood in order to boot up a Node runtime in the browser. The Svelte tutorial uses Rollup in a web worker - it does not use WebContainers because a simple web worker is both faster and more reliable (there are known issues with iOS mobile). -# create a new project in my-app -npx sv create my-app -``` - -## Developing - -Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: - -```bash -npm run dev +WebContainers require [cross-origin isolation](https://webcontainers.io/guides/quickstart#cross-origin-isolation), which means the document needs to have these headers: -# or start the server and open the app in a new browser tab -npm run dev -- --open +``` +Cross-Origin-Embedder-Policy: require-corp +Cross-Origin-Opener-Policy: same-origin ``` -## Building - -To create a production version of your app: +Because we're doing soft navigation between pages, these headers need to be set for all responses, not just the ones from `/tutorial`. -```bash -npm run build -``` +The result of setting these headers is that the site can no longer embed URLs from other sites (like images from another domain) without those domains either having a `cross-origin-resource-policy: cross-origin` header (which most don't) or us adding the `crossorigin="anonymous"` attribute to the elements that load those URLs. -You can preview the production build with `npm run preview`. +When writing content for the tutorial, you need to be aware of the differences of loading content: -> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. +- When using root-relative paths, for a SvelteKit exercise the 'root' is the `static` directory inside the exercise itself, but for a Svelte exercise it is the root of the app so assets should do inside `apps/svelte.dev/static/tutorial`. +- When importing relative assets in a Svelte exercise, Rollup inlines them into the bundle as base64 diff --git a/apps/svelte.dev/content/blog/2019-01-31-svelte-on-the-changelog.md b/apps/svelte.dev/content/blog/2019-01-31-svelte-on-the-changelog.md index ca5805ac6d..6f965e050c 100644 --- a/apps/svelte.dev/content/blog/2019-01-31-svelte-on-the-changelog.md +++ b/apps/svelte.dev/content/blog/2019-01-31-svelte-on-the-changelog.md @@ -18,4 +18,4 @@ Unless you hang out in our [Discord server](https://svelte.dev/chat) or follow [ On the podcast [Adam](https://twitter.com/adamstac), [Jerod](https://twitter.com/jerodsanto) and I talk about some of the changes and why we're making them. You can listen here or on the [podcast page](https://changelog.com/podcast/332). -

The Changelog 332: A UI framework without the framework – Listen on Changelog.com

+

The Changelog 332: A UI framework without the framework – Listen on Changelog.com

diff --git a/apps/svelte.dev/content/blog/2019-04-22-svelte-3-rethinking-reactivity.md b/apps/svelte.dev/content/blog/2019-04-22-svelte-3-rethinking-reactivity.md index a4dd7b2b3b..d358b662ed 100644 --- a/apps/svelte.dev/content/blog/2019-04-22-svelte-3-rethinking-reactivity.md +++ b/apps/svelte.dev/content/blog/2019-04-22-svelte-3-rethinking-reactivity.md @@ -24,7 +24,7 @@ To make that possible we first needed to rethink the concept at the heart of mod
- +
'Rethinking Reactivity' from You Gotta Love Frontend Code Camp
diff --git a/apps/svelte.dev/content/blog/2020-11-05-whats-the-deal-with-sveltekit.md b/apps/svelte.dev/content/blog/2020-11-05-whats-the-deal-with-sveltekit.md index a89181fc6a..31c6cfdc0d 100644 --- a/apps/svelte.dev/content/blog/2020-11-05-whats-the-deal-with-sveltekit.md +++ b/apps/svelte.dev/content/blog/2020-11-05-whats-the-deal-with-sveltekit.md @@ -16,7 +16,7 @@ This was slightly tongue-in-cheek — as the talk explains, it's really more of
- +
'Futuristic Web Development' from Svelte Summit
diff --git a/apps/svelte.dev/content/blog/2022-12-14-announcing-sveltekit-1.0.md b/apps/svelte.dev/content/blog/2022-12-14-announcing-sveltekit-1.0.md index 0b8f081ef7..c008f32614 100644 --- a/apps/svelte.dev/content/blog/2022-12-14-announcing-sveltekit-1.0.md +++ b/apps/svelte.dev/content/blog/2022-12-14-announcing-sveltekit-1.0.md @@ -14,7 +14,7 @@ To get started, run `npm create svelte@latest`, and visit the [docs](https://kit
- +
Svelte Radio Live: the Christmas special
diff --git a/apps/svelte.dev/content/blog/2023-03-09-zero-config-type-safety.md b/apps/svelte.dev/content/blog/2023-03-09-zero-config-type-safety.md index f7e53d122c..3c0aad7059 100644 --- a/apps/svelte.dev/content/blog/2023-03-09-zero-config-type-safety.md +++ b/apps/svelte.dev/content/blog/2023-03-09-zero-config-type-safety.md @@ -11,7 +11,7 @@ But what if we didn't even need the annotations? Since `load` and `data` are par As of today, yes: it can. - + If you're using VSCode, just upgrade the Svelte extension to the latest version, and you'll never have to annotate your `load` functions or `data` props again. Extensions for other editors can also use this feature, as long as they support the Language Server Protocol and TypeScript plugins. It even works with the latest version of our CLI diagnostics tool `svelte-check`! diff --git a/apps/svelte.dev/content/blog/2023-08-31-view-transitions.md b/apps/svelte.dev/content/blog/2023-08-31-view-transitions.md index 3366955ea4..7a9c7b13f2 100644 --- a/apps/svelte.dev/content/blog/2023-08-31-view-transitions.md +++ b/apps/svelte.dev/content/blog/2023-08-31-view-transitions.md @@ -74,7 +74,7 @@ onNavigate((navigation) => { With that, every navigation that occurs will trigger a view transition. You can already see this in action – by default, the browser will crossfade between the old and new pages. - +
How the code works @@ -161,7 +161,7 @@ header { Now, the header will not transition in and out on navigation, but the rest of the page will. - +
Fixing the types @@ -213,7 +213,7 @@ li[aria-current='page']::before { By adding that single line, the indicator will now smoothly slide to its new position instead of jumping. - + (It might be easy to miss the difference – look at the small moving triangle indicator at the top of the screen!) diff --git a/apps/svelte.dev/content/tutorial/+assets/src/app.html b/apps/svelte.dev/content/tutorial/+assets/src/app.html index 06134bbe94..8aff02247e 100644 --- a/apps/svelte.dev/content/tutorial/+assets/src/app.html +++ b/apps/svelte.dev/content/tutorial/+assets/src/app.html @@ -7,258 +7,8 @@ %sveltekit.head% - + +
%sveltekit.body%
diff --git a/apps/svelte.dev/content/tutorial/+assets/static/shared.css b/apps/svelte.dev/content/tutorial/+assets/static/shared.css new file mode 100644 index 0000000000..36ef044cac --- /dev/null +++ b/apps/svelte.dev/content/tutorial/+assets/static/shared.css @@ -0,0 +1,251 @@ +/* this file is duplicated in the site's static assets. If you change it here, you should also change it there. */ +html { + --bg-1: hsl(0, 0%, 100%); + --bg-2: hsl(206, 20%, 90%); + --bg-3: hsl(206, 20%, 80%); + --fg-1: hsl(0, 0%, 13%); + --fg-2: hsl(0, 0%, 20%); + --fg-2: hsl(0, 0%, 30%); + --link: hsl(208, 77%, 47%); + --link-hover: hsl(208, 77%, 55%); + --link-active: hsl(208, 77%, 40%); + + &.dark { + --bg-1: hsl(0, 0%, 18%); + --bg-2: hsl(0, 0%, 30%); + --bg-3: hsl(0, 0%, 40%); + --fg-1: hsl(0, 0%, 90%); + --fg-2: hsl(0, 0%, 70%); + --fg-3: hsl(0, 0%, 60%); + --link: hsl(206, 96%, 72%); + --link-hover: hsl(206, 96%, 78%); + --link-active: hsl(206, 96%, 64%); + } +} + +body { + --border-radius: 4px; + --font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, + 'Open Sans', 'Helvetica Neue', sans-serif; + --font-mono: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', + monospace; + background: var(--bg-1); + color: var(--fg-1); + font-family: var(--font); + line-height: 1.5; + margin: 1rem; + height: calc(100vh - 2rem); +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-weight: normal; + font-variant-numeric: tabular-nums; + line-height: 1.1; +} + +:is(h1, h2, h3, h4, h5, h6, p) { + margin: 1rem 0.1rem; +} + +label { + margin: 0.5rem 0.1rem; +} + +:is(h1, h2, h3, h4, h5, h6, p, label):first-child { + margin-top: 0; +} + +:is(h1, h2, h3, h4, h5, h6, p, label):last-child { + margin-bottom: 0; +} + +a { + color: var(--link); +} + +a:hover { + color: var(--link-hover); +} + +a:active { + color: var(--link-active); +} + +label { + display: flex; + gap: 0.5rem; + align-items: center; +} + +label input { + margin: 0; +} + +button, +input, +select { + font-family: inherit; + font-size: inherit; +} + +button { + background: var(--link); + color: var(--bg-1); + padding: 0.5rem 1rem; + border: none; + border-radius: var(--border-radius); +} + +button:hover { + background: var(--link-hover); +} + +button:active { + background: var(--link-active); +} + +:is(button, button:hover, button:active):disabled { + background: var(--link); + filter: grayscale(1); + opacity: 0.4; +} + +input, +textarea, +select { + padding: 0.5rem; + border: 1px solid var(--bg-2); + border-radius: var(--border-radius); + box-sizing: border-box; +} + +input, +textarea { + background: var(--bg-1); + color: inherit; +} + +select:not([multiple]) { + background: var(--bg-2); +} + +textarea { + font-family: var(--font-mono); + font-size: 0.9rem; +} + +form { + display: flex; + flex-direction: column; + gap: 1rem; + align-items: baseline; +} + +ul:has(li):has(form) { + list-style: none; + padding: 0; +} + +li form { + flex-direction: row; + gap: 0.5rem; + margin: 0.5rem 0; +} + +nav { + position: relative; + display: flex; + gap: 1em; + padding: 1em; + background: var(--bg-2); + z-index: 2; + margin: 0 0 1em 0; + border-radius: var(--border-radius); +} + +nav a { + text-decoration: none; +} + +nav a[aria-current='true'] { + border-bottom: 2px solid; +} + +ul:has(form) { + list-style: none; + padding: 0; +} + +progress { + margin: 0.5rem 0; +} + +progress:first-child { + margin-top: 0; +} + +progress:lsat-child { + margin-bottom: 0; +} + +.error { + color: red; +} + +code { + background: var(--bg-2); + font-family: var(--font-mono); + font-size: 0.9em; + padding: 0.15rem 0.3rem; + border-radius: var(--border-radius); +} + +ul.todos { + padding: 0; +} + +ul.todos li:not(:has(> form)), +ul.todos li form { + position: relative; + display: flex; + align-items: center; + padding: 0.5em 0.5em 0.5em 1em; + margin: 0 0 0.5em 0; + gap: 0.5em; + border-radius: 5px; + user-select: none; + background: var(--bg-1); + filter: drop-shadow(2px 3px 6px rgba(0, 0, 0, 0.1)); + transition: + filter 0.2s, + opacity 0.2s; +} + +ul.todos .done { + filter: none; + opacity: 0.4; +} + +ul.todos button { + border: none; + background-color: transparent; + background-repeat: no-repeat; + background-position: 50% 50%; + background-size: 1rem 1rem; + cursor: pointer; + width: 3em; + height: 3em; + margin: -0.5em -0.5em -0.5em 0; + aspect-ratio: 1; + opacity: 0.5; + transition: opacity 0.2s; +} + +ul.todos button:hover { + opacity: 1; +} diff --git a/apps/svelte.dev/content/tutorial/01-svelte/01-introduction/03-dynamic-attributes/+assets/app-a/src/lib/App.svelte b/apps/svelte.dev/content/tutorial/01-svelte/01-introduction/03-dynamic-attributes/+assets/app-a/src/lib/App.svelte index a8fa70b044..94db39ab72 100644 --- a/apps/svelte.dev/content/tutorial/01-svelte/01-introduction/03-dynamic-attributes/+assets/app-a/src/lib/App.svelte +++ b/apps/svelte.dev/content/tutorial/01-svelte/01-introduction/03-dynamic-attributes/+assets/app-a/src/lib/App.svelte @@ -1,5 +1,5 @@ diff --git a/apps/svelte.dev/content/tutorial/01-svelte/01-introduction/03-dynamic-attributes/+assets/app-a/static/image.gif b/apps/svelte.dev/content/tutorial/01-svelte/01-introduction/03-dynamic-attributes/+assets/app-a/static/image.gif deleted file mode 100644 index 3ce9c237d5..0000000000 Binary files a/apps/svelte.dev/content/tutorial/01-svelte/01-introduction/03-dynamic-attributes/+assets/app-a/static/image.gif and /dev/null differ diff --git a/apps/svelte.dev/content/tutorial/01-svelte/01-introduction/03-dynamic-attributes/+assets/app-b/src/lib/App.svelte b/apps/svelte.dev/content/tutorial/01-svelte/01-introduction/03-dynamic-attributes/+assets/app-b/src/lib/App.svelte index 684601da80..1ef6d8c326 100644 --- a/apps/svelte.dev/content/tutorial/01-svelte/01-introduction/03-dynamic-attributes/+assets/app-b/src/lib/App.svelte +++ b/apps/svelte.dev/content/tutorial/01-svelte/01-introduction/03-dynamic-attributes/+assets/app-b/src/lib/App.svelte @@ -1,5 +1,5 @@ diff --git a/apps/svelte.dev/content/tutorial/02-advanced-svelte/04-actions/02-adding-parameters-to-actions/+assets/app-a/src/lib/App.svelte b/apps/svelte.dev/content/tutorial/02-advanced-svelte/04-actions/02-adding-parameters-to-actions/+assets/app-a/src/lib/App.svelte index a15a5222d8..50b3a24c24 100644 --- a/apps/svelte.dev/content/tutorial/02-advanced-svelte/04-actions/02-adding-parameters-to-actions/+assets/app-a/src/lib/App.svelte +++ b/apps/svelte.dev/content/tutorial/02-advanced-svelte/04-actions/02-adding-parameters-to-actions/+assets/app-a/src/lib/App.svelte @@ -1,7 +1,5 @@ - +

Welcome to my site!

diff --git a/apps/svelte.dev/content/tutorial/02-advanced-svelte/10-module-context/01-sharing-code/+assets/app-a/src/lib/AudioPlayer.svelte b/apps/svelte.dev/content/tutorial/02-advanced-svelte/10-module-context/01-sharing-code/+assets/app-a/src/lib/AudioPlayer.svelte index 85be476d6c..2cc409342f 100644 --- a/apps/svelte.dev/content/tutorial/02-advanced-svelte/10-module-context/01-sharing-code/+assets/app-a/src/lib/AudioPlayer.svelte +++ b/apps/svelte.dev/content/tutorial/02-advanced-svelte/10-module-context/01-sharing-code/+assets/app-a/src/lib/AudioPlayer.svelte @@ -144,4 +144,4 @@ height: 100%; background: var(--bg-3); } - \ No newline at end of file + diff --git a/apps/svelte.dev/content/tutorial/02-advanced-svelte/10-module-context/01-sharing-code/+assets/app-a/src/lib/sound-off.svg b/apps/svelte.dev/content/tutorial/02-advanced-svelte/10-module-context/01-sharing-code/+assets/app-a/src/lib/sound-off.svg deleted file mode 100644 index 3caeb9744c..0000000000 --- a/apps/svelte.dev/content/tutorial/02-advanced-svelte/10-module-context/01-sharing-code/+assets/app-a/src/lib/sound-off.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/svelte.dev/content/tutorial/02-advanced-svelte/10-module-context/01-sharing-code/+assets/app-a/src/lib/sound-on.svg b/apps/svelte.dev/content/tutorial/02-advanced-svelte/10-module-context/01-sharing-code/+assets/app-a/src/lib/sound-on.svg deleted file mode 100644 index f7c59bcdac..0000000000 --- a/apps/svelte.dev/content/tutorial/02-advanced-svelte/10-module-context/01-sharing-code/+assets/app-a/src/lib/sound-on.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/svelte.dev/content/tutorial/02-advanced-svelte/10-module-context/01-sharing-code/+assets/app-a/src/lib/tracks.js b/apps/svelte.dev/content/tutorial/02-advanced-svelte/10-module-context/01-sharing-code/+assets/app-a/src/lib/tracks.js index a05933cb3c..da3c750792 100644 --- a/apps/svelte.dev/content/tutorial/02-advanced-svelte/10-module-context/01-sharing-code/+assets/app-a/src/lib/tracks.js +++ b/apps/svelte.dev/content/tutorial/02-advanced-svelte/10-module-context/01-sharing-code/+assets/app-a/src/lib/tracks.js @@ -1,28 +1,28 @@ export const tracks = [ { // https://musopen.org/music/9862-the-blue-danube-op-314/ - src: 'https://learn.svelte.dev/assets/media/music/strauss.mp3', + src: 'https://sveltejs.github.io/assets/music/strauss.mp3', title: 'The Blue Danube Waltz', artist: 'Johann Strauss' }, { // https://musopen.org/music/43775-the-planets-op-32/ - src: 'https://learn.svelte.dev/assets/media/music/holst.mp3', + src: 'https://sveltejs.github.io/assets/music/holst.mp3', title: 'Mars, the Bringer of War', artist: 'Gustav Holst' }, { // https://musopen.org/music/8010-3-gymnopedies/ - src: 'https://learn.svelte.dev/assets/media/music/satie.mp3', + src: 'https://sveltejs.github.io/assets/music/satie.mp3', title: 'Gymnopédie no. 1', artist: 'Erik Satie' }, { // https://musopen.org/music/43683-requiem-in-d-minor-k-626/ - src: 'https://learn.svelte.dev/assets/media/music/mozart.mp3', + src: 'https://sveltejs.github.io/assets/music/mozart.mp3', title: 'Requiem in D minor, K. 626 - III. Sequence - Lacrymosa', artist: 'Wolfgang Amadeus Mozart' } diff --git a/apps/svelte.dev/content/tutorial/02-advanced-svelte/10-module-context/01-sharing-code/+assets/app-b/src/lib/AudioPlayer.svelte b/apps/svelte.dev/content/tutorial/02-advanced-svelte/10-module-context/01-sharing-code/+assets/app-b/src/lib/AudioPlayer.svelte index 554037180c..dea02efa15 100644 --- a/apps/svelte.dev/content/tutorial/02-advanced-svelte/10-module-context/01-sharing-code/+assets/app-b/src/lib/AudioPlayer.svelte +++ b/apps/svelte.dev/content/tutorial/02-advanced-svelte/10-module-context/01-sharing-code/+assets/app-b/src/lib/AudioPlayer.svelte @@ -156,4 +156,4 @@ height: 100%; background: var(--bg-3); } - \ No newline at end of file + diff --git a/apps/svelte.dev/content/tutorial/02-advanced-svelte/12-next-steps/01-congratulations/+assets/app-a/src/lib/App.svelte b/apps/svelte.dev/content/tutorial/02-advanced-svelte/12-next-steps/01-congratulations/+assets/app-a/src/lib/App.svelte index d82d0e171e..1f8071e63b 100644 --- a/apps/svelte.dev/content/tutorial/02-advanced-svelte/12-next-steps/01-congratulations/+assets/app-a/src/lib/App.svelte +++ b/apps/svelte.dev/content/tutorial/02-advanced-svelte/12-next-steps/01-congratulations/+assets/app-a/src/lib/App.svelte @@ -43,13 +43,13 @@ {/each} diff --git a/apps/svelte.dev/middleware.js b/apps/svelte.dev/middleware.js deleted file mode 100644 index 7e22e4a750..0000000000 --- a/apps/svelte.dev/middleware.js +++ /dev/null @@ -1,15 +0,0 @@ -export const config = { - // TODO probably needs to be adjusted once tutorial content is here - matcher: ['/((?!assets/).*)'] -}; - -export default function middleware(_request, _event) { - const response = new Response(); - - response.headers.set('cross-origin-opener-policy', 'same-origin'); - response.headers.set('cross-origin-embedder-policy', 'require-corp'); - response.headers.set('cross-origin-resource-policy', 'cross-origin'); - response.headers.set('x-middleware-next', '1'); - - return response; -} diff --git a/apps/svelte.dev/scripts/create-tutorial-zip/common/static/tutorial/dark-theme.css b/apps/svelte.dev/scripts/create-tutorial-zip/common/static/tutorial/dark-theme.css deleted file mode 100644 index 831107c4f5..0000000000 --- a/apps/svelte.dev/scripts/create-tutorial-zip/common/static/tutorial/dark-theme.css +++ /dev/null @@ -1,5 +0,0 @@ -body { - background-color: #333; - color: whitesmoke; - transition: all 0.5s; -} diff --git a/apps/svelte.dev/src/hooks.server.js b/apps/svelte.dev/src/hooks.server.js index f43968601c..f897d286ae 100644 --- a/apps/svelte.dev/src/hooks.server.js +++ b/apps/svelte.dev/src/hooks.server.js @@ -36,11 +36,5 @@ export async function handle({ event, resolve }) { preload: ({ type }) => type === 'js' || type === 'css' || type === 'font' }); - if (event.url.pathname.startsWith('/tutorial')) { - response.headers.set('cross-origin-opener-policy', 'same-origin'); - response.headers.set('cross-origin-embedder-policy', 'require-corp'); - response.headers.set('cross-origin-resource-policy', 'cross-origin'); - } - return response; } diff --git a/apps/svelte.dev/src/lib/tutorial/adapters/rollup/index.svelte.ts b/apps/svelte.dev/src/lib/tutorial/adapters/rollup/index.svelte.ts new file mode 100644 index 0000000000..3c56d9a145 --- /dev/null +++ b/apps/svelte.dev/src/lib/tutorial/adapters/rollup/index.svelte.ts @@ -0,0 +1,93 @@ +import type { Adapter, FileStub, Stub, Warning } from '$lib/tutorial'; +import Bundler from '@sveltejs/repl/bundler'; +// @ts-ignore package exports don't have types +import * as yootils from 'yootils'; + +/** Rollup bundler singleton */ +let bundler: Bundler; + +export const state = new (class RollupState { + progress = $state.raw({ value: 0, text: 'initialising' }); + bundle = $state.raw(null); + warnings = $state.raw>({}); +})(); + +/** + * @returns {Promise} + */ +export async function create(): Promise { + bundler?.destroy(); + + state.progress = { value: 0, text: 'loading files' }; + + let done = false; + + bundler = new Bundler({ + packages_url: 'https://unpkg.com', + svelte_url: `https://unpkg.com/svelte@next`, // TODO remove @next once 5.0 is released + // svelte_url: `${browser ? location.origin : ''}/svelte`, // TODO think about bringing back main-build for Playground? + onstatus(val) { + if (!done && val === null) { + done = true; + state.progress = { value: 1, text: 'ready' }; + } + } + }); + + state.progress = { value: 0.5, text: 'loading svelte compiler' }; + + /** Paths and contents of the currently loaded file stubs */ + let current_stubs = stubs_to_map([]); + + async function compile() { + const result = await bundler.bundle( + [...current_stubs.values()] + // TODO we can probably remove all the SvelteKit specific stuff from the tutorial content once this settles down + .filter((f): f is FileStub => f.name.startsWith('/src/lib/') && f.type === 'file') + .map((f) => ({ + name: f.name.slice(9).split('.').slice(0, -1).join('.'), + source: f.contents, + type: f.name.split('.').pop() ?? 'svelte' + })) + ); + state.bundle = result; + + const _warnings: Record = {}; + for (const warning of result?.warnings ?? []) { + const file = '/src/lib/' + warning.filename; + _warnings[file] = _warnings[file] || []; + _warnings[file].push(warning); + } + state.warnings = _warnings; + } + + const q = yootils.queue(1); + + return { + reset: (stubs) => { + return q.add(async () => { + current_stubs = stubs_to_map(stubs, current_stubs); + + await compile(); + + return false; + }); + }, + update: (file) => { + return q.add(async () => { + current_stubs.set(file.name, file); + + await compile(); + + return false; + }); + } + }; +} + +function stubs_to_map(files: Stub[], map = new Map()) { + for (const file of files) { + map.set(file.name, file); + } + return map; +} diff --git a/apps/svelte.dev/src/lib/tutorial/adapters/webcontainer/index.js b/apps/svelte.dev/src/lib/tutorial/adapters/webcontainer/index.svelte.ts similarity index 67% rename from apps/svelte.dev/src/lib/tutorial/adapters/webcontainer/index.js rename to apps/svelte.dev/src/lib/tutorial/adapters/webcontainer/index.svelte.ts index 0f86cb9112..4c2f3f2755 100644 --- a/apps/svelte.dev/src/lib/tutorial/adapters/webcontainer/index.js +++ b/apps/svelte.dev/src/lib/tutorial/adapters/webcontainer/index.svelte.ts @@ -1,41 +1,41 @@ -import { WebContainer } from '@webcontainer/api'; +import { WebContainer, type DirectoryNode, type FileSystemTree } from '@webcontainer/api'; import base64 from 'base64-js'; import AnsiToHtml from 'ansi-to-html'; // @ts-ignore package exports don't have types import * as yootils from 'yootils'; -import { get_depth } from '../../../utils/path'; -import { escape_html } from '../../../utils/escape'; +import { get_depth } from '../../../utils/path.js'; +import { escape_html } from '../../../utils/escape.js'; import { ready } from '../common/index.js'; +import type { Adapter, FileStub, Stub, Warning } from '$lib/tutorial'; const converter = new AnsiToHtml({ fg: 'var(--sk-text-3)' }); -/** @type {import('@webcontainer/api').WebContainer} Web container singleton */ -let vm; +/** Web container singleton */ +let vm: WebContainer; -/** - * @param {import('svelte/store').Writable} base - * @param {import('svelte/store').Writable} error - * @param {import('svelte/store').Writable<{ value: number, text: string }>} progress - * @param {import('svelte/store').Writable} logs - * @param {import('svelte/store').Writable>} warnings - * @returns {Promise} - */ -export async function create(base, error, progress, logs, warnings) { - progress.set({ value: 0, text: 'loading files' }); +export const state = new (class WCState { + progress = $state.raw({ value: 0, text: 'initialising' }); + base = $state.raw(null); + error = $state.raw(null); + logs = $state.raw([]); + warnings = $state.raw>({}); +})(); + +export async function create(): Promise { + state.progress = { value: 0, text: 'loading files' }; const q = yootils.queue(1); - /** @type {Map>} */ - const q_per_file = new Map(); + const q_per_file = new Map>(); /** Paths and contents of the currently loaded file stubs */ let current_stubs = stubs_to_map([]); - progress.set({ value: 1 / 5, text: 'booting webcontainer' }); + state.progress = { value: 1 / 5, text: 'booting webcontainer' }; vm = await WebContainer.boot(); - progress.set({ value: 2 / 5, text: 'writing virtual files' }); + state.progress = { value: 2 / 5, text: 'writing virtual files' }; const common = await ready; await vm.mount({ 'common.zip': { @@ -46,17 +46,12 @@ export async function create(base, error, progress, logs, warnings) { } }); - /** @type {Record} */ - let $warnings; - warnings.subscribe((value) => ($warnings = value)); - - /** @type {any} */ - let timeout; + let warnings: Record = {}; + let timeout: any; - /** @param {number} msec */ - function schedule_to_update_warning(msec) { + function schedule_to_update_warning(msec: number) { clearTimeout(timeout); - timeout = setTimeout(() => warnings.set($warnings), msec); + timeout = setTimeout(() => (state.warnings = { ...warnings }), msec); } const log_stream = () => @@ -64,14 +59,14 @@ export async function create(base, error, progress, logs, warnings) { write(chunk) { if (chunk === '\x1B[1;1H') { // clear screen - logs.set([]); + state.logs = []; } else if (chunk?.startsWith('svelte:warnings:')) { - /** @type {import('$lib/tutorial').Warning} */ - const warn = JSON.parse(chunk.slice(16)); - const current = $warnings[warn.filename]; + const warn: Warning = JSON.parse(chunk.slice(16)); + const filename = warn.filename.startsWith('/') ? warn.filename : '/' + warn.filename; + const current = warnings[filename]; if (!current) { - $warnings[warn.filename] = [warn]; + warnings[filename] = [warn]; // the exact same warning may be given multiple times in a row } else if (!current.some((s) => s.code === warn.code && s.pos === warn.pos)) { current.push(warn); @@ -80,12 +75,12 @@ export async function create(base, error, progress, logs, warnings) { schedule_to_update_warning(100); } else { const log = converter.toHtml(escape_html(chunk)).replace(/\n/g, '
'); - logs.update(($logs) => [...$logs, log]); + state.logs = [...state.logs, log]; } } }); - progress.set({ value: 3 / 5, text: 'unzipping files' }); + state.progress = { value: 3 / 5, text: 'unzipping files' }; const unzip = await vm.spawn('node', ['unzip.cjs']); unzip.output.pipeTo(log_stream()); const code = await unzip.exit; @@ -97,11 +92,11 @@ export async function create(base, error, progress, logs, warnings) { await vm.spawn('chmod', ['a+x', 'node_modules/vite/bin/vite.js']); vm.on('server-ready', (_port, url) => { - base.set(url); + state.base = url; }); vm.on('error', ({ message }) => { - error.set(new Error(message)); + state.error = new Error(message); }); let launched = false; @@ -110,7 +105,7 @@ export async function create(base, error, progress, logs, warnings) { if (launched) return; launched = true; - progress.set({ value: 4 / 5, text: 'starting dev server' }); + state.progress = { value: 4 / 5, text: 'starting dev server' }; await new Promise(async (fulfil, reject) => { const error_unsub = vm.on('error', (error) => { @@ -120,7 +115,7 @@ export async function create(base, error, progress, logs, warnings) { const ready_unsub = vm.on('server-ready', (_port, base) => { ready_unsub(); - progress.set({ value: 5 / 5, text: 'ready' }); + state.progress = { value: 5 / 5, text: 'ready' }; fulfil(base); // this will be the last thing that happens if everything goes well }); @@ -143,8 +138,7 @@ export async function create(base, error, progress, logs, warnings) { return { reset: (stubs) => { return q.add(async () => { - /** @type {import('$lib/tutorial').Stub[]} */ - const to_write = []; + const to_write: Stub[] = []; const force_delete = []; @@ -157,9 +151,7 @@ export async function create(base, error, progress, logs, warnings) { continue; } - const current = /** @type {import('$lib/tutorial').FileStub} */ ( - current_stubs.get(stub.name) - ); + const current = current_stubs.get(stub.name) as FileStub; if (current?.contents !== stub.contents) { to_write.push(stub); @@ -181,14 +173,14 @@ export async function create(base, error, progress, logs, warnings) { // initialize warnings of written files to_write - .filter((stub) => stub.type === 'file' && $warnings[stub.name]) - .forEach((stub) => ($warnings[stub.name] = [])); + .filter((stub) => stub.type === 'file' && warnings[stub.name]) + .forEach((stub) => (warnings[stub.name] = [])); // remove warnings of deleted files to_delete - .filter((stubname) => $warnings[stubname]) - .forEach((stubname) => delete $warnings[stubname]); + .filter((stubname) => warnings[stubname]) + .forEach((stubname) => delete warnings[stubname]); - warnings.set($warnings); + state.warnings = { ...warnings }; current_stubs = stubs_to_map(stubs); @@ -228,38 +220,36 @@ export async function create(base, error, progress, logs, warnings) { q_per_file.set(file.name, (queue = [file])); return q.add(async () => { - /** @type {import('@webcontainer/api').FileSystemTree} */ - const root = {}; + const root: FileSystemTree = {}; let tree = root; const path = file.name.split('/').slice(1); - const basename = /** @type {string} */ (path.pop()); + const basename = path.pop()!; for (const part of path) { if (!tree[part]) { - /** @type {import('@webcontainer/api').FileSystemTree} */ - const directory = {}; + const directory: FileSystemTree = {}; tree[part] = { directory }; } - tree = /** @type {import('@webcontainer/api').DirectoryNode} */ (tree[part]).directory; + tree = (tree[part] as DirectoryNode).directory; } const will_restart = is_config(file); while (queue && queue.length > 0) { // if the file is updated many times rapidly, get the most recently updated one - const file = /** @type {import('$lib/tutorial').FileStub} */ (queue.pop()); + const file = queue.pop()!; queue.length = 0; tree[basename] = to_file(file); // initialize warnings of this file - $warnings[file.name] = []; + warnings[file.name] = []; schedule_to_update_warning(100); await vm.mount(root); @@ -282,17 +272,11 @@ export async function create(base, error, progress, logs, warnings) { }; } -/** - * @param {import('$lib/tutorial').Stub} file - */ -function is_config(file) { +function is_config(file: Stub) { return file.type === 'file' && is_config_path(file.name); } -/** - * @param {string} path - */ -function is_config_path(path) { +function is_config_path(path: string) { return ['/vite.config.js', '/svelte.config.js', '/.env'].includes(path); } @@ -315,13 +299,8 @@ function wait_for_restart_vite() { }); } -/** - * @param {import('$lib/tutorial').Stub[]} stubs - * @returns {import('@webcontainer/api').FileSystemTree} - */ -function convert_stubs_to_tree(stubs, depth = 1) { - /** @type {import('@webcontainer/api').FileSystemTree} */ - const tree = {}; +function convert_stubs_to_tree(stubs: Stub[], depth = 1) { + const tree: FileSystemTree = {}; for (const stub of stubs) { if (get_depth(stub.name) === depth) { @@ -340,8 +319,7 @@ function convert_stubs_to_tree(stubs, depth = 1) { return tree; } -/** @param {import('$lib/tutorial').FileStub} file */ -function to_file(file) { +function to_file(file: FileStub) { // special case if (file.name === '/src/app.html' || file.name === '/src/error.html') { const contents = file.contents + ''; @@ -358,11 +336,7 @@ function to_file(file) { }; } -/** - * @param {import('$lib/tutorial').Stub[]} files - * @returns {Map} - */ -function stubs_to_map(files, map = new Map()) { +function stubs_to_map(files: Stub[], map = new Map()) { for (const file of files) { map.set(file.name, file); } diff --git a/apps/svelte.dev/src/routes/_home/Video.svelte b/apps/svelte.dev/src/routes/_home/Video.svelte index 13fa62007a..de5a87b12b 100644 --- a/apps/svelte.dev/src/routes/_home/Video.svelte +++ b/apps/svelte.dev/src/routes/_home/Video.svelte @@ -88,6 +88,7 @@