From f61e2c3551cb0af1c650815d5f6ab921f945823b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 13 Dec 2022 20:14:11 -0500 Subject: [PATCH 001/380] use millennial lowercase more consistently (#134) --- .../tutorial/02-sveltekit/02-routing/01-pages/README.md | 8 ++++---- .../02-routing/01-pages/app-a/src/routes/+page.svelte | 8 ++++---- .../01-pages/app-b/src/routes/about/+page.svelte | 8 ++++---- .../tutorial/02-sveltekit/02-routing/02-layouts/README.md | 4 ++-- .../02-routing/02-layouts/app-b/src/routes/+layout.svelte | 4 ++-- .../02-routing/02-layouts/app-b/src/routes/+page.svelte | 4 ++-- .../02-layouts/app-b/src/routes/about/+page.svelte | 4 ++-- .../02-routing/03-params/app-a/src/routes/+layout.svelte | 4 ++-- .../02-routing/03-params/app-a/src/routes/+page.svelte | 2 +- .../01-page-data/app-a/src/routes/+layout.svelte | 4 ++-- .../01-page-data/app-a/src/routes/+page.svelte | 2 +- .../app-a/src/routes/+layout.svelte | 4 ++-- 12 files changed, 28 insertions(+), 28 deletions(-) diff --git a/content/tutorial/02-sveltekit/02-routing/01-pages/README.md b/content/tutorial/02-sveltekit/02-routing/01-pages/README.md index d7f921b47..beb6cb2c9 100644 --- a/content/tutorial/02-sveltekit/02-routing/01-pages/README.md +++ b/content/tutorial/02-sveltekit/02-routing/01-pages/README.md @@ -13,12 +13,12 @@ Let's add a second route, `src/routes/about/+page.svelte`, which maps to `/about ```svelte /// file: src/routes/about/+page.svelte -

About

-

This is the about page.

+

about

+

this is the about page.

``` We can now navigate between `/` and `/about`. diff --git a/content/tutorial/02-sveltekit/02-routing/01-pages/app-a/src/routes/+page.svelte b/content/tutorial/02-sveltekit/02-routing/01-pages/app-a/src/routes/+page.svelte index 60f6c57b7..4599db5f5 100644 --- a/content/tutorial/02-sveltekit/02-routing/01-pages/app-a/src/routes/+page.svelte +++ b/content/tutorial/02-sveltekit/02-routing/01-pages/app-a/src/routes/+page.svelte @@ -1,7 +1,7 @@ -

Home

-

This is the home page.

+

home

+

this is the home page.

diff --git a/content/tutorial/02-sveltekit/02-routing/01-pages/app-b/src/routes/about/+page.svelte b/content/tutorial/02-sveltekit/02-routing/01-pages/app-b/src/routes/about/+page.svelte index 43ce718a3..1a14400ed 100644 --- a/content/tutorial/02-sveltekit/02-routing/01-pages/app-b/src/routes/about/+page.svelte +++ b/content/tutorial/02-sveltekit/02-routing/01-pages/app-b/src/routes/about/+page.svelte @@ -1,7 +1,7 @@ -

About

-

This is the about page.

+

about

+

this is the about page.

diff --git a/content/tutorial/02-sveltekit/02-routing/02-layouts/README.md b/content/tutorial/02-sveltekit/02-routing/02-layouts/README.md index fa885e194..4e7eb973b 100644 --- a/content/tutorial/02-sveltekit/02-routing/02-layouts/README.md +++ b/content/tutorial/02-sveltekit/02-routing/02-layouts/README.md @@ -19,8 +19,8 @@ src/routes/ ```svelte /// file: src/routes/about/+page.svelte diff --git a/content/tutorial/02-sveltekit/02-routing/02-layouts/app-b/src/routes/+layout.svelte b/content/tutorial/02-sveltekit/02-routing/02-layouts/app-b/src/routes/+layout.svelte index 8a38e3a9a..439e52c54 100644 --- a/content/tutorial/02-sveltekit/02-routing/02-layouts/app-b/src/routes/+layout.svelte +++ b/content/tutorial/02-sveltekit/02-routing/02-layouts/app-b/src/routes/+layout.svelte @@ -1,6 +1,6 @@ diff --git a/content/tutorial/02-sveltekit/02-routing/02-layouts/app-b/src/routes/+page.svelte b/content/tutorial/02-sveltekit/02-routing/02-layouts/app-b/src/routes/+page.svelte index d48a73513..9f05291c7 100644 --- a/content/tutorial/02-sveltekit/02-routing/02-layouts/app-b/src/routes/+page.svelte +++ b/content/tutorial/02-sveltekit/02-routing/02-layouts/app-b/src/routes/+page.svelte @@ -1,2 +1,2 @@ -

Home

-

This is the home page.

+

home

+

this is the home page.

diff --git a/content/tutorial/02-sveltekit/02-routing/02-layouts/app-b/src/routes/about/+page.svelte b/content/tutorial/02-sveltekit/02-routing/02-layouts/app-b/src/routes/about/+page.svelte index f4692177f..34c2ee423 100644 --- a/content/tutorial/02-sveltekit/02-routing/02-layouts/app-b/src/routes/about/+page.svelte +++ b/content/tutorial/02-sveltekit/02-routing/02-layouts/app-b/src/routes/about/+page.svelte @@ -1,2 +1,2 @@ -

About

-

This is the about page.

+

about

+

this is the about page.

diff --git a/content/tutorial/02-sveltekit/02-routing/03-params/app-a/src/routes/+layout.svelte b/content/tutorial/02-sveltekit/02-routing/03-params/app-a/src/routes/+layout.svelte index 140c2cffc..307ba0a2c 100644 --- a/content/tutorial/02-sveltekit/02-routing/03-params/app-a/src/routes/+layout.svelte +++ b/content/tutorial/02-sveltekit/02-routing/03-params/app-a/src/routes/+layout.svelte @@ -1,6 +1,6 @@ diff --git a/content/tutorial/02-sveltekit/02-routing/03-params/app-a/src/routes/+page.svelte b/content/tutorial/02-sveltekit/02-routing/03-params/app-a/src/routes/+page.svelte index e5324dcb6..0a88878c3 100644 --- a/content/tutorial/02-sveltekit/02-routing/03-params/app-a/src/routes/+page.svelte +++ b/content/tutorial/02-sveltekit/02-routing/03-params/app-a/src/routes/+page.svelte @@ -1 +1 @@ -

Home Page

+

home

diff --git a/content/tutorial/02-sveltekit/03-loading-data/01-page-data/app-a/src/routes/+layout.svelte b/content/tutorial/02-sveltekit/03-loading-data/01-page-data/app-a/src/routes/+layout.svelte index 140c2cffc..307ba0a2c 100644 --- a/content/tutorial/02-sveltekit/03-loading-data/01-page-data/app-a/src/routes/+layout.svelte +++ b/content/tutorial/02-sveltekit/03-loading-data/01-page-data/app-a/src/routes/+layout.svelte @@ -1,6 +1,6 @@ diff --git a/content/tutorial/02-sveltekit/03-loading-data/01-page-data/app-a/src/routes/+page.svelte b/content/tutorial/02-sveltekit/03-loading-data/01-page-data/app-a/src/routes/+page.svelte index e5324dcb6..0a88878c3 100644 --- a/content/tutorial/02-sveltekit/03-loading-data/01-page-data/app-a/src/routes/+page.svelte +++ b/content/tutorial/02-sveltekit/03-loading-data/01-page-data/app-a/src/routes/+page.svelte @@ -1 +1 @@ -

Home Page

+

home

diff --git a/content/tutorial/02-sveltekit/06-errors-and-redirects/xx-custom-error-messages/app-a/src/routes/+layout.svelte b/content/tutorial/02-sveltekit/06-errors-and-redirects/xx-custom-error-messages/app-a/src/routes/+layout.svelte index 8a38e3a9a..439e52c54 100644 --- a/content/tutorial/02-sveltekit/06-errors-and-redirects/xx-custom-error-messages/app-a/src/routes/+layout.svelte +++ b/content/tutorial/02-sveltekit/06-errors-and-redirects/xx-custom-error-messages/app-a/src/routes/+layout.svelte @@ -1,6 +1,6 @@ From b9f155a317d8f446b876532fa3fb71816a4b4b6e Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 13 Dec 2022 20:25:35 -0500 Subject: [PATCH 002/380] squelch warning --- src/lib/components/SplitPane.svelte | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/lib/components/SplitPane.svelte b/src/lib/components/SplitPane.svelte index 3b8915530..7bc6d6faf 100644 --- a/src/lib/components/SplitPane.svelte +++ b/src/lib/components/SplitPane.svelte @@ -118,11 +118,17 @@ window.addEventListener('touchend', ontouchend, false); }; - node.addEventListener('touchstart', touchdown, false); + node.addEventListener('touchstart', touchdown, { + capture: true, + passive: false + }); return { destroy() { - node.removeEventListener('touchstart', touchdown, false); + node.removeEventListener('touchstart', touchdown, { + capture: true, + passive: false + }); } }; } From 198057add3a13a9f530ad738c4e66c77c72d0a0f Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 13 Dec 2022 21:53:48 -0500 Subject: [PATCH 003/380] provide immediate UI feedback on solve/reset --- src/routes/tutorial/[slug]/+page.svelte | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/routes/tutorial/[slug]/+page.svelte b/src/routes/tutorial/[slug]/+page.svelte index b8f12a776..d0df6c5b4 100644 --- a/src/routes/tutorial/[slug]/+page.svelte +++ b/src/routes/tutorial/[slug]/+page.svelte @@ -187,7 +187,6 @@ */ async function load_files(stubs) { adapter = await reset_adapter(stubs); - update_complete_states(stubs); } /** @@ -369,6 +368,8 @@ $files = Object.values(completed ? data.exercise.a : $endstate); if (completed) { reset_complete_states(); + } else { + update_complete_states($files); } load_files($files); }} From 50a2ec88f93744d2e0c0f63e5e53be6006c86301 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 13 Dec 2022 22:55:09 -0500 Subject: [PATCH 004/380] delete old directories (#136) * delete old directories * remove logging --- src/lib/client/adapters/webcontainer/index.js | 47 ++++++++++++------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/src/lib/client/adapters/webcontainer/index.js b/src/lib/client/adapters/webcontainer/index.js index 2cb043e64..bbb0abba4 100644 --- a/src/lib/client/adapters/webcontainer/index.js +++ b/src/lib/client/adapters/webcontainer/index.js @@ -33,8 +33,10 @@ async function _create(stubs) { * @type {Promise | undefined} */ let running; - /** @type {Map} Paths and contents of the currently loaded file stubs */ - let current = stubs_to_map(stubs); + + /** Paths and contents of the currently loaded file stubs */ + let current_stubs = stubs_to_map(stubs); + /** @type {boolean} Track whether there was an error from vite dev server */ let vite_error = false; @@ -150,22 +152,35 @@ async function _create(stubs) { running = new Promise((fulfil) => (resolve = fulfil)); vite_error = false; - const old = current; - const new_stubs = stubs.filter( - (stub) => stub.type !== 'file' || old.get(stub.name) !== stub.contents - ); - current = stubs_to_map(stubs); + let added_new_file = false; + + /** @type {import('$lib/types').Stub[]} */ + const to_write = []; for (const stub of stubs) { if (stub.type === 'file') { - old.delete(stub.name); + const current = /** @type {import('$lib/types').FileStub} */ (current_stubs.get(stub.name)); + + if (current?.contents !== stub.contents) { + to_write.push(stub); + } + + if (!current) added_new_file = true; + } else { + // always add directories, otherwise convert_stubs_to_tree will fail + to_write.push(stub); } + + current_stubs.delete(stub.name); } + const to_delete = Array.from(current_stubs.keys()); + current_stubs = stubs_to_map(stubs); + // For some reason, server-ready is fired again when the vite dev server is restarted. // We need to wait for it to finish before we can continue, else we might // request files from Vite before it's ready, leading to a timeout. - const will_restart = will_restart_vite_dev_server(new_stubs); + const will_restart = will_restart_vite_dev_server(to_write); const promise = will_restart ? new Promise((fulfil, reject) => { const error_unsub = vm.on('error', (error) => { @@ -188,11 +203,11 @@ async function _create(stubs) { }) : Promise.resolve(); - for (const file of old.keys()) { + for (const file of to_delete) { await vm.fs.rm(file, { force: true, recursive: true }); } - await vm.loadFiles(convert_stubs_to_tree(new_stubs)); + await vm.loadFiles(convert_stubs_to_tree(to_write)); await promise; await new Promise((f) => setTimeout(f, 200)); // wait for chokidar @@ -200,7 +215,7 @@ async function _create(stubs) { // Also trigger a reload of the iframe in case new files were added / old ones deleted, // because that can result in a broken UI state - return will_restart || vite_error || !!old.size || !!new_stubs.length; + return will_restart || vite_error || to_delete.length > 0 || added_new_file; } /** @@ -237,7 +252,7 @@ async function _create(stubs) { await vm.loadFiles(root); - stubs_to_map(stubs, current); + stubs_to_map(stubs, current_stubs); await new Promise((f) => setTimeout(f, 200)); // wait for chokidar @@ -319,13 +334,11 @@ function to_file(stub) { /** * @param {import('$lib/types').Stub[]} stubs - * @returns {Map} + * @returns {Map} */ function stubs_to_map(stubs, map = new Map()) { for (const stub of stubs) { - if (stub.type === 'file') { - map.set(stub.name, stub.contents); - } + map.set(stub.name, stub); } return map; } From 9b61e5ee682c3c0048e5f22799f1ba2ca7002069 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 13 Dec 2022 22:59:24 -0500 Subject: [PATCH 005/380] reduce indirection --- src/routes/tutorial/[slug]/+page.svelte | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/routes/tutorial/[slug]/+page.svelte b/src/routes/tutorial/[slug]/+page.svelte index d0df6c5b4..a1872d0a4 100644 --- a/src/routes/tutorial/[slug]/+page.svelte +++ b/src/routes/tutorial/[slug]/+page.svelte @@ -171,7 +171,7 @@ reset_complete_states(); - await load_files($files); + await reset_adapter($files); loading = false; initial = false; @@ -182,13 +182,6 @@ } } - /** - * @param {import('$lib/types').Stub[]} stubs - */ - async function load_files(stubs) { - adapter = await reset_adapter(stubs); - } - /** * @param {CustomEvent} event */ @@ -356,8 +349,8 @@ readonly={mobile} constraints={editing_constraints} {selected} - on:change={async () => { - await load_files($files); // TODO make this automatic? + on:change={() => { + reset_adapter($files); }} /> @@ -371,7 +364,7 @@ } else { update_complete_states($files); } - load_files($files); + reset_adapter($files); }} > {#if completed && Object.keys(data.exercise.b).length > 0} From 491f150c02d5d6b04f212d7d34f8b9c4f891701f Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 14 Dec 2022 00:34:45 -0500 Subject: [PATCH 006/380] show progress indicator (#137) * show progress indicator * tidy up * get rid of waterfall * nicer error page --- src/lib/client/adapters/webcontainer/index.js | 323 ++++++++---------- src/routes/tutorial/[slug]/+page.svelte | 11 +- src/routes/tutorial/[slug]/Loading.svelte | 172 +++++----- 3 files changed, 249 insertions(+), 257 deletions(-) diff --git a/src/lib/client/adapters/webcontainer/index.js b/src/lib/client/adapters/webcontainer/index.js index bbb0abba4..532dafed1 100644 --- a/src/lib/client/adapters/webcontainer/index.js +++ b/src/lib/client/adapters/webcontainer/index.js @@ -5,28 +5,38 @@ import { ready } from '../common/index.js'; /** @type {import('@webcontainer/api').WebContainer} Web container singleton */ let vm; -/** @type {Promise | undefined} */ -let instance; + +/** @type {Promise} */ +let promise; /** * @param {import('$lib/types').Stub[]} stubs + * @param {(progress: number, status: string) => void} callback * @returns {Promise} */ -export async function create(stubs) { - if (!instance) { - instance = _create(stubs); +export async function create(stubs, callback) { + if (/safari/i.test(navigator.userAgent) && !/chrome/i.test(navigator.userAgent)) { + throw new Error('WebContainers are not supported by Safari'); + } + + callback(0, 'loading files'); + + if (!promise) { + promise = _create(stubs, callback); } else { - const adapter = await instance; + const adapter = await promise; await adapter.reset(stubs); } - return instance; + + return promise; } /** * @param {import('$lib/types').Stub[]} stubs + * @param {(progress: number, status: string) => void} callback * @returns {Promise} */ -async function _create(stubs) { +async function _create(stubs, callback) { /** * Keeps track of the latest create/reset to ensure things are not processed in parallel. * (if this turns out to be insufficient, we can use a queue) @@ -40,45 +50,43 @@ async function _create(stubs) { /** @type {boolean} Track whether there was an error from vite dev server */ let vite_error = false; - const tree = convert_stubs_to_tree(stubs); + callback(1 / 6, 'loading webcontainer'); + const WebContainer = await load(); - const common = await ready; - tree['common.zip'] = { - file: { contents: new Uint8Array(common.zipped) } - }; - tree['unzip.cjs'] = { - file: { contents: common.unzip } - }; + callback(2 / 6, 'booting webcontainer'); + vm = await WebContainer.boot(); - if (/safari/i.test(navigator.userAgent) && !/chrome/i.test(navigator.userAgent)) { - throw new Error('WebContainers are not supported by Safari'); - } + callback(3 / 6, 'writing virtual files'); + const common = await ready; + await vm.loadFiles({ + 'common.zip': { + file: { contents: new Uint8Array(common.zipped) } + }, + 'unzip.cjs': { + file: { contents: common.unzip } + }, + ...convert_stubs_to_tree(stubs) + }); - /** @type {string} */ - let base; - - base = await new Promise(async (fulfil, reject) => { - /** @type {any} */ - let timeout; - function reset_timeout() { - clearTimeout(timeout); - timeout = setTimeout(() => { - reject(new Error('Timed out starting WebContainer')); - }, 15000); + callback(4 / 6, 'unzipping files'); + const unzip = await vm.run( + { + command: 'node', + args: ['unzip.cjs'] + }, + { + stderr: (data) => console.error(`[unzip] ${data}`) } + ); + const code = await unzip.onExit; + if (code !== 0) { + throw new Error('Failed to initialize WebContainer'); + } - reset_timeout(); - - // There can only be one instance, else it throws an error - guard against this case - // if there was an error later on or a timeout and the user tries again - if (!vm) { - console.log('loading webcontainer'); - const WebContainer = await load(); - - console.log('booting webcontainer'); - vm = await WebContainer.boot(); - } + await vm.run({ command: 'chmod', args: ['a+x', 'node_modules/vite/bin/vite.js'] }); + callback(5 / 6, 'starting dev server'); + const base = await new Promise(async (fulfil, reject) => { const error_unsub = vm.on('error', (error) => { error_unsub(); reject(new Error(error.message)); @@ -86,43 +94,18 @@ async function _create(stubs) { const ready_unsub = vm.on('server-ready', (port, base) => { ready_unsub(); - console.log(`server ready on port ${port} at ${performance.now()}: ${base}`); + callback(6 / 6, 'ready'); fulfil(base); // this will be the last thing that happens if everything goes well }); - reset_timeout(); - console.log('loading files'); - await vm.loadFiles(tree); - - reset_timeout(); - console.log('unpacking modules'); - const unzip = await vm.run( - { - command: 'node', - args: ['unzip.cjs'] - }, - { - stderr: (data) => console.error(`[unzip] ${data}`) - } - ); - const code = await unzip.onExit; - if (code !== 0) { - reject(new Error('Failed to initialize WebContainer')); - } - - reset_timeout(); - console.log('starting dev server'); - await vm.run({ command: 'chmod', args: ['a+x', 'node_modules/vite/bin/vite.js'] }); await run_dev(); async function run_dev() { const process = await vm.run( { command: 'turbo', args: ['run', 'dev'] }, { - stdout: () => { - if (!base) { - reset_timeout(); - } + stdout: (data) => { + console.log(`[dev] ${data}`); }, stderr: (data) => { vite_error = true; @@ -141,135 +124,123 @@ async function _create(stubs) { } }); - /** - * Deletes old files and adds new ones - * @param {import('$lib/types').Stub[]} stubs - */ - async function reset(stubs) { - await running; - /** @type {Function} */ - let resolve = () => {}; - running = new Promise((fulfil) => (resolve = fulfil)); - vite_error = false; - - let added_new_file = false; - - /** @type {import('$lib/types').Stub[]} */ - const to_write = []; - - for (const stub of stubs) { - if (stub.type === 'file') { - const current = /** @type {import('$lib/types').FileStub} */ (current_stubs.get(stub.name)); + return { + base, + reset: async (stubs) => { + await running; + /** @type {Function} */ + let resolve = () => {}; + running = new Promise((fulfil) => (resolve = fulfil)); + vite_error = false; + + let added_new_file = false; + + /** @type {import('$lib/types').Stub[]} */ + const to_write = []; + + for (const stub of stubs) { + if (stub.type === 'file') { + const current = /** @type {import('$lib/types').FileStub} */ ( + current_stubs.get(stub.name) + ); + + if (current?.contents !== stub.contents) { + to_write.push(stub); + } - if (current?.contents !== stub.contents) { + if (!current) added_new_file = true; + } else { + // always add directories, otherwise convert_stubs_to_tree will fail to_write.push(stub); } - if (!current) added_new_file = true; - } else { - // always add directories, otherwise convert_stubs_to_tree will fail - to_write.push(stub); + current_stubs.delete(stub.name); } - current_stubs.delete(stub.name); - } - - const to_delete = Array.from(current_stubs.keys()); - current_stubs = stubs_to_map(stubs); - - // For some reason, server-ready is fired again when the vite dev server is restarted. - // We need to wait for it to finish before we can continue, else we might - // request files from Vite before it's ready, leading to a timeout. - const will_restart = will_restart_vite_dev_server(to_write); - const promise = will_restart - ? new Promise((fulfil, reject) => { - const error_unsub = vm.on('error', (error) => { - error_unsub(); - resolve(); - reject(new Error(error.message)); - }); - - const ready_unsub = vm.on('server-ready', (port, base) => { - ready_unsub(); - console.log(`server ready on port ${port} at ${performance.now()}: ${base}`); - resolve(); - fulfil(undefined); - }); - - setTimeout(() => { - resolve(); - reject(new Error('Timed out resetting WebContainer')); - }, 10000); - }) - : Promise.resolve(); - - for (const file of to_delete) { - await vm.fs.rm(file, { force: true, recursive: true }); - } + const to_delete = Array.from(current_stubs.keys()); + current_stubs = stubs_to_map(stubs); + + // For some reason, server-ready is fired again when the vite dev server is restarted. + // We need to wait for it to finish before we can continue, else we might + // request files from Vite before it's ready, leading to a timeout. + const will_restart = will_restart_vite_dev_server(to_write); + const promise = will_restart + ? new Promise((fulfil, reject) => { + const error_unsub = vm.on('error', (error) => { + error_unsub(); + resolve(); + reject(new Error(error.message)); + }); + + const ready_unsub = vm.on('server-ready', (port, base) => { + ready_unsub(); + console.log(`server ready on port ${port} at ${performance.now()}: ${base}`); + resolve(); + fulfil(undefined); + }); + + setTimeout(() => { + resolve(); + reject(new Error('Timed out resetting WebContainer')); + }, 10000); + }) + : Promise.resolve(); + + for (const file of to_delete) { + await vm.fs.rm(file, { force: true, recursive: true }); + } - await vm.loadFiles(convert_stubs_to_tree(to_write)); - await promise; - await new Promise((f) => setTimeout(f, 200)); // wait for chokidar + await vm.loadFiles(convert_stubs_to_tree(to_write)); + await promise; + await new Promise((f) => setTimeout(f, 200)); // wait for chokidar - resolve(); + resolve(); - // Also trigger a reload of the iframe in case new files were added / old ones deleted, - // because that can result in a broken UI state - return will_restart || vite_error || to_delete.length > 0 || added_new_file; - } + // Also trigger a reload of the iframe in case new files were added / old ones deleted, + // because that can result in a broken UI state + return will_restart || vite_error || to_delete.length > 0 || added_new_file; + }, + update: async (stubs) => { + await running; - /** - * Loads new files but keeps the old ones - * @param {import('$lib/types').FileStub[]} stubs - */ - async function update(stubs) { - await running; + /** @type {import('@webcontainer/api').FileSystemTree} */ + const root = {}; - /** @type {import('@webcontainer/api').FileSystemTree} */ - const root = {}; + for (const stub of stubs) { + let tree = root; - for (const stub of stubs) { - let tree = root; + const path = stub.name.split('/').slice(1); + const basename = /** @type {string} */ (path.pop()); - const path = stub.name.split('/').slice(1); - const basename = /** @type {string} */ (path.pop()); + for (const part of path) { + if (!tree[part]) { + /** @type {import('@webcontainer/api').FileSystemTree} */ + const directory = {}; - for (const part of path) { - if (!tree[part]) { - /** @type {import('@webcontainer/api').FileSystemTree} */ - const directory = {}; + tree[part] = { + directory + }; + } - tree[part] = { - directory - }; + tree = /** @type {import('@webcontainer/api').DirectoryEntry} */ (tree[part]).directory; } - tree = /** @type {import('@webcontainer/api').DirectoryEntry} */ (tree[part]).directory; + tree[basename] = to_file(stub); } - tree[basename] = to_file(stub); - } - - await vm.loadFiles(root); + await vm.loadFiles(root); - stubs_to_map(stubs, current_stubs); + stubs_to_map(stubs, current_stubs); - await new Promise((f) => setTimeout(f, 200)); // wait for chokidar + await new Promise((f) => setTimeout(f, 200)); // wait for chokidar - return will_restart_vite_dev_server(stubs); - } - - async function destroy() { - vm.teardown(); - // @ts-ignore - vm = null; - } - - return { - base, - reset, - update, - destroy + return will_restart_vite_dev_server(stubs); + }, + destroy: async () => { + vm.teardown(); + // @ts-ignore + vm = null; + } }; } diff --git a/src/routes/tutorial/[slug]/+page.svelte b/src/routes/tutorial/[slug]/+page.svelte index a1872d0a4..b73c99610 100644 --- a/src/routes/tutorial/[slug]/+page.svelte +++ b/src/routes/tutorial/[slug]/+page.svelte @@ -37,6 +37,9 @@ /** @type {Error | null} */ let error = null; + let progress = 0; + let status = 'initialising'; + /** @type {Record} */ let expected = {}; /** @type {Record}*/ @@ -117,7 +120,11 @@ } else { const module = await import('$lib/client/adapters/webcontainer/index.js'); - adapter = await module.create(stubs); + adapter = await module.create(stubs, (p, s) => { + progress = p; + status = s; + }); + set_iframe_src(adapter.base + path); } @@ -414,6 +421,8 @@ { error = null; load_exercise(); diff --git a/src/routes/tutorial/[slug]/Loading.svelte b/src/routes/tutorial/[slug]/Loading.svelte index 2efca6dc1..e40ec0251 100644 --- a/src/routes/tutorial/[slug]/Loading.svelte +++ b/src/routes/tutorial/[slug]/Loading.svelte @@ -1,94 +1,78 @@
{#if error} - {@html get_error_message(error)} - + {#if /safari/i.test(navigator.userAgent) && !/chrome/i.test(navigator.userAgent)} +

This app requires modern web platform features. Please use a browser other than Safari.

+ {:else} + {error.message} +

Yikes!

+ {#if /firefox/i.test(navigator.userAgent)} +

+ We couldn't start the app. Please ensure + + third party cookies + are enabled for this site, and disable Enhanced Tracking Protection. +

+ {:else if /chrome/i.test(navigator.userAgent) && !/edg/i.test(navigator.userAgent)} +

+ We couldn't start the app. Please ensure + + third party cookies + are enabled for this site. +

+ {:else} +

+ We couldn't start the app. Please ensure third party cookies are enabled for this site. +

+ {/if} + {/if} {:else} + + + + + {#if initial} -

initializing... this may take a few seconds

+
+
+
+ {status} {/if} - -
- {#if initial} -
    -
  • Booting WebContainer
  • - {#if $grayscale < 0.65} -
  • Unpacking modules
  • - {/if} - {#if $grayscale < 0.15} -
  • Starting dev server
  • - {/if} -
- {/if} - - - - -
{/if}
From 1ce65469220ce7d66e1933f466ccd949f795a34d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 14 Dec 2022 01:15:08 -0500 Subject: [PATCH 007/380] fix some styles --- .../app-a/src/routes/+layout.svelte | 13 ++++++++++--- content/tutorial/common/src/app.html | 7 ++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/content/tutorial/04-advanced-sveltekit/03-advanced-routing/05-breaking-out-of-layouts/app-a/src/routes/+layout.svelte b/content/tutorial/04-advanced-sveltekit/03-advanced-routing/05-breaking-out-of-layouts/app-a/src/routes/+layout.svelte index ad888314c..a1836f323 100644 --- a/content/tutorial/04-advanced-sveltekit/03-advanced-routing/05-breaking-out-of-layouts/app-a/src/routes/+layout.svelte +++ b/content/tutorial/04-advanced-sveltekit/03-advanced-routing/05-breaking-out-of-layouts/app-a/src/routes/+layout.svelte @@ -15,7 +15,7 @@ border: 1px solid #999; padding: 1em; margin: 1em 0 0 0; - border-radius: 5px; + border-radius: 2px; } :global(.layout::before) { @@ -23,9 +23,16 @@ content: attr(data-name) ' layout'; left: 1em; top: -1em; - background-color: #ff531a; - color: white; + background-color: white; + color: #222; padding: 0.5em; line-height: 1; } + + @media (prefers-color-scheme: dark) { + :global(.layout::before) { + background: #2e2e2e; + color: #e6e6e6; + } + } diff --git a/content/tutorial/common/src/app.html b/content/tutorial/common/src/app.html index ca0eef762..30c510e28 100644 --- a/content/tutorial/common/src/app.html +++ b/content/tutorial/common/src/app.html @@ -31,6 +31,7 @@ background-color: #ffeedd; z-index: 2; margin: 0 0 1em 0; + border-radius: 2px; } button, @@ -60,16 +61,16 @@ @media (prefers-color-scheme: dark) { body { background: hsl(0, 0%, 18%); - color: hsl(0, 0%, 90%); + color: rgb(230, 230, 230); } code { background: hsl(0, 0%, 40%); } a { - color: hsl(204, 100%, 63%); + color: hsl(204, 100%, 83%); } nav { - background-color: #1a0d01; + background-color: #345; } } From ca0b91202b286a9868213f71f56903eb21d352d5 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 14 Dec 2022 01:16:32 -0500 Subject: [PATCH 008/380] preserve URL when resetting (#138) * dont reset path - closes #118 * oops --- src/routes/tutorial/[slug]/+page.svelte | 73 ++++++++++++------------- 1 file changed, 34 insertions(+), 39 deletions(-) diff --git a/src/routes/tutorial/[slug]/+page.svelte b/src/routes/tutorial/[slug]/+page.svelte index b73c99610..e34a1f7ac 100644 --- a/src/routes/tutorial/[slug]/+page.svelte +++ b/src/routes/tutorial/[slug]/+page.svelte @@ -107,7 +107,37 @@ return destroy; }); - afterNavigate(load_exercise); + afterNavigate(async () => { + try { + $files = Object.values(data.exercise.a); + $scope = data.exercise.scope; + + selected.set( + /** @type {import('$lib/types').FileStub} */ ( + $files.find((stub) => stub.name === data.exercise.focus) + ) + ); + + clearTimeout(timeout); + loading = true; + + reset_complete_states(); + + await reset_adapter($files); + + if (adapter && path !== data.exercise.path) { + path = data.exercise.path; + set_iframe_src(adapter.base + path); + } + + loading = false; + initial = false; + } catch (e) { + loading = false; + error = /** @type {Error} */ (e); + console.error(e); + } + }); /** * Loads the adapter initially or resets it. This method can throw. @@ -155,40 +185,14 @@ }, 10000); }); - if (reload_iframe || iframe.src !== adapter.base + data.exercise.path) { + if (reload_iframe) { await new Promise((fulfil) => setTimeout(fulfil, 200)); - set_iframe_src(adapter.base + data.exercise.path); + set_iframe_src(adapter.base + path); } return adapter; } - async function load_exercise() { - try { - $files = Object.values(data.exercise.a); - $scope = data.exercise.scope; - selected.set( - /** @type {import('$lib/types').FileStub} */ ( - $files.find((stub) => stub.name === data.exercise.focus) - ) - ); - - clearTimeout(timeout); - loading = true; - - reset_complete_states(); - - await reset_adapter($files); - - loading = false; - initial = false; - } catch (e) { - loading = false; - error = /** @type {Error} */ (e); - console.error(e); - } - } - /** * @param {CustomEvent} event */ @@ -418,16 +422,7 @@ {/if} {#if loading || error} - { - error = null; - load_exercise(); - }} - /> + {/if}
From a34312963a95172deef864429cbd3f10d60ab089 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Wed, 14 Dec 2022 16:09:43 +0100 Subject: [PATCH 009/380] [docs] rephrase SvelteKit intro --- .../01-concepts/01-introducing-sveltekit/README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/content/tutorial/02-sveltekit/01-concepts/01-introducing-sveltekit/README.md b/content/tutorial/02-sveltekit/01-concepts/01-introducing-sveltekit/README.md index 077f9fdaf..f1f3470f2 100644 --- a/content/tutorial/02-sveltekit/01-concepts/01-introducing-sveltekit/README.md +++ b/content/tutorial/02-sveltekit/01-concepts/01-introducing-sveltekit/README.md @@ -2,9 +2,7 @@ title: What is SvelteKit? --- -So far, we've been working on individual components, or groups of components, in isolation. But to build a complete app, you need more than just components. - -That's where SvelteKit comes in. Whereas Svelte is a _component framework_, SvelteKit is an _app framework_ (or 'metaframework', depending on who you ask) that solves the tricky problems of building something production-ready: +SvelteKit is a framework for building extremely high-performance web apps. Whereas Svelte is a _component framework_, SvelteKit is an _app framework_ (or 'metaframework', depending on who you ask) that solves the tricky problems of building something production-ready: - Routing - Server-side rendering @@ -20,4 +18,4 @@ That's where SvelteKit comes in. Whereas Svelte is a _component framework_, Svel SvelteKit apps are server-rendered by default (like traditional 'multi-page apps' or MPAs) for excellent first load performance and SEO characteristics, but can then transition to client-side navigation (like modern 'single-page apps' or SPAs) to avoid jankily reloading everything (including things like third-party analytics code) when the user navigates. They can run anywhere JavaScript runs, though — as we'll see — your users may not need to run any JavaScript at all. -If that sounds complicated, worry not: you've been using SvelteKit this whole time! +If that sounds complicated, worry not: SvelteKit is the framework that grows with you! Start simple and add new features as they come. This tutorial will go over the core concepts, while the [Advanced SvelteKit](/handle) tutorial teaches you how to tackle more complex use cases. From d52015f902bcb9397b25a5d4434d1fb8491f67a4 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Thu, 15 Dec 2022 13:57:11 +0100 Subject: [PATCH 010/380] [fix] add color-scheme meta tag styles inputs/scrollbars etc correctly in dark mode fixes #142 --- content/tutorial/common/src/app.html | 1 + src/app.html | 1 + 2 files changed, 2 insertions(+) diff --git a/content/tutorial/common/src/app.html b/content/tutorial/common/src/app.html index 30c510e28..b24ae5c3a 100644 --- a/content/tutorial/common/src/app.html +++ b/content/tutorial/common/src/app.html @@ -4,6 +4,7 @@ + %sveltekit.head% diff --git a/src/routes/tutorial/[slug]/chevron.svg b/src/routes/tutorial/[slug]/chevron.svg new file mode 100644 index 000000000..bab0e6cbb --- /dev/null +++ b/src/routes/tutorial/[slug]/chevron.svg @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/routes/tutorial/[slug]/refresh.svg b/src/routes/tutorial/[slug]/refresh.svg index 99308ad90..bcdad913b 100644 --- a/src/routes/tutorial/[slug]/refresh.svg +++ b/src/routes/tutorial/[slug]/refresh.svg @@ -1,4 +1,6 @@ - - - + + + \ No newline at end of file From e350963e5262543098de0e0d374b981dc2ec3ae4 Mon Sep 17 00:00:00 2001 From: tomoam <29677552+tomoam@users.noreply.github.com> Date: Mon, 23 Jan 2023 19:30:10 +0900 Subject: [PATCH 030/380] docs: add filename label (#172) --- .../tutorial/02-sveltekit/07-page-options/04-prerender/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/content/tutorial/02-sveltekit/07-page-options/04-prerender/README.md b/content/tutorial/02-sveltekit/07-page-options/04-prerender/README.md index d9fa996fc..ea6cf68d6 100644 --- a/content/tutorial/02-sveltekit/07-page-options/04-prerender/README.md +++ b/content/tutorial/02-sveltekit/07-page-options/04-prerender/README.md @@ -11,6 +11,7 @@ The tradeoff is that the build process takes longer, and prerendered content can To prerender a page, set `prerender` to `true`: ```js +/// file: src/routes/+page.server.js export const prerender = true; ``` From fe5db26599111714c55b61fca0d4699ac1011618 Mon Sep 17 00:00:00 2001 From: Marcus Nygren Date: Mon, 23 Jan 2023 11:30:59 +0100 Subject: [PATCH 031/380] docs: add missing import (#173) --- .../tutorial/02-sveltekit/04-forms/03-form-validation/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/content/tutorial/02-sveltekit/04-forms/03-form-validation/README.md b/content/tutorial/02-sveltekit/04-forms/03-form-validation/README.md index 432168eca..682fa54f8 100644 --- a/content/tutorial/02-sveltekit/04-forms/03-form-validation/README.md +++ b/content/tutorial/02-sveltekit/04-forms/03-form-validation/README.md @@ -56,6 +56,7 @@ It would be much better to stay on the same page and provide an indication of wh ```js /// file: src/routes/+page.server.js +++import { fail } from '@sveltejs/kit';+++ +import * as db from '$lib/server/database.js'; export function load({ cookies }) {...} From 07a99b2a538a9b2835d573fee3dd3d8016282164 Mon Sep 17 00:00:00 2001 From: tomoam <29677552+tomoam@users.noreply.github.com> Date: Mon, 23 Jan 2023 19:31:35 +0900 Subject: [PATCH 032/380] docs: fix position of single quote (#171) --- .../02-sveltekit/07-page-options/05-trailingslash/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/tutorial/02-sveltekit/07-page-options/05-trailingslash/README.md b/content/tutorial/02-sveltekit/07-page-options/05-trailingslash/README.md index e5375f823..09c137093 100644 --- a/content/tutorial/02-sveltekit/07-page-options/05-trailingslash/README.md +++ b/content/tutorial/02-sveltekit/07-page-options/05-trailingslash/README.md @@ -13,7 +13,7 @@ If you instead want to ensure that a trailing slash is always present, you can s export const trailingSlash = 'always'; ``` -To accommodate both cases (this is not recommended!), use `'ignore`': +To accommodate both cases (this is not recommended!), use `'ignore'`: ```js /// file: src/routes/ignore/+page.server.js From d87ab3065e76f2747d328ab404bdbfcde1afdeec Mon Sep 17 00:00:00 2001 From: tomoam <29677552+tomoam@users.noreply.github.com> Date: Mon, 23 Jan 2023 19:32:46 +0900 Subject: [PATCH 033/380] fix: properly detect selected file (#170) --- src/lib/components/filetree/File.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/components/filetree/File.svelte b/src/lib/components/filetree/File.svelte index bd2e61e9b..d911ddb5f 100644 --- a/src/lib/components/filetree/File.svelte +++ b/src/lib/components/filetree/File.svelte @@ -33,7 +33,7 @@ : []; -
+
Date: Mon, 23 Jan 2023 10:35:22 +0000 Subject: [PATCH 034/380] fix: load images (#174) from https://via.placeholder.com using crossorigin="anonymous" attribute --- content/tutorial/01-svelte/07-lifecycle/01-onmount/README.md | 2 -- .../01-svelte/07-lifecycle/01-onmount/app-a/src/lib/App.svelte | 1 + .../01-svelte/07-lifecycle/01-onmount/app-b/src/lib/App.svelte | 1 + 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/content/tutorial/01-svelte/07-lifecycle/01-onmount/README.md b/content/tutorial/01-svelte/07-lifecycle/01-onmount/README.md index 3fcdab956..371848d57 100644 --- a/content/tutorial/01-svelte/07-lifecycle/01-onmount/README.md +++ b/content/tutorial/01-svelte/07-lifecycle/01-onmount/README.md @@ -2,8 +2,6 @@ title: onMount --- -> The images in this exercise don't currently work. You can switch to the old tutorial instead: https://svelte.dev/tutorial/onmount - Every component has a _lifecycle_ that starts when it is created, and ends when it is destroyed. There are a handful of functions that allow you to run code at key moments during that lifecycle. The one you'll use most frequently is `onMount`, which runs after the component is first rendered to the DOM. diff --git a/content/tutorial/01-svelte/07-lifecycle/01-onmount/app-a/src/lib/App.svelte b/content/tutorial/01-svelte/07-lifecycle/01-onmount/app-a/src/lib/App.svelte index 1133a6dec..b4041eebd 100644 --- a/content/tutorial/01-svelte/07-lifecycle/01-onmount/app-a/src/lib/App.svelte +++ b/content/tutorial/01-svelte/07-lifecycle/01-onmount/app-a/src/lib/App.svelte @@ -8,6 +8,7 @@ {#each photos as photo}
{photo.title} diff --git a/content/tutorial/01-svelte/07-lifecycle/01-onmount/app-b/src/lib/App.svelte b/content/tutorial/01-svelte/07-lifecycle/01-onmount/app-b/src/lib/App.svelte index c074739b0..5e3ebfeb0 100644 --- a/content/tutorial/01-svelte/07-lifecycle/01-onmount/app-b/src/lib/App.svelte +++ b/content/tutorial/01-svelte/07-lifecycle/01-onmount/app-b/src/lib/App.svelte @@ -17,6 +17,7 @@ {#each photos as photo}
{photo.title} From dd0b9faf4e3de0fbb753fce318d484dbabe636b2 Mon Sep 17 00:00:00 2001 From: Ivanbtrujillo Date: Mon, 23 Jan 2023 10:39:18 +0000 Subject: [PATCH 035/380] fix: add marked dependency for Part 1 / Bindings / Textarea inputs (#175) ..by adding it as node_modules to app-a Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- .../06-bindings/05-textarea-inputs/README.md | 2 - .../app-a/node_modules/.bin/marked | 1 + .../app-a/node_modules/marked/LICENSE.md | 44 + .../app-a/node_modules/marked/README.md | 99 + .../app-a/node_modules/marked/bin/marked.js | 217 ++ .../app-a/node_modules/marked/lib/marked.cjs | 2723 ++++++++++++++++ .../node_modules/marked/lib/marked.esm.js | 2824 +++++++++++++++++ .../node_modules/marked/lib/marked.umd.js | 2729 ++++++++++++++++ .../app-a/node_modules/marked/man/marked.1 | 92 + .../node_modules/marked/man/marked.1.txt | 86 + .../app-a/node_modules/marked/marked.min.js | 6 + .../app-a/node_modules/marked/package.json | 94 + .../app-a/node_modules/marked/src/Lexer.js | 503 +++ .../app-a/node_modules/marked/src/Parser.js | 286 ++ .../app-a/node_modules/marked/src/Renderer.js | 203 ++ .../app-a/node_modules/marked/src/Slugger.js | 55 + .../node_modules/marked/src/TextRenderer.js | 42 + .../node_modules/marked/src/Tokenizer.js | 794 +++++ .../app-a/node_modules/marked/src/defaults.js | 29 + .../app-a/node_modules/marked/src/helpers.js | 276 ++ .../app-a/node_modules/marked/src/marked.js | 366 +++ .../app-a/node_modules/marked/src/rules.js | 305 ++ .../05-textarea-inputs/app-a/package.json | 8 + 23 files changed, 11782 insertions(+), 2 deletions(-) create mode 120000 content/tutorial/01-svelte/06-bindings/05-textarea-inputs/app-a/node_modules/.bin/marked create mode 100644 content/tutorial/01-svelte/06-bindings/05-textarea-inputs/app-a/node_modules/marked/LICENSE.md create mode 100644 content/tutorial/01-svelte/06-bindings/05-textarea-inputs/app-a/node_modules/marked/README.md create mode 100755 content/tutorial/01-svelte/06-bindings/05-textarea-inputs/app-a/node_modules/marked/bin/marked.js create mode 100644 content/tutorial/01-svelte/06-bindings/05-textarea-inputs/app-a/node_modules/marked/lib/marked.cjs create mode 100644 content/tutorial/01-svelte/06-bindings/05-textarea-inputs/app-a/node_modules/marked/lib/marked.esm.js create mode 100644 content/tutorial/01-svelte/06-bindings/05-textarea-inputs/app-a/node_modules/marked/lib/marked.umd.js create mode 100644 content/tutorial/01-svelte/06-bindings/05-textarea-inputs/app-a/node_modules/marked/man/marked.1 create mode 100644 content/tutorial/01-svelte/06-bindings/05-textarea-inputs/app-a/node_modules/marked/man/marked.1.txt create mode 100644 content/tutorial/01-svelte/06-bindings/05-textarea-inputs/app-a/node_modules/marked/marked.min.js create mode 100644 content/tutorial/01-svelte/06-bindings/05-textarea-inputs/app-a/node_modules/marked/package.json create mode 100644 content/tutorial/01-svelte/06-bindings/05-textarea-inputs/app-a/node_modules/marked/src/Lexer.js create mode 100644 content/tutorial/01-svelte/06-bindings/05-textarea-inputs/app-a/node_modules/marked/src/Parser.js create mode 100644 content/tutorial/01-svelte/06-bindings/05-textarea-inputs/app-a/node_modules/marked/src/Renderer.js create mode 100644 content/tutorial/01-svelte/06-bindings/05-textarea-inputs/app-a/node_modules/marked/src/Slugger.js create mode 100644 content/tutorial/01-svelte/06-bindings/05-textarea-inputs/app-a/node_modules/marked/src/TextRenderer.js create mode 100644 content/tutorial/01-svelte/06-bindings/05-textarea-inputs/app-a/node_modules/marked/src/Tokenizer.js create mode 100644 content/tutorial/01-svelte/06-bindings/05-textarea-inputs/app-a/node_modules/marked/src/defaults.js create mode 100644 content/tutorial/01-svelte/06-bindings/05-textarea-inputs/app-a/node_modules/marked/src/helpers.js create mode 100644 content/tutorial/01-svelte/06-bindings/05-textarea-inputs/app-a/node_modules/marked/src/marked.js create mode 100644 content/tutorial/01-svelte/06-bindings/05-textarea-inputs/app-a/node_modules/marked/src/rules.js create mode 100644 content/tutorial/01-svelte/06-bindings/05-textarea-inputs/app-a/package.json diff --git a/content/tutorial/01-svelte/06-bindings/05-textarea-inputs/README.md b/content/tutorial/01-svelte/06-bindings/05-textarea-inputs/README.md index 047669e7d..3fb4ff242 100644 --- a/content/tutorial/01-svelte/06-bindings/05-textarea-inputs/README.md +++ b/content/tutorial/01-svelte/06-bindings/05-textarea-inputs/README.md @@ -2,8 +2,6 @@ title: Textarea inputs --- -> This exercise doesn't currently work. You can switch to the old tutorial instead: https://svelte.dev/tutorial/textarea-inputs - The ` ``` In cases like these, where the names match, we can also use a shorthand form: ```svelte +/// file: App.svelte ``` diff --git a/content/tutorial/01-svelte/06-bindings/06-select-bindings/README.md b/content/tutorial/01-svelte/06-bindings/06-select-bindings/README.md index bc4b35f54..c1ce601c9 100644 --- a/content/tutorial/01-svelte/06-bindings/06-select-bindings/README.md +++ b/content/tutorial/01-svelte/06-bindings/06-select-bindings/README.md @@ -5,6 +5,7 @@ title: Select bindings We can also use `bind:value` with ` ``` diff --git a/content/tutorial/01-svelte/06-bindings/07-multiple-select-bindings/README.md b/content/tutorial/01-svelte/06-bindings/07-multiple-select-bindings/README.md index 1107d74b5..1ffe9885e 100644 --- a/content/tutorial/01-svelte/06-bindings/07-multiple-select-bindings/README.md +++ b/content/tutorial/01-svelte/06-bindings/07-multiple-select-bindings/README.md @@ -7,6 +7,7 @@ A select can have a `multiple` attribute, in which case it will populate an arra Returning to our [earlier ice cream example](/tutorial/group-inputs), we can replace the checkboxes with a ` diff --git a/content/tutorial/01-svelte/07-lifecycle/01-onmount/README.md b/content/tutorial/01-svelte/07-lifecycle/01-onmount/README.md index 3fcdab956..4a1cfa630 100644 --- a/content/tutorial/01-svelte/07-lifecycle/01-onmount/README.md +++ b/content/tutorial/01-svelte/07-lifecycle/01-onmount/README.md @@ -11,6 +11,7 @@ The one you'll use most frequently is `onMount`, which runs after the component We'll add an `onMount` handler that loads some data over the network: ```svelte +/// file: App.svelte

The count is {count_value}

@@ -39,14 +41,24 @@ You now declared `unsubscribe`, but it still needs to be called, for example thr It starts to get a bit boilerplatey though, especially if your component subscribes to multiple stores. Instead, Svelte has a trick up its sleeve — you can reference a store value by prefixing the store name with `$`: ```svelte +/// file: App.svelte -

The count is {$count}

+

The count is {+++$count+++}

``` > Auto-subscription only works with store variables that are declared (or imported) at the top-level scope of a component. diff --git a/content/tutorial/01-svelte/08-stores/03-readable-stores/README.md b/content/tutorial/01-svelte/08-stores/03-readable-stores/README.md index 5e5385270..0f6d1dbee 100644 --- a/content/tutorial/01-svelte/08-stores/03-readable-stores/README.md +++ b/content/tutorial/01-svelte/08-stores/03-readable-stores/README.md @@ -4,9 +4,10 @@ title: Readable stores Not all stores should be writable by whoever has a reference to them. For example, you might have a store representing the mouse position or the user's geolocation, and it doesn't make sense to be able to set those values from 'outside'. For those cases, we have _readable_ stores. -Click over to the `stores.js` tab. The first argument to `readable` is an initial value, which can be `null` or `undefined` if you don't have one yet. The second argument is a `start` function that takes a `set` callback and returns a `stop` function. The `start` function is called when the store gets its first subscriber; `stop` is called when the last subscriber unsubscribes. +Open `stores.js`. The first argument to `readable` is an initial value, which can be `null` or `undefined` if you don't have one yet. The second argument is a `start` function that takes a `set` callback and returns a `stop` function. The `start` function is called when the store gets its first subscriber; `stop` is called when the last subscriber unsubscribes. ```js +/// file: stores.js export const time = readable(new Date(), function start(set) { const interval = setInterval(() => { set(new Date()); diff --git a/content/tutorial/01-svelte/08-stores/04-derived-stores/README.md b/content/tutorial/01-svelte/08-stores/04-derived-stores/README.md index ca71e5d0d..d96a3cf5f 100644 --- a/content/tutorial/01-svelte/08-stores/04-derived-stores/README.md +++ b/content/tutorial/01-svelte/08-stores/04-derived-stores/README.md @@ -5,7 +5,11 @@ title: Derived stores You can create a store whose value is based on the value of one or more _other_ stores with `derived`. Building on our previous example, we can create a store that derives the time the page has been open: ```js -export const elapsed = derived(time, ($time) => Math.round(($time - start) / 1000)); +/// file: stores.js +export const elapsed = derived( + time, + ($time) => Math.round(($time - start) / 1000) +); ``` > It's possible to derive a store from multiple inputs, and to explicitly `set` a value instead of returning it (which is useful for deriving values asynchronously). Consult the [API reference](https://svelte.dev/docs#run-time-svelte-store-derived) for more information. diff --git a/content/tutorial/01-svelte/08-stores/04-derived-stores/app-a/src/lib/stores.js b/content/tutorial/01-svelte/08-stores/04-derived-stores/app-a/src/lib/stores.js index 854dd7d43..7eaf0ca8e 100644 --- a/content/tutorial/01-svelte/08-stores/04-derived-stores/app-a/src/lib/stores.js +++ b/content/tutorial/01-svelte/08-stores/04-derived-stores/app-a/src/lib/stores.js @@ -12,4 +12,7 @@ export const time = readable(new Date(), function start(set) { const start = new Date(); -export const elapsed = derived(time, ($time) => {}); +export const elapsed = derived( + time, + ($time) => {} +); diff --git a/content/tutorial/01-svelte/08-stores/04-derived-stores/app-b/src/lib/stores.js b/content/tutorial/01-svelte/08-stores/04-derived-stores/app-b/src/lib/stores.js index 741f92784..ff3fb8458 100644 --- a/content/tutorial/01-svelte/08-stores/04-derived-stores/app-b/src/lib/stores.js +++ b/content/tutorial/01-svelte/08-stores/04-derived-stores/app-b/src/lib/stores.js @@ -12,4 +12,7 @@ export const time = readable(new Date(), function start(set) { const start = new Date(); -export const elapsed = derived(time, ($time) => Math.round(($time - start) / 1000)); +export const elapsed = derived( + time, + ($time) => Math.round(($time - start) / 1000) +); diff --git a/content/tutorial/01-svelte/08-stores/05-custom-stores/README.md b/content/tutorial/01-svelte/08-stores/05-custom-stores/README.md index ebc427678..f54d73cbb 100644 --- a/content/tutorial/01-svelte/08-stores/05-custom-stores/README.md +++ b/content/tutorial/01-svelte/08-stores/05-custom-stores/README.md @@ -7,14 +7,15 @@ As long as an object correctly implements the `subscribe` method, it's a store. For example, the `count` store from our earlier example could include `increment`, `decrement` and `reset` methods and avoid exposing `set` and `update`: ```js +/// file: stores.js function createCount() { const { subscribe, set, update } = writable(0); return { subscribe, - increment: () => update((n) => n + 1), - decrement: () => update((n) => n - 1), - reset: () => set(0) + increment: () => +++update((n) => n + 1)+++, + decrement: () => +++update((n) => n - 1)+++, + reset: () => +++set(0)+++ }; } ``` diff --git a/content/tutorial/01-svelte/08-stores/06-store-bindings/README.md b/content/tutorial/01-svelte/08-stores/06-store-bindings/README.md index 05691b05d..ea16d03c9 100644 --- a/content/tutorial/01-svelte/08-stores/06-store-bindings/README.md +++ b/content/tutorial/01-svelte/08-stores/06-store-bindings/README.md @@ -4,17 +4,19 @@ title: Store bindings If a store is writable — i.e. it has a `set` method — you can bind to its value, just as you can bind to local component state. -In this example we have a writable store `name` and a derived store `greeting`. Update the `` element: +In this example we're exporting a writable store `name` and a derived store `greeting` from `stores.js`. Update the `` element in `App.svelte`: ```svelte - +/// file: App.svelte + ``` Changing the input value will now update `name` and all its dependents. -We can also assign directly to store values inside a component. Add a ` diff --git a/content/tutorial/02-sveltekit/02-routing/02-layouts/README.md b/content/tutorial/02-sveltekit/02-routing/02-layouts/README.md index 97cb90f5e..e7812fead 100644 --- a/content/tutorial/02-sveltekit/02-routing/02-layouts/README.md +++ b/content/tutorial/02-sveltekit/02-routing/02-layouts/README.md @@ -6,11 +6,11 @@ Different routes of your app will often share common UI. Instead of repeating it In this app we have two routes, `src/routes/+page.svelte` and `src/routes/about/+page.svelte`, that contain the same navigation UI. Let's create a new file, `src/routes/+layout.svelte`... -```diff +``` src/routes/ ├ about/ │ └ +page.svelte -+├ +layout.svelte ++++├ +layout.svelte+++ └ +page.svelte ``` diff --git a/content/tutorial/02-sveltekit/03-loading-data/01-page-data/README.md b/content/tutorial/02-sveltekit/03-loading-data/01-page-data/README.md index 9ab42c065..f23ff32c3 100644 --- a/content/tutorial/02-sveltekit/03-loading-data/01-page-data/README.md +++ b/content/tutorial/02-sveltekit/03-loading-data/01-page-data/README.md @@ -32,6 +32,7 @@ export function load() { We can access this data in `src/routes/blog/+page.svelte` via the `data` prop: ```svelte +/// file: src/routes/blog/+page.svelte ++++++ diff --git a/content/tutorial/02-sveltekit/04-forms/03-form-validation/README.md b/content/tutorial/02-sveltekit/04-forms/03-form-validation/README.md index 682fa54f8..6c7799b46 100644 --- a/content/tutorial/02-sveltekit/04-forms/03-form-validation/README.md +++ b/content/tutorial/02-sveltekit/04-forms/03-form-validation/README.md @@ -7,6 +7,7 @@ Users are a mischievous bunch, who will submit all kinds of nonsensical data if The first line of defense is the browser's [built-in form validation](https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation#using_built-in_form_validation), which makes it easy to, for example, mark an `` as required: ```svelte +/// file: src/routes/+page.svelte