diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 9acc6de3f..cb004fcf5 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: - node-version: [18.x] + node-version: [20.x] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 000000000..0598ad131 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) 2023 [these people](https://github.com/sveltejs/learn.svelte.dev/graphs/contributors) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index d99fc947c..8866c4475 100644 --- a/README.md +++ b/README.md @@ -6,21 +6,21 @@ A soup-to-nuts interactive tutorial on how to build apps with Svelte. This repo uses [pnpm](https://pnpm.io/). -## Running the app +## Developing the app -First, run `node scripts/create-common-bundle`. This packages up everything that's needed to run a SvelteKit app (Vite, Esbuild, SvelteKit, Svelte compiler etc) which can subsequently be unpacked on a server to create and run and instance of a SvelteKit application (which powers the output window of the tutorial). +First, run `node scripts/create-common-bundle`. This packages up everything that's needed to run a SvelteKit app (Vite, esbuild, SvelteKit, Svelte compiler, etc.) which can subsequently be unpacked on a server to create and run an instance of a SvelteKit application (which powers the output window of the tutorial). Then, run `dev`: -The next steps depend on whether you want to run this locally in filesystem mode, or in WebContainer mode. For now, it works with filesystem mode only locally. In future, it will run both locally and on the web (using [WebContainers](https://blog.stackblitz.com/posts/introducing-webcontainers/)). +```bash +node scripts/create-common-bundle +pnpm dev +``` -### Local/filesystem mode +To build for production and run locally: -1. add an `.env` file with `PUBLIC_USE_FILESYSTEM=true` in it -2. Run the app locally with `pnpm dev` or `pnpm build && pnpm preview`. - -### WebContainer mode - -1. if an `.env` file exists, modify it so there's `PUBLIC_USE_FILESYSTEM=` in it -2. Run the app locally with `pnpm dev` or `pnpm build && pnpm preview`. +```bash +pnpm build +pnpm preview +``` ## Creating new tutorials @@ -28,4 +28,4 @@ Tutorials live inside `content`. Each tutorial consists of a `README.md`, which ## Bumping tutorial dependencies -Bump the dependency (for example Svelte) in both the root and the `content/common` `package.json`. In the root do `pnpm i` (to update `pnpm-lock.yaml`), in `content/common` do `npm i` (to update `package-lock.json`). After deployment things might be out of date because Vercel caches things, redeploy without cache in that case. +Bump the dependency (for example Svelte) in both the root and the `content/common` `package.json`. In the root do `pnpm i` (to update `pnpm-lock.yaml`), in `content/common` do `npm i` (to update `package-lock.json`). \ No newline at end of file diff --git a/backend/+server.js b/backend/+server.js deleted file mode 100644 index 951581de2..000000000 --- a/backend/+server.js +++ /dev/null @@ -1,14 +0,0 @@ -import { json } from '@sveltejs/kit'; -import { create } from './apps'; - -/** @type {import('./$types').RequestHandler} */ -export async function POST({ request }) { - return json( - await create({ - files: await request.json() - }), - { - status: 201 // should this be a 200? - } - ); -} diff --git a/backend/README.md b/backend/README.md deleted file mode 100644 index 39de3f70c..000000000 --- a/backend/README.md +++ /dev/null @@ -1 +0,0 @@ -This used to live in src/routes/backend, but it makes it impossible to use with edge functions. Keeping it here in case we need it in future. diff --git a/backend/[id]/+server.js b/backend/[id]/+server.js deleted file mode 100644 index 918ddfefd..000000000 --- a/backend/[id]/+server.js +++ /dev/null @@ -1,17 +0,0 @@ -import { clear, update } from '../apps'; - -/** @type {import('./$types').RequestHandler} */ -export async function PUT({ url, request, params }) { - const { id } = params; - const files = await request.json(); - - if (url.searchParams.has('reset')) { - clear({ id, files }); - } - - update({ id, files }); - - return new Response(undefined, { - status: 201 - }); -} diff --git a/backend/apps.js b/backend/apps.js deleted file mode 100644 index 80b0669d8..000000000 --- a/backend/apps.js +++ /dev/null @@ -1,208 +0,0 @@ -import fs from 'node:fs'; -import path from 'node:path'; -import { spawn } from 'node:child_process'; -import { createRequire } from 'node:module'; -import * as ports from 'port-authority'; -import { broadcast, ready } from './ws'; - -/** - * @typedef {{ - * process: import('child_process').ChildProcess, - * filenames: string[] - * }} App */ - -fs.rmSync('.apps', { recursive: true, force: true }); -fs.mkdirSync('.apps', { recursive: true }); - -// poor man's HMR, pending https://github.com/vitejs/vite/issues/7887 -// @ts-expect-error -if (globalThis.__apps) { - globalThis.__apps.forEach((app) => { - app.process.kill(); - }); -} - -const require = createRequire(import.meta.url); -const vite_pkg_file = require.resolve('vite/package.json'); -const vite_pkg = JSON.parse(fs.readFileSync(vite_pkg_file, 'utf-8')); -const vite = path.resolve(vite_pkg_file, '..', vite_pkg.bin['vite']); - -/** @type {Map} */ -const apps = new Map(); -globalThis.__apps = apps; - -const hooks_src = `/** @type {import('@sveltejs/kit').Handle} */ -export async function handle({ event, resolve }) { - const response = await resolve(event); - - 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; -}`; - -/** - * @param {{ - * files: import('$lib/types').FileStub[] - * }} options - */ -export async function create({ files }) { - const id = String(Date.now()); - const filenames = write_files(id, files); - - // TODO this enables embedding on cross-origin sites, which is - // necessary for the JSNation talk, but will currently break if an app - // already has a src/hooks.server.js file (though it could be worked - // around easily enough if necessary) - if (!files.find((stub) => stub.name === '/src/hooks.server.js')) { - fs.writeFileSync(`.apps/${id}/src/hooks.server.js`, hooks_src); - } - - const port = await ports.find(3001); - - apps.set(id, { - process: launch(id, String(port)), - filenames - }); - - await ports.waitUntilBusy(port); - - await ready; - - return { - id, - port - }; -} - -/** - * @param {{ - * id: string; - * files: import('$lib/types').FileStub[] - * }} options - */ -export function clear({ id, files }) { - const app = apps.get(id); - - if (!app) { - throw new Error(`app ${id} does not exist`); - } - - const dir = `.apps/${id}`; - const old_filenames = new Set(app.filenames); - - /** @type {string[]} */ - const filenames = []; - - for (const file of files) { - if (file.type === 'file') { - filenames.push(file.name); - old_filenames.delete(file.name); - } - } - - for (const file of old_filenames) { - if (fs.existsSync(dir + file)) { - fs.unlinkSync(dir + file); - } - } - - app.filenames = filenames; -} - -/** - * @param {{ - * id: string; - * files: import('$lib/types').FileStub[] - * }} options - */ -export function update({ id, files }) { - if (!apps.has(id)) { - throw new Error(`app ${id} does not exist`); - } - - write_files(id, files); -} - -/** - * @param {{ id: string }} options - */ -export function destroy({ id }) { - const dir = `.apps/${id}`; - - fs.rmSync(dir, { recursive: true, force: true }); - - apps.get(id)?.process.kill(); - apps.delete(id); -} - -/** - * @param {string} id - * @param {string} port - */ -function launch(id, port) { - const cwd = `.apps/${id}`; - - const process = spawn('node', [vite, 'dev', '--port', port], { - cwd - }); - - process.stdout.on('data', (data) => { - broadcast({ id, data: data.toString(), type: 'stdout' }); - }); - - process.stderr.on('data', (data) => { - broadcast({ id, data: data.toString(), type: 'stderr' }); - }); - - return process; -} - -/** - * - * @param {string} file - * @param {string | Buffer} contents - */ -function write_if_changed(file, contents) { - if (typeof contents === 'string' && fs.existsSync(file)) { - const existing = fs.readFileSync(file, 'utf-8'); - if (contents === existing) return; - } - - fs.mkdirSync(path.dirname(file), { recursive: true }); - fs.writeFileSync(file, contents); -} - -/** - * - * @param {string} id - * @param {import('$lib/types').FileStub[]} files - */ -function write_files(id, files) { - const dir = `.apps/${id}`; - - /** @type {string[]} */ - const filenames = []; - - for (const file of files) { - if (file.type === 'file') { - filenames.push(file.name); - - const dest = `${dir}/${file.name}`; - let content = file.text ? file.contents : Buffer.from(file.contents, 'base64'); - - if (file.name === '/src/app.html' && typeof content === 'string') { - // TODO handle case where config.kit.files.template is different - content = content.replace( - '', - '' - ); - } - - write_if_changed(dest, content); - } - } - - return filenames; -} diff --git a/backend/destroy/+server.js b/backend/destroy/+server.js deleted file mode 100644 index ad9c89efd..000000000 --- a/backend/destroy/+server.js +++ /dev/null @@ -1,14 +0,0 @@ -import { destroy } from '../apps'; - -// this is implemented as a POST handler because it is -// triggered by `navigator.sendBeacon` rather than `fetch` - -/** @type {import('./$types').RequestHandler} */ -export function POST({ url }) { - const id = /** @type {string} */ (url.searchParams.get('id')); - destroy({ id }); - - return new Response(undefined, { - status: 204 - }); -} diff --git a/backend/ws.js b/backend/ws.js deleted file mode 100644 index e02317c0d..000000000 --- a/backend/ws.js +++ /dev/null @@ -1,33 +0,0 @@ -// import './websocket.js'; -import { WebSocketServer } from 'ws'; - -// poor man's HMR, pending https://github.com/vitejs/vite/issues/7887 -// @ts-expect-error -if (globalThis.__wss) globalThis.__wss.close(); - -const wss = new WebSocketServer({ port: 4567 }); -globalThis.__wss = wss; - -/** @type {Set} */ -const connections = new Set(); - -wss.on('connection', (ws) => { - connections.add(ws); - - ws.on('close', () => { - connections.delete(ws); - }); -}); - -export const ready = new Promise((fulfil) => { - wss.on('listening', () => { - fulfil(undefined); - }); -}); - -/** @param {any} data */ -export function broadcast(data) { - for (const connection of connections) { - connection.send(JSON.stringify(data)); - } -} diff --git a/content/tutorial/01-svelte/01-introduction/01-welcome-to-svelte/README.md b/content/tutorial/01-svelte/01-introduction/01-welcome-to-svelte/README.md index 14105cdf0..0701a6901 100644 --- a/content/tutorial/01-svelte/01-introduction/01-welcome-to-svelte/README.md +++ b/content/tutorial/01-svelte/01-introduction/01-welcome-to-svelte/README.md @@ -20,9 +20,9 @@ You can build your entire app with Svelte (for example, using an application fra This tutorial is split into four main parts: -- [Welcome to Svelte](/tutorial/welcome-to-svelte) (you are here) -- [Introduction to SvelteKit](/tutorial/introducing-sveltekit) +- [Basic Svelte](/tutorial/welcome-to-svelte) (you are here) - [Advanced Svelte](/tutorial/tweens) +- [Basic SvelteKit](/tutorial/introducing-sveltekit) - [Advanced SvelteKit](/tutorial/optional-params) Each section will present an exercise designed to illustrate a feature. Later exercises build on the knowledge gained in earlier ones, so it's recommended that you go from start to finish. If necessary, you can navigate via the menu above. diff --git a/content/tutorial/01-svelte/01-introduction/03-dynamic-attributes/README.md b/content/tutorial/01-svelte/01-introduction/03-dynamic-attributes/README.md index e03524b1a..6c325239f 100644 --- a/content/tutorial/01-svelte/01-introduction/03-dynamic-attributes/README.md +++ b/content/tutorial/01-svelte/01-introduction/03-dynamic-attributes/README.md @@ -32,5 +32,5 @@ It's not uncommon to have an attribute where the name and value are the same, li ```svelte /// file: App.svelte -A man dances. +{name} dances. ``` diff --git a/content/tutorial/01-svelte/01-introduction/06-html-tags/README.md b/content/tutorial/01-svelte/01-introduction/06-html-tags/README.md index 914e76bbf..801b22a1d 100644 --- a/content/tutorial/01-svelte/01-introduction/06-html-tags/README.md +++ b/content/tutorial/01-svelte/01-introduction/06-html-tags/README.md @@ -13,4 +13,4 @@ In Svelte, you do this with the special `{@html ...}` tag:

{+++@html+++ string}

``` -> **Warning!** Svelte doesn't perform any sanitization of the expression inside `{@html ...}` before it gets inserted into the DOM. In other words, if you use this feature it's critical that you manually escape HTML that comes from sources you don't trust, otherwise you risk exposing your users to Cross-Site Scripting (XSS) attacks. +> **Warning!** Svelte doesn't perform any sanitization of the expression inside `{@html ...}` before it gets inserted into the DOM. This isn't an issue if the content is something you trust like an article you wrote yourself. However if it's some untrusted user content, e.g. a comment on an article, then it's critical that you manually escape it, otherwise you risk exposing your users to Cross-Site Scripting (XSS) attacks. diff --git a/content/tutorial/01-svelte/02-reactivity/02-reactive-declarations/README.md b/content/tutorial/01-svelte/02-reactivity/02-reactive-declarations/README.md index 16fb5dfb1..0ef6f276f 100644 --- a/content/tutorial/01-svelte/02-reactivity/02-reactive-declarations/README.md +++ b/content/tutorial/01-svelte/02-reactivity/02-reactive-declarations/README.md @@ -12,6 +12,8 @@ let count = 0; +++$: doubled = count * 2;+++ ``` +If a reactive statement consists entirely of an assignment to an undeclared variable, Svelte will inject a `let` declaration on your behalf. + > Don't worry if this looks a little alien. It's [valid](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/label) (if unconventional) JavaScript, which Svelte interprets to mean 're-run this code whenever any of the referenced values change'. Once you get used to it, there's no going back. Let's use `doubled` in our markup: @@ -24,3 +26,5 @@ Let's use `doubled` in our markup: ``` Of course, you could just write `{count * 2}` in the markup instead — you don't have to use reactive values. Reactive values become particularly valuable (no pun intended) when you need to reference them multiple times, or you have values that depend on _other_ reactive values. + +> Note that reactive declarations and statements will run after other script code and before component markup is rendered. diff --git a/content/tutorial/01-svelte/02-reactivity/04-updating-arrays-and-objects/README.md b/content/tutorial/01-svelte/02-reactivity/04-updating-arrays-and-objects/README.md index 74e3a581f..49e0654ab 100644 --- a/content/tutorial/01-svelte/02-reactivity/04-updating-arrays-and-objects/README.md +++ b/content/tutorial/01-svelte/02-reactivity/04-updating-arrays-and-objects/README.md @@ -38,8 +38,9 @@ A simple rule of thumb: the name of the updated variable must appear on the left ```js /// no-file +const obj = { foo: { bar: 1 } }; const foo = obj.foo; -foo.bar = 'baz'; +foo.bar = 2; ``` ...won't trigger reactivity on `obj.foo.bar`, unless you follow it up with `obj = obj`. diff --git a/content/tutorial/01-svelte/02-reactivity/04-updating-arrays-and-objects/app-a/src/lib/App.svelte b/content/tutorial/01-svelte/02-reactivity/04-updating-arrays-and-objects/app-a/src/lib/App.svelte index 77d176923..ab6ef7074 100644 --- a/content/tutorial/01-svelte/02-reactivity/04-updating-arrays-and-objects/app-a/src/lib/App.svelte +++ b/content/tutorial/01-svelte/02-reactivity/04-updating-arrays-and-objects/app-a/src/lib/App.svelte @@ -5,7 +5,7 @@ numbers.push(numbers.length + 1); } - $: sum = numbers.reduce((t, n) => t + n, 0); + $: sum = numbers.reduce((total, currentNumber) => total + currentNumber, 0);

{numbers.join(' + ')} = {sum}

diff --git a/content/tutorial/01-svelte/02-reactivity/04-updating-arrays-and-objects/app-b/src/lib/App.svelte b/content/tutorial/01-svelte/02-reactivity/04-updating-arrays-and-objects/app-b/src/lib/App.svelte index d15f58c4d..730ae8cc7 100644 --- a/content/tutorial/01-svelte/02-reactivity/04-updating-arrays-and-objects/app-b/src/lib/App.svelte +++ b/content/tutorial/01-svelte/02-reactivity/04-updating-arrays-and-objects/app-b/src/lib/App.svelte @@ -5,7 +5,7 @@ numbers = [...numbers, numbers.length + 1]; } - $: sum = numbers.reduce((t, n) => t + n, 0); + $: sum = numbers.reduce((total, currentNumber) => total + currentNumber, 0);

{numbers.join(' + ')} = {sum}

diff --git a/content/tutorial/01-svelte/03-props/03-spread-props/app-a/src/lib/App.svelte b/content/tutorial/01-svelte/03-props/03-spread-props/app-a/src/lib/App.svelte index 4fb555240..2d5d7123b 100644 --- a/content/tutorial/01-svelte/03-props/03-spread-props/app-a/src/lib/App.svelte +++ b/content/tutorial/01-svelte/03-props/03-spread-props/app-a/src/lib/App.svelte @@ -4,7 +4,7 @@ const pkg = { name: 'svelte', speed: 'blazing', - version: 3, + version: 4, website: 'https://svelte.dev' }; diff --git a/content/tutorial/01-svelte/03-props/03-spread-props/app-b/src/lib/App.svelte b/content/tutorial/01-svelte/03-props/03-spread-props/app-b/src/lib/App.svelte index 5934bdabf..3ded4fd56 100644 --- a/content/tutorial/01-svelte/03-props/03-spread-props/app-b/src/lib/App.svelte +++ b/content/tutorial/01-svelte/03-props/03-spread-props/app-b/src/lib/App.svelte @@ -4,7 +4,7 @@ const pkg = { name: 'svelte', speed: 'blazing', - version: 3, + version: 4, website: 'https://svelte.dev' }; diff --git a/content/tutorial/01-svelte/04-logic/01-if-blocks/README.md b/content/tutorial/01-svelte/04-logic/01-if-blocks/README.md index 45d60b4bc..845b4ab99 100644 --- a/content/tutorial/01-svelte/04-logic/01-if-blocks/README.md +++ b/content/tutorial/01-svelte/04-logic/01-if-blocks/README.md @@ -18,4 +18,4 @@ To conditionally render some markup, we wrap it in an `if` block. Let's add some {/if}+++ ``` -Try it — update the component, and click on the buttons. +Try it — update the component, and click on the button. diff --git a/content/tutorial/01-svelte/04-logic/04-each-blocks/README.md b/content/tutorial/01-svelte/04-logic/04-each-blocks/README.md index 9c9e7ffc3..3011cbb8f 100644 --- a/content/tutorial/01-svelte/04-logic/04-each-blocks/README.md +++ b/content/tutorial/01-svelte/04-logic/04-each-blocks/README.md @@ -11,7 +11,7 @@ Instead of laboriously copying, pasting and editing, we can get rid of all but t
+++{#each colors as color}+++
``` diff --git a/content/tutorial/01-svelte/05-events/03-event-modifiers/README.md b/content/tutorial/01-svelte/05-events/03-event-modifiers/README.md index a47995a7c..5cccee218 100644 --- a/content/tutorial/01-svelte/05-events/03-event-modifiers/README.md +++ b/content/tutorial/01-svelte/05-events/03-event-modifiers/README.md @@ -17,7 +17,7 @@ The full list of modifiers: - `stopPropagation` — calls `event.stopPropagation()`, preventing the event reaching the next element - `passive` — improves scrolling performance on touch/wheel events (Svelte will add it automatically where it's safe to do so) - `nonpassive` — explicitly set `passive: false` -- `capture` — fires the handler during the _capture_ phase instead of the _bubbling_ phase ([MDN docs](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#event_capture)) +- `capture` — fires the handler during the [_capture_](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#event_capture) phase instead of the [_bubbling_](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#event_bubbling) phase - `once` — remove the handler after the first time it runs - `self` — only trigger handler if event.target is the element itself - `trusted` — only trigger handler if `event.isTrusted` is `true`, meaning the event was triggered by a user action rather than because some JavaScript called `element.dispatchEvent(...)` diff --git a/content/tutorial/01-svelte/05-events/04-component-events/README.md b/content/tutorial/01-svelte/05-events/04-component-events/README.md index 71c34ee8f..12457f8ed 100644 --- a/content/tutorial/01-svelte/05-events/04-component-events/README.md +++ b/content/tutorial/01-svelte/05-events/04-component-events/README.md @@ -28,4 +28,4 @@ Then, add an `on:message` handler in `App.svelte`: ``` -> You can also try changing the event name to something else. For instance, change `dispatch('message')` to `dispatch('greet')` in `Inner.svelte` and change the attribute name from `on:message` to `on:greet` in `App.svelte`. +> You can also try changing the event name to something else. For instance, change `dispatch('message', {...})` to `dispatch('greet', {...})` in `Inner.svelte` and change the attribute name from `on:message` to `on:greet` in `App.svelte`. diff --git a/content/tutorial/01-svelte/05-events/06-dom-event-forwarding/app-a/src/lib/App.svelte b/content/tutorial/01-svelte/05-events/06-dom-event-forwarding/app-a/src/lib/App.svelte index b47a06f1e..33d318c91 100644 --- a/content/tutorial/01-svelte/05-events/06-dom-event-forwarding/app-a/src/lib/App.svelte +++ b/content/tutorial/01-svelte/05-events/06-dom-event-forwarding/app-a/src/lib/App.svelte @@ -6,6 +6,7 @@ audio.src = horn; function handleClick() { + audio.load(); audio.play(); } diff --git a/content/tutorial/01-svelte/06-bindings/03-checkbox-inputs/README.md b/content/tutorial/01-svelte/06-bindings/03-checkbox-inputs/README.md index 8df2be7c2..dc79573de 100644 --- a/content/tutorial/01-svelte/06-bindings/03-checkbox-inputs/README.md +++ b/content/tutorial/01-svelte/06-bindings/03-checkbox-inputs/README.md @@ -6,5 +6,5 @@ Checkboxes are used for toggling between states. Instead of binding to `input.va ```svelte /// file: App.svelte - + ``` 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 a996ca991..4195b5c69 100644 --- a/content/tutorial/01-svelte/07-lifecycle/01-onmount/README.md +++ b/content/tutorial/01-svelte/07-lifecycle/01-onmount/README.md @@ -4,7 +4,7 @@ title: 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. -In this exercise, we have a `` that we'd like to animate, using the `paint` function in `gradient.js`. Begin by importing the function from `svelte`: +In this exercise, we have a `` that we'd like to animate, using the `paint` function in `gradient.js`. Begin by importing the `onMount` function from `svelte`: ```svelte /// file: App.svelte @@ -14,7 +14,7 @@ In this exercise, we have a `` that we'd like to animate, using the `pai ``` -Then, add a function that runs when the component mounts: +Then, add a callback that runs when the component mounts: ```svelte /// file: App.svelte @@ -41,7 +41,7 @@ So far so good — you should see gently undulating colours in the shape of the ```js /// file: App.svelte onMount(() => { - const canvas = document.querySelector('canvas') + const canvas = document.querySelector('canvas'); const context = canvas.getContext('2d'); +++let frame =+++ requestAnimationFrame(function loop(t) { @@ -49,10 +49,8 @@ onMount(() => { paint(context, t); }); - loop(); - +++ return () => { cancelAnimationFrame(frame); };+++ }); -``` \ No newline at end of file +``` 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 39b42393d..b36b80ce0 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 @@ -5,7 +5,7 @@ +> diff --git a/content/tutorial/01-svelte/07-lifecycle/02-update/app-b/src/lib/App.svelte b/content/tutorial/01-svelte/07-lifecycle/02-update/app-b/src/lib/App.svelte index 58eb97852..c80a29b93 100644 --- a/content/tutorial/01-svelte/07-lifecycle/02-update/app-b/src/lib/App.svelte +++ b/content/tutorial/01-svelte/07-lifecycle/02-update/app-b/src/lib/App.svelte @@ -9,7 +9,10 @@ let autoscroll = false; beforeUpdate(() => { - autoscroll = div && div.offsetHeight + div.scrollTop > div.scrollHeight - 20; + if (div) { + const scrollableDistance = div.scrollHeight - div.offsetHeight; + autoscroll = div.scrollTop > scrollableDistance - 20; + } }); afterUpdate(() => { @@ -23,14 +26,10 @@ const typing = { author: 'eliza', text: '...' }; - let comments = [ - { author: 'eliza', text: eliza.getInitial() } - ]; + let comments = []; async function handleKeydown(event) { if (event.key === 'Enter' && event.target.value) { - event.target.value = ''; - const comment = { author: 'user', text: event.target.value @@ -41,6 +40,7 @@ text: eliza.transform(comment.text) }; + event.target.value = ''; comments = [...comments, comment]; await pause(200 * (1 + Math.random())); @@ -52,37 +52,66 @@ } -
-

Eliza

- -
- {#each comments as comment} -
- {comment.text} -
- {/each} +
+
+
+
+

Eliza

+ +
+ {eliza.getInitial()} +
+
+ + {#each comments as comment} +
+ {comment.text} +
+ {/each} +
+ +
- -
diff --git a/content/tutorial/01-svelte/07-lifecycle/03-tick/app-a/src/lib/App.svelte b/content/tutorial/01-svelte/07-lifecycle/03-tick/app-a/src/lib/App.svelte index 940badc0b..e2c2e5569 100644 --- a/content/tutorial/01-svelte/07-lifecycle/03-tick/app-a/src/lib/App.svelte +++ b/content/tutorial/01-svelte/07-lifecycle/03-tick/app-a/src/lib/App.svelte @@ -27,7 +27,7 @@ diff --git a/content/tutorial/02-advanced-svelte/02-transitions/09-deferred-transitions/app-a/src/lib/TodoList.svelte b/content/tutorial/02-advanced-svelte/02-transitions/09-deferred-transitions/app-a/src/lib/TodoList.svelte new file mode 100644 index 000000000..ddd1fcfe3 --- /dev/null +++ b/content/tutorial/02-advanced-svelte/02-transitions/09-deferred-transitions/app-a/src/lib/TodoList.svelte @@ -0,0 +1,40 @@ + + +
    + {#each $store.filter((todo) => todo.done === done) as todo (todo.id)} +
  • + +
  • + {/each} +
+ + \ No newline at end of file diff --git a/content/tutorial/02-advanced-svelte/02-transitions/09-deferred-transitions/app-a/src/lib/remove.svg b/content/tutorial/02-advanced-svelte/02-transitions/09-deferred-transitions/app-a/src/lib/remove.svg new file mode 100644 index 000000000..434ab0956 --- /dev/null +++ b/content/tutorial/02-advanced-svelte/02-transitions/09-deferred-transitions/app-a/src/lib/remove.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/content/tutorial/03-advanced-svelte/02-transitions/09-deferred-transitions/app-a/src/lib/todos.js b/content/tutorial/02-advanced-svelte/02-transitions/09-deferred-transitions/app-a/src/lib/todos.js similarity index 100% rename from content/tutorial/03-advanced-svelte/02-transitions/09-deferred-transitions/app-a/src/lib/todos.js rename to content/tutorial/02-advanced-svelte/02-transitions/09-deferred-transitions/app-a/src/lib/todos.js diff --git a/content/tutorial/03-advanced-svelte/02-transitions/09-deferred-transitions/app-a/src/lib/transition.js b/content/tutorial/02-advanced-svelte/02-transitions/09-deferred-transitions/app-a/src/lib/transition.js similarity index 100% rename from content/tutorial/03-advanced-svelte/02-transitions/09-deferred-transitions/app-a/src/lib/transition.js rename to content/tutorial/02-advanced-svelte/02-transitions/09-deferred-transitions/app-a/src/lib/transition.js diff --git a/content/tutorial/02-advanced-svelte/02-transitions/09-deferred-transitions/app-b/src/lib/TodoList.svelte b/content/tutorial/02-advanced-svelte/02-transitions/09-deferred-transitions/app-b/src/lib/TodoList.svelte new file mode 100644 index 000000000..680de1fd9 --- /dev/null +++ b/content/tutorial/02-advanced-svelte/02-transitions/09-deferred-transitions/app-b/src/lib/TodoList.svelte @@ -0,0 +1,44 @@ + + +
    + {#each $store.filter((todo) => todo.done === done) as todo (todo.id)} +
  • + +
  • + {/each} +
+ + \ No newline at end of file diff --git a/content/tutorial/03-advanced-svelte/02-transitions/meta.json b/content/tutorial/02-advanced-svelte/02-transitions/meta.json similarity index 100% rename from content/tutorial/03-advanced-svelte/02-transitions/meta.json rename to content/tutorial/02-advanced-svelte/02-transitions/meta.json diff --git a/content/tutorial/03-advanced-svelte/03-animations/01-animate/README.md b/content/tutorial/02-advanced-svelte/03-animations/01-animate/README.md similarity index 93% rename from content/tutorial/03-advanced-svelte/03-animations/01-animate/README.md rename to content/tutorial/02-advanced-svelte/03-animations/01-animate/README.md index 69cb3a625..53f0221a1 100644 --- a/content/tutorial/03-advanced-svelte/03-animations/01-animate/README.md +++ b/content/tutorial/02-advanced-svelte/03-animations/01-animate/README.md @@ -15,15 +15,16 @@ First, import the `flip` function — flip stands for ['First, Last, Invert, Pla import { send, receive } from './transition.js'; export let store; - export let filter; + export let done; ``` -Then add it to the `