Merge branch 'master' of https://github.com/sveltejs/svelte into fix-@html-template-set-innerHTML

pull/7364/head
lidlanca 4 years ago
commit 10337582c5

1
.gitignore vendored

@ -9,6 +9,7 @@ node_modules
/compiler.*js /compiler.*js
/index.*js /index.*js
/ssr.*js /ssr.*js
/action
/internal /internal
/store /store
/easing /easing

@ -1,5 +1,36 @@
# Svelte changelog # Svelte changelog
## Unreleased
* Return the context object in `setContext` [#7427](https://github.com/sveltejs/svelte/issues/7427)
* Fix `{@const}` tag not working inside Component when there's no `let:` [#7189](https://github.com/sveltejs/svelte/issues/7189)
* Ignore comments in `{#each}` blocks when containing elements with `animate:` ([#3999](https://github.com/sveltejs/svelte/issues/3999))
* Add a third parameter to the returned function of `createEventDispatcher` that allows passing an object of `{ cancelable: true }` to create a cancelable custom event. The returned function when called will also return a boolean depending on whether the event is cancelled ([#7064](https://github.com/sveltejs/svelte/pull/7064))
* Fix value of `let:` bindings not updating in certain cases ([#7440](https://github.com/sveltejs/svelte/issues/7440))
* Strip leading newline after `<pre>` and `<textarea>` ([#7264](https://github.com/sveltejs/svelte/issues/7264))
* Fix local transition for `{#key}` block ([#5950](https://github.com/sveltejs/svelte/issues/5950))
* Fix errorneous setting `textContent` for `<template>` element ([#7297](https://github.com/sveltejs/svelte/pull/7297))
## 3.47.0
* Add support for dynamic elements through `<svelte:element>` ([#2324](https://github.com/sveltejs/svelte/issues/2324))
* Miscellaneous variable context fixes in `{@const}` ([#7222](https://github.com/sveltejs/svelte/pull/7222))
* Fix `{#key}` block not being reactive when the key variable is not otherwise used ([#7408](https://github.com/sveltejs/svelte/issues/7408))
* Add `Symbol` as a known global ([#7418](https://github.com/sveltejs/svelte/issues/7418))
## 3.46.6
* Actually include action TypeScript interface in published package ([#7407](https://github.com/sveltejs/svelte/pull/7407))
## 3.46.5
* Add TypeScript interfaces for typing actions ([#6538](https://github.com/sveltejs/svelte/issues/6538))
* Do not generate `unused-export-let` warning inside `<script context="module">` blocks ([#7055](https://github.com/sveltejs/svelte/issues/7055))
* Do not collapse whitespace-only CSS vars ([#7152](https://github.com/sveltejs/svelte/issues/7152))
* Add `aria-description` to the list of allowed ARIA attributes ([#7301](https://github.com/sveltejs/svelte/issues/7301))
* Fix attribute escaping during SSR ([#7327](https://github.com/sveltejs/svelte/issues/7327))
* Prevent `.innerHTML` optimization from being used when `style:` directive is present ([#7386](https://github.com/sveltejs/svelte/issues/7386))
## 3.46.4 ## 3.46.4
* Avoid `maximum call stack size exceeded` errors on large components ([#4694](https://github.com/sveltejs/svelte/issues/4694)) * Avoid `maximum call stack size exceeded` errors on large components ([#4694](https://github.com/sveltejs/svelte/issues/4694))

@ -67,7 +67,7 @@ Working on your first Pull Request? You can learn how from this free video serie
If you would like to request a new feature or enhancement but are not yet thinking about opening a pull request, you can also file an issue with [feature template](https://github.com/sveltejs/svelte/issues/new?template=feature_request.md). If you would like to request a new feature or enhancement but are not yet thinking about opening a pull request, you can also file an issue with [feature template](https://github.com/sveltejs/svelte/issues/new?template=feature_request.md).
If you're only fixing a bug, it's fine to submit a pull request right away but we still recommend that you file an issue detailing what you're fixing. This is helpful in case we don't accept that specific fix but want to keep track of the issue. If you're only fixing a bug, it's fine to submit a pull request right away, but we still recommend that you file an issue detailing what you're fixing. This is helpful in case we don't accept that specific fix but want to keep track of the issue.
### Sending a pull request ### Sending a pull request

86
package-lock.json generated

@ -1,11 +1,11 @@
{ {
"name": "svelte", "name": "svelte",
"version": "3.46.4", "version": "3.47.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"version": "3.46.4", "version": "3.47.0",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@ampproject/remapping": "^0.3.0", "@ampproject/remapping": "^0.3.0",
@ -671,9 +671,9 @@
} }
}, },
"node_modules/ansi-regex": { "node_modules/ansi-regex": {
"version": "4.1.0", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">=6" "node": ">=6"
@ -1682,9 +1682,9 @@
} }
}, },
"node_modules/eslint/node_modules/ansi-regex": { "node_modules/eslint/node_modules/ansi-regex": {
"version": "5.0.0", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@ -2989,9 +2989,9 @@
"dev": true "dev": true
}, },
"node_modules/json-schema": { "node_modules/json-schema": {
"version": "0.2.3", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
"integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
"dev": true "dev": true
}, },
"node_modules/json-schema-traverse": { "node_modules/json-schema-traverse": {
@ -3025,18 +3025,18 @@
} }
}, },
"node_modules/jsprim": { "node_modules/jsprim": {
"version": "1.4.1", "version": "1.4.2",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
"integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==",
"dev": true, "dev": true,
"engines": [
"node >=0.6.0"
],
"dependencies": { "dependencies": {
"assert-plus": "1.0.0", "assert-plus": "1.0.0",
"extsprintf": "1.3.0", "extsprintf": "1.3.0",
"json-schema": "0.2.3", "json-schema": "0.4.0",
"verror": "1.10.0" "verror": "1.10.0"
},
"engines": {
"node": ">=0.6.0"
} }
}, },
"node_modules/kleur": { "node_modules/kleur": {
@ -3235,9 +3235,9 @@
} }
}, },
"node_modules/minimist": { "node_modules/minimist": {
"version": "1.2.5", "version": "1.2.6",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
"dev": true "dev": true
}, },
"node_modules/mkdirp": { "node_modules/mkdirp": {
@ -5008,9 +5008,9 @@
} }
}, },
"node_modules/wide-align/node_modules/ansi-regex": { "node_modules/wide-align/node_modules/ansi-regex": {
"version": "3.0.0", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">=4" "node": ">=4"
@ -5626,9 +5626,9 @@
"dev": true "dev": true
}, },
"ansi-regex": { "ansi-regex": {
"version": "4.1.0", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
"dev": true "dev": true
}, },
"ansi-styles": { "ansi-styles": {
@ -6267,9 +6267,9 @@
}, },
"dependencies": { "dependencies": {
"ansi-regex": { "ansi-regex": {
"version": "5.0.0", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true "dev": true
}, },
"ansi-styles": { "ansi-styles": {
@ -7426,9 +7426,9 @@
"dev": true "dev": true
}, },
"json-schema": { "json-schema": {
"version": "0.2.3", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
"integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
"dev": true "dev": true
}, },
"json-schema-traverse": { "json-schema-traverse": {
@ -7459,14 +7459,14 @@
} }
}, },
"jsprim": { "jsprim": {
"version": "1.4.1", "version": "1.4.2",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
"integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==",
"dev": true, "dev": true,
"requires": { "requires": {
"assert-plus": "1.0.0", "assert-plus": "1.0.0",
"extsprintf": "1.3.0", "extsprintf": "1.3.0",
"json-schema": "0.2.3", "json-schema": "0.4.0",
"verror": "1.10.0" "verror": "1.10.0"
} }
}, },
@ -7629,9 +7629,9 @@
} }
}, },
"minimist": { "minimist": {
"version": "1.2.5", "version": "1.2.6",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
"dev": true "dev": true
}, },
"mkdirp": { "mkdirp": {
@ -9058,9 +9058,9 @@
}, },
"dependencies": { "dependencies": {
"ansi-regex": { "ansi-regex": {
"version": "3.0.0", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==",
"dev": true "dev": true
}, },
"string-width": { "string-width": {

@ -1,6 +1,6 @@
{ {
"name": "svelte", "name": "svelte",
"version": "3.46.4", "version": "3.47.0",
"description": "Cybernetically enhanced web apps", "description": "Cybernetically enhanced web apps",
"module": "index.mjs", "module": "index.mjs",
"main": "index", "main": "index",
@ -16,7 +16,7 @@
"transition", "transition",
"easing", "easing",
"motion", "motion",
"svelte", "action",
"README.md" "README.md"
], ],
"exports": { "exports": {
@ -39,6 +39,9 @@
"import": "./compiler.mjs", "import": "./compiler.mjs",
"require": "./compiler.js" "require": "./compiler.js"
}, },
"./action": {
"types": "./types/runtime/action/index.d.ts"
},
"./animate": { "./animate": {
"types": "./types/runtime/animate/index.d.ts", "types": "./types/runtime/animate/index.d.ts",
"import": "./animate/index.mjs", "import": "./animate/index.mjs",
@ -110,7 +113,7 @@
"bugs": { "bugs": {
"url": "https://github.com/sveltejs/svelte/issues" "url": "https://github.com/sveltejs/svelte/issues"
}, },
"homepage": "https://github.com/sveltejs/svelte#README", "homepage": "https://svelte.dev",
"devDependencies": { "devDependencies": {
"@ampproject/remapping": "^0.3.0", "@ampproject/remapping": "^0.3.0",
"@rollup/plugin-commonjs": "^11.0.0", "@rollup/plugin-commonjs": "^11.0.0",

@ -12,7 +12,9 @@ Because of that, getting started with it can be a little bit confusing at first.
## 1. Use the REPL ## 1. Use the REPL
The [Svelte REPL](/repl) is the easiest way to begin. You can choose from a list of examples to get you started, and tweak them until they do what you want. The [Svelte REPL](/repl) (Read-Eval-Print Loop) is the easiest way to begin. This is an interactive environment that allows you to modify code and instantly see the result.
You can choose from a list of [examples](/examples/), click the [REPL](/repl) link, and then tweak them until they do what you want.
<aside><p>You'll need to have <a href="https://nodejs.org/">Node.js</a> installed, and know how to use the terminal</p></aside> <aside><p>You'll need to have <a href="https://nodejs.org/">Node.js</a> installed, and know how to use the terminal</p></aside>
@ -31,8 +33,7 @@ npm install
npm run dev npm run dev
``` ```
This will serve your app on [localhost:5000](http://localhost:5000) and rebuild it with [Rollup](https://rollupjs.org) every time you make a change to the files in `svelte-app/src`. This will serve your app on [localhost:8080](http://localhost:8080) and rebuild it with [Rollup](https://rollupjs.org) every time you make a change to the files in `svelte-app/src`.
## 2. Use degit ## 2. Use degit
@ -50,7 +51,7 @@ npm install
npm run dev npm run dev
``` ```
This will create a new project in the `my-svelte-project` directory, install its dependencies, and start a server on http://localhost:5000. This will create a new project in the `my-svelte-project` directory, install its dependencies, and start a server on http://localhost:8080.
You can find more information about using TypeScript [here](/blog/svelte-and-typescript). You can find more information about using TypeScript [here](/blog/svelte-and-typescript).

@ -8,7 +8,7 @@ draft: true
*__Coming soon__* *__Coming soon__*
This post will walk you through setting up your editor so that recognises Svelte files: This post will walk you through setting up your editor so that it recognises Svelte files:
* eslint-plugin-svelte3 * eslint-plugin-svelte3
* svelte-vscode * svelte-vscode

@ -78,7 +78,7 @@ npm run dev
Running the `dev` script starts a program called [Rollup](https://rollupjs.org/guide/en/). Rollup's job is to take your application's source files (so far, just `src/main.js` and `src/App.svelte`), pass them to other programs (including Svelte, in our case) and convert them into the code that will actually run when you open the application in a browser. Running the `dev` script starts a program called [Rollup](https://rollupjs.org/guide/en/). Rollup's job is to take your application's source files (so far, just `src/main.js` and `src/App.svelte`), pass them to other programs (including Svelte, in our case) and convert them into the code that will actually run when you open the application in a browser.
Speaking of which, open a browser and navigate to http://localhost:5000. This is your application running on a local *web server* (hence 'localhost') on port 5000. Speaking of which, open a browser and navigate to http://localhost:8080. This is your application running on a local *web server* (hence 'localhost') on port 8080.
Try changing `src/App.svelte` and saving it. The application will reload with your changes. Try changing `src/App.svelte` and saving it. The application will reload with your changes.
@ -99,7 +99,7 @@ Your `public` directory now contains a compressed `bundle.js` file containing yo
npm run start npm run start
``` ```
This will run the app on http://localhost:5000. This will run the app on http://localhost:8080.
## Next steps ## Next steps

@ -0,0 +1,87 @@
---
title: "What's new in Svelte: March 2022"
description: "Svelte Summit Spring is coming... and page endpoints are here!"
author: Daniel Sandoval
authorURL: https://desandoval.net
---
Just announced: [Svelte Summit Spring](https://www.sveltesummit.com/) will be taking place on April 30, 2022. The 5th Virtual Svelte Conference is [looking for speakers](https://www.sveltesummit.com/#speakers) and [sponsors](https://www.sveltesummit.com/sponsors)... so it's time to dust off those proposals!
Also, some long-requested features were added to SvelteKit this month... including page endpoints! This change in how the `load` function works makes it easier to fetch data required for basic pages, redirect from POST responses and handle 404s and other errors.
More on that and other new features and fixes below!
## What's new in SvelteKit
- The docs are now searchable and multipage with type definitions and hoverable code examples - Check them out at [kit.svelte.dev/docs](https://kit.svelte.dev/docs/)
- Page endpoints significantly decrease the boilerplate needed when loading a page ([Issue](https://github.com/sveltejs/kit/issues/3532), [PR](https://github.com/sveltejs/kit/pull/3679), [Docs](https://kit.svelte.dev/docs/routing#endpoints-page-endpoints))
- Application versioning and update detection support lets you determine what to do when a route fails to load after an app update ([Issue](https://github.com/sveltejs/kit/issues/87), [PR](https://github.com/sveltejs/kit/pull/3412), [Docs](https://kit.svelte.dev/docs/configuration#version))
- A new option in `npm init svelte@next` will now set up Playwright automatically for testing ([PR](https://github.com/sveltejs/kit/pull/4056))
**Breaking Changes**
- The `target` option is no longer available. Instead, the `init` script hydrates its `parentNode` ([#3674](https://github.com/sveltejs/kit/pull/3674))
- App-level types now live in the `App` namespace which allows you to type global types like `Stuff` or `Session` ([#3670](https://github.com/sveltejs/kit/pull/3670))
- `JSONString` is now `JSONValue` ([#3683](https://github.com/sveltejs/kit/pull/3683))
- `createIndexFiles` has been removed — it is now controlled by the `trailingSlash` option ([#3801](https://github.com/sveltejs/kit/pull/3801))
- SvelteKit will no longer exclude root-relative external links from prerendering, which will cause 404s if these URLs are intended to be served by a separate app. Use a custom [`prerender.onError`](https://kit.svelte.dev/docs/configuration#prerender) handler if you need to ignore them ([#3826](https://github.com/sveltejs/kit/pull/3826))
## New in Language Tools
- Accessing properties in markups has been improved in the Svelte language tools ([105.12.0](https://github.com/sveltejs/language-tools/releases/tag/extensions-105.12.0)) - working around some known issues with autocomplete ([#538](https://github.com/sveltejs/language-tools/issues/538) / [#1302](https://github.com/sveltejs/language-tools/issues/1302))
---
## Community Showcase
**Apps & Sites**
- [SvelteStorm](https://github.com/open-source-labs/SvelteStorm) is specifically tailored to provide all of the essential tools a Svelte developer needs to build a Svelte application
- [Supachat](https://github.com/Lleweraf/supachat) is a real-time chat app using Svelte and Supabase
- [Radicle](https://radicle.xyz/) is a peer-to-peer stack for building software together
- [The Making Known](https://the-making-known.com/) is a narrated encounter with posters designed by the Nazi German government to communicate with the occupied nations of Belgium, France, and Luxembourg during the Second World War
- [Svelte Kanban](https://github.com/V-Py/svelte-kanban) is a simple Svelte Kanban made in pure CSS
- [fngrng](https://github.com/nvlgzr/fngrng) is a typing trainer focussed on accuracy over speed
- [Generative grids](https://svelte.dev/repl/873988ce33db43f097c0ca69df57b3ac?version=3.46.4) is a neat little generative SVG grid in a Svelte REPL, with randomly generated color palettes and shapes
- [LifeHash](https://github.com/BlockchainCommons/lifehash.info) is a method of hash visualization that creates beautiful, deterministic icons
- [TypedWebhook.tools](https://typedwebhook.tools/) is a webhook testing tool for checking payloads, with automatic type generation
- [Speedskating](https://github.com/spiegelgraphics/speedskating) is an animation widget to show olympic speedskating runs. Built with Svelte, D3 and regl
- [Web tail](https://github.com/mishankov/web-tail) is a web application to view lines from file on local system or on remote server
Want to work on a SvelteKit site with others? [Contribute to the Svelte Society site](https://github.com/svelte-society/sveltesociety.dev/issues)!
**Learning Resources**
_To Read_
- [Svelte Components as Web Components](https://medium.com/@yesmeno/svelte-components-as-web-components-b400d1253504) by Matias Meno
- [Simple Svelte Routing with Reactive URLs](https://bjornlu.com/blog/simple-svelte-routing-with-reactive-urls) by Bjorn Lu
- [Leveling Up my Sveltekit / Sanity.io Blog Content with Featured Videos and Syntax Highlighting](https://ryanboddy.net/level-up-blog) by Ryan Boddy
- [How This Blog Makes the Most of GitHub](https://paullj.github.io/posts/how-this-blog-makes-the-most-of-github/) by paullj
- [FullStack JWT Auth: Introducing SvelteKit](https://dev.to/sirneij/fullstack-jwt-introducing-sveltekit-3jcn) by John Idogun
- [Svelte-Cubed: Adding Motion to 3D Scenes](https://dev.to/alexwarnes/svelte-cubed-adding-motion-to-3d-scenes-51lo) by Alex Warnes
- [Creating a RSS feed with Sanity and Svelte Kit](https://ghostdev.xyz/posts/creating-a-rss-feed-with-sanity-and-svelte-kit) by GHOST
- [How to use Svelte's style directive](https://geoffrich.net/posts/style-directives/) by Geoff Rich
- [SvelteKit and the "Client pattern"](https://retro.cloud/sveltekit-and-the-client-pattern/) by Julian Laubstein
_To Watch_
- [~~Shadow~~ Page Endpoints In Svelte Kit - Weekly Svelte](https://www.youtube.com/watch?v=PoYPZT7ruqI) by LevelUpTuts
- [Testing For Beginners (Playlist)](https://www.youtube.com/watch?v=y53wwdBr5AI&list=PLA9WiRZ-IS_z7KpqhPELfEMbhAGRwZrzn) by Joy of Code
- [KitQL - The native SvelteKit library for GraphQL](https://www.youtube.com/watch?v=6pH4fnFN70w) by Jean-Yves COUËT
**Libraries, Tools & Components**
- [gosvelte](https://github.com/sachinbhutani/gosvelte) is a proof of concept to serve Svelte-generated pages on GoLang HTTP server with server data being sent as props to svelte components
- [svelte-ethers-store](https://www.npmjs.com/package/svelte-ethers-store) uses the ethers.js library as a collection of readable Svelte stores for Svelte, Sapper or SvelteKit
- [Fluid Grid](https://fluid-grid.com/) is a CSS grid system for future web
- [stirstack](https://github.com/seeReadCode/stirstack) is an opinionated framework that combines Svelte.js, TailwindCSS, InertiaJS and Ruby on Rails
- [OATHqr](https://codeberg.org/vhs/oathqr) helps users create security credentials for use with 2FA/MFA and other OATH-enabled apps. Use it to generate scannable QR codes for one-time password authenticator apps such as Aegis or YubiKey
- [svelte-GridTiles](https://github.com/honeybeeSunshine/svelte-GridTiles) is a drag and drop resizable tiles library built on a responsive grid
- [Miscellaneous Svelte Components](https://github.com/alex-knyaz/Miscellaneous-svelte-components/) is a collection of miscellaneous svelte components alex-knyaz often use in my projects
- [walk-and-graph-svelte-components](https://github.com/j2l/walk-and-graph-svelte-components) is a CLI node script to walk svelte and js files, to draw a beautiful JPG of your dependencies aka "imports"
- [Felte](https://www.npmjs.com/package/felte) is a simple to use form library for Svelte
- [svelte-use-tooltip](https://github.com/untemps/svelte-use-tooltip) is a Svelte action to display a tooltip
- [persistent-svelte-store](https://github.com/omer-g/persistent-svelte-store) is a generic persistent writable store, built from scratch in TypeScript according to the Svelte store contract
What'd we miss? Join us on [Reddit](https://www.reddit.com/r/sveltejs/) or [Discord](https://discord.com/invite/yy75DKs) to continue the conversation.
See y'all next month!

@ -0,0 +1,104 @@
---
title: "What's new in Svelte: April 2022"
description: "Goodbye fallthrough routes, hello param validators!"
author: Daniel Sandoval
authorURL: https://desandoval.net
---
This month, we felt a shift in the way SvelteKit handles page properties. The last holdout of the use-cases that required fallthrough routes, validating parameter properties, has been replaced by a more specific solution.
More on that, and what else is new in Svelte, as we dive in...
## What's new in SvelteKit
- Param matchers allow you to check if a url parameter matches before rendering a page - replacing the need for fallthrough routes for this purpose ([Docs](https://kit.svelte.dev/docs/routing#advanced-routing-matching), [#4334](https://github.com/sveltejs/kit/pull/4334))
- Explicit redirects can now be handled directly from endpoints ([#4260](https://github.com/sveltejs/kit/pull/4260))
- `svelte-kit sync` ([#4182](https://github.com/sveltejs/kit/pull/4182)), TypeScript 4.6 ([#4190](https://github.com/sveltejs/kit/pull/4190)) and Vite 2.9 were released - adding non-blocking dependency optimization and experimental CSS source maps in dev mode as well as a number of bug fixes contributed by the SvelteKit team ([#4468](https://github.com/sveltejs/kit/pull/4468))
**New Config Options**
- `outDir` fixes path issues in monorepos and other situations where the desired output directory is outside the project directory ([Docs](https://kit.svelte.dev/docs/configuration#outdir), [#4176](https://github.com/sveltejs/kit/pull/4176))
- `endpointExtensions` prevents files other than .js and .ts files from being treated as endpoints, unless you specify endpointExtensions ([Docs](https://kit.svelte.dev/docs/configuration#endpointextensions), [#4197](https://github.com/sveltejs/kit/pull/4197))
- `prerender.default` lets you prerender every page without having to write `export const prerender = true` in every page file ([Docs](https://kit.svelte.dev/docs/configuration#prerender), [#4192](https://github.com/sveltejs/kit/pull/4192))
**Breaking Changes**
- Fallthrough routes have been removed. For migration tips, check out the PR ([#4330](https://github.com/sveltejs/kit/pull/4330))
- `tabindex="-1"` is only added to `<body>` during navigation ([#4140](https://github.com/sveltejs/kit/pull/4140) and [#4184](https://github.com/sveltejs/kit/pull/4184))
- Adapters are now required to supply a `getClientAddress` function ([#4289](https://github.com/sveltejs/kit/pull/4289))
- `InputProps` and `OutputProps` can now be typed separately in generated `Load` ([#4305](https://github.com/sveltejs/kit/pull/4305))
- The `\$` character is no longer allowed in dynamic parameters ([#4334](https://github.com/sveltejs/kit/pull/4334))
- `svelte-kit package` has been marked as experimental so changes to it after Kit 1.0 will not be considered breaking ([#4164](https://github.com/sveltejs/kit/pull/4164))
## New across the Svelte ecosystem
- Svelte: Lots of new types for TypeScript and Svelte plugin users - including `style:` directives and Svelte Actions (**3.46.4** and **3.46.5**)
- Language Tools: Svelte project files are now importable/findable through references without having them imported in a TS file ([105.13.0](https://github.com/sveltejs/language-tools/releases/tag/extensions-105.13.0))
- Language Tools: Region folding is now supported in html with `<!--#region-->`/`<!--#endregion-->` ([105.13.0](https://github.com/sveltejs/language-tools/releases/tag/extensions-105.13.0))
---
## Community Showcase
**Apps & Sites built with Svelte**
- [Launcher](https://launcher.team/) is an open-source app launcher powered by SvelteKit, Prisma, and Tailwind
- [Paaster](https://paaster.io/) is a secure by default end to end encrypted pastebin built with Svelte, Vite, Typescript, Python, Starlette, rclone & Docker.
- [Simple AF Video Converter](https://github.com/berlyozzy/Simple-AF-Video-Converter) is an Electron wrapper around ffmpeg.wasm to make converting videos between formats easier
- [Streamchaser](https://github.com/streamchaser/streamchaser) seeks to simplify movie, series and documentary search through a centralized entertainment technology platform
- [Svelte Color Picker](https://github.com/V-Py/svelte-material-color-picker) is a simple color picker made with Svelte
- [ConcertMash](https://github.com/mcmxcdev/ConcertMash) is a small website that interacts with the Spotify API and generates new playlists based on the upcoming concerts you're attending
- [Modulus](https://modulus.vision/) is a Design+Code Think Tank conceived with the main mission to evolve design and technology
- [Multiply](https://www.multiply.us/) is an integrated PR and Social agency moving at the speed of culture
- [yia!](https://www.yia.co.nz/) is a Young Innovator Award competition in New Zealand
- [Write to Russia](https://www.writetorussia.org/index) is a community email writing platform to communicate with public `.ru` email addresses
- [Markdown Playground](https://github.com/Petros-K/markdown-playground) is an online playground dedicated for your markdown experiments.
- [RatherMisty](https://rathermisty.com/) is a no frills weather app with weather data from Open-Meteo
- [Minecraft Profile Pic (MCPFP)](https://github.com/MauritsWilke/mcpfp) is a site to generate Minecraft profile pictures with ease
- [WebGL Fluid Simulation](https://github.com/jpaquim/svelte-webgl-fluid-simulation) is a configurable fluid simulation built with Svelte and WebGL
- [This @NobelPeaceOslo exhibition](https://twitter.com/perbyhring/status/1504754949791621120) was built using printed graphics, projected motion graphics, particle animations and generative sound design
Itching to contribute to a modern SvelteKit website? [Help build the Svelte Society site](https://github.com/svelte-society/sveltesociety.dev/issues)!
**Learning Resources**
_To Attend_
- [Svelte Summit: Spring](https://www.sveltesummit.com/) will take place on April 30, 2022! Join us for the 5th virtual Svelte conference on [YouTube](https://www.sveltesummit.com/) and Discord 🍾
_To Read_
- [Svelte(Kit) TypeScript Showcase + general TypeScript tips](https://github.com/ivanhofer/sveltekit-typescript-showcase) by Hofer Ivan
- [Local constants in Svelte with the @const tag](https://geoffrich.net/posts/local-constants/) by Geoff Rich
- [Design Patterns for Building Reusable Svelte Components](https://render.com/blog/svelte-design-patterns) by Eric Liu
- [Svelte is better than React](https://labs.hamy.xyz/posts/svelte-is-better-than-react/) by Hamilton Greene
- [Making Visualizations Literally with Svelte and D3](https://www.connorrothschild.com/post/svelte-and-d3) by Connor Rothschild
- [Coordinating Multiple Elements with Svelte Deferred Transitions](https://imfeld.dev/writing/svelte_deferred_transitions) by Daniel Imfeld
- [Animate on scroll with Svelte Inview - Little Bits](https://dev.to/maciekgrzybek/animate-on-scroll-with-svelte-inview-266f) by Maciek Grzybek
- [Lazy-Loading Firebase with SvelteKit](https://www.captaincodeman.com/lazy-loading-firebase-with-sveltekit) and [HeadlessUI Components with Svelte](https://www.captaincodeman.com/headlessui-components-with-svelte) by Captain Codeman
- [SvelteKit Accessibility Testing: Automated CI A11y Tests](https://rodneylab.com/sveltekit-accessibility-testing/) by Rodney Lab
- [Getting Started with KitQL and GraphCMS](https://scottspence.com/posts/getting-started-with-kitql-and-graphcms) by Scott Spence
- [React ⇆ Svelte Cheatsheet](https://dev.to/joshnuss/react-to-svelte-cheatsheet-1a2a) lists the similarities and differences between the two libraries - by Joshua Nussbaum
_To Watch_
- [Svelte Extravaganza | Async](https://www.youtube.com/watch?v=mT4CLVHgtSg) by pngwn
- [6 Svelte Packages You Should Know](https://www.youtube.com/watch?v=y5SrUKcX_Co) and [Basic React To Svelte Conversion](https://www.youtube.com/watch?v=DiSuwLlhOxs) by LevelUpTuts
- [Page/Shadow Endpoint in SvelteKit](https://www.youtube.com/watch?v=j-9D5UDyVOM) by WebJeda
- [Custom Svelte Store: Higher Order Store](https://www.youtube.com/watch?v=p1aPfVyZ1IY) by lihautan
- [SvelteKit For Beginners (Playlist)](https://www.youtube.com/watch?v=bLBHecY4-ak&list=PLA9WiRZ-IS_zXZZyW4qfj0akvOAtk6MFS) by Joy of Code - follow along with the [blog guide](https://joyofcode.xyz/sveltekit-for-beginners)
- [Fullstack SvelteKit Auth 🔐 with Firebase & Magic Links! 🪄](https://www.youtube.com/watch?v=MAHE4iQgh5Q) by Johnny Magrippis
- [Firebase Authentication in SvelteKit! Full Stack App](https://www.youtube.com/watch?v=N6Y3hqhZvNI) by Ryan Boddy
**Libraries, Tools & Components**
- [SvelTable](https://sveltable.io/) is a feature rich, data table component built with Svelte
- [svelte-cyberComp](https://github.com/Cybersteam00/svelte-cyberComp) is a powerful, lightweight component library written in Svelte and Typescript
- [Flowbite Svelte](https://github.com/shinokada/flowbite-svelte) is an unofficial Flowbite component library for Svelte
- [Svelte-Tide-Project](https://github.com/jbertovic/svelte-tide-project) is a starter template for Svelte frontend apps with Rust Tide backend server
- [Fetch Inject](https://github.com/vhscom/fetch-inject#sveltekit) implements a performance optimization technique for managing asynchronous JavaScript dependencies - now with Svelte support
- [svelte-utterances](https://github.com/shinokada/svelte-utterances) is a lightweight comments widget built on GitHub issues
- [Liquivelte](https://github.com/malipetek/liquivelte-vscode) allows you to create your Shopify theme with Svelte-like components
- [@storyblok/svelte](https://github.com/storyblok/storyblok-svelte) is the Svelte SDK you need to interact with Storyblok API and enable the Real-time Visual Editing Experience
- [@svelte-on-solana/wallet-adapter](https://github.com/svelte-on-solana/wallet-adapter) is a modular TypeScript wallet adapter and UI components for Solana/Anchor applications using SvelteJS as framework
- [svelte-lookat](https://www.npmjs.com/package/svelte-lookat) creates a div which makes all its children follow the mouse cursor or the user's face when using a mobile phone
Join us on [Reddit](https://www.reddit.com/r/sveltejs/) or [Discord](https://discord.com/invite/yy75DKs) to continue the conversation.
See y'all next month!

@ -1627,6 +1627,28 @@ If `this` is falsy, no component is rendered.
<svelte:component this={currentSelection.component} foo={bar}/> <svelte:component this={currentSelection.component} foo={bar}/>
``` ```
### `<svelte:element>`
```sv
<svelte:element this={expression}/>
```
---
The `<svelte:element>` element lets you render an element of a dynamically specified type. This is useful for example when rich text content from a CMS. If the tag is changed, the children will be preserved unless there's a transition attached to the element. Any properties and event listeners present will be applied to the element.
The only supported binding is `bind:this`, since the element type specific bindings that Svelte does at build time (e.g. `bind:value` for input elements) does not work with a dynamic tag type.
If `this` has a nullish value, a warning will be logged in development mode.
```sv
<script>
let tag = 'div';
export let handler;
</script>
<svelte:element this={tag} on:click={handler}>Foo</svelte:element>
```
### `<svelte:window>` ### `<svelte:window>`
@ -1683,7 +1705,7 @@ All except `scrollX` and `scrollY` are readonly.
Similarly to `<svelte:window>`, this element allows you to add listeners to events on `document.body`, such as `mouseenter` and `mouseleave`, which don't fire on `window`. It also lets you use [actions](/docs#template-syntax-element-directives-use-action) on the `<body>` element. Similarly to `<svelte:window>`, this element allows you to add listeners to events on `document.body`, such as `mouseenter` and `mouseleave`, which don't fire on `window`. It also lets you use [actions](/docs#template-syntax-element-directives-use-action) on the `<body>` element.
`<svelte:body>` also has to appear at the top level of your component. As with `<svelte:window>`, this element may only appear the top level of your component and must never be inside a block or element.
```sv ```sv
<svelte:body <svelte:body
@ -1704,7 +1726,7 @@ Similarly to `<svelte:window>`, this element allows you to add listeners to even
This element makes it possible to insert elements into `document.head`. During server-side rendering, `head` content is exposed separately to the main `html` content. This element makes it possible to insert elements into `document.head`. During server-side rendering, `head` content is exposed separately to the main `html` content.
As with `<svelte:window>` and `<svelte:body>`, this element has to appear at the top level of your component and cannot be inside a block or other element. As with `<svelte:window>` and `<svelte:body>`, this element may only appear at the top level of your component and must never be inside a block or element.
```sv ```sv
<svelte:head> <svelte:head>

@ -148,7 +148,7 @@ setContext(key: any, context: any)
--- ---
Associates an arbitrary `context` object with the current component and the specified `key`. The context is then available to children of the component (including slotted content) with `getContext`. Associates an arbitrary `context` object with the current component and the specified `key` and returns that object. The context is then available to children of the component (including slotted content) with `getContext`.
Like lifecycle functions, this must be called during component initialisation. Like lifecycle functions, this must be called during component initialisation.
@ -221,14 +221,14 @@ Retrieves the whole context map that belongs to the closest parent component. Mu
#### `createEventDispatcher` #### `createEventDispatcher`
```js ```js
dispatch: ((name: string, detail?: any) => void) = createEventDispatcher(); dispatch: ((name: string, detail?: any, options?: DispatchOptions) => boolean) = createEventDispatcher();
``` ```
--- ---
Creates an event dispatcher that can be used to dispatch [component events](/docs#template-syntax-component-directives-on-eventname). Event dispatchers are functions that can take two arguments: `name` and `detail`. Creates an event dispatcher that can be used to dispatch [component events](/docs#template-syntax-component-directives-on-eventname). Event dispatchers are functions that can take two arguments: `name` and `detail`.
Component events created with `createEventDispatcher` create a [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent). These events do not [bubble](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#Event_bubbling_and_capture) and are not cancellable with `event.preventDefault()`. The `detail` argument corresponds to the [CustomEvent.detail](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/detail) property and can contain any type of data. Component events created with `createEventDispatcher` create a [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent). These events do not [bubble](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#Event_bubbling_and_capture). The `detail` argument corresponds to the [CustomEvent.detail](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/detail) property and can contain any type of data.
```sv ```sv
<script> <script>
@ -254,6 +254,27 @@ Events dispatched from child components can be listened to in their parent. Any
<Child on:notify="{callbackFunction}"/> <Child on:notify="{callbackFunction}"/>
``` ```
---
Events can be cancelable by passing a third parameter to the dispatch function. The function returns `false` if the event is cancelled with `event.preventDefault()`, otherwise it returns `true`.
```sv
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
function notify() {
const shouldContinue = dispatch('notify', 'detail value', { cancelable: true });
if (shouldContinue) {
// no one called preventDefault
} else {
// a listener called preventDefault
}
}
</script>
```
### `svelte/store` ### `svelte/store`
The `svelte/store` module exports functions for creating [readable](/docs#run-time-svelte-store-readable), [writable](/docs#run-time-svelte-store-writable) and [derived](/docs#run-time-svelte-store-derived) stores. The `svelte/store` module exports functions for creating [readable](/docs#run-time-svelte-store-readable), [writable](/docs#run-time-svelte-store-writable) and [derived](/docs#run-time-svelte-store-derived) stores.

@ -2,11 +2,7 @@
title: Compile time title: Compile time
--- ---
Typically, you won't interact with the Svelte compiler directly, but will instead integrate it into your build system using a bundler plugin: Typically, you won't interact with the Svelte compiler directly, but will instead integrate it into your build system using a bundler plugin. The bundler plugin that the Svelte team most recommends and invests in is [vite-plugin-svelte](https://github.com/sveltejs/vite-plugin-svelte). The [SvelteKit](https://kit.svelte.dev/) framework provides a setup leveraging `vite-plugin-svelte` to build applications as well as a [tool for packaging Svelte component libraries](https://kit.svelte.dev/docs/packaging). Svelte Society maintains a list of [other bundler plugins](https://sveltesociety.dev/tools/#bundling) for additional tools like Rollup and Webpack.
* [rollup-plugin-svelte](https://github.com/sveltejs/rollup-plugin-svelte) for users of [Rollup](https://rollupjs.org)
* [svelte-loader](https://github.com/sveltejs/svelte-loader) for users of [webpack](https://webpack.js.org)
* or one of the [community-maintained plugins](https://sveltesociety.dev/tools)
Nonetheless, it's useful to understand how to use the compiler, since bundler plugins generally expose compiler options to you. Nonetheless, it's useful to understand how to use the compiler, since bundler plugins generally expose compiler options to you.
@ -77,7 +73,7 @@ The following options can be passed to the compiler. None are required:
| `accessors` | `false` | If `true`, getters and setters will be created for the component's props. If `false`, they will only be created for readonly exported values (i.e. those declared with `const`, `class` and `function`). If compiling with `customElement: true` this option defaults to `true`. | `accessors` | `false` | If `true`, getters and setters will be created for the component's props. If `false`, they will only be created for readonly exported values (i.e. those declared with `const`, `class` and `function`). If compiling with `customElement: true` this option defaults to `true`.
| `customElement` | `false` | If `true`, tells the compiler to generate a custom element constructor instead of a regular Svelte component. | `customElement` | `false` | If `true`, tells the compiler to generate a custom element constructor instead of a regular Svelte component.
| `tag` | `null` | A `string` that tells Svelte what tag name to register the custom element with. It must be a lowercase alphanumeric string with at least one hyphen, e.g. `"my-element"`. | `tag` | `null` | A `string` that tells Svelte what tag name to register the custom element with. It must be a lowercase alphanumeric string with at least one hyphen, e.g. `"my-element"`.
| `css` | `true` | If `true`, styles will be included in the JavaScript class and injected at runtime. It's recommended that you set this to `false` and use the CSS that is statically generated, as it will result in smaller JavaScript bundles and better performance. | `css` | `true` | If `true`, styles will be included in the JavaScript class and injected at runtime for the components actually rendered. If `false`, the CSS will be returned in the `css` field of the compilation result. Most Svelte bundler plugins will set this to `false` and use the CSS that is statically generated for better performance, as it will result in smaller JavaScript bundles and the output can be served as cacheable `.css` files.
| `cssHash` | See right | A function that takes a `{ hash, css, name, filename }` argument and returns the string that is used as a classname for scoped CSS. It defaults to returning `svelte-${hash(css)}` | `cssHash` | See right | A function that takes a `{ hash, css, name, filename }` argument and returns the string that is used as a classname for scoped CSS. It defaults to returning `svelte-${hash(css)}`
| `loopGuardTimeout` | 0 | A `number` that tells Svelte to break the loop if it blocks the thread for more than `loopGuardTimeout` ms. This is useful to prevent infinite loops. **Only available when `dev: true`** | `loopGuardTimeout` | 0 | A `number` that tells Svelte to break the loop if it blocks the thread for more than `loopGuardTimeout` ms. This is useful to prevent infinite loops. **Only available when `dev: true`**
| `preserveComments` | `false` | If `true`, your HTML comments will be preserved during server-side rendering. By default, they are stripped out. | `preserveComments` | `false` | If `true`, your HTML comments will be preserved during server-side rendering. By default, they are stripped out.

@ -0,0 +1,12 @@
<script>
const options = ['h1', 'h3', 'p'];
let selected = options[0];
</script>
<select bind:value={selected}>
{#each options as option}
<option value={option}>{option}</option>
{/each}
</select>
<svelte:element this={selected}>I'm a {selected} tag</svelte:element>

@ -2,4 +2,4 @@
question: How do I do hot module reloading? question: How do I do hot module reloading?
--- ---
We recommend using [SvelteKit](https://kit.svelte.dev/), which supports HMR out of the box and is built on top of Vite and svelte-hmr. There are also community plugins for [rollup](https://github.com/rixo/rollup-plugin-svelte-hot) and [webpack](https://github.com/rixo/svelte-loader-hot). We recommend using [SvelteKit](https://kit.svelte.dev/), which supports HMR out of the box and is built on top of [Vite](https://vitejs.dev/) and [svelte-hmr](https://github.com/sveltejs/svelte-hmr). There are also community plugins for [rollup](https://github.com/rixo/rollup-plugin-svelte-hot) and [webpack](https://github.com/sveltejs/svelte-loader).

@ -0,0 +1,5 @@
---
question: Are there any third-party resources?
---
Svelte Society maintains a [list of books and videos](https://sveltesociety.dev/resources).

@ -1,18 +0,0 @@
---
question: Are there any video courses?
---
Rich Harris, the creator of Svelte, taught a course:
- [Frontend Masters](https://frontendmasters.com/courses/svelte/)
There are also a number of third-party courses:
- [Egghead](https://egghead.io/browse/frameworks/svelte)
- [Udemy](https://www.udemy.com/courses/search/?q=sveltejs+svelte) (Note: Udemy frequently has discounts over 90%)
- [Pluralsight](https://www.pluralsight.com/search?q=svelte)
Finally, there are also YouTube channels and playlists that teach Svelte:
- [Svelte Master](https://www.youtube.com/channel/UCg6SQd5jnWo5Y70rZD9SQFA)
- [Svelte Tutorial for Beginners](https://www.youtube.com/watch?v=zojEMeQGGHs&list=PL4cUxeGkcC9hlbrVO_2QFVqVPhlZmz7tO) by The Net Ninja

@ -1,9 +0,0 @@
---
question: Are there any books?
---
There are a few books:
- [Svelte Handbook](https://flaviocopes.com/page/svelte-handbook/) by Flavio Copes
- [Svelte 3 Up and Running](https://www.amazon.com/dp/B08D6T6BKS/) by Alessandro Segala
- [Svelte and Sapper in Action](https://www.manning.com/books/svelte-and-sapper-in-action) by R. Mark Volkmann

@ -8,4 +8,5 @@ There are a few approaches that people take when testing, but it generally invol
Some resources for getting started with unit testing: Some resources for getting started with unit testing:
- [Svelte Testing Library](https://testing-library.com/docs/svelte-testing-library/example/) - [Svelte Testing Library](https://testing-library.com/docs/svelte-testing-library/example/)
- [Example using vitest](https://github.com/vitest-dev/vitest/tree/main/examples/svelte)
- [Example using uvu test runner with JSDOM](https://github.com/lukeed/uvu/tree/master/examples/svelte) - [Example using uvu test runner with JSDOM](https://github.com/lukeed/uvu/tree/master/examples/svelte)

@ -2,9 +2,13 @@
title: Nested components title: Nested components
--- ---
It would be impractical to put your entire app in a single component. Instead, we can import components from other files and include them as though we were including elements. It would be impractical to put your entire app in a single component. Instead, we can import components from other files and then use them as though we were including elements.
Add a `<script>` tag that imports `Nested.svelte`... We now present you 2 files in the editor on the right (upper bar) to click on, `App.svelte` and `Nested.svelte`.
Each `.svelte` file holds a component that is a reusable self-contained block of code that encapsulates HTML, CSS, and JavaScript that belong together.
Let's add a `<script>` tag to `App.svelte` that imports the file (our component) `Nested.svelte` into our app...
```html ```html
<script> <script>
@ -12,7 +16,7 @@ Add a `<script>` tag that imports `Nested.svelte`...
</script> </script>
``` ```
...then add it to the markup: ...then use component `Nested` in the app markup:
```html ```html
<p>This is a paragraph.</p> <p>This is a paragraph.</p>

@ -14,7 +14,11 @@ First, you'll need to integrate Svelte with a build tool. There are officially m
Don't worry if you're relatively new to web development and haven't used these tools before. We've prepared a simple step-by-step guide, [Svelte for new developers](/blog/svelte-for-new-developers), which walks you through the process. Don't worry if you're relatively new to web development and haven't used these tools before. We've prepared a simple step-by-step guide, [Svelte for new developers](/blog/svelte-for-new-developers), which walks you through the process.
You'll also want to configure your text editor. There are [plugins](https://sveltesociety.dev/tools#editor-support) for many popular editors as well as an official [VS Code extension](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). If your editor does not have a Svelte plugin then you can follow [this guide](/blog/setting-up-your-editor) to configure your text editor to treat `.svelte` files the same as `.html` for the sake of syntax highlighting. You'll also want to configure your text editor. There are [plugins](https://sveltesociety.dev/tools#editor-support) for many popular editors as well as an official [VS Code extension](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode).
<!--
NOTE: Removed until we have better place for setting-up-your-editor guide. See https://github.com/sveltejs/svelte/pull/7310#issuecomment-1049923609
If your editor does not have a Svelte plugin then you can follow [this guide](/blog/setting-up-your-editor) to configure your text editor to treat `.svelte` files the same as `.html` for the sake of syntax highlighting. -->
Then, once you've got your project set up, using Svelte components is easy. The compiler turns each component into a regular JavaScript class — just import it and instantiate with `new`: Then, once you've got your project set up, using Svelte components is easy. The compiler turns each component into a regular JavaScript class — just import it and instantiate with `new`:

@ -4,7 +4,7 @@ title: Keyed each blocks
By default, when you modify the value of an `each` block, it will add and remove items at the *end* of the block, and update any values that have changed. That might not be what you want. By default, when you modify the value of an `each` block, it will add and remove items at the *end* of the block, and update any values that have changed. That might not be what you want.
It's easier to show why than to explain. Click the 'Remove first thing' button a few times, and notice what happens: It removes the first `<Thing>` component, but the *last* DOM node. Then it updates the `name` value in the remaining DOM nodes, but not the emoji. It's easier to show why than to explain. Click the 'Remove first thing' button a few times, and notice what happens: it does not remove the first `<Thing>` component, but rather the *last* DOM node. Then it updates the `name` value in the remaining DOM nodes, but not the emoji.
Instead, we'd like to remove only the first `<Thing>` component and its DOM node, and leave the others unaffected. Instead, we'd like to remove only the first `<Thing>` component and its DOM node, and leave the others unaffected.

@ -10,4 +10,4 @@ We can also use `bind:value` with `<select>` elements. Update line 20:
Note that the `<option>` values are objects rather than strings. Svelte doesn't mind. Note that the `<option>` values are objects rather than strings. Svelte doesn't mind.
> Because we haven't set an initial value of `selected`, the binding will set it to the default value (the first in the list) automatically. Be careful though — until the binding is initialised, `selected` remains undefined, so we can't blindly reference e.g. `selected.id` in the template. > Because we haven't set an initial value of `selected`, the binding will set it to the default value (the first in the list) automatically. Be careful though — until the binding is initialised, `selected` remains undefined, so we can't blindly reference e.g. `selected.id` in the template. If your use case allows it, you could also set an initial value to bypass this problem.

@ -6,15 +6,17 @@ You can even bind to properties inside an `each` block.
```html ```html
{#each todos as todo} {#each todos as todo}
<input <div class:done={todo.done}>
type=checkbox <input
bind:checked={todo.done} type=checkbox
> bind:checked={todo.done}
>
<input <input
placeholder="What needs to be done?" placeholder="What needs to be done?"
bind:value={todo.text} bind:value={todo.text}
> >
</div>
{/each} {/each}
``` ```

@ -0,0 +1,15 @@
<script>
let bgOpacity = 0.5;
$: color = bgOpacity < 0.6 ? '#000' : '#fff';
</script>
<input type="range" min="0" max="1" step="0.1" bind:value={bgOpacity} />
<p>This is a paragraph.</p>
<style>
p {
font-family: "Comic Sans MS", cursive;
background: rgba(255, 62, 0, var(--opacity));
}
</style>

@ -0,0 +1,15 @@
<script>
let bgOpacity = 0.5;
$: color = bgOpacity < 0.6 ? '#000' : '#fff';
</script>
<input type="range" min="0" max="1" step="0.1" bind:value={bgOpacity} />
<p style="color: {color}; --opacity: {bgOpacity};">This is a paragraph.</p>
<style>
p {
font-family: "Comic Sans MS", cursive;
background: rgba(255, 62, 0, var(--opacity));
}
</style>

@ -0,0 +1,10 @@
---
title: Inline styles
---
Apart from adding styles inside style tags, you can also add styles to individual elements using the style attribute. Usually you will want to do styling through CSS, but this can come in handy for dynamic styles, especially when combined with CSS custom properties.
Add the following style attribute to the paragraph element:
`style="color: {color}; --opacity: {bgOpacity};"`
Great, now you can style the paragraph using variables that change based on your input without having to make a class for every possible value.

@ -0,0 +1,15 @@
<script>
let bgOpacity = 0.5;
$: color = bgOpacity < 0.6 ? '#000' : '#fff';
</script>
<input type="range" min="0" max="1" step="0.1" bind:value={bgOpacity} />
<p style="color: {color}; --opacity: {bgOpacity};">This is a paragraph.</p>
<style>
p {
font-family: "Comic Sans MS", cursive;
background: rgba(255, 62, 0, var(--opacity));
}
</style>

@ -0,0 +1,15 @@
<script>
let bgOpacity = 0.5;
$: color = bgOpacity < 0.6 ? "#000" : "#fff";
</script>
<input type="range" min="0" max="1" step="0.1" bind:value={bgOpacity} />
<p style:color style:--opacity={bgOpacity}>This is a paragraph.</p>
<style>
p {
font-family: "Comic Sans MS", cursive;
background: rgba(255, 62, 0, var(--opacity));
}
</style>

@ -0,0 +1,18 @@
---
title: The style directive
---
Being able to set CSS properties dynamically is nice. However, this can get unwieldy if you have to write a long string. Mistakes like missing any of the semicolons could make the whole string invalid. Therefore, Svelte provides a nicer way to write inline styles with the style directive.
Change the style attribute of the paragraph to the following:
```html
<p
style:color
style:--opacity="{bgOpacity}"
>
```
The style directive shares a few qualities with the class directive. You can use a shorthand when the name of the property and the variable are the same. So `style:color="{color}"` can be written as just `style:color`.
Similar to the class directive, the style directive will take precedence if you try to set the same property through a style attribute.

@ -0,0 +1,3 @@
{
"title": "Advanced styling"
}

@ -1,3 +0,0 @@
{
"title": "Classes"
}

@ -0,0 +1,18 @@
<script>
const options = ['h1', 'h3', 'p'];
let selected = options[0];
</script>
<select bind:value={selected}>
{#each options as option}
<option value={option}>{option}</option>
{/each}
</select>
{#if selected === 'h1'}
<h1>I'm a h1 tag</h1>
{:else if selected === 'h3'}
<h3>I'm a h3 tag</h3>
{:else if selected === 'p'}
<p>I'm a p tag</p>
{/if}

@ -0,0 +1,12 @@
<script>
const options = ['h1', 'h3', 'p'];
let selected = options[0];
</script>
<select bind:value={selected}>
{#each options as option}
<option value={option}>{option}</option>
{/each}
</select>
<svelte:element this={selected}>I'm a {selected} tag</svelte:element>

@ -0,0 +1,23 @@
---
title: <svelte:element>
---
Sometimes we don't know in advance what kind of DOM element to render. `<svelte:element>` comes in handy here. Instead of a sequence of `if` blocks...
```html
{#if selected === 'h1'}
<h1>I'm a h1 tag</h1>
{:else if selected === 'h3'}
<h3>I'm a h3 tag</h3>
{:else if selected === 'p'}
<p>I'm a p tag</p>
{/if}
```
...we can have a single dynamic component:
```html
<svelte:element this={selected}>I'm a {selected} tag</svelte:element>
```
The `this` value can be any string, or a falsy value — if it's falsy, no element is rendered.

@ -16,7 +16,7 @@ Anything exported from a `context="module"` script block becomes an export from
</script> </script>
``` ```
...we can then import it from `App.svelte`... ...we can then import it in `App.svelte`...
```html ```html
<script> <script>

@ -519,7 +519,7 @@ export default class Component {
return result; return result;
} }
private _extract_exports(node: ExportDefaultDeclaration | ExportNamedDeclaration | ExportAllDeclaration, module_script) { private _extract_exports(node: ExportDefaultDeclaration | ExportNamedDeclaration | ExportAllDeclaration, module_script: boolean) {
if (node.type === 'ExportDefaultDeclaration') { if (node.type === 'ExportDefaultDeclaration') {
return this.error(node as any, compiler_errors.default_export); return this.error(node as any, compiler_errors.default_export);
} }
@ -539,7 +539,7 @@ export default class Component {
extract_names(declarator.id).forEach(name => { extract_names(declarator.id).forEach(name => {
const variable = this.var_lookup.get(name); const variable = this.var_lookup.get(name);
variable.export_name = name; variable.export_name = name;
if (variable.writable && !(variable.referenced || variable.referenced_from_script || variable.subscribable)) { if (!module_script && variable.writable && !(variable.referenced || variable.referenced_from_script || variable.subscribable)) {
this.warn(declarator as any, compiler_warnings.unused_export_let(this.name.name, name)); this.warn(declarator as any, compiler_warnings.unused_export_let(this.name.name, name));
} }
}); });
@ -559,7 +559,7 @@ export default class Component {
if (variable) { if (variable) {
variable.export_name = specifier.exported.name; variable.export_name = specifier.exported.name;
if (variable.writable && !(variable.referenced || variable.referenced_from_script || variable.subscribable)) { if (!module_script && variable.writable && !(variable.referenced || variable.referenced_from_script || variable.subscribable)) {
this.warn(specifier as any, compiler_warnings.unused_export_let(this.name.name, specifier.exported.name)); this.warn(specifier as any, compiler_warnings.unused_export_let(this.name.name, specifier.exported.name));
} }
} }

@ -242,10 +242,18 @@ export default {
code: 'invalid-animation', code: 'invalid-animation',
message: 'An element that uses the animate directive must be the immediate child of a keyed each block' message: 'An element that uses the animate directive must be the immediate child of a keyed each block'
}, },
invalid_animation_key: {
code: 'invalid-animation',
message: 'An element that uses the animate directive must be used inside a keyed each block. Did you forget to add a key to your each block?'
},
invalid_animation_sole: { invalid_animation_sole: {
code: 'invalid-animation', code: 'invalid-animation',
message: 'An element that uses the animate directive must be the sole child of a keyed each block' message: 'An element that uses the animate directive must be the sole child of a keyed each block'
}, },
invalid_animation_dynamic_element: {
code: 'invalid-animation',
message: '<svelte:element> cannot have a animate directive'
},
invalid_directive_value: { invalid_directive_value: {
code: 'invalid-directive-value', code: 'invalid-directive-value',
message: 'Can only bind to an identifier (e.g. `foo`) or a member expression (e.g. `foo.bar` or `foo[baz]`)' message: 'Can only bind to an identifier (e.g. `foo`) or a member expression (e.g. `foo.bar` or `foo[baz]`)'

@ -145,6 +145,10 @@ class Declaration {
? this.node.value.children[0] ? this.node.value.children[0]
: this.node.value; : this.node.value;
// Don't minify whitespace in custom properties, since some browsers (Chromium < 99)
// treat --foo: ; and --foo:; differently
if (first.type === 'Raw' && /^\s+$/.test(first.value)) return;
let start = first.start; let start = first.start;
while (/\s/.test(code.original[start])) start += 1; while (/\s/.test(code.original[start])) start += 1;

@ -26,12 +26,17 @@ export default class Animation extends Node {
} }
const block = parent.parent; const block = parent.parent;
if (!block || block.type !== 'EachBlock' || !block.key) { if (!block || block.type !== 'EachBlock') {
// TODO can we relax the 'immediate child' rule? // TODO can we relax the 'immediate child' rule?
component.error(this, compiler_errors.invalid_animation_immediate); component.error(this, compiler_errors.invalid_animation_immediate);
return; return;
} }
if (!block.key) {
component.error(this, compiler_errors.invalid_animation_key);
return;
}
(block as EachBlock).has_animation = true; (block as EachBlock).has_animation = true;
this.expression = info.expression this.expression = info.expression

@ -63,7 +63,7 @@ export default class EachBlock extends AbstractBlock {
([this.const_tags, this.children] = get_const_tags(info.children, component, this, this)); ([this.const_tags, this.children] = get_const_tags(info.children, component, this, this));
if (this.has_animation) { if (this.has_animation) {
this.children = this.children.filter(child => !isEmptyNode(child)); this.children = this.children.filter(child => !isEmptyNode(child) && !isCommentNode(child));
if (this.children.length !== 1) { if (this.children.length !== 1) {
const child = this.children.find(child => !!(child as Element).animation); const child = this.children.find(child => !!(child as Element).animation);
@ -83,3 +83,6 @@ export default class EachBlock extends AbstractBlock {
function isEmptyNode(node: INode) { function isEmptyNode(node: INode) {
return node.type === 'Text' && node.data.trim() === ''; return node.type === 'Text' && node.data.trim() === '';
} }
function isCommentNode(node: INode) {
return node.type === 'Comment';
}

@ -11,19 +11,22 @@ import StyleDirective from './StyleDirective';
import Text from './Text'; import Text from './Text';
import { namespaces } from '../../utils/namespaces'; import { namespaces } from '../../utils/namespaces';
import map_children from './shared/map_children'; import map_children from './shared/map_children';
import { dimensions } from '../../utils/patterns'; import { dimensions, start_newline } from '../../utils/patterns';
import fuzzymatch from '../../utils/fuzzymatch'; import fuzzymatch from '../../utils/fuzzymatch';
import list from '../../utils/list'; import list from '../../utils/list';
import Let from './Let'; import Let from './Let';
import TemplateScope from './shared/TemplateScope'; import TemplateScope from './shared/TemplateScope';
import { INode } from './interfaces'; import { INode } from './interfaces';
import Component from '../Component'; import Component from '../Component';
import Expression from './shared/Expression';
import { string_literal } from '../utils/stringify';
import { Literal } from 'estree';
import compiler_warnings from '../compiler_warnings'; import compiler_warnings from '../compiler_warnings';
import compiler_errors from '../compiler_errors'; import compiler_errors from '../compiler_errors';
const svg = /^(?:altGlyph|altGlyphDef|altGlyphItem|animate|animateColor|animateMotion|animateTransform|circle|clipPath|color-profile|cursor|defs|desc|discard|ellipse|feBlend|feColorMatrix|feComponentTransfer|feComposite|feConvolveMatrix|feDiffuseLighting|feDisplacementMap|feDistantLight|feDropShadow|feFlood|feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feImage|feMerge|feMergeNode|feMorphology|feOffset|fePointLight|feSpecularLighting|feSpotLight|feTile|feTurbulence|filter|font|font-face|font-face-format|font-face-name|font-face-src|font-face-uri|foreignObject|g|glyph|glyphRef|hatch|hatchpath|hkern|image|line|linearGradient|marker|mask|mesh|meshgradient|meshpatch|meshrow|metadata|missing-glyph|mpath|path|pattern|polygon|polyline|radialGradient|rect|set|solidcolor|stop|svg|switch|symbol|text|textPath|tref|tspan|unknown|use|view|vkern)$/; const svg = /^(?:altGlyph|altGlyphDef|altGlyphItem|animate|animateColor|animateMotion|animateTransform|circle|clipPath|color-profile|cursor|defs|desc|discard|ellipse|feBlend|feColorMatrix|feComponentTransfer|feComposite|feConvolveMatrix|feDiffuseLighting|feDisplacementMap|feDistantLight|feDropShadow|feFlood|feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feImage|feMerge|feMergeNode|feMorphology|feOffset|fePointLight|feSpecularLighting|feSpotLight|feTile|feTurbulence|filter|font|font-face|font-face-format|font-face-name|font-face-src|font-face-uri|foreignObject|g|glyph|glyphRef|hatch|hatchpath|hkern|image|line|linearGradient|marker|mask|mesh|meshgradient|meshpatch|meshrow|metadata|missing-glyph|mpath|path|pattern|polygon|polyline|radialGradient|rect|set|solidcolor|stop|svg|switch|symbol|text|textPath|tref|tspan|unknown|use|view|vkern)$/;
const aria_attributes = 'activedescendant atomic autocomplete busy checked colcount colindex colspan controls current describedby details disabled dropeffect errormessage expanded flowto grabbed haspopup hidden invalid keyshortcuts label labelledby level live modal multiline multiselectable orientation owns placeholder posinset pressed readonly relevant required roledescription rowcount rowindex rowspan selected setsize sort valuemax valuemin valuenow valuetext'.split(' '); const aria_attributes = 'activedescendant atomic autocomplete busy checked colcount colindex colspan controls current describedby description details disabled dropeffect errormessage expanded flowto grabbed haspopup hidden invalid keyshortcuts label labelledby level live modal multiline multiselectable orientation owns placeholder posinset pressed readonly relevant required roledescription rowcount rowindex rowspan selected setsize sort valuemax valuemin valuenow valuetext'.split(' ');
const aria_attribute_set = new Set(aria_attributes); const aria_attribute_set = new Set(aria_attributes);
const aria_roles = 'alert alertdialog application article banner blockquote button caption cell checkbox code columnheader combobox complementary contentinfo definition deletion dialog directory document emphasis feed figure form generic graphics-document graphics-object graphics-symbol grid gridcell group heading img link list listbox listitem log main marquee math meter menu menubar menuitem menuitemcheckbox menuitemradio navigation none note option paragraph presentation progressbar radio radiogroup region row rowgroup rowheader scrollbar search searchbox separator slider spinbutton status strong subscript superscript switch tab table tablist tabpanel term textbox time timer toolbar tooltip tree treegrid treeitem'.split(' '); const aria_roles = 'alert alertdialog application article banner blockquote button caption cell checkbox code columnheader combobox complementary contentinfo definition deletion dialog directory document emphasis feed figure form generic graphics-document graphics-object graphics-symbol grid gridcell group heading img link list listbox listitem log main marquee math meter menu menubar menuitem menuitemcheckbox menuitemradio navigation none note option paragraph presentation progressbar radio radiogroup region row rowgroup rowheader scrollbar search searchbox separator slider spinbutton status strong subscript superscript switch tab table tablist tabpanel term textbox time timer toolbar tooltip tree treegrid treeitem'.split(' ');
@ -190,14 +193,43 @@ export default class Element extends Node {
children: INode[]; children: INode[];
namespace: string; namespace: string;
needs_manual_style_scoping: boolean; needs_manual_style_scoping: boolean;
tag_expr: Expression;
get is_dynamic_element() {
return this.name === 'svelte:element';
}
constructor(component: Component, parent: Node, scope: TemplateScope, info: any) { constructor(component: Component, parent: Node, scope: TemplateScope, info: any) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.name = info.name; this.name = info.name;
if (info.name === 'svelte:element') {
if (typeof info.tag !== 'string') {
this.tag_expr = new Expression(component, this, scope, info.tag);
} else {
this.tag_expr = new Expression(component, this, scope, string_literal(info.tag) as Literal);
}
} else {
this.tag_expr = new Expression(component, this, scope, string_literal(this.name) as Literal);
}
this.namespace = get_namespace(parent as Element, this, component.namespace); this.namespace = get_namespace(parent as Element, this, component.namespace);
if (this.namespace !== namespaces.foreign) { if (this.namespace !== namespaces.foreign) {
if (this.name === 'pre' || this.name === 'textarea') {
const first = info.children[0];
if (first && first.type === 'Text') {
// The leading newline character needs to be stripped because of a qirk,
// it is ignored by browsers if the tag and its contents are set through
// innerHTML (NOT if set through the innerHTML of the tag or dynamically).
// Therefore strip it here but add it back in the appropriate
// places if there's another newline afterwards.
// see https://html.spec.whatwg.org/multipage/syntax.html#element-restrictions
// see https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element
first.data = first.data.replace(start_newline, '');
}
}
if (this.name === 'textarea') { if (this.name === 'textarea') {
if (info.children.length > 0) { if (info.children.length > 0) {
const value_attribute = info.attributes.find(node => node.name === 'value'); const value_attribute = info.attributes.find(node => node.name === 'value');

@ -27,10 +27,7 @@ export default class SlotTemplate extends Node {
this.validate_slot_template_placement(); this.validate_slot_template_placement();
const has_let = info.attributes.some((node) => node.type === 'Let'); scope = scope.child();
if (has_let) {
scope = scope.child();
}
info.attributes.forEach((node) => { info.attributes.forEach((node) => {
switch (node.type) { switch (node.type) {

@ -90,7 +90,7 @@ export default class Expression {
} }
if (template_scope.is_let(name)) { if (template_scope.is_let(name)) {
if (!function_expression) { // TODO should this be `!lazy` ? if (!lazy) {
contextual_dependencies.add(name); contextual_dependencies.add(name);
dependencies.add(name); dependencies.add(name);
} }
@ -254,11 +254,17 @@ export default class Expression {
const declaration = b`const ${id} = ${node}`; const declaration = b`const ${id} = ${node}`;
if (owner.type === 'ConstTag') { if (owner.type === 'ConstTag') {
let child_scope = scope;
walk(node, { walk(node, {
enter(node: Node) { enter(node: Node, parent: any) {
if (node.type === 'Identifier') { if (map.has(node)) child_scope = map.get(node);
if (node.type === 'Identifier' && is_reference(node, parent)) {
if (child_scope.has(node.name)) return;
this.replace(block.renderer.reference(node, ctx)); this.replace(block.renderer.reference(node, ctx));
} }
},
leave(node: Node) {
if (map.has(node)) child_scope = child_scope.parent;
} }
}); });
} else if (dependencies.size === 0 && contextual_dependencies.size === 0) { } else if (dependencies.size === 0 && contextual_dependencies.size === 0) {

@ -48,6 +48,7 @@ export default class Block {
hydrate: Array<Node | Node[]>; hydrate: Array<Node | Node[]>;
mount: Array<Node | Node[]>; mount: Array<Node | Node[]>;
measure: Array<Node | Node[]>; measure: Array<Node | Node[]>;
restore_measurements: Array<Node | Node[]>;
fix: Array<Node | Node[]>; fix: Array<Node | Node[]>;
animate: Array<Node | Node[]>; animate: Array<Node | Node[]>;
intro: Array<Node | Node[]>; intro: Array<Node | Node[]>;
@ -96,6 +97,7 @@ export default class Block {
hydrate: [], hydrate: [],
mount: [], mount: [],
measure: [], measure: [],
restore_measurements: [],
fix: [], fix: [],
animate: [], animate: [],
intro: [], intro: [],
@ -326,6 +328,12 @@ export default class Block {
${this.chunks.measure} ${this.chunks.measure}
}`; }`;
if (this.chunks.restore_measurements.length) {
properties.restore_measurements = x`function #restore_measurements(#measurement) {
${this.chunks.restore_measurements}
}`;
}
properties.fix = x`function #fix() { properties.fix = x`function #fix() {
${this.chunks.fix} ${this.chunks.fix}
}`; }`;
@ -379,6 +387,7 @@ export default class Block {
m: ${properties.mount}, m: ${properties.mount},
p: ${properties.update}, p: ${properties.update},
r: ${properties.measure}, r: ${properties.measure},
s: ${properties.restore_measurements},
f: ${properties.fix}, f: ${properties.fix},
a: ${properties.animate}, a: ${properties.animate},
i: ${properties.intro}, i: ${properties.intro},

@ -12,7 +12,7 @@ import { namespaces } from '../../../../utils/namespaces';
import AttributeWrapper from './Attribute'; import AttributeWrapper from './Attribute';
import StyleAttributeWrapper from './StyleAttribute'; import StyleAttributeWrapper from './StyleAttribute';
import SpreadAttributeWrapper from './SpreadAttribute'; import SpreadAttributeWrapper from './SpreadAttribute';
import { dimensions } from '../../../../utils/patterns'; import { dimensions, start_newline } from '../../../../utils/patterns';
import Binding from './Binding'; import Binding from './Binding';
import add_to_set from '../../../utils/add_to_set'; import add_to_set from '../../../utils/add_to_set';
import { add_event_handler } from '../shared/add_event_handlers'; import { add_event_handler } from '../shared/add_event_handlers';
@ -26,6 +26,7 @@ import Action from '../../../nodes/Action';
import MustacheTagWrapper from '../MustacheTag'; import MustacheTagWrapper from '../MustacheTag';
import RawMustacheTagWrapper from '../RawMustacheTag'; import RawMustacheTagWrapper from '../RawMustacheTag';
import is_dynamic from '../shared/is_dynamic'; import is_dynamic from '../shared/is_dynamic';
import create_debugging_comment from '../shared/create_debugging_comment';
import { push_array } from '../../../../utils/push_array'; import { push_array } from '../../../../utils/push_array';
interface BindingGroup { interface BindingGroup {
@ -134,6 +135,8 @@ const events = [
} }
]; ];
const CHILD_DYNAMIC_ELEMENT_BLOCK = 'child_dynamic_element';
export default class ElementWrapper extends Wrapper { export default class ElementWrapper extends Wrapper {
node: Element; node: Element;
fragment: FragmentWrapper; fragment: FragmentWrapper;
@ -147,6 +150,9 @@ export default class ElementWrapper extends Wrapper {
var: any; var: any;
void: boolean; void: boolean;
child_dynamic_element_block?: Block = null;
child_dynamic_element?: ElementWrapper = null;
constructor( constructor(
renderer: Renderer, renderer: Renderer,
block: Block, block: Block,
@ -156,6 +162,24 @@ export default class ElementWrapper extends Wrapper {
next_sibling: Wrapper next_sibling: Wrapper
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
if (node.is_dynamic_element && block.type !== CHILD_DYNAMIC_ELEMENT_BLOCK) {
this.child_dynamic_element_block = block.child({
comment: create_debugging_comment(node, renderer.component),
name: renderer.component.get_unique_name('create_dynamic_element'),
type: CHILD_DYNAMIC_ELEMENT_BLOCK
});
renderer.blocks.push(this.child_dynamic_element_block);
this.child_dynamic_element = new ElementWrapper(
renderer,
this.child_dynamic_element_block,
parent,
node,
strip_whitespace,
next_sibling
);
}
this.var = { this.var = {
type: 'Identifier', type: 'Identifier',
name: node.name.replace(/[^a-zA-Z0-9_$]/g, '_') name: node.name.replace(/[^a-zA-Z0-9_$]/g, '_')
@ -199,6 +223,8 @@ export default class ElementWrapper extends Wrapper {
block.add_animation(); block.add_animation();
} }
block.add_dependencies(node.tag_expr.dependencies);
// add directive and handler dependencies // add directive and handler dependencies
[node.animation, node.outro, ...node.actions, ...node.classes, ...node.styles].forEach(directive => { [node.animation, node.outro, ...node.actions, ...node.classes, ...node.styles].forEach(directive => {
if (directive && directive.expression) { if (directive && directive.expression) {
@ -219,7 +245,9 @@ export default class ElementWrapper extends Wrapper {
node.classes.length > 0 || node.classes.length > 0 ||
node.intro || node.outro || node.intro || node.outro ||
node.handlers.length > 0 || node.handlers.length > 0 ||
node.styles.length > 0 ||
this.node.name === 'option' || this.node.name === 'option' ||
node.tag_expr.dynamic_dependencies().length ||
renderer.options.dev renderer.options.dev
) { ) {
this.parent.cannot_use_innerhtml(); // need to use add_location this.parent.cannot_use_innerhtml(); // need to use add_location
@ -231,6 +259,110 @@ export default class ElementWrapper extends Wrapper {
} }
render(block: Block, parent_node: Identifier, parent_nodes: Identifier) { render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
if (this.child_dynamic_element) {
this.render_dynamic_element(block, parent_node, parent_nodes);
} else {
this.render_element(block, parent_node, parent_nodes);
}
}
render_dynamic_element(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
this.child_dynamic_element.render(
this.child_dynamic_element_block,
null,
(x`#nodes` as unknown) as Identifier
);
const previous_tag = block.get_unique_name('previous_tag');
const tag = this.node.tag_expr.manipulate(block);
block.add_variable(previous_tag, tag);
block.chunks.init.push(b`
${this.renderer.options.dev && b`@validate_dynamic_element(${tag});`}
let ${this.var} = ${tag} && ${this.child_dynamic_element_block.name}(#ctx);
`);
block.chunks.create.push(b`
if (${this.var}) ${this.var}.c();
`);
if (this.renderer.options.hydratable) {
block.chunks.claim.push(b`
if (${this.var}) ${this.var}.l(${parent_nodes});
`);
}
block.chunks.mount.push(b`
if (${this.var}) ${this.var}.m(${parent_node || '#target'}, ${parent_node ? 'null' : '#anchor'});
`);
const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes);
const has_transitions = !!(this.node.intro || this.node.outro);
const not_equal = this.renderer.component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`;
block.chunks.update.push(b`
if (${tag}) {
if (!${previous_tag}) {
${this.var} = ${this.child_dynamic_element_block.name}(#ctx);
${this.var}.c();
${has_transitions && b`@transition_in(${this.var})`}
${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor});
} else if (${not_equal}(${previous_tag}, ${tag})) {
${this.var}.d(1);
${this.renderer.options.dev && b`@validate_dynamic_element(${tag});`}
${this.var} = ${this.child_dynamic_element_block.name}(#ctx);
${this.var}.c();
${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor});
} else {
${this.var}.p(#ctx, #dirty);
}
} else if (${previous_tag}) {
${
has_transitions
? b`
@group_outros();
@transition_out(${this.var}, 1, 1, () => {
${this.var} = null;
});
@check_outros();
`
: b`
${this.var}.d(1);
${this.var} = null;
`
}
}
${previous_tag} = ${tag};
`);
if (this.child_dynamic_element_block.has_intros) {
block.chunks.intro.push(b`@transition_in(${this.var});`);
}
if (this.child_dynamic_element_block.has_outros) {
block.chunks.outro.push(b`@transition_out(${this.var});`);
}
block.chunks.destroy.push(b`if (${this.var}) ${this.var}.d(detaching)`);
if (this.node.animation) {
const measurements = block.get_unique_name('measurements');
block.add_variable(measurements);
block.chunks.measure.push(b`${measurements} = ${this.var}.r()`);
block.chunks.fix.push(b`${this.var}.f();`);
block.chunks.animate.push(b`
${this.var}.s(${measurements});
${this.var}.a()
`);
}
}
is_dom_node() {
return super.is_dom_node() && !this.child_dynamic_element;
}
render_element(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
const { renderer } = this; const { renderer } = this;
if (this.node.name === 'noscript') return; if (this.node.name === 'noscript') return;
@ -248,7 +380,7 @@ export default class ElementWrapper extends Wrapper {
if (renderer.options.hydratable) { if (renderer.options.hydratable) {
if (parent_nodes) { if (parent_nodes) {
block.chunks.claim.push(b` block.chunks.claim.push(b`
${node} = ${this.get_claim_statement(parent_nodes)}; ${node} = ${this.get_claim_statement(block, parent_nodes)};
`); `);
if (!this.void && this.node.children.length > 0) { if (!this.void && this.node.children.length > 0) {
@ -360,6 +492,8 @@ export default class ElementWrapper extends Wrapper {
b`@add_location(${this.var}, ${renderer.file_var}, ${loc.line - 1}, ${loc.column}, ${this.node.start});` b`@add_location(${this.var}, ${renderer.file_var}, ${loc.line - 1}, ${loc.column}, ${this.node.start});`
); );
} }
block.renderer.dirty(this.node.tag_expr.dynamic_dependencies());
} }
can_use_textcontent() { can_use_textcontent() {
@ -367,7 +501,7 @@ export default class ElementWrapper extends Wrapper {
} }
get_render_statement(block: Block) { get_render_statement(block: Block) {
const { name, namespace } = this.node; const { name, namespace, tag_expr } = this.node;
if (namespace === namespaces.svg) { if (namespace === namespaces.svg) {
return x`@svg_element("${name}")`; return x`@svg_element("${name}")`;
@ -382,22 +516,32 @@ export default class ElementWrapper extends Wrapper {
return x`@element_is("${name}", ${is.render_chunks(block).reduce((lhs, rhs) => x`${lhs} + ${rhs}`)})`; return x`@element_is("${name}", ${is.render_chunks(block).reduce((lhs, rhs) => x`${lhs} + ${rhs}`)})`;
} }
return x`@element("${name}")`; const reference = tag_expr.manipulate(block);
return x`@element(${reference})`;
} }
get_claim_statement(nodes: Identifier) { get_claim_statement(block: Block, nodes: Identifier) {
const attributes = this.attributes const attributes = this.attributes
.filter((attr) => !(attr instanceof SpreadAttributeWrapper) && !attr.property_name) .filter((attr) => !(attr instanceof SpreadAttributeWrapper) && !attr.property_name)
.map((attr) => p`${(attr as StyleAttributeWrapper | AttributeWrapper).name}: true`); .map((attr) => p`${(attr as StyleAttributeWrapper | AttributeWrapper).name}: true`);
const name = this.node.namespace let reference;
? this.node.name if (this.node.tag_expr.node.type === 'Literal') {
: this.node.name.toUpperCase(); if (this.node.namespace) {
reference = `"${this.node.tag_expr.node.value}"`;
} else {
reference = `"${(this.node.tag_expr.node.value as String || '').toUpperCase()}"`;
}
} else if (this.node.namespace) {
reference = x`${this.node.tag_expr.manipulate(block)}`;
} else {
reference = x`(${this.node.tag_expr.manipulate(block)} || 'null').toUpperCase()`;
}
if (this.node.namespace === namespaces.svg) { if (this.node.namespace === namespaces.svg) {
return x`@claim_svg_element(${nodes}, "${name}", { ${attributes} })`; return x`@claim_svg_element(${nodes}, ${reference}, { ${attributes} })`;
} else { } else {
return x`@claim_element(${nodes}, "${name}", { ${attributes} })`; return x`@claim_element(${nodes}, ${reference}, { ${attributes} })`;
} }
} }
@ -850,6 +994,11 @@ export default class ElementWrapper extends Wrapper {
${rect} = ${this.var}.getBoundingClientRect(); ${rect} = ${this.var}.getBoundingClientRect();
`); `);
if (block.type === CHILD_DYNAMIC_ELEMENT_BLOCK) {
block.chunks.measure.push(b`return ${rect}`);
block.chunks.restore_measurements.push(b`${rect} = #measurement;`);
}
block.chunks.fix.push(b` block.chunks.fix.push(b`
@fix_position(${this.var}); @fix_position(${this.var});
${stop_animation}(); ${stop_animation}();
@ -969,6 +1118,9 @@ export default class ElementWrapper extends Wrapper {
function to_html(wrappers: Array<ElementWrapper | TextWrapper | MustacheTagWrapper | RawMustacheTagWrapper>, block: Block, literal: any, state: any, can_use_raw_text?: boolean) { function to_html(wrappers: Array<ElementWrapper | TextWrapper | MustacheTagWrapper | RawMustacheTagWrapper>, block: Block, literal: any, state: any, can_use_raw_text?: boolean) {
wrappers.forEach(wrapper => { wrappers.forEach(wrapper => {
if (wrapper instanceof TextWrapper) { if (wrapper instanceof TextWrapper) {
// Don't add the <pre>/<textare> newline logic here because pre/textarea.innerHTML
// would keep the leading newline, too, only someParent.innerHTML = '..<pre/textarea>..' won't
if ((wrapper as TextWrapper).use_space()) state.quasi.value.raw += ' '; if ((wrapper as TextWrapper).use_space()) state.quasi.value.raw += ' ';
const parent = wrapper.node.parent as Element; const parent = wrapper.node.parent as Element;
@ -996,22 +1148,16 @@ function to_html(wrappers: Array<ElementWrapper | TextWrapper | MustacheTagWrapp
// element // element
state.quasi.value.raw += `<${wrapper.node.name}`; state.quasi.value.raw += `<${wrapper.node.name}`;
const is_empty_textarea = wrapper.node.name === 'textarea' && wrapper.fragment.nodes.length === 0;
(wrapper as ElementWrapper).attributes.forEach((attr: AttributeWrapper) => { (wrapper as ElementWrapper).attributes.forEach((attr: AttributeWrapper) => {
if (is_empty_textarea && attr.node.name === 'value') {
// The value attribute of <textarea> renders as content.
return;
}
state.quasi.value.raw += ` ${fix_attribute_casing(attr.node.name)}="`; state.quasi.value.raw += ` ${fix_attribute_casing(attr.node.name)}="`;
attr.node.chunks.forEach(chunk => { to_html_for_attr_value(attr, block, literal, state);
if (chunk.type === 'Text') {
state.quasi.value.raw += escape_html(chunk.data);
} else {
literal.quasis.push(state.quasi);
literal.expressions.push(chunk.manipulate(block));
state.quasi = {
type: 'TemplateElement',
value: { raw: '' }
};
}
});
state.quasi.value.raw += '"'; state.quasi.value.raw += '"';
}); });
@ -1019,6 +1165,29 @@ function to_html(wrappers: Array<ElementWrapper | TextWrapper | MustacheTagWrapp
if (!wrapper.void) { if (!wrapper.void) {
state.quasi.value.raw += '>'; state.quasi.value.raw += '>';
if (wrapper.node.name === 'pre') {
// Two or more leading newlines are required to restore the leading newline immediately after `<pre>`.
// see https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element
const first = wrapper.fragment.nodes[0];
if (first && first.node.type === 'Text' && start_newline.test(first.node.data)) {
state.quasi.value.raw += '\n';
}
}
if (is_empty_textarea) {
// The <textarea> renders the value attribute as content because the content is stored in the value attribute.
const value_attribute = wrapper.attributes.find(attr => attr.node.name === 'value');
if (value_attribute) {
// Two or more leading newlines are required to restore the leading newline immediately after `<textarea>`.
// see https://html.spec.whatwg.org/multipage/syntax.html#element-restrictions
const first = value_attribute.node.chunks[0];
if (first && first.type === 'Text' && start_newline.test(first.data)) {
state.quasi.value.raw += '\n';
}
to_html_for_attr_value(value_attribute, block, literal, state);
}
}
to_html(wrapper.fragment.nodes as Array<ElementWrapper | TextWrapper>, block, literal, state); to_html(wrapper.fragment.nodes as Array<ElementWrapper | TextWrapper>, block, literal, state);
state.quasi.value.raw += `</${wrapper.node.name}>`; state.quasi.value.raw += `</${wrapper.node.name}>`;
@ -1028,3 +1197,19 @@ function to_html(wrappers: Array<ElementWrapper | TextWrapper | MustacheTagWrapp
} }
}); });
} }
function to_html_for_attr_value(attr: AttributeWrapper | StyleAttributeWrapper | SpreadAttributeWrapper, block: Block, literal: any, state: any) {
attr.node.chunks.forEach(chunk => {
if (chunk.type === 'Text') {
state.quasi.value.raw += escape_html(chunk.data);
} else {
literal.quasis.push(state.quasi);
literal.expressions.push(chunk.manipulate(block));
state.quasi = {
type: 'TemplateElement',
value: { raw: '' }
};
}
});
}

@ -1,7 +1,6 @@
import Wrapper from './shared/Wrapper'; import Wrapper from './shared/Wrapper';
import Renderer from '../Renderer'; import Renderer from '../Renderer';
import Block from '../Block'; import Block from '../Block';
import EachBlock from '../../nodes/EachBlock';
import KeyBlock from '../../nodes/KeyBlock'; import KeyBlock from '../../nodes/KeyBlock';
import create_debugging_comment from './shared/create_debugging_comment'; import create_debugging_comment from './shared/create_debugging_comment';
import FragmentWrapper from './Fragment'; import FragmentWrapper from './Fragment';
@ -19,7 +18,7 @@ export default class KeyBlockWrapper extends Wrapper {
renderer: Renderer, renderer: Renderer,
block: Block, block: Block,
parent: Wrapper, parent: Wrapper,
node: EachBlock, node: KeyBlock,
strip_whitespace: boolean, strip_whitespace: boolean,
next_sibling: Wrapper next_sibling: Wrapper
) { ) {
@ -36,6 +35,7 @@ export default class KeyBlockWrapper extends Wrapper {
name: renderer.component.get_unique_name('create_key_block'), name: renderer.component.get_unique_name('create_key_block'),
type: 'key' type: 'key'
}); });
block.add_dependencies(node.expression.dependencies);
renderer.blocks.push(block); renderer.blocks.push(block);
} }
@ -106,7 +106,7 @@ export default class KeyBlockWrapper extends Wrapper {
} }
${this.var} = ${this.block.name}(#ctx); ${this.var} = ${this.block.name}(#ctx);
${this.var}.c(); ${this.var}.c();
${has_transitions && b`@transition_in(${this.var})`} ${has_transitions && b`@transition_in(${this.var}, 1)`}
${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor});
`; `;

@ -8,8 +8,10 @@ import Expression from '../../nodes/shared/Expression';
import remove_whitespace_children from './utils/remove_whitespace_children'; import remove_whitespace_children from './utils/remove_whitespace_children';
import fix_attribute_casing from '../../render_dom/wrappers/Element/fix_attribute_casing'; import fix_attribute_casing from '../../render_dom/wrappers/Element/fix_attribute_casing';
import { namespaces } from '../../../utils/namespaces'; import { namespaces } from '../../../utils/namespaces';
import { start_newline } from '../../../utils/patterns';
import { Expression as ESExpression } from 'estree';
export default function(node: Element, renderer: Renderer, options: RenderOptions) { export default function (node: Element, renderer: Renderer, options: RenderOptions) {
const children = remove_whitespace_children(node.children, node.next); const children = remove_whitespace_children(node.children, node.next);
@ -22,7 +24,8 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
node.attributes.some((attribute) => attribute.name === 'contenteditable') node.attributes.some((attribute) => attribute.name === 'contenteditable')
); );
renderer.add_string(`<${node.name}`); renderer.add_string('<');
add_tag_name();
const class_expression_list = node.classes.map(class_directive => { const class_expression_list = node.classes.map(class_directive => {
const { expression, name } = class_directive; const { expression, name } = class_directive;
@ -164,17 +167,48 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
renderer.add_expression(x`($$value => $$value === void 0 ? ${result} : $$value)(${node_contents})`); renderer.add_expression(x`($$value => $$value === void 0 ? ${result} : $$value)(${node_contents})`);
} else { } else {
if (node.name === 'textarea') {
// Two or more leading newlines are required to restore the leading newline immediately after `<textarea>`.
// see https://html.spec.whatwg.org/multipage/syntax.html#element-restrictions
const value_attribute = node.attributes.find(({ name }) => name === 'value');
if (value_attribute) {
const first = value_attribute.chunks[0];
if (first && first.type === 'Text' && start_newline.test(first.data)) {
renderer.add_string('\n');
}
}
}
renderer.add_expression(node_contents); renderer.add_expression(node_contents);
} }
if (!is_void(node.name)) { add_close_tag();
renderer.add_string(`</${node.name}>`);
}
} else { } else {
if (node.name === 'pre') {
// Two or more leading newlines are required to restore the leading newline immediately after `<pre>`.
// see https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element
// see https://html.spec.whatwg.org/multipage/syntax.html#element-restrictions
const first = children[0];
if (first && first.type === 'Text' && start_newline.test(first.data)) {
renderer.add_string('\n');
}
}
renderer.render(children, options); renderer.render(children, options);
add_close_tag();
}
function add_close_tag() {
if (!is_void(node.name)) { if (!is_void(node.name)) {
renderer.add_string(`</${node.name}>`); renderer.add_string('</');
add_tag_name();
renderer.add_string('>');
}
}
function add_tag_name() {
if (node.tag_expr.node.type === 'Literal') {
renderer.add_string(node.tag_expr.node.value as string);
} else {
renderer.add_expression(node.tag_expr.node as ESExpression);
} }
} }
} }

@ -99,6 +99,10 @@ export default {
code: `invalid-${slug}-content`, code: `invalid-${slug}-content`,
message: `<${name}> cannot have children` message: `<${name}> cannot have children`
}), }),
invalid_element_definition: {
code: 'invalid-element-definition',
message: 'Invalid element definition'
},
invalid_element_placement: (slug: string, name: string) => ({ invalid_element_placement: (slug: string, name: string) => ({
code: `invalid-${slug}-placement`, code: `invalid-${slug}-placement`,
message: `<${name}> tags cannot be inside elements or blocks` message: `<${name}> tags cannot be inside elements or blocks`
@ -161,6 +165,10 @@ export default {
code: 'missing-attribute-value', code: 'missing-attribute-value',
message: 'Expected value for the attribute' message: 'Expected value for the attribute'
}, },
missing_element_definition: {
code: 'missing-element-definition',
message: '<svelte:element> must have a \'this\' attribute'
},
unclosed_script: { unclosed_script: {
code: 'unclosed-script', code: 'unclosed-script',
message: '<script> must have a closing tag' message: '<script> must have a closing tag'

@ -19,7 +19,7 @@ const meta_tags = new Map([
['svelte:body', 'Body'] ['svelte:body', 'Body']
]); ]);
const valid_meta_tags = Array.from(meta_tags.keys()).concat('svelte:self', 'svelte:component', 'svelte:fragment'); const valid_meta_tags = Array.from(meta_tags.keys()).concat('svelte:self', 'svelte:component', 'svelte:fragment', 'svelte:element');
const specials = new Map([ const specials = new Map([
[ [
@ -41,6 +41,7 @@ const specials = new Map([
const SELF = /^svelte:self(?=[\s/>])/; const SELF = /^svelte:self(?=[\s/>])/;
const COMPONENT = /^svelte:component(?=[\s/>])/; const COMPONENT = /^svelte:component(?=[\s/>])/;
const SLOT = /^svelte:fragment(?=[\s/>])/; const SLOT = /^svelte:fragment(?=[\s/>])/;
const ELEMENT = /^svelte:element(?=[\s/>])/;
function parent_is_head(stack) { function parent_is_head(stack) {
let i = stack.length; let i = stack.length;
@ -169,7 +170,7 @@ export default function tag(parser: Parser) {
if (name === 'svelte:component') { if (name === 'svelte:component') {
const index = element.attributes.findIndex(attr => attr.type === 'Attribute' && attr.name === 'this'); const index = element.attributes.findIndex(attr => attr.type === 'Attribute' && attr.name === 'this');
if (!~index) { if (index === -1) {
parser.error(parser_errors.missing_component_definition, start); parser.error(parser_errors.missing_component_definition, start);
} }
@ -181,6 +182,19 @@ export default function tag(parser: Parser) {
element.expression = definition.value[0].expression; element.expression = definition.value[0].expression;
} }
if (name === 'svelte:element') {
const index = element.attributes.findIndex(attr => attr.type === 'Attribute' && attr.name === 'this');
if (index === -1) {
parser.error(parser_errors.missing_element_definition, start);
}
const definition = element.attributes.splice(index, 1)[0];
if (definition.value === true) {
parser.error(parser_errors.invalid_element_definition, definition.start);
}
element.tag = definition.value[0].data || definition.value[0].expression;
}
// special cases top-level <script> and <style> // special cases top-level <script> and <style>
if (specials.has(name) && parser.stack.length === 1) { if (specials.has(name) && parser.stack.length === 1) {
const special = specials.get(name); const special = specials.get(name);
@ -247,6 +261,7 @@ function read_tag_name(parser: Parser) {
} }
if (parser.read(COMPONENT)) return 'svelte:component'; if (parser.read(COMPONENT)) return 'svelte:component';
if (parser.read(ELEMENT)) return 'svelte:element';
if (parser.read(SLOT)) return 'svelte:fragment'; if (parser.read(SLOT)) return 'svelte:fragment';

@ -56,6 +56,7 @@ export const globals = new Set([
'setTimeout', 'setTimeout',
'String', 'String',
'SVGElement', 'SVGElement',
'Symbol',
'SyntaxError', 'SyntaxError',
'TypeError', 'TypeError',
'undefined', 'undefined',

@ -1,5 +1,6 @@
export const whitespace = /[ \t\r\n]/; export const whitespace = /[ \t\r\n]/;
export const start_whitespace = /^[ \t\r\n]*/; export const start_whitespace = /^[ \t\r\n]*/;
export const end_whitespace = /[ \t\r\n]*$/; export const end_whitespace = /[ \t\r\n]*$/;
export const start_newline = /^\r?\n/;
export const dimensions = /^(?:offset|client)(?:Width|Height)$/; export const dimensions = /^(?:offset|client)(?:Width|Height)$/;

@ -0,0 +1,42 @@
/**
* Actions can return an object containing the two properties defined in this interface. Both are optional.
* - update: An action can have a parameter. This method will be called whenever that parameter changes,
* immediately after Svelte has applied updates to the markup.
* - destroy: Method that is called after the element is unmounted
*
* Example usage:
* ```ts
* export function myAction(node: HTMLElement, parameter: Parameter): ActionReturn<Parameter> {
* // ...
* return {
* update: (updatedParameter) => {...},
* destroy: () => {...}
* };
* }
* ```
*
* Docs: https://svelte.dev/docs#template-syntax-element-directives-use-action
*/
export interface ActionReturn<Parameter = any> {
update?: (parameter: Parameter) => void;
destroy?: () => void;
}
/**
* Actions are functions that are called when an element is created.
* You can use this interface to type such actions.
* The following example defines an action that only works on `<div>` elements
* and optionally accepts a parameter which it has a default value for:
* ```ts
* export const myAction: Action<HTMLDivElement, { someProperty: boolean }> = (node, param = { someProperty: true }) => {
* // ...
* }
* ```
* You can return an object with methods `update` and `destroy` from the function.
* See interface `ActionReturn` for more details.
*
* Docs: https://svelte.dev/docs#template-syntax-element-directives-use-action
*/
export interface Action<Element = HTMLElement, Parameter = any> {
<Node extends Element>(node: Node, parameter?: Parameter): void | ActionReturn<Parameter>;
}

@ -2,7 +2,7 @@ import { custom_event, append, append_hydration, insert, insert_hydration, detac
import { SvelteComponent } from './Component'; import { SvelteComponent } from './Component';
export function dispatch_dev<T=any>(type: string, detail?: T) { export function dispatch_dev<T=any>(type: string, detail?: T) {
document.dispatchEvent(custom_event(type, { version: '__VERSION__', ...detail }, true)); document.dispatchEvent(custom_event(type, { version: '__VERSION__', ...detail }, { bubbles: true }));
} }
export function append_dev(target: Node, node: Node) { export function append_dev(target: Node, node: Node) {
@ -107,6 +107,12 @@ export function validate_slots(name, slot, keys) {
} }
} }
export function validate_dynamic_element(tag: unknown) {
if (tag && typeof tag !== 'string') {
throw new Error('<svelte:element> expects "this" attribute to be a string.');
}
}
type Props = Record<string, any>; type Props = Record<string, any>;
export interface SvelteComponentDev { export interface SvelteComponentDev {
$set(props?: Props): void; $set(props?: Props): void;

@ -634,9 +634,9 @@ export function toggle_class(element, name, toggle) {
element.classList[toggle ? 'add' : 'remove'](name); element.classList[toggle ? 'add' : 'remove'](name);
} }
export function custom_event<T=any>(type: string, detail?: T, bubbles: boolean = false) { export function custom_event<T=any>(type: string, detail?: T, { bubbles = false, cancelable = false } = {}): CustomEvent<T> {
const e: CustomEvent<T> = document.createEvent('CustomEvent'); const e: CustomEvent<T> = document.createEvent('CustomEvent');
e.initCustomEvent(type, bubbles, false, detail); e.initCustomEvent(type, bubbles, cancelable, detail);
return e; return e;
} }

@ -27,27 +27,39 @@ export function onDestroy(fn: () => any) {
get_current_component().$$.on_destroy.push(fn); get_current_component().$$.on_destroy.push(fn);
} }
export function createEventDispatcher< export interface DispatchOptions {
EventMap extends {} = any cancelable?: boolean;
>(): <EventKey extends Extract<keyof EventMap, string>>(type: EventKey, detail?: EventMap[EventKey]) => void { }
export function createEventDispatcher<EventMap extends {} = any>(): <
EventKey extends Extract<keyof EventMap, string>
>(
type: EventKey,
detail?: EventMap[EventKey],
options?: DispatchOptions
) => boolean {
const component = get_current_component(); const component = get_current_component();
return (type: string, detail?: any) => { return (type: string, detail?: any, { cancelable = false } = {}): boolean => {
const callbacks = component.$$.callbacks[type]; const callbacks = component.$$.callbacks[type];
if (callbacks) { if (callbacks) {
// TODO are there situations where events could be dispatched // TODO are there situations where events could be dispatched
// in a server (non-DOM) environment? // in a server (non-DOM) environment?
const event = custom_event(type, detail); const event = custom_event(type, detail, { cancelable });
callbacks.slice().forEach(fn => { callbacks.slice().forEach(fn => {
fn.call(component, event); fn.call(component, event);
}); });
return !event.defaultPrevented;
} }
return true;
}; };
} }
export function setContext<T>(key, context: T) { export function setContext<T>(key, context: T): T {
get_current_component().$$.context.set(key, context); get_current_component().$$.context.set(key, context);
return context;
} }
export function getContext<T>(key): T { export function getContext<T>(key): T {

@ -177,7 +177,8 @@ export function create_ssr_component(fn) {
export function add_attribute(name, value, boolean) { export function add_attribute(name, value, boolean) {
if (value == null || (boolean && !value)) return ''; if (value == null || (boolean && !value)) return '';
return ` ${name}${value === true && boolean_attributes.has(name) ? '' : `=${typeof value === 'string' ? JSON.stringify(escape(value)) : `"${value}"`}`}`; const assignment = (boolean && value === true) ? '' : `="${escape_attribute_value(value.toString())}"`;
return ` ${name}${assignment}`;
} }
export function add_classes(classes) { export function add_classes(classes) {

@ -1 +1 @@
:root{--root-test:20}div.svelte-xyz{--test:10} :root{--root-test:20}div.svelte-xyz{--test:10}div.svelte-xyz{--foo: ;--bar: !important}

@ -7,4 +7,9 @@
div { div {
--test: 10; --test: 10;
} }
div {
--foo: ;
--bar: !important;
}
</style> </style>

@ -0,0 +1,21 @@
export default {
warnings: [
{
code: 'css-unused-selector',
end: {
character: 86,
column: 8,
line: 7
},
frame:
' 5: color: red;\n 6: }\n 7: .unused {\n ^\n 8: font-style: italic;\n 9: }',
message: 'Unused CSS selector ".unused"',
pos: 79,
start: {
character: 79,
column: 1,
line: 7
}
}
]
};

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save