Merge branch 'master' into fix-files-binding

pull/9080/head
Tee Ming 3 years ago committed by GitHub
commit 96c2eac523
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,3 +1,6 @@
## Svelte compiler rewrite
Please note that [the Svelte codebase is currently being rewritten](https://svelte.dev/blog/runes). Thus, it's best to hold off on new features or refactorings for the time being.
### Before submitting the PR, please make sure you do the following ### Before submitting the PR, please make sure you do the following

@ -147,7 +147,7 @@ When adding a new breaking change, follow this template in your pull request:
## License ## License
By contributing to Svelte, you agree that your contributions will be licensed under its [MIT license](https://github.com/sveltejs/svelte/blob/master/LICENSE). By contributing to Svelte, you agree that your contributions will be licensed under its [MIT license](https://github.com/sveltejs/svelte/blob/master/LICENSE.md).
## Questions ## Questions

@ -46,7 +46,7 @@ For all the patches and performance updates from this month, check out the [Svel
**Learning Resources** **Learning Resources**
_Featuring Svelte Contributors and Ambassadors_ _Featuring Svelte Contributors and Ambassadors_
- [Exploring Svelte 4 w/ Kevin AK: Performance, Compatibility, & Web Component Support | Modern Web Pod](https://www.youtube.com/watch?v=YOL0HGGVib4) by This Dot Media - [Exploring Svelte 4 w/ Kevin AK: Performance, Compatibility, & Web Component Support | Modern Web Pod](https://www.youtube.com/watch?v=YOL0HGGVib4) by This Dot Media
- [Svelte Sirens Stream Design Systems: Lessons Learned](https://www.youtube.com/live/YHZaiIGSqsE?feature=share) featuring Eric Liu creator of Carbon Components Svelte and Svelde the docgen library - [Svelte Sirens Stream Design Systems: Lessons Learned](https://www.youtube.com/live/YHZaiIGSqsE?feature=share) featuring Eric Liu, creator of Carbon Components Svelte and the `sveld` docgen library
- This Week in Svelte: - This Week in Svelte:
- [2023 June 30](https://www.youtube.com/watch?v=sDz4_BLoYQ4) - Svelte 4.0.1, SK 1.21, lists, screen readers, loading - [2023 June 30](https://www.youtube.com/watch?v=sDz4_BLoYQ4) - Svelte 4.0.1, SK 1.21, lists, screen readers, loading
- [2023 July 7](https://www.youtube.com/watch?v=0tq1ph4DDFA) - Svelte 4.0.5, Kit 1.22.1, Svelte 5, local storage and markdown - [2023 July 7](https://www.youtube.com/watch?v=0tq1ph4DDFA) - Svelte 4.0.5, Kit 1.22.1, Svelte 5, local storage and markdown

@ -0,0 +1,264 @@
---
title: Unlocking view transitions in SvelteKit 1.24
description: Streamlined page transitions with onNavigate
author: Geoff Rich
authorURL: https://geoffrich.net
---
The [view transitions API](https://developer.chrome.com/docs/web-platform/view-transitions/) has been sweeping the web development world lately, and for good reason. It streamlines the process of animating between two page states, which is especially useful for page transitions.
However, until now, you couldnt easily use this API in a SvelteKit app, since it was difficult to slot into the right place in the navigation lifecycle. SvelteKit 1.24 brought a new [`onNavigate`](https://kit.svelte.dev/docs/modules#$app-navigation-onnavigate) lifecycle hook to make view transitions integration much easier lets dive in.
## How view transitions work
You can trigger a view transition by calling `document.startViewTransition` and passing a callback that updates the DOM somehow. For our purposes today, SvelteKit will update the DOM as the user navigates. Once the callback finishes, the browser will transition to the new page state — by default, it does a crossfade between the old and the new states.
```js
// @errors: 2339
const domUpdate = async () => {};
// ---cut---
document.startViewTransition(async () => {
await domUpdate(); // mock function for demonstration purposes
});
```
Behind the scenes, the browser does something really clever. When the transition starts, it captures the current state of the page and takes a screenshot. It then holds that screenshot in place while the DOM is updating. Once the DOM has finished updating, it captures the new state, and animates between the two states.
While its only implemented in Chrome (and other Chromium-based browsers) for now, [WebKit is also in favor](https://github.com/WebKit/standards-positions/issues/48#issuecomment-1679760489) of it. Even if youre on an unsupported browser, its a perfect candidate for progressive enhancement since we can always fall back to a non-animated navigation.
Its important to note that view transitions is a browser API, not a SvelteKit one. `onNavigate` is the only SvelteKit-specific API well use today. Everything else can be used wherever you write for the web! For more on the view transitions API, I highly recommend the [Chrome explainer](https://developer.chrome.com/docs/web-platform/view-transitions/) by Jake Archibald.
## How `onNavigate` works
Before learning how to write view transitions, let's highlight the function that makes it all possible: [`onNavigate`](https://kit.svelte.dev/docs/modules#$app-navigation-onnavigate).
Until recently, SvelteKit had two navigation lifecycle functions: [`beforeNavigate`](https://kit.svelte.dev/docs/modules#$app-navigation-beforenavigate), which fires before a navigation starts, and [`afterNavigate`](https://kit.svelte.dev/docs/modules#$app-navigation-afternavigate), which fires after the page has been updated following a navigation. SvelteKit 1.24 introduces a third: `onNavigate`, which will fire on every navigation, immediately before the new page is rendered. Importantly, it will run _after_ any data loading for the page has completed since starting a view transition prevents any interaction with the page, we want to start it as late as possible.
You can also return a promise from `onNavigate`, which will suspend the navigation until it resolves. This will let us wait to complete the navigation until the view transition has started.
```js
// @errors: 2304 7006
function delayNavigation() {
return new Promise((res) => setTimeout(res, 100));
}
onNavigate(async (navigation) => {
// do some work immediately before the navigation completes
// optionally return a promise to delay navigation until it resolves
return delayNavigation();
});
```
With that out of the way, let's see how you can use view transitions in your SvelteKit app.
## Getting started with view transitions
The best way to see view transitions in action is to try it yourself. You can spin up the SvelteKit demo app by running `npm create svelte@latest` in your local terminal, or in your browser on [StackBlitz](https://sveltekit.new). Make sure to use a browser that supports the view transitions API. Once you have the app running, add the following to the script block in `src/routes/+layout.svelte`.
```js
// @errors: 2305 7006 2339 2810
import { onNavigate } from '$app/navigation';
onNavigate((navigation) => {
if (!document.startViewTransition) return;
return new Promise((resolve) => {
document.startViewTransition(async () => {
resolve();
await navigation.complete;
});
});
});
```
With that, every navigation that occurs will trigger a view transition. You can already see this in action by default, the browser will crossfade between the old and new pages.
<video src="https://sveltejs.github.io/assets/video/vt-demo-1.mp4" controls muted playsinline></video>
<details>
<summary>How the code works</summary>
This code may look a bit intimidating if you're curious, I can break it down line-by-line, but for now its enough to know that adding it will allow you to interact with the view transitions API during navigation.
As mentioned above, the `onNavigate` callback will run immediately before the new page is rendered after a navigation. Inside the callback, we check if `document.startViewTransition` exists. If it doesnt (i.e. the browser doesnt support it), we exit early.
We then return a promise to delay completing the navigation until the view transition has started. We use a [promise constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise) so that we can control when the promise resolves.
```js
// @errors: 1108
return new Promise((resolve) => {
document.startViewTransition(async () => {
resolve();
await navigation.complete;
});
});
```
Inside the promise constructor, we start the view transition. Inside the view transition callback we resolve the promise we just returned, which indicates to SvelteKit that it should finish the navigation. Its important that the navigation waits to finish until _after_ we start the view transition the browser needs to snapshot the old state so it can transition to the new state.
Finally, inside the view transition callback we wait for SvelteKit to finish the navigation by awaiting `navigation.complete`. Once `navigation.complete` resolves, the new page has been loaded into the DOM and the browser can animate between the two states.
Its a bit of a mouthful, but by not abstracting it we allow you to interact with the view transition directly and make any customizations you require.
</details>
## Customizing the transition with CSS
We can also customize this page transition using CSS animation. In the style block of your `+layout.svelte`, add the following CSS rules.
```css
@keyframes fade-in {
from {
opacity: 0;
}
}
@keyframes fade-out {
to {
opacity: 0;
}
}
@keyframes slide-from-right {
from {
transform: translateX(30px);
}
}
@keyframes slide-to-left {
to {
transform: translateX(-30px);
}
}
:root::view-transition-old(root) {
animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out, 300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
}
:root::view-transition-new(root) {
animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in, 300ms cubic-bezier(0.4, 0, 0.2, 1) both
slide-from-right;
}
```
Now when you navigate between pages, the old page will fade out and slide to the left, and the new page will fade in and slide from the right. These particular animation styles come from Jake Archibalds excellent [Chrome Developers article on view transitions](https://developer.chrome.com/docs/web-platform/view-transitions/), which is well worth a read if you want to understand everything you can do with this API.
Note that we have to add `:root` before the `::view-transition` pseudoelements these elements are only on the root of the document, so we dont want Svelte to [scope them](/docs/svelte-components#style) to the component.
You might have noticed that the entire page slides in and out, even though the header is the same on both the old and new page. To make for a smoother transition, we can give the header a unique `view-transition-name` so that it is animated separately from the rest of the page. In `src/routes/Header.svelte`, find the `header` CSS selector in the style block and add a view transition name.
```css
header {
display: flex;
justify-content: space-between;
view-transition-name: header;
}
```
Now, the header will not transition in and out on navigation, but the rest of the page will.
<video src="https://sveltejs.github.io/assets/video/vt-demo-2.mp4" controls muted playsinline></video>
<details>
<summary>Fixing the types</summary>
Since `startViewTransition` is not supported by all browsers, your IDE may not know that it exists. To make the errors go away and get the correct typings, add the following to your `app.d.ts`:
```ts
declare global {
// preserve any customizations you have here
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface Platform {}
}
// add these lines
interface ViewTransition {
updateCallbackDone: Promise<void>;
ready: Promise<void>;
finished: Promise<void>;
skipTransition: () => void;
}
interface Document {
startViewTransition(updateCallback: () => Promise<void>): ViewTransition;
}
}
export {};
```
</details>
## Transitioning individual elements
We just saw how giving an element a `view-transition-name` separates it out from the rest of the page's animation. Setting a `view-transition-name` also instructs the browser to smoothly animate it to its new position after the transition completes. The `view-transition-name` acts as a unique identifier so the browser can identify matching elements from the old and new states.
Lets see what that looks like our demo apps navigation has a small triangle indicating the active page. Right now, it abruptly appears in the new position after we navigate. Lets give it a `view-transition-name` so the browser animates it to its new position instead.
Inside `src/routes/Header.svelte`, find the CSS rule creating the active page indicator and give it a `view-transition-name`:
```css
li[aria-current='page']::before {
/* other existing rules */
view-transition-name: active-page;
}
```
By adding that single line, the indicator will now smoothly slide to its new position instead of jumping.
<video src="https://sveltejs.github.io/assets/video/vt-demo-3.mp4" controls muted playsinline></video>
(It might be easy to miss the difference look at the small moving triangle indicator at the top of the screen!)
## Reduced motion
Its important to respect our users [motion preferences](https://web.dev/prefers-reduced-motion/) while implementing animation on the web. Just because you can implement an extreme page transition doesnt mean you should. To disable all page transitions for users who prefer reduced motion, you can add the following to the global `styles.css`:
```css
@media (prefers-reduced-motion) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}
```
While this may be the safest option, reduced motion does not necessarily mean no animation. Instead, you could consider your view transitions on a case-by-case basis. For instance, maybe we disable the sliding animation, but leave the default crossfade (which doesnt involve motion). You can do so by wrapping the `::view-transition` rules you want to disable in a `prefers-reduced-motion: no-preference` media-query:
```css
@media (prefers-reduced-motion: no-preference) {
:root::view-transition-old(root) {
animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out, 300ms cubic-bezier(0.4, 0, 0.2, 1) both
slide-to-left;
}
:root::view-transition-new(root) {
animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in, 300ms cubic-bezier(
0.4,
0,
0.2,
1
) both slide-from-right;
}
}
```
## Whats next?
As you can see, SvelteKit doesnt abstract a whole lot about _how_ view transitions work youre interacting directly with the browsers built-in `document.startViewTransition` and `::view-transition` APIs, rather than framework abstractions like those found in Nuxt and Astro. Were eager to see how people end up using view transitions in SvelteKit apps, and whether it makes sense to add higher level abstractions of our own in future.
## Resources
You can find the demo code from this post [on GitHub](https://github.com/geoffrich/sveltekit-onnavigate-demo) and the live version [deployed to Vercel](https://sveltekit-onnavigate-demo.vercel.app/). Here are some other view transitions resources you may find helpful:
- [MDN view transitions docs](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API)
- [Chrome view transitions API explainer](https://developer.chrome.com/docs/web-platform/view-transitions/)
- [Rich Harris demoing view transitions with onNavigate](https://www.youtube.com/shorts/weOCWOD2UIo)
- [My Svelte Summit video showing how to use view transitions for FLIP animations](https://youtu.be/K95TQ-Yh7Cw)
- [Fruit list demo](https://sveltekit-shared-element-transitions-codelab.vercel.app/fruits) ([source](https://github.com/geoffrich/sveltekit-view-transitions))
- [Svelte Summit video list demo](https://http-203-svelte.vercel.app/) (based on a [Jake Archibald demo](https://http203-playlist.netlify.app/)) ([source](https://github.com/geoffrich/http-203-svelte))

@ -0,0 +1,97 @@
---
title: "What's new in Svelte: September 2023"
description: "New parameters in SvelteKit's redirect and an onNavigate lifecycle function come to life"
author: Dani Sandoval
authorURL: https://dreamindani.com
---
Happy September y'all! With all the [sneak peeks at what's coming soon in Svelte 5](https://twitter.com/Rich_Harris/status/1688581184018583558), we thought it'd be best to look back at the last month to see what's shipped and what the community is building with Svelte.
Before we jump in, a warm welcome to the new Svelte Ambassadors: [@cainux](https://github.com/cainux) and [@grischaerbe](https://github.com/grischaerbe)! Welcome to the crew ⛴️
## What's new in Svelte & Language Tools
- `svelteHTML` has moved from language-tools into Svelte core so that `svelte/element` types will now load correctly (**4.2.0** in Svelte, **107.10.0** in Language Tools)
## What's new in SvelteKit
- `URL` is now accepted in the `redirect` function (**1.23.0**, [Docs](https://kit.svelte.dev/docs/modules#sveltejs-kit-redirect), [#10570](https://github.com/sveltejs/kit/pull/10570))
- Mistyped route filenames will now throw a warning (**1.23.0**, [#10558](https://github.com/sveltejs/kit/pull/10558))
- The new `onNavigate` lifecycle function enables view transitions - Check out the [blog post](https://svelte.dev/blog/view-transitions) for more info (**1.24.0**, [Docs](https://kit.svelte.dev/docs/modules#app-navigation-onnavigate), [#9605](https://github.com/sveltejs/kit/pull/9605))
But that's just the new features! For all the patches and performance updates from this month, check out the [SvelteKit CHANGELOG](https://github.com/sveltejs/kit/blob/master/packages/kit/CHANGELOG.md). You can also find adapter-specific CHANGELOGs in each of [the `adapter` directories](https://github.com/sveltejs/kit/tree/master/packages).
---
## Community Showcase
**Apps & Sites built with Svelte**
- [Planet Of The Bugs](https://planetofthebugs.xyz/) allows developers to practice and hone their skill-sets by exposing them to an endless supply of unique, curated issues and bugs from popular open-source projects on Github
- [Minesweeper](https://github.com/ProductionPanic/minesweeper/tree/main) is an Android game built with SvelteKit, Capacitor, TailwindCSS and DaisyUI (check it out on the [Google Play Store](https://play.google.com/store/apps/details?id=com.production.panic.minesweeper&pli=1))
- [Pendor](https://www.pendor.ai/) is an AI component generator for Svelte
- [Avatars Pro](https://senja.io/testimonial-widgets/avatars-pro) is a social proof widget made for the web
- [Pomodoro Focus](https://github.com/con-dog/pomodoro-focus) is a pomodoro timer browser extension
- [memegen](https://github.com/bhupeshpr25/memegen) is a Firefox web extension that allows users to generate memes using various templates
- [Resgen](https://resgen.app/) is a Chrome extension that tailors resumes based on job descriptions and your experiences
- [Icono Search](https://www.icono-search.com) is an AI-powered video search engine
- [digital-paper](https://github.com/danferns/digital-paper) is a writing app with no backspace or undo
- [Ubuntu 22.04 in Svelte](https://github.com/manhhungpc/ubuntu2204-svelte) aims to replicate the Ubuntu 22.04 desktop experience on the web
- [My Queue](https://www.myqueue.so/) creates a playlist of written articles by turning them into audio stories
**Learning Resources**
_Featuring Svelte Contributors and Ambassadors_
- [Svelte Society - London August 2023](https://www.youtube.com/watch?v=90Psdk5rAnU)
- [Building a Blog using SvelteKit and Nostr as a CMS (Part 1](https://kevinak.se/blog/building-a-blog-using-sveltekit-and-nostr-as-a-cms-part-1-1690807337563)) by Kev
- [Mastering SvelteKit with Geoff Rich | JS Drops](https://www.youtube.com/watch?v=MaF8kRbHbi0) by This Dot Media
- [Using GitHub Contributions To Flex On The Normies](https://youtu.be/f9fd1L1FEts?si=3hbihW-X5-GKSJxN), [Learn Svelte By Making A Matching Game](https://www.youtube.com/watch?v=w2q9caYXgkg) and [Who Needs API Permission When You Can Use Web Scraping](https://www.youtube.com/watch?v=T-lBPpeokfY) by Joy of Code
- [The missing guide to understanding adapter-static in SvelteKit](https://khromov.se/the-missing-guide-to-understanding-adapter-static-in-sveltekit/) by Stanislav Khromov
- This Week in Svelte:
- [2023 July 28](https://www.youtube.com/watch?v=mvTEQ_C0qRQ) - Screen reader market share, Svelte to plain JS, Web Components
- [2023 Aug 4](https://www.youtube.com/watch?v=Ye8cCJyPZjg) - Svelte 4.1.2, SvelteKit 1.22.4, ES Modules, Types in markup
- [2023 August 11](https://www.youtube.com/watch?v=A8XUaiCVkCI) - Svelte 4.2.0, SvelteKit 1.22.5, How to create Toggle Switches
- [2023 August 18](https://www.youtube.com/watch?v=nJ5Wf3uL7dM) - SvelteKit 1.22.6, accessible form error summaries
- [2023 August 25](https://www.youtube.com/watch?v=JoPzvlBKXXE) - SvelteKit 1.23.0, Bun and SvelteKit, Enhanced search
- Svienna (Svelte Society Vienna) Sessions
- [Ermin Celikovic - You might not need a slider library](https://www.youtube.com/watch?v=dSUmtijkFOc)
- [Lukas Stracke - How to use sentry.io in your SvelteKit App](https://www.youtube.com/watch?v=u41-MtPGH04)
- [Jean-Yves Couet - SvelteKit & Remult... fullstack apps in minutes!](https://www.youtube.com/watch?v=N8d290fTzq8)
- Sirens Sessions
- [Prismic Slice Machines & SvelteKit](https://www.youtube.com/watch?v=19Meb-yMsAg) with Sam Littlefair
- [Medusa and SvelteKit E-Commerce Stack](https://www.youtube.com/watch?v=rVVHxows9dY) with Lacey Pevey
- [Design Systems: Lessons Learned](https://www.youtube.com/watch?v=YHZaiIGSqsE) with Eric Liu
_To Watch_
- [Image optimization in SvelteKit with vite-imagetools](https://www.youtube.com/watch?v=285vSLe9LQ8) by hartenfellerdev
- [Building a Todo App with Rust and SvelteKit: Complete Tutorial](https://www.youtube.com/watch?v=w7is2bCTUg0) and [Stripe Payment In SvelteKit With Dynamic Pricing](https://www.youtube.com/watch?v=o8gvCLgz1vs) by SvelteRust
- [Leaflet maps in SvelteKit like it's 2023 (HowTo)](https://www.youtube.com/watch?v=JFctWXEzFZw)
ShipBit
_To Read_
- [Internationalization in SvelteKit (Series)](https://blog.aakashgoplani.in/series/i18n-in-sveltekit) by Aakash Goplani
- [The easiest Chatbot you will ever build](https://simon-prammer.vercel.app/blog/post/sveltekit-langchain) and [Intro to LangSmith🦜🛠](https://simon-prammer.vercel.app/blog/post/langsmith) by Simon Prammer
- [SvelteKit: How to make code-based router, instead of file-based router [August 2023]](https://dev.to/maxcore/sveltekit-how-to-make-code-based-router-instead-of-file-based-router-august-2023-5f9) by Max Core
- [SvelteKit Hydration Gotcha](https://www.captaincodeman.com/sveltekit-hydration-gotcha) by Captain Codeman
- [Automatically generate sitemap.xml in SvelteKit](https://alex-schnabl.medium.com/automatically-generate-sitemap-xml-in-sveltekit-910bd09d17e7) by Alex Schnabl
- [Discovering Svelte: Things I Learned While Using Svelte](https://www.tronic247.com/discovering-svelte-things-i-learned-while-using-svelte/) by Posandu Mapa
- [Typed fetch with Sveltekit and Hono using RPC](https://dev.to/subhendupsingh/typed-fetch-with-sveltekit-and-hono-using-rpc-2clf) by Subhendu Pratap Singh
- [Svelte Context Module Scripts Explained](https://raqueebuddinaziz.com/blog/svelte-context-module-scripts-explained) by raqueebuddin aziz
- [Building with GPT4 and Svelte](https://kvak.io/meoweler) by levmiseri
- [Type-safe User Authentication in SvelteKit with Lucia, Planetscale, and Upstash Redis](https://upstash.com/blog/lucia-sveltekit) by Chris Jayden
- [Document Svelte Projects with HTML and JSDoc Comments](https://blog.robino.dev/posts/doc-comments-svelte) by Ross Robino
**Libraries, Tools & Components**
- [Carta](https://github.com/BearToCode/carta-md) is a lightweight, fast and extensible Svelte Markdown editor and viewer, based on Marked
- [Threlte](https://threlte.xyz/), the 3D framework built from Svelte and Three.js has released version 6
- [vite-plugin-web-extension](https://vite-plugin-web-extension.aklinker1.io/guide/frontend-frameworks.html#svelte-integration) works great with Svelte to make building browser extensions easier
- [Salvia-kit Svelte Dashboards](https://github.com/salvia-kit/svelte-dashboards) contains 10 free dashboard templates for SvelteKit
- [drab](https://github.com/rossrobino/drab) is an Unstyled Svelte component library
- [svelte-img-previewer](https://www.npmjs.com/package/svelte-img-previewer?activeTab=readme) is a tool for displaying images from input file types in Svelte
- [sveltekit-search-params](https://github.com/paoloricciuti/sveltekit-search-params) describes itself as the fastest way to read AND write from query search params in SvelteKit
That's it for this month! Feel free to let us know if we missed anything on [Reddit](https://www.reddit.com/r/sveltejs/) or [Discord](https://discord.gg/svelte).
Until next time 👋

@ -0,0 +1,224 @@
---
title: Introducing runes
description: "Rethinking 'rethinking reactivity'"
author: The Svelte team
authorURL: /
---
In 2019, Svelte 3 turned JavaScript into a [reactive language](/blog/svelte-3-rethinking-reactivity). Svelte is a web UI framework that uses a compiler to turn declarative component code like this...
```svelte
<script>
let count = 0;
function increment() {
count += 1;
}
</script>
<button on:click={increment}>
clicks: {count}
</button>
```
...into tightly optimized JavaScript that updates the document when state like `count` changes. Because the compiler can 'see' where `count` is referenced, the generated code is [highly efficient](/blog/virtual-dom-is-pure-overhead), and because we're hijacking syntax like `let` and `=` instead of using cumbersome APIs, you can [write less code](/blog/write-less-code).
A common piece of feedback we get is 'I wish I could write all my JavaScript like this'. When you're used to things inside components magically updating, going back to boring old procedural code feels like going from colour to black-and-white.
Svelte 5 changes all that with _runes_, which unlock _universal, fine-grained reactivity_.
<div class="max">
<figure style="max-width: 960px; margin: 0 auto">
<div style="aspect-ratio: 1.755; position: relative; margin: 0 auto;">
<iframe style="position: absolute; width: 100%; height: 100%; left: 0; top: 0; margin: 0;" src="https://www.youtube-nocookie.com/embed/RVnxF3j3N8U" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
<figcaption>Introducing runes</figcaption>
</figure>
</div>
## Before we begin
Even though we're changing how things work under the hood, Svelte 5 should be a drop-in replacement for almost everyone. The new features are opt-in — your existing components will continue to work.
We don't yet have a release date for Svelte 5. What we're showing you here is a work-in-progress that is likely to change!
## What are runes?
> **rune** /ro͞on/ _noun_
>
> A letter or mark used as a mystical or magic symbol.
Runes are symbols that influence the Svelte compiler. Whereas Svelte today uses `let`, `=`, the [`export`](https://learn.svelte.dev/tutorial/declaring-props) keyword and the [`$:`](https://learn.svelte.dev/tutorial/reactive-declarations) label to mean specific things, runes use _function syntax_ to achieve the same things and more.
For example, to declare a piece of reactive state, we can use the `$state` rune:
```diff
<script>
- let count = 0;
+ let count = $state(0);
function increment() {
count += 1;
}
</script>
<button on:click={increment}>
clicks: {count}
</button>
```
At first glance, this might seem like a step back — perhaps even [un-Svelte-like](https://twitter.com/stolinski/status/1438173489479958536). Isn't it better if `let count` is reactive by default?
Well, no. The reality is that as applications grow in complexity, figuring out which values are reactive and which aren't can get tricky. And the heuristic only works for `let` declarations at the top level of a component, which can cause confusion. Having code behave one way inside `.svelte` files and another inside `.js` can make it hard to refactor code, for example if you need to turn something into a [store](https://learn.svelte.dev/tutorial/writable-stores) so that you can use it in multiple places.
## Beyond components
With runes, reactivity extends beyond the boundaries of your `.svelte` files. Suppose we wanted to encapsulate our counter logic in a way that could be reused between components. Today, you would use a [custom store](https://learn.svelte.dev/tutorial/custom-stores) in a `.js` or `.ts` file:
```js
import { writable } from 'svelte/store';
export function createCounter() {
const { subscribe, update } = writable(0);
return {
subscribe,
increment: () => update((n) => n + 1)
};
}
```
Because this implements the _store contract_ — the returned value has a `subscribe` method — we can reference the store value by prefixing the store name with `$`:
```diff
<script>
+ import { createCounter } from './counter.js';
+
+ const counter = createCounter();
- let count = 0;
-
- function increment() {
- count += 1;
- }
</script>
-<button on:click={increment}>
- clicks: {count}
+<button on:click={counter.increment}>
+ clicks: {$counter}
</button>
```
This works, but it's pretty weird! We've found that the store API can get rather unwieldy when you start doing more complex things.
With runes, things get much simpler:
```diff
-import { writable } from 'svelte/store';
export function createCounter() {
- const { subscribe, update } = writable(0);
+ let count = $state(0);
return {
- subscribe,
- increment: () => update((n) => n + 1)
+ get count() { return count },
+ increment: () => count += 1
};
}
```
```diff
<script>
import { createCounter } from './counter.js';
const counter = createCounter();
</script>
<button on:click={counter.increment}>
- clicks: {$counter}
+ clicks: {counter.count}
</button>
```
Note that we're using a [get property](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get) in the returned object, so that `counter.count` always refers to the current value rather than the value at the time the function was called.
## Runtime reactivity
Today, Svelte uses _compile-time reactivity_. This means that if you have some code that uses the `$:` label to re-run automatically when dependencies change, those dependencies are determined when Svelte compiles your component:
```svelte
<script>
export let width;
export let height;
// the compiler knows it should recalculate `area`
// when either `width` or `height` change...
$: area = width * height;
// ...and that it should log the value of `area`
// when _it_ changes
$: console.log(area);
</script>
```
This works well... until it doesn't. Suppose we refactored the code above:
```js
// @errors: 7006 2304
const multiplyByHeight = (width) => width * height;
$: area = multiplyByHeight(width);
```
Because the `$: area = ...` declaration can only 'see' `width`, it won't be recalculated when `height` changes. As a result, code is hard to refactor, and understanding the intricacies of when Svelte chooses to update which values can become rather tricky beyond a certain level of complexity.
Svelte 5 introduces the `$derived` and `$effect` runes, which instead determine the dependencies of their expressions when they are evaluated:
```svelte
<script>
let { width, height } = $props(); // instead of `export let`
const area = $derived(width * height);
$effect(() => {
console.log(area);
});
</script>
```
As with `$state`, `$derived` and `$effect` can also be used in your `.js` and `.ts` files.
## Signal boost
Like every other framework, we've come to the realisation that [Knockout](https://knockoutjs.com/) was right all along.
Svelte 5's reactivity is powered by _signals_, which are essentially [what Knockout was doing in 2010](https://dev.to/this-is-learning/the-evolution-of-signals-in-javascript-8ob). More recently, signals have been popularised by [Solid](https://www.solidjs.com/) and adopted by a multitude of other frameworks.
We're doing things a bit differently though. In Svelte 5, signals are an under-the-hood implementation detail rather than something you interact with directly. As such, we don't have the same API design constraints, and can maximise both efficiency _and_ ergonomics. For example, we avoid the type narrowing issues that arise when values are accessed by function call, and when compiling in server-side rendering mode we can ditch the signals altogether, since on the server they're nothing but overhead.
Signals unlock _fine-grained reactivity_, meaning that (for example) changes to a value inside a large list needn't invalidate all the _other_ members of the list. As such, Svelte 5 is ridonkulously fast.
## Simpler times ahead
Runes are an additive feature, but they make a whole bunch of existing concepts obsolete:
- the difference between `let` at the top level of a component and everywhere else
- `export let`
- `$:`, with all its attendant quirks
- different behaviour between `<script>` and `<script context="module">`
- the store API, parts of which are genuinely quite complicated
- the `$` store prefix
- `$$props` and `$$restProps`
- lifecycle functions (things like `onMount` can just be `$effect` functions)
For those of you who already use Svelte, it's new stuff to learn, albeit hopefully stuff that makes your Svelte apps easier to build and maintain. But newcomers won't need to learn all those things — it'll just be in a section of the docs titled 'old stuff'.
This is just the beginning though. We have a long list of ideas for subsequent releases that will make Svelte simpler and more capable.
## Try it!
You can't use Svelte 5 in production yet. We're in the thick of it at the moment and can't tell you when it'll be ready to use in your apps.
But we didn't want to leave you hanging. We've created a [preview site](https://svelte-5-preview.vercel.app) with detailed explanations of the new features and an interactive playground. You can also visit the `#svelte-5-runes` channel of the [Svelte Discord](/chat) to learn more. We'd love to have your feedback!

@ -0,0 +1,24 @@
---
title: "Hacktoberfest 2023 with SvelteKit"
description: "SvelteKit joins in the Hacktoberfest event in 2023"
author: Willow (GHOST) & Braden Wiggins
authorURL: https://ghostdev.xyz
---
# Sveltekit 🧡 Hacktoberfest
We're excited to announce SvelteKit's participation in this year's Hacktoberfest! Hacktoberfest is a global event that takes place every October, during which developers are encouraged to contribute to open-source projects like SvelteKit. The goal is to foster a vibrant open-source community, celebrate shared knowledge, and make the world of coding more accessible to all. You can find other participating repositories by searching the [`hacktoberfest` topic](https://github.com/topics/hacktoberfest) on github.
## Getting Started
The first step is to [register for Hacktoberfest](https://hacktoberfest.com/register). Once you've registered, any PRs submitted to Sveltekit will count towards your participation in the event!
## Contributing
If you need inspiration when looking for an issue to fix, check out the [low hanging fruit](https://github.com/sveltejs/kit/labels/low%20hanging%20fruit), [contributions-welcome](https://github.com/sveltejs/kit/labels/contributions-welcome), [ready to implement](https://github.com/sveltejs/kit/labels/ready%20to%20implement), or [documentation](https://github.com/sveltejs/kit/labels/documentation) tags.
It's a good idea to communicate clearly and often about what you're trying to solve or take on. You can do this by commenting on the issues you intend to take on. This helps avoid duplicate work and ensures that your contribution is in line with the project's goals. Nobody likes to have their work rejected, so it's best to ask questions early and often!
Join our [Discord](https://svelte.dev/chat) and ask questions in the dedicated `#hacktoberfest` channel. We're happy to help you get started!
We're excited to see what you've got in store for SvelteKit! Happy hacking! 🎃

@ -0,0 +1,83 @@
---
title: "What's new in Svelte: October 2023"
description: "Reactions to Runes and SvelteKit +server fallbacks"
author: Dani Sandoval
authorURL: https://dreamindani.com
---
Svelte 5 isn't out yet (you can, however, [preview it now](https://svelte-5-preview.vercel.app/)), but that doesn't mean we don't get a sneak peek! Most notably are [Runes](https://svelte.dev/blog/runes) - a simpler way to manage reactive variables in Svelte code. There's lots of links the showcase section for deeper dives on all things Runes, but let's talk about what else been released this month...
## What's new in Svelte & Language Tools
- [Svelte 4.2.1](https://github.com/sveltejs/svelte/blob/master/packages/svelte/CHANGELOG.md#421) was released with a bunch of fixes to HTML, CSS and sourcemap compilation
- [The latest version of the Svelte language tools](https://github.com/sveltejs/language-tools/releases/tag/extensions-107.11.0) [enhances component references](https://github.com/sveltejs/language-tools/pull/2157) in the "Find All References" command, [fixes a persistent issue with automated types going missing](https://github.com/sveltejs/language-tools/pull/2160) after restarting a project and [adds fallback handling to auto-types](https://github.com/sveltejs/language-tools/issues/2156) (like those found in SvelteKit's `+server.js` files)
## What's new in SvelteKit
- `+server.js` now has a catch-all handler that handles all unimplemented valid server requests. Just export a `fallback` function! (**1.25.0**, [Docs](https://kit.svelte.dev/docs/routing#server-fallback-method-handler), [#9755](https://github.com/sveltejs/kit/pull/9755))
That's all for the new features! If you're looking for other patches and performance updates, check out the [SvelteKit CHANGELOG](https://github.com/sveltejs/kit/blob/master/packages/kit/CHANGELOG.md). You can also find adapter-specific CHANGELOGs in each of [the `adapter` directories](https://github.com/sveltejs/kit/tree/master/packages).
---
## Community Showcase
[Svelte Summit Fall](https://www.sveltesummit.com/) is happening on Nov 11, 2023. The 7th Virtual Svelte Conference is [open for proposals until October 15](https://sessionize.com/svelte-summit-fall-2023/) - anyone can submit!
Threlte [is throwing a hackathon](https://threlte.xyz/hackathon) (**motion warning for the landing page** - it will respect Reduce Motion settings). The kickoff event is on Sunday, 15 October 2023 16:00 UTC.
**Apps & Sites built with Svelte**
- [game-of-life-svelte](https://github.com/StephenGunn/game-of-life-svelte) is a Conway's Game of Life implementation using SvelteKit tech
- [Limey](https://limey.io/) is an easy-to-use website builder for simple sites and landing pages
- [Appwrite's new landing page](https://appwrite.io/) is now written with SvelteKit (previously covered was their [console UI](https://github.com/appwrite/console) in Svelte)
- [PlaceIt](https://github.com/Dae314/placeit-game) is a game about numbers and places
- [Sveltroid](https://sveltroid.vercel.app/) is a fan-made recreation of Metroid Prime: Remastered ([code](https://github.com/TylerTonyJohnson/Metroid))
- [Bolighub](https://www.bolighub.dk/) is a Denmark housing search portal
- [Dithering](https://www.sigrist.dev/dithering) is a tool to dither photos with plenty of options
- [Rocky Mountain Slam](https://www.rockymountainslam.com/) is an interactive map to follow Jason Heyn as he attempts to complete the first ever Rocky Mountain Slam ([code](https://github.com/martyheyn/rocky-mnt-slam))
**Learning Resources**
_Featuring Svelte Contributors and Ambassadors_
- [Svelte 5: Introducing Runes... with Rich Harris](https://www.youtube.com/watch?v=RVnxF3j3N8U) and its follow-up: [Svelte 5 runes: what's the deal with getters and setters?](https://www.youtube.com/watch?v=NR8L5m73dtE)
- [Conditionally stream data in SvelteKit](https://geoffrich.net/posts/conditionally-stream-data/) by Geoff Rich
- [Svelte Runes Change How Reactivity Works In Svelte](https://www.youtube.com/watch?v=TOTUXiYZhf4), [Make A 3D GitHub Skyline With Svelte To Flex On Your Peers](https://www.youtube.com/watch?v=f9fd1L1FEts), [Simple Page Transitions Using The View Transitions API With SvelteKit](https://www.youtube.com/watch?v=q_2irZO4SS8) and [Using JavaScript Libraries With Svelte Is Easy](https://www.youtube.com/watch?v=N9OjaQ0XtKQ) by Joy of Code
- [Modern Web Podcast S11E2](https://modernweb.podbean.com/e/modern-web-podcast-s11e2-exploring-svelte-open-source-and-discord-bots-with-willow-ghost/) - Exploring Svelte, Open Source, and Discord Bots with Willow (GHOST)
- [We are back! Svelte 5, Transitions, What's New?!](https://www.svelteradio.com/episodes/we-are-back-svelte-5-transitions-whats-new) by Svelte Radio
- This Week in Svelte:
- [2023 September 1](https://www.youtube.com/watch?v=fonBnVCIrjE) - SvelteKit 1.24.0, View Transitions API, AbortController
- [2023 September 8](https://www.youtube.com/watch?v=jfBjmczZwRc) - SvelteKit 1.24.1, Capacitor walkthrough, reusing prop types
- [2023 September 15](https://www.youtube.com/watch?v=qH2FavwhU88) - SvelteKit 1.25.0, deserialize form data, magic is coming
- [2023 September 22](https://www.youtube.com/watch?v=ek7KE1EDu2w) - Svelte 5 Runes!
_To Watch_
- [RUNES - Coming in Svelte v5 | My Take](https://www.youtube.com/watch?v=iCK1coch1wA) by Coding Garden
- [Don't Sleep on Svelte 5](https://www.youtube.com/watch?v=DgNWssn2vpc) and [Level Up Your Svelte Stores](https://www.youtube.com/watch?v=-vjNAyL2JCQ) by Huntabyte
- [Introduction To Svelte Runes (Every Svelte Rune Explained)](https://www.youtube.com/watch?v=gihSBVfyFbI) by Cooper Codes
- [Svelte Runes: Awesome or Awful?](https://www.youtube.com/watch?v=JRZCqUOmFwY) by Jack Herrington
- [Let Build A Youtube Clone With SvelteKit (Svelte, Tailwind Css, RapidApi, Shadcn Svelte, Axios, etc)](https://www.youtube.com/watch?v=65yMfpsoH4o) by Lawal Adebola
_To Read_
- [Create the Perfect Sharable Rune in Svelte](https://dev.to/jdgamble555/create-the-perfect-sharable-rune-in-svelte-ij8) by Jonathan Gamble
- [You Don't Need to "Learn" Svelte](https://kaviisuri.com/you-dont-need-to-learn-svelte) by KaviiSuri
- [Build Websites with Prismic and SvelteKit](https://prismic.io/blog/sveltekit-prismic-integration) by Angelo Ashmore
- [How to embed Svelte apps inside PHP?](https://www.okupter.com/blog/php-embed-svelte) by Justin Ahinon
- [Using Web Browser's Indexed DB in SvelteKit](https://dev.to/theether0/using-web-browsers-indexed-db-in-sveltekit-3oo3) by Shivam Meena
- [Integrate Storybook in Svelte: Doing it the Svelte-way](https://mainmatter.com/blog/2023/09/18/integrate-storybook-in-svelte-doing-it-the-svelte-way/) by Oscar Dominguez
- [The Sveltekit tutorial: Part 1 | What, why, and how?](https://tntman.tech/posts/sveltekit-guide-part-1) by Suyashtnt
**Libraries, Tools & Components**
- [KitForStartups](https://github.com/okupter/kitforstartups) is an Open Source SvelteKit SaaS boilerplate
- [SuperNavigation](https://github.com/0xDjole/super-navigation) is a mobile-like navigation UX for the web
- [skeleton-material-theme](https://github.com/plasmatech8/skeleton-material-theme) is a Material theme for the Skeleton UI library
- [better-i18n-for-svelte](https://github.com/versiobit/better-i18n-for-svelte) is a SEO focused library for multi-language SvelteKit sites
- [uico](https://github.com/rossrobino/uico) is a Tailwind plugin that provides utility classes for basic UI elements
- [svelte-maskify](https://www.npmjs.com/package/svelte-maskify) is a action wrapper for AlpineJS masks
- [sveltekit-capacitor](https://github.com/Hugos68/sveltekit-capacitor) is a template for building a SvelteKit SPA with Capacitor
- [router-gen.ts](https://gist.github.com/HugeLetters/7a2813897dfe08fa948a13cac8a359c7) is a type-safe router for SvelteKit
That's it for this month! Feel free to let us know if we missed anything on [Reddit](https://www.reddit.com/r/sveltejs/) or [Discord](https://discord.gg/svelte).
Until next time 👋

@ -21,7 +21,7 @@ SvelteKit will handle calling [the Svelte compiler](https://www.npmjs.com/packag
### Alternatives to SvelteKit ### Alternatives to SvelteKit
If you don't want to use SvelteKit for some reason, you can also use Svelte with Vite (but without SvelteKit) by running `npm init vite` and selecting the `svelte` option. With this, `npm run build` will generate HTML, JS and CSS files inside the `dist` directory. In most cases, you will probably need to [choose a routing library](/faq#is-there-a-router) as well. If you don't want to use SvelteKit for some reason, you can also use Svelte with Vite (but without SvelteKit) by running `npm create vite@latest` and selecting the `svelte` option. With this, `npm run build` will generate HTML, JS and CSS files inside the `dist` directory. In most cases, you will probably need to [choose a routing library](/faq#is-there-a-router) as well.
Alternatively, there are [plugins for all the major web bundlers](https://sveltesociety.dev/tools#bundling) to handle Svelte compilation — which will output `.js` and `.css` that you can insert into your HTML — but most others won't handle SSR. Alternatively, there are [plugins for all the major web bundlers](https://sveltesociety.dev/tools#bundling) to handle Svelte compilation — which will output `.js` and `.css` that you can insert into your HTML — but most others won't handle SSR.

@ -56,8 +56,9 @@ All other attributes are included unless their value is [nullish](https://develo
An expression might include characters that would cause syntax highlighting to fail in regular HTML, so quoting the value is permitted. The quotes do not affect how the value is parsed: An expression might include characters that would cause syntax highlighting to fail in regular HTML, so quoting the value is permitted. The quotes do not affect how the value is parsed:
<!-- prettier-ignore -->
```svelte ```svelte
<button disabled={number !== 42}>...</button> <button disabled="{number !== 42}">...</button>
``` ```
When the attribute name and value match (`name={name}`), they can be replaced with `{name}`. When the attribute name and value match (`name={name}`), they can be replaced with `{name}`.
@ -105,11 +106,13 @@ An element or component can have multiple spread attributes, interspersed with r
## Text expressions ## Text expressions
A JavaScript expression can be included as text by surrounding it with curly braces.
```svelte ```svelte
{expression} {expression}
``` ```
Text can also contain JavaScript expressions: Curly braces can be included in a Svelte template by using their [HTML entity](https://developer.mozilla.org/docs/Glossary/Entity) strings: `&lbrace;`, `&lcub;`, or `&#123;` for `{` and `&rbrace;`, `&rcub;`, or `&#125;` for `}`.
> If you're using a regular expression (`RegExp`) [literal notation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#literal_notation_and_constructor), you'll need to wrap it in parentheses. > If you're using a regular expression (`RegExp`) [literal notation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#literal_notation_and_constructor), you'll need to wrap it in parentheses.

@ -5,14 +5,17 @@ title: Logic blocks
## {#if ...} ## {#if ...}
```svelte ```svelte
<!--- copy: false --->
{#if expression}...{/if} {#if expression}...{/if}
``` ```
```svelte ```svelte
<!--- copy: false --->
{#if expression}...{:else if expression}...{/if} {#if expression}...{:else if expression}...{/if}
``` ```
```svelte ```svelte
<!--- copy: false --->
{#if expression}...{:else}...{/if} {#if expression}...{:else}...{/if}
``` ```
@ -41,22 +44,27 @@ Additional conditions can be added with `{:else if expression}`, optionally endi
## {#each ...} ## {#each ...}
```svelte ```svelte
<!--- copy: false --->
{#each expression as name}...{/each} {#each expression as name}...{/each}
``` ```
```svelte ```svelte
<!--- copy: false --->
{#each expression as name, index}...{/each} {#each expression as name, index}...{/each}
``` ```
```svelte ```svelte
<!--- copy: false --->
{#each expression as name (key)}...{/each} {#each expression as name (key)}...{/each}
``` ```
```svelte ```svelte
<!--- copy: false --->
{#each expression as name, index (key)}...{/each} {#each expression as name, index (key)}...{/each}
``` ```
```svelte ```svelte
<!--- copy: false --->
{#each expression as name}...{:else}...{/each} {#each expression as name}...{:else}...{/each}
``` ```
@ -125,29 +133,35 @@ Since Svelte 4 it is possible to iterate over iterables like `Map` or `Set`. Ite
## {#await ...} ## {#await ...}
```svelte ```svelte
<!--- copy: false --->
{#await expression}...{:then name}...{:catch name}...{/await} {#await expression}...{:then name}...{:catch name}...{/await}
``` ```
```svelte ```svelte
<!--- copy: false --->
{#await expression}...{:then name}...{/await} {#await expression}...{:then name}...{/await}
``` ```
```svelte ```svelte
<!--- copy: false --->
{#await expression then name}...{/await} {#await expression then name}...{/await}
``` ```
```svelte ```svelte
<!--- copy: false --->
{#await expression catch name}...{/await} {#await expression catch name}...{/await}
``` ```
Await blocks allow you to branch on the three possible states of a Promise — pending, fulfilled or rejected. In SSR mode, only the pending state will be rendered on the server. Await blocks allow you to branch on the three possible states of a Promise — pending, fulfilled or rejected.
In SSR mode, only the pending branch will be rendered on the server.
If the provided expression is not a Promise only the fulfilled branch will be rendered, including in SSR mode.
```svelte ```svelte
{#await promise} {#await promise}
<!-- promise is pending --> <!-- promise is pending -->
<p>waiting for the promise to resolve...</p> <p>waiting for the promise to resolve...</p>
{:then value} {:then value}
<!-- promise was fulfilled --> <!-- promise was fulfilled or not a Promise -->
<p>The value is {value}</p> <p>The value is {value}</p>
{:catch error} {:catch error}
<!-- promise was rejected --> <!-- promise was rejected -->
@ -186,6 +200,7 @@ Similarly, if you only want to show the error state, you can omit the `then` blo
## {#key ...} ## {#key ...}
```svelte ```svelte
<!--- copy: false --->
{#key expression}...{/key} {#key expression}...{/key}
``` ```

@ -5,6 +5,7 @@ title: Special tags
## {@html ...} ## {@html ...}
```svelte ```svelte
<!--- copy: false --->
{@html expression} {@html expression}
``` ```
@ -24,10 +25,12 @@ The expression should be valid standalone HTML — `{@html "<div>"}content{@html
## {@debug ...} ## {@debug ...}
```svelte ```svelte
<!--- copy: false --->
{@debug} {@debug}
``` ```
```svelte ```svelte
<!--- copy: false --->
{@debug var1, var2, ..., varN} {@debug var1, var2, ..., varN}
``` ```
@ -65,6 +68,7 @@ The `{@debug}` tag without any arguments will insert a `debugger` statement that
## {@const ...} ## {@const ...}
```svelte ```svelte
<!--- copy: false --->
{@const assignment} {@const assignment}
``` ```

@ -7,10 +7,12 @@ As well as attributes, elements can have _directives_, which control the element
## on:_eventname_ ## on:_eventname_
```svelte ```svelte
<!--- copy: false --->
on:eventname={handler} on:eventname={handler}
``` ```
```svelte ```svelte
<!--- copy: false --->
on:eventname|modifiers={handler} on:eventname|modifiers={handler}
``` ```
@ -72,7 +74,6 @@ If the `on:` directive is used without a value, the component will _forward_ the
It's possible to have multiple event listeners for the same event: It's possible to have multiple event listeners for the same event:
```svelte ```svelte
<!--- file: App.svelte --->
<script> <script>
let counter = 0; let counter = 0;
function increment() { function increment() {
@ -91,6 +92,7 @@ It's possible to have multiple event listeners for the same event:
## bind:_property_ ## bind:_property_
```svelte ```svelte
<!--- copy: false --->
bind:property={variable} bind:property={variable}
``` ```
@ -186,6 +188,8 @@ Elements with the `contenteditable` attribute support the following bindings:
There are slight differences between each of these, read more about them [here](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent#Differences_from_innerText). There are slight differences between each of these, read more about them [here](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent#Differences_from_innerText).
<!-- for some reason puts the comment and html on same line -->
<!-- prettier-ignore -->
```svelte ```svelte
<div contenteditable="true" bind:innerHTML={html} /> <div contenteditable="true" bind:innerHTML={html} />
``` ```
@ -273,13 +277,13 @@ Block-level elements have 4 read-only bindings, measured using a technique simil
## bind:group ## bind:group
```svelte ```svelte
<!--- copy: false --->
bind:group={variable} bind:group={variable}
``` ```
Inputs that work together can use `bind:group`. Inputs that work together can use `bind:group`.
```svelte ```svelte
<!--- file: App.svelte --->
<script> <script>
let tortilla = 'Plain'; let tortilla = 'Plain';
@ -304,13 +308,13 @@ Inputs that work together can use `bind:group`.
## bind:this ## bind:this
```svelte ```svelte
<!--- copy: false --->
bind:this={dom_node} bind:this={dom_node}
``` ```
To get a reference to a DOM node, use `bind:this`. To get a reference to a DOM node, use `bind:this`.
```svelte ```svelte
<!--- file: App.svelte --->
<script> <script>
import { onMount } from 'svelte'; import { onMount } from 'svelte';
@ -329,10 +333,12 @@ To get a reference to a DOM node, use `bind:this`.
## class:_name_ ## class:_name_
```svelte ```svelte
<!--- copy: false --->
class:name={value} class:name={value}
``` ```
```svelte ```svelte
<!--- copy: false --->
class:name class:name
``` ```
@ -393,14 +399,17 @@ When `style:` directives are combined with `style` attributes, the directives wi
## use:_action_ ## use:_action_
```svelte ```svelte
<!--- copy: false --->
use:action use:action
``` ```
```svelte ```svelte
<!--- copy: false --->
use:action={parameters} use:action={parameters}
``` ```
```ts ```ts
/// copy: false
// @noErrors // @noErrors
action = (node: HTMLElement, parameters: any) => { action = (node: HTMLElement, parameters: any) => {
update?: (parameters: any) => void, update?: (parameters: any) => void,
@ -411,7 +420,6 @@ action = (node: HTMLElement, parameters: any) => {
Actions are functions that are called when an element is created. They can return an object with a `destroy` method that is called after the element is unmounted: Actions are functions that are called when an element is created. They can return an object with a `destroy` method that is called after the element is unmounted:
```svelte ```svelte
<!--- file: App.svelte --->
<script> <script>
/** @type {import('svelte/action').Action} */ /** @type {import('svelte/action').Action} */
function foo(node) { function foo(node) {
@ -433,7 +441,6 @@ An action can have a parameter. If the returned value has an `update` method, it
> Don't worry about the fact that we're redeclaring the `foo` function for every component instance — Svelte will hoist any functions that don't depend on local state out of the component definition. > Don't worry about the fact that we're redeclaring the `foo` function for every component instance — Svelte will hoist any functions that don't depend on local state out of the component definition.
```svelte ```svelte
<!--- file: App.svelte --->
<script> <script>
export let bar; export let bar;
@ -461,30 +468,37 @@ Read more in the [`svelte/action`](/docs/svelte-action) page.
## transition:_fn_ ## transition:_fn_
```svelte ```svelte
<!--- copy: false --->
transition:fn transition:fn
``` ```
```svelte ```svelte
<!--- copy: false --->
transition:fn={params} transition:fn={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
transition:fn|global transition:fn|global
``` ```
```svelte ```svelte
<!--- copy: false --->
transition:fn|global={params} transition:fn|global={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
transition:fn|local transition:fn|local
``` ```
```svelte ```svelte
<!--- copy: false --->
transition:fn|local={params} transition:fn|local={params}
``` ```
```js ```js
/// copy: false
// @noErrors // @noErrors
transition = (node: HTMLElement, params: any, options: { direction: 'in' | 'out' | 'both' }) => { transition = (node: HTMLElement, params: any, options: { direction: 'in' | 'out' | 'both' }) => {
delay?: number, delay?: number,
@ -544,7 +558,6 @@ The `t` argument passed to `css` is a value between `0` and `1` after the `easin
The function is called repeatedly _before_ the transition begins, with different `t` and `u` arguments. The function is called repeatedly _before_ the transition begins, with different `t` and `u` arguments.
```svelte ```svelte
<!--- file: App.svelte --->
<script> <script>
import { elasticOut } from 'svelte/easing'; import { elasticOut } from 'svelte/easing';
@ -644,50 +657,62 @@ An element with transitions will dispatch the following events in addition to an
## in:_fn_/out:_fn_ ## in:_fn_/out:_fn_
```svelte ```svelte
<!--- copy: false --->
in:fn in:fn
``` ```
```svelte ```svelte
<!--- copy: false --->
in:fn={params} in:fn={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
in:fn|global in:fn|global
``` ```
```svelte ```svelte
<!--- copy: false --->
in:fn|global={params} in:fn|global={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
in:fn|local in:fn|local
``` ```
```svelte ```svelte
<!--- copy: false --->
in:fn|local={params} in:fn|local={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
out:fn out:fn
``` ```
```svelte ```svelte
<!--- copy: false --->
out:fn={params} out:fn={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
out:fn|global out:fn|global
``` ```
```svelte ```svelte
<!--- copy: false --->
out:fn|global={params} out:fn|global={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
out:fn|local out:fn|local
``` ```
```svelte ```svelte
<!--- copy: false --->
out:fn|local={params} out:fn|local={params}
``` ```
@ -704,14 +729,17 @@ Unlike with `transition:`, transitions applied with `in:` and `out:` are not bid
## animate:_fn_ ## animate:_fn_
```svelte ```svelte
<!--- copy: false --->
animate:name animate:name
``` ```
```svelte ```svelte
<!--- copy: false --->
animate:name={params} animate:name={params}
``` ```
```js ```js
/// copy: false
// @noErrors // @noErrors
animation = (node: HTMLElement, { from: DOMRect, to: DOMRect } , params: any) => { animation = (node: HTMLElement, { from: DOMRect, to: DOMRect } , params: any) => {
delay?: number, delay?: number,
@ -723,6 +751,7 @@ animation = (node: HTMLElement, { from: DOMRect, to: DOMRect } , params: any) =>
``` ```
```ts ```ts
/// copy: false
// @noErrors // @noErrors
DOMRect { DOMRect {
bottom: number, bottom: number,
@ -772,7 +801,6 @@ The function is called repeatedly _before_ the animation begins, with different
<!-- TODO: Types --> <!-- TODO: Types -->
```svelte ```svelte
<!--- file: App.svelte --->
<script> <script>
import { cubicOut } from 'svelte/easing'; import { cubicOut } from 'svelte/easing';
@ -806,7 +834,6 @@ A custom animation function can also return a `tick` function, which is called _
> If it's possible to use `css` instead of `tick`, do so — CSS animations can run off the main thread, preventing jank on slower devices. > If it's possible to use `css` instead of `tick`, do so — CSS animations can run off the main thread, preventing jank on slower devices.
```svelte ```svelte
<!--- file: App.svelte --->
<script> <script>
import { cubicOut } from 'svelte/easing'; import { cubicOut } from 'svelte/easing';

@ -5,28 +5,24 @@ title: Component directives
## on:_eventname_ ## on:_eventname_
```svelte ```svelte
<!--- copy: false --->
on:eventname={handler} on:eventname={handler}
``` ```
Components can emit events using [`createEventDispatcher`](/docs/svelte#createeventdispatcher) or by forwarding DOM events. Components can emit events using [`createEventDispatcher`](/docs/svelte#createeventdispatcher) or by forwarding DOM events.
```svelte ```svelte
<!-- SomeComponent.svelte -->
<script> <script>
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
</script> </script>
<!-- programmatic dispatching --> <!-- programmatic dispatching -->
<button on:click={() => dispatch('hello')}> <button on:click={() => dispatch('hello')}> one </button>
one
</button>
<!-- declarative event forwarding --> <!-- declarative event forwarding -->
<button on:click> <button on:click> two </button>
two
</button>
``` ```
Listening for component events looks the same as listening for DOM events: Listening for component events looks the same as listening for DOM events:
@ -44,6 +40,7 @@ As with DOM events, if the `on:` directive is used without a value, the event wi
## --style-props ## --style-props
```svelte ```svelte
<!--- copy: false --->
--style-props="anycssvalue" --style-props="anycssvalue"
``` ```
@ -78,7 +75,6 @@ For SVG namespace, the example above desugars into using `<g>` instead:
Svelte's CSS Variables support allows for easily themeable components: Svelte's CSS Variables support allows for easily themeable components:
```svelte ```svelte
<!-- Slider.svelte -->
<style> <style>
.potato-slider-rail { .potato-slider-rail {
background-color: var(--rail-color, var(--theme-color, 'purple')); background-color: var(--rail-color, var(--theme-color, 'purple'));
@ -118,6 +114,7 @@ While Svelte props are reactive without binding, that reactivity only flows down
## bind:this ## bind:this
```svelte ```svelte
<!--- copy: false --->
bind:this={component_instance} bind:this={component_instance}
``` ```

@ -198,7 +198,6 @@ If `this` is the name of a [void element](https://developer.mozilla.org/en-US/do
<script> <script>
let tag = 'div'; let tag = 'div';
/** @type {(e: MouseEvent) => void} */
export let handler; export let handler;
</script> </script>

@ -61,9 +61,7 @@ Note that the value of a `writable` is lost when it is destroyed, for example wh
Creates a store whose value cannot be set from 'outside', the first argument is the store's initial value, and the second argument to `readable` is the same as the second argument to `writable`. Creates a store whose value cannot be set from 'outside', the first argument is the store's initial value, and the second argument to `readable` is the same as the second argument to `writable`.
```js ```ts
<!--- file: App.svelte --->
// ---cut---
import { readable } from 'svelte/store'; import { readable } from 'svelte/store';
const time = readable(new Date(), (set) => { const time = readable(new Date(), (set) => {
@ -114,7 +112,7 @@ The callback can set a value asynchronously by accepting a second argument, `set
In this case, you can also pass a third argument to `derived` — the initial value of the derived store before `set` or `update` is first called. If no initial value is specified, the store's initial value will be `undefined`. In this case, you can also pass a third argument to `derived` — the initial value of the derived store before `set` or `update` is first called. If no initial value is specified, the store's initial value will be `undefined`.
```js ```ts
// @filename: ambient.d.ts // @filename: ambient.d.ts
import { type Writable } from 'svelte/store'; import { type Writable } from 'svelte/store';
@ -129,13 +127,17 @@ export {};
// ---cut--- // ---cut---
import { derived } from 'svelte/store'; import { derived } from 'svelte/store';
const delayed = derived(a, ($a, set) => { const delayed = derived(
setTimeout(() => set($a), 1000); a,
}, 2000); ($a, set) => {
setTimeout(() => set($a), 1000);
},
2000
);
const delayedIncrement = derived(a, ($a, set, update) => { const delayedIncrement = derived(a, ($a, set, update) => {
set($a); set($a);
setTimeout(() => update(x => x + 1), 1000); setTimeout(() => update((x) => x + 1), 1000);
// every time $a produces a value, this produces two // every time $a produces a value, this produces two
// values, $a immediately and then $a + 1 a second later // values, $a immediately and then $a + 1 a second later
}); });
@ -143,7 +145,7 @@ const delayedIncrement = derived(a, ($a, set, update) => {
If you return a function from the callback, it will be called when a) the callback runs again, or b) the last subscriber unsubscribes. If you return a function from the callback, it will be called when a) the callback runs again, or b) the last subscriber unsubscribes.
```js ```ts
// @filename: ambient.d.ts // @filename: ambient.d.ts
import { type Writable } from 'svelte/store'; import { type Writable } from 'svelte/store';
@ -224,7 +226,7 @@ Generally, you should read the value of a store by subscribing to it and using t
> This works by creating a subscription, reading the value, then unsubscribing. It's therefore not recommended in hot code paths. > This works by creating a subscription, reading the value, then unsubscribing. It's therefore not recommended in hot code paths.
```js ```ts
// @filename: ambient.d.ts // @filename: ambient.d.ts
import { type Writable } from 'svelte/store'; import { type Writable } from 'svelte/store';

@ -9,14 +9,17 @@ The `svelte/transition` module exports seven functions: `fade`, `blur`, `fly`, `
> EXPORT_SNIPPET: svelte/transition#fade > EXPORT_SNIPPET: svelte/transition#fade
```svelte ```svelte
<!--- copy: false --->
transition:fade={params} transition:fade={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
in:fade={params} in:fade={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
out:fade={params} out:fade={params}
``` ```
@ -45,14 +48,17 @@ You can see the `fade` transition in action in the [transition tutorial](https:/
> EXPORT_SNIPPET: svelte/transition#blur > EXPORT_SNIPPET: svelte/transition#blur
```svelte ```svelte
<!--- copy: false --->
transition:blur={params} transition:blur={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
in:blur={params} in:blur={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
out:blur={params} out:blur={params}
``` ```
@ -81,14 +87,17 @@ Animates a `blur` filter alongside an element's opacity.
> EXPORT_SNIPPET: svelte/transition#fly > EXPORT_SNIPPET: svelte/transition#fly
```svelte ```svelte
<!--- copy: false --->
transition:fly={params} transition:fly={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
in:fly={params} in:fly={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
out:fly={params} out:fly={params}
``` ```
@ -126,14 +135,17 @@ You can see the `fly` transition in action in the [transition tutorial](https://
> EXPORT_SNIPPET: svelte/transition#slide > EXPORT_SNIPPET: svelte/transition#slide
```svelte ```svelte
<!--- copy: false --->
transition:slide={params} transition:slide={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
in:slide={params} in:slide={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
out:slide={params} out:slide={params}
``` ```
@ -165,14 +177,17 @@ Slides an element in and out.
> EXPORT_SNIPPET: svelte/transition#scale > EXPORT_SNIPPET: svelte/transition#scale
```svelte ```svelte
<!--- copy: false --->
transition:scale={params} transition:scale={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
in:scale={params} in:scale={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
out:scale={params} out:scale={params}
``` ```
@ -204,14 +219,17 @@ Animates the opacity and scale of an element. `in` transitions animate from an e
> EXPORT_SNIPPET: svelte/transition#draw > EXPORT_SNIPPET: svelte/transition#draw
```svelte ```svelte
<!--- copy: false --->
transition:draw={params} transition:draw={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
in:draw={params} in:draw={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
out:draw={params} out:draw={params}
``` ```

@ -9,6 +9,7 @@ The `svelte/animate` module exports one function for use with Svelte [animations
> EXPORT_SNIPPET: svelte/animate#flip > EXPORT_SNIPPET: svelte/animate#flip
```svelte ```svelte
<!--- copy: false --->
animate:flip={params} animate:flip={params}
``` ```

@ -5,7 +5,6 @@ title: svelte/action
Actions are functions that are called when an element is created. They can return an object with a `destroy` method that is called after the element is unmounted: Actions are functions that are called when an element is created. They can return an object with a `destroy` method that is called after the element is unmounted:
```svelte ```svelte
<!--- file: App.svelte --->
<script> <script>
/** @type {import('svelte/action').Action} */ /** @type {import('svelte/action').Action} */
function foo(node) { function foo(node) {
@ -27,7 +26,6 @@ An action can have a parameter. If the returned value has an `update` method, it
> Don't worry that we're redeclaring the `foo` function for every component instance — Svelte will hoist any functions that don't depend on local state out of the component definition. > Don't worry that we're redeclaring the `foo` function for every component instance — Svelte will hoist any functions that don't depend on local state out of the component definition.
```svelte ```svelte
<!--- file: App.svelte --->
<script> <script>
/** @type {string} */ /** @type {string} */
export let bar; export let bar;
@ -56,7 +54,6 @@ An action can have a parameter. If the returned value has an `update` method, it
Sometimes actions emit custom events and apply custom attributes to the element they are applied to. To support this, actions typed with `Action` or `ActionReturn` type can have a last parameter, `Attributes`: Sometimes actions emit custom events and apply custom attributes to the element they are applied to. To support this, actions typed with `Action` or `ActionReturn` type can have a last parameter, `Attributes`:
```svelte ```svelte
<!--- file: App.svelte --->
<script> <script>
/** /**
* @type {import('svelte/action').Action<HTMLDivElement, { prop: any }, { 'on:emit': (e: CustomEvent<string>) => void }>} * @type {import('svelte/action').Action<HTMLDivElement, { prop: any }, { 'on:emit': (e: CustomEvent<string>) => void }>}

@ -90,7 +90,7 @@ Each `markup`, `script` or `style` function must return an object (or a Promise
> Preprocessor functions should return a `map` object whenever possible or else debugging becomes harder as stack traces can't link to the original code correctly. > Preprocessor functions should return a `map` object whenever possible or else debugging becomes harder as stack traces can't link to the original code correctly.
```js ```ts
// @filename: ambient.d.ts // @filename: ambient.d.ts
declare global { declare global {
var source: string; var source: string;
@ -128,6 +128,7 @@ const { code } = await preprocess(
If a `dependencies` array is returned, it will be included in the result object. This is used by packages like [vite-plugin-svelte](https://github.com/sveltejs/vite-plugin-svelte) and [rollup-plugin-svelte](https://github.com/sveltejs/rollup-plugin-svelte) to watch additional files for changes, in the case where your `<style>` tag has an `@import` (for example). If a `dependencies` array is returned, it will be included in the result object. This is used by packages like [vite-plugin-svelte](https://github.com/sveltejs/vite-plugin-svelte) and [rollup-plugin-svelte](https://github.com/sveltejs/rollup-plugin-svelte) to watch additional files for changes, in the case where your `<style>` tag has an `@import` (for example).
```ts ```ts
/// file: preprocess-sass.js
// @filename: ambient.d.ts // @filename: ambient.d.ts
declare global { declare global {
var source: string; var source: string;
@ -204,6 +205,7 @@ Multiple preprocessors can be used together. The output of the first becomes the
> In Svelte 3, all `markup` functions ran first, then all `script` and then all `style` preprocessors. This order was changed in Svelte 4. > In Svelte 3, all `markup` functions ran first, then all `script` and then all `style` preprocessors. This order was changed in Svelte 4.
```js ```js
/// file: multiple-preprocessor.js
// @errors: 2322 // @errors: 2322
// @filename: ambient.d.ts // @filename: ambient.d.ts
declare global { declare global {
@ -255,6 +257,7 @@ The `walk` function provides a way to walk the abstract syntax trees generated b
The walker takes an abstract syntax tree to walk and an object with two optional methods: `enter` and `leave`. For each node, `enter` is called (if present). Then, unless `this.skip()` is called during `enter`, each of the children are traversed, and then `leave` is called on the node. The walker takes an abstract syntax tree to walk and an object with two optional methods: `enter` and `leave`. For each node, `enter` is called (if present). Then, unless `this.skip()` is called during `enter`, each of the children are traversed, and then `leave` is called on the node.
```js ```js
/// file: compiler-walk.js
// @filename: ambient.d.ts // @filename: ambient.d.ts
declare global { declare global {
var ast: import('estree').Node; var ast: import('estree').Node;

@ -67,6 +67,7 @@ Whereas children of `target` are normally left alone, `hydrate: true` will cause
The existing DOM doesn't need to match the component — Svelte will 'repair' the DOM as it goes. The existing DOM doesn't need to match the component — Svelte will 'repair' the DOM as it goes.
```ts ```ts
/// file: index.js
// @filename: ambient.d.ts // @filename: ambient.d.ts
import { SvelteComponent, ComponentConstructorOptions } from 'svelte'; import { SvelteComponent, ComponentConstructorOptions } from 'svelte';
@ -150,6 +151,7 @@ Causes the `callback` function to be called whenever the component dispatches an
A function is returned that will remove the event listener when called. A function is returned that will remove the event listener when called.
```ts ```ts
/// file: index.js
// @filename: ambient.d.ts // @filename: ambient.d.ts
import { SvelteComponent, ComponentConstructorOptions } from 'svelte'; import { SvelteComponent, ComponentConstructorOptions } from 'svelte';
@ -231,6 +233,7 @@ If a component is compiled with `accessors: true`, each instance will have gette
By default, `accessors` is `false`, unless you're compiling as a custom element. By default, `accessors` is `false`, unless you're compiling as a custom element.
```js ```js
/// file: index.js
// @filename: ambient.d.ts // @filename: ambient.d.ts
import { SvelteComponent, ComponentConstructorOptions } from 'svelte'; import { SvelteComponent, ComponentConstructorOptions } from 'svelte';

@ -59,42 +59,13 @@ It will show up on hover.
Note: The `@component` is necessary in the HTML comment which describes your component. Note: The `@component` is necessary in the HTML comment which describes your component.
## What about TypeScript support?
You need to install a preprocessor such as [svelte-preprocess](https://github.com/sveltejs/svelte-preprocess). You can run type checking from the command line with [svelte-check](https://www.npmjs.com/package/svelte-check).
To declare the type of a reactive variable in a Svelte template, you should use the following syntax:
```ts
const count: number = 100;
// ---cut---
let x: number;
$: x = count + 1;
```
To import a type or interface make sure to use [TypeScript's `type` modifier](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-export):
```ts
// @filename: SomeFile.ts
export interface SomeInterface {
foo: string;
}
// @filename: index.ts
// ---cut---
import type { SomeInterface } from './SomeFile';
```
You must use the `type` modifier because `svelte-preprocess` doesn't know whether an import is a type or a value — it only transpiles one file at a time without knowledge of the other files and therefore can't safely erase imports which only contain types without this modifier present.
## Does Svelte scale? ## Does Svelte scale?
There will be a blog post about this eventually, but in the meantime, check out [this issue](https://github.com/sveltejs/svelte/issues/2546). There will be a blog post about this eventually, but in the meantime, check out [this issue](https://github.com/sveltejs/svelte/issues/2546).
## Is there a UI component library? ## Is there a UI component library?
There are several UI component libraries as well as standalone components. Find them under the [components section](https://sveltesociety.dev/components#design-systems) of the Svelte Society website. There are several UI component libraries as well as standalone components. Find them under the [design systems section of the components page](https://sveltesociety.dev/components#design-systems) on the Svelte Society website.
## How do I test Svelte apps? ## How do I test Svelte apps?
@ -131,6 +102,17 @@ If you need hash-based routing on the client side, check out [svelte-spa-router]
You can see a [community-maintained list of routers on sveltesociety.dev](https://sveltesociety.dev/components#routers). You can see a [community-maintained list of routers on sveltesociety.dev](https://sveltesociety.dev/components#routers).
## Can I tell Svelte not to remove my unused styles?
No. Svelte removes the styles from the component and warns you about them in order to prevent issues that would otherwise arise.
Svelte's component style scoping works by generating a class unique to the given component, adding it to the relevant elements in the component that are under Svelte's control, and then adding it to each of the selectors in that component's styles. When the compiler can't see what elements a style selector applies to, there would be two bad options for keeping it:
- If it keeps the selector and adds the scoping class to it, the selector will likely not match the expected elements in the component, and they definitely won't if they were created by a child component or `{@html ...}`.
- If it keeps the selector without adding the scoping class to it, the given style will become a global style, affecting your entire page.
If you need to style something that Svelte can't identify at compile time, you will need to explicitly opt into global styles by using `:global(...)`. But also keep in mind that you can wrap `:global(...)` around only part of a selector. `.foo :global(.bar) { ... }` will style any `.bar` elements that appear within the component's `.foo` elements. As long as there's some parent element in the current component to start from, partially global selectors like this will almost always be able to get you what you want.
## Is Svelte v2 still available? ## Is Svelte v2 still available?
New features aren't being added to it, and bugs will probably only be fixed if they are extremely nasty or present some sort of security vulnerability. New features aren't being added to it, and bugs will probably only be fixed if they are extremely nasty or present some sort of security vulnerability.

@ -140,6 +140,26 @@ declare namespace svelteHTML {
Then make sure that `d.ts` file is referenced in your `tsconfig.json`. If it reads something like `"include": ["src/**/*"]` and your `d.ts` file is inside `src`, it should work. You may need to reload for the changes to take effect. Then make sure that `d.ts` file is referenced in your `tsconfig.json`. If it reads something like `"include": ["src/**/*"]` and your `d.ts` file is inside `src`, it should work. You may need to reload for the changes to take effect.
Since Svelte version 4.2 / `svelte-check` version 3.5 / VS Code extension version 107.10.0 you can also declare the typings by augmenting the `svelte/elements` module like this:
```ts
/// file: additional-svelte-typings.d.ts
import { HTMLButtonAttributes } from 'svelte/elements';
declare module 'svelte/elements' {
export interface SvelteHTMLElements {
'custom-button': HTMLButtonAttributes;
}
// allows for more granular control over what element to add the typings to
export interface HTMLButtonAttributes {
veryexperimentalattribute?: string;
}
}
export {}; // ensure this is not an ambient module, else types will be overridden instead of augmented
```
## Experimental advanced typings ## Experimental advanced typings
A few features are missing from taking full advantage of TypeScript in more advanced use cases like typing that a component implements a certain interface, explicitly typing slots, or using generics. These things are possible using experimental advanced type capabilities. See [this RFC](https://github.com/dummdidumm/rfcs/blob/ts-typedefs-within-svelte-components/text/ts-typing-props-slots-events.md) for more information on how to make use of them. A few features are missing from taking full advantage of TypeScript in more advanced use cases like typing that a component implements a certain interface, explicitly typing slots, or using generics. These things are possible using experimental advanced type capabilities. See [this RFC](https://github.com/dummdidumm/rfcs/blob/ts-typedefs-within-svelte-components/text/ts-typing-props-slots-events.md) for more information on how to make use of them.

@ -68,6 +68,10 @@
</div> </div>
<style> <style>
:root {
scrollbar-gutter: stable;
}
.easing-vis { .easing-vis {
display: flex; display: flex;
max-height: 95%; max-height: 95%;

@ -74,18 +74,14 @@
ul { ul {
list-style: none; list-style: none;
padding: 0; padding: 0;
display: flex; display: grid;
flex-direction: column; grid-template-columns: 1fr;
align-items: flex-start; gap: 3px;
font-size: 18px; font-size: 18px;
} }
li { li {
padding: 5px 10px; display: grid;
background: #eee;
border-radius: 2px;
margin: 3px 0;
cursor: pointer;
} }
li:hover { li:hover {
@ -93,9 +89,16 @@
color: white; color: white;
} }
.selected { button {
border: none;
border-radius: 2px;
padding: 2px;
}
.selected > button {
background: #ff3e00; background: #ff3e00;
color: white; color: white;
font-weight: bold;
} }
h3 { h3 {

@ -4,8 +4,6 @@
{#if x > 10} {#if x > 10}
<p>{x} is greater than 10</p> <p>{x} is greater than 10</p>
{:else if 5 > x}
<p>{x} is less than 5</p>
{:else} {:else}
<p>{x} is between 5 and 10</p> <p>{x} is between 0 and 10</p>
{/if} {/if}

@ -1,5 +1,45 @@
# svelte # svelte
## 4.2.2
### Patch Changes
- fix: support camelCase properties on custom elements ([#9328](https://github.com/sveltejs/svelte/pull/9328))
- fix: add missing plaintext-only value to contenteditable type ([#9242](https://github.com/sveltejs/svelte/pull/9242))
- chore: upgrade magic-string to 0.30.4 ([#9292](https://github.com/sveltejs/svelte/pull/9292))
- fix: ignore trailing comments when comparing nodes ([#9197](https://github.com/sveltejs/svelte/pull/9197))
## 4.2.1
### Patch Changes
- fix: update style directive when style attribute is present and is updated via an object prop ([#9187](https://github.com/sveltejs/svelte/pull/9187))
- fix: css sourcemap generation with unicode filenames ([#9120](https://github.com/sveltejs/svelte/pull/9120))
- fix: do not add module declared variables as dependencies ([#9122](https://github.com/sveltejs/svelte/pull/9122))
- fix: handle `svelte:element` with dynamic this and spread attributes ([#9112](https://github.com/sveltejs/svelte/pull/9112))
- fix: silence false positive reactive component warning ([#9094](https://github.com/sveltejs/svelte/pull/9094))
- fix: head duplication when binding is present ([#9124](https://github.com/sveltejs/svelte/pull/9124))
- fix: take custom attribute name into account when reflecting property ([#9140](https://github.com/sveltejs/svelte/pull/9140))
- fix: add `indeterminate` to the list of HTMLAttributes ([#9180](https://github.com/sveltejs/svelte/pull/9180))
- fix: recognize option value on spread attribute ([#9125](https://github.com/sveltejs/svelte/pull/9125))
## 4.2.0
### Minor Changes
- feat: move `svelteHTML` from language-tools into core to load the correct `svelte/element` types ([#9070](https://github.com/sveltejs/svelte/pull/9070))
## 4.1.2 ## 4.1.2
### Patch Changes ### Patch Changes

@ -486,7 +486,7 @@ export interface HTMLAttributes<T extends EventTarget> extends AriaAttributes, D
accesskey?: string | undefined | null; accesskey?: string | undefined | null;
autofocus?: boolean | undefined | null; autofocus?: boolean | undefined | null;
class?: string | undefined | null; class?: string | undefined | null;
contenteditable?: Booleanish | 'inherit' | undefined | null; contenteditable?: Booleanish | 'inherit' | 'plaintext-only' | undefined | null;
contextmenu?: string | undefined | null; contextmenu?: string | undefined | null;
dir?: string | undefined | null; dir?: string | undefined | null;
draggable?: Booleanish | undefined | null; draggable?: Booleanish | undefined | null;
@ -808,6 +808,7 @@ export interface HTMLInputAttributes extends HTMLAttributes<HTMLInputElement> {
formnovalidate?: boolean | undefined | null; formnovalidate?: boolean | undefined | null;
formtarget?: string | undefined | null; formtarget?: string | undefined | null;
height?: number | string | undefined | null; height?: number | string | undefined | null;
indeterminate?: boolean | undefined | null;
list?: string | undefined | null; list?: string | undefined | null;
max?: number | string | undefined | null; max?: number | string | undefined | null;
maxlength?: number | undefined | null; maxlength?: number | undefined | null;
@ -1404,6 +1405,7 @@ export interface SVGAttributes<T extends EventTarget> extends AriaAttributes, DO
'text-rendering'?: number | string | undefined | null; 'text-rendering'?: number | string | undefined | null;
to?: number | string | undefined | null; to?: number | string | undefined | null;
transform?: string | undefined | null; transform?: string | undefined | null;
'transform-origin'?: string | undefined | null;
u1?: number | string | undefined | null; u1?: number | string | undefined | null;
u2?: number | string | undefined | null; u2?: number | string | undefined | null;
'underline-position'?: number | string | undefined | null; 'underline-position'?: number | string | undefined | null;

@ -1,6 +1,6 @@
{ {
"name": "svelte", "name": "svelte",
"version": "4.1.2", "version": "4.2.2",
"description": "Cybernetically enhanced web apps", "description": "Cybernetically enhanced web apps",
"type": "module", "type": "module",
"module": "src/runtime/index.js", "module": "src/runtime/index.js",
@ -19,6 +19,7 @@
"motion.d.ts", "motion.d.ts",
"action.d.ts", "action.d.ts",
"elements.d.ts", "elements.d.ts",
"svelte-html.d.ts",
"README.md" "README.md"
], ],
"exports": { "exports": {
@ -82,7 +83,7 @@
"posttest": "agadoo src/internal/index.js", "posttest": "agadoo src/internal/index.js",
"prepublishOnly": "pnpm build", "prepublishOnly": "pnpm build",
"types": "node ./scripts/generate-dts.js", "types": "node ./scripts/generate-dts.js",
"lint": "prettier . --cache --plugin-search-dir=. --check && eslint \"{src,test}/**/*.{ts,js}\" --cache" "lint": "prettier . --cache --plugin-search-dir=. --check && eslint \"{scripts,src,test}/**/*.js\" --cache --fix"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -113,7 +114,7 @@
"estree-walker": "^3.0.3", "estree-walker": "^3.0.3",
"is-reference": "^3.0.1", "is-reference": "^3.0.1",
"locate-character": "^3.0.0", "locate-character": "^3.0.0",
"magic-string": "^0.30.0", "magic-string": "^0.30.4",
"periscopic": "^3.1.0" "periscopic": "^3.1.0"
}, },
"devDependencies": { "devDependencies": {
@ -128,8 +129,9 @@
"agadoo": "^3.0.0", "agadoo": "^3.0.0",
"dts-buddy": "^0.1.7", "dts-buddy": "^0.1.7",
"esbuild": "^0.18.11", "esbuild": "^0.18.11",
"eslint-plugin-lube": "^0.1.7",
"happy-dom": "^9.20.3", "happy-dom": "^9.20.3",
"jsdom": "^21.1.2", "jsdom": "22.0.0",
"kleur": "^4.1.5", "kleur": "^4.1.5",
"rollup": "^3.26.2", "rollup": "^3.26.2",
"source-map": "^0.7.4", "source-map": "^0.7.4",

@ -0,0 +1,6 @@
{
"plugins": ["lube"],
"rules": {
"lube/svelte-naming-convention": ["error", { "fixSameNames": true }]
}
}

@ -1,8 +1,8 @@
// Compile all Svelte files in a directory to JS and CSS files // Compile all Svelte files in a directory to JS and CSS files
// Usage: node scripts/compile-test.js <directory> // Usage: node scripts/compile-test.js <directory>
import { mkdirSync, readFileSync, writeFileSync } from 'fs'; import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import path from 'path'; import path from 'node:path';
import glob from 'tiny-glob/sync.js'; import glob from 'tiny-glob/sync.js';
import { compile } from '../src/compiler/index.js'; import { compile } from '../src/compiler/index.js';

@ -1,18 +1,18 @@
import * as fs from 'fs'; import * as fs from 'node:fs';
import { createBundle } from 'dts-buddy'; import { createBundle } from 'dts-buddy';
// It may look weird, but the imports MUST be ending with index.js to be properly resolved in all TS modes // It may look weird, but the imports MUST be ending with index.js to be properly resolved in all TS modes
for (const name of ['action', 'animate', 'easing', 'motion', 'store', 'transition']) { for (const name of ['action', 'animate', 'easing', 'motion', 'store', 'transition']) {
fs.writeFileSync(`${name}.d.ts`, `import './types/index.js';`); fs.writeFileSync(`${name}.d.ts`, "import './types/index.js';");
} }
fs.writeFileSync('index.d.ts', `import './types/index.js';`); fs.writeFileSync('index.d.ts', "import './types/index.js';");
fs.writeFileSync('compiler.d.ts', `import './types/index.js';`); fs.writeFileSync('compiler.d.ts', "import './types/index.js';");
// TODO: some way to mark these as deprecated // TODO: some way to mark these as deprecated
fs.mkdirSync('./types/compiler', { recursive: true }); fs.mkdirSync('./types/compiler', { recursive: true });
fs.writeFileSync('./types/compiler/preprocess.d.ts', `import '../index.js';`); fs.writeFileSync('./types/compiler/preprocess.d.ts', "import '../index.js';");
fs.writeFileSync('./types/compiler/interfaces.d.ts', `import '../index.js';`); fs.writeFileSync('./types/compiler/interfaces.d.ts', "import '../index.js';");
await createBundle({ await createBundle({
output: 'types/index.d.ts', output: 'types/index.d.ts',

@ -6,8 +6,8 @@ Please run `node scripts/globals-extractor.js` at the project root.
see: https://github.com/microsoft/TypeScript/tree/main/lib see: https://github.com/microsoft/TypeScript/tree/main/lib
---------------------------------------------------------------------- */ ---------------------------------------------------------------------- */
import http from 'https'; import http from 'node:https';
import fs from 'fs'; import fs from 'node:fs';
const GLOBAL_TS_PATH = './src/compiler/utils/globals.js'; const GLOBAL_TS_PATH = './src/compiler/utils/globals.js';
@ -16,7 +16,7 @@ const GLOBAL_TS_PATH = './src/compiler/utils/globals.js';
const SPECIALS = ['global', 'globalThis', 'InternalError', 'process', 'undefined']; const SPECIALS = ['global', 'globalThis', 'InternalError', 'process', 'undefined'];
const get_url = (name) => const get_url = (name) =>
`https://raw.githubusercontent.com/microsoft/TypeScript/main/lib/lib.${name}.d.ts`; `https://raw.githubusercontent.com/microsoft/TypeScript/main/src/lib/${name}.d.ts`;
const extract_name = (split) => split.match(/^[a-zA-Z0-9_$]+/)[0]; const extract_name = (split) => split.match(/^[a-zA-Z0-9_$]+/)[0];
const extract_functions_and_references = (name, data) => { const extract_functions_and_references = (name, data) => {

@ -0,0 +1,6 @@
{
"plugins": ["lube"],
"rules": {
"lube/svelte-naming-convention": ["error", { "fixSameNames": true }]
}
}

@ -1555,8 +1555,8 @@ export default class Component {
}, []) }, [])
); );
if (cycle && cycle.length) { if (cycle && cycle.length) {
const declarationList = lookup.get(cycle[0]); const declaration_list = lookup.get(cycle[0]);
const declaration = declarationList[0]; const declaration = declaration_list[0];
return this.error(declaration.node, compiler_errors.cyclical_reactive_declaration(cycle)); return this.error(declaration.node, compiler_errors.cyclical_reactive_declaration(cycle));
} }

@ -4,7 +4,7 @@ import { b } from 'code-red';
* @param {any} program * @param {any} program
* @param {import('estree').Identifier} name * @param {import('estree').Identifier} name
* @param {string} banner * @param {string} banner
* @param {any} sveltePath * @param {any} svelte_path
* @param {Array<{ name: string; alias: import('estree').Identifier }>} helpers * @param {Array<{ name: string; alias: import('estree').Identifier }>} helpers
* @param {Array<{ name: string; alias: import('estree').Identifier }>} globals * @param {Array<{ name: string; alias: import('estree').Identifier }>} globals
* @param {import('estree').ImportDeclaration[]} imports * @param {import('estree').ImportDeclaration[]} imports
@ -15,21 +15,21 @@ export default function create_module(
program, program,
name, name,
banner, banner,
sveltePath = 'svelte', svelte_path = 'svelte',
helpers, helpers,
globals, globals,
imports, imports,
module_exports, module_exports,
exports_from exports_from
) { ) {
const internal_path = `${sveltePath}/internal`; const internal_path = `${svelte_path}/internal`;
helpers.sort((a, b) => (a.name < b.name ? -1 : 1)); helpers.sort((a, b) => (a.name < b.name ? -1 : 1));
globals.sort((a, b) => (a.name < b.name ? -1 : 1)); globals.sort((a, b) => (a.name < b.name ? -1 : 1));
return esm( return esm(
program, program,
name, name,
banner, banner,
sveltePath, svelte_path,
internal_path, internal_path,
helpers, helpers,
globals, globals,
@ -41,11 +41,11 @@ export default function create_module(
/** /**
* @param {any} source * @param {any} source
* @param {any} sveltePath * @param {any} svelte_path
*/ */
function edit_source(source, sveltePath) { function edit_source(source, svelte_path) {
return source === 'svelte' || source.startsWith('svelte/') return source === 'svelte' || source.startsWith('svelte/')
? source.replace('svelte', sveltePath) ? source.replace('svelte', svelte_path)
: source; : source;
} }
@ -84,7 +84,7 @@ function get_internal_globals(globals, helpers) {
* @param {any} program * @param {any} program
* @param {import('estree').Identifier} name * @param {import('estree').Identifier} name
* @param {string} banner * @param {string} banner
* @param {string} sveltePath * @param {string} svelte_path
* @param {string} internal_path * @param {string} internal_path
* @param {Array<{ name: string; alias: import('estree').Identifier }>} helpers * @param {Array<{ name: string; alias: import('estree').Identifier }>} helpers
* @param {Array<{ name: string; alias: import('estree').Identifier }>} globals * @param {Array<{ name: string; alias: import('estree').Identifier }>} globals
@ -96,7 +96,7 @@ function esm(
program, program,
name, name,
banner, banner,
sveltePath, svelte_path,
internal_path, internal_path,
helpers, helpers,
globals, globals,
@ -118,7 +118,7 @@ function esm(
/** @param {any} node */ /** @param {any} node */
function rewrite_import(node) { function rewrite_import(node) {
const value = edit_source(node.source.value, sveltePath); const value = edit_source(node.source.value, svelte_path);
if (node.source.value !== value) { if (node.source.value !== value) {
node.source.value = value; node.source.value = value;
node.source.raw = null; node.source.raw = null;

@ -126,6 +126,6 @@ export default class Binding extends Node {
* @param {import('./shared/Node.js').default} node * @param {import('./shared/Node.js').default} node
* @returns {node is import('./Element.js').default} * @returns {node is import('./Element.js').default}
*/ */
function isElement(node) { function is_element(node) {
return !!(/** @type {any} */ (node).is_media_node); return !!(/** @type {any} */ (node).is_media_node);
} }

@ -84,7 +84,9 @@ export default class EachBlock extends AbstractBlock {
this.has_animation = false; this.has_animation = false;
[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) && !isCommentNode(child)); this.children = this.children.filter(
(child) => !is_empty_node(child) && !is_comment_node(child)
);
if (this.children.length !== 1) { if (this.children.length !== 1) {
const child = this.children.find( const child = this.children.find(
(child) => !!(/** @type {import('./Element.js').default} */ (child).animation) (child) => !!(/** @type {import('./Element.js').default} */ (child).animation)
@ -102,11 +104,11 @@ export default class EachBlock extends AbstractBlock {
} }
/** @param {import('./interfaces.js').INode} node */ /** @param {import('./interfaces.js').INode} node */
function isEmptyNode(node) { function is_empty_node(node) {
return node.type === 'Text' && node.data.trim() === ''; return node.type === 'Text' && node.data.trim() === '';
} }
/** @param {import('./interfaces.js').INode} node */ /** @param {import('./interfaces.js').INode} node */
function isCommentNode(node) { function is_comment_node(node) {
return node.type === 'Comment'; return node.type === 'Comment';
} }

@ -1,5 +1,6 @@
import { is_html, is_svg, is_void } from '../../../shared/utils/names.js'; import { is_html, is_svg, is_void } from '../../../shared/utils/names.js';
import Node from './shared/Node.js'; import Node from './shared/Node.js';
import { walk } from 'estree-walker';
import Attribute from './Attribute.js'; import Attribute from './Attribute.js';
import Binding from './Binding.js'; import Binding from './Binding.js';
import EventHandler from './EventHandler.js'; import EventHandler from './EventHandler.js';
@ -430,7 +431,7 @@ export default class Element extends Node {
} }
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 = get_value_attribute(info.attributes);
if (value_attribute) { if (value_attribute) {
component.error(value_attribute, compiler_errors.textarea_duplicate_value); component.error(value_attribute, compiler_errors.textarea_duplicate_value);
return; return;
@ -449,7 +450,7 @@ export default class Element extends Node {
// Special case — treat these the same way: // Special case — treat these the same way:
// <option>{foo}</option> // <option>{foo}</option>
// <option value={foo}>{foo}</option> // <option value={foo}>{foo}</option>
const value_attribute = info.attributes.find((attribute) => attribute.name === 'value'); const value_attribute = get_value_attribute(info.attributes);
if (!value_attribute) { if (!value_attribute) {
info.attributes.push({ info.attributes.push({
type: 'Attribute', type: 'Attribute',
@ -875,7 +876,7 @@ export default class Element extends Node {
) { ) {
const interactive_handlers = handlers const interactive_handlers = handlers
.map((handler) => handler.name) .map((handler) => handler.name)
.filter((handlerName) => a11y_interactive_handlers.has(handlerName)); .filter((handler_name) => a11y_interactive_handlers.has(handler_name));
if (interactive_handlers.length > 0) { if (interactive_handlers.length > 0) {
component.warn( component.warn(
this, this,
@ -1420,3 +1421,30 @@ function within_custom_element(parent) {
} }
return false; return false;
} }
/**
* @param {any[]} attributes
*/
function get_value_attribute(attributes) {
let node_value;
attributes.forEach((node) => {
if (node.type !== 'Spread' && node.name.toLowerCase() === 'value') {
node_value = node;
}
if (node.type === 'Spread') {
walk(/** @type {any} */ (node.expression), {
enter(/** @type {import('estree').Node} */ node) {
if (node_value) {
this.skip();
}
if (node.type === 'Identifier') {
if (/** @type {import('estree').Identifier} */ (node).name.toLowerCase() === 'value') {
node_value = node;
}
}
}
});
}
});
return node_value;
}

@ -73,8 +73,8 @@ function sort_consts_nodes(consts_nodes, component) {
}, []) }, [])
); );
if (cycle && cycle.length) { if (cycle && cycle.length) {
const nodeList = lookup.get(cycle[0]); const node_list = lookup.get(cycle[0]);
const node = nodeList[0]; const node = node_list[0];
component.error(node.node, compiler_errors.cyclical_const_tags(cycle)); component.error(node.node, compiler_errors.cyclical_const_tags(cycle));
} }

@ -50,7 +50,7 @@ export function invalidate(renderer, scope, node, names, main_execution_context
if ( if (
node.type === 'AssignmentExpression' && node.type === 'AssignmentExpression' &&
node.operator === '=' && node.operator === '=' &&
nodes_match(node.left, node.right) && nodes_match(node.left, node.right, ['trailingComments', 'leadingComments']) &&
tail.length === 0 tail.length === 0
) { ) {
return get_invalidated(head, node); return get_invalidated(head, node);

@ -103,8 +103,8 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
this.parent.has_dynamic_value = true; this.parent.has_dynamic_value = true;
} }
} }
if (this.parent.node.namespace == namespaces.foreign) { if (this.parent.node.namespace == namespaces.foreign || this.parent.node.name.includes('-')) {
// leave attribute case alone for elements in the "foreign" namespace // leave attribute case alone for elements in the "foreign" namespace and for custom elements
this.name = this.node.name; this.name = this.node.name;
this.metadata = this.get_metadata(); this.metadata = this.get_metadata();
this.is_indirectly_bound_value = false; this.is_indirectly_bound_value = false;

@ -990,7 +990,8 @@ export default class ElementWrapper extends Wrapper {
const static_attributes = []; const static_attributes = [];
this.attributes.forEach((attr) => { this.attributes.forEach((attr) => {
if (attr instanceof SpreadAttributeWrapper) { if (attr instanceof SpreadAttributeWrapper) {
static_attributes.push({ type: 'SpreadElement', argument: attr.node.expression.node }); const snippet = { type: 'SpreadElement', argument: attr.node.expression.manipulate(block) };
static_attributes.push(snippet);
} else { } else {
const name = attr.property_name || attr.name; const name = attr.property_name || attr.name;
static_attributes.push(p`${name}: ${attr.get_value(block)}`); static_attributes.push(p`${name}: ${attr.get_value(block)}`);
@ -1240,11 +1241,7 @@ export default class ElementWrapper extends Wrapper {
} }
if (this.dynamic_style_dependencies.size > 0) { if (this.dynamic_style_dependencies.size > 0) {
maybe_create_style_changed_var(); maybe_create_style_changed_var();
// If all dependencies are same as the style attribute dependencies, then we can skip the dirty check condition = x`${condition} || ${style_changed_var}`;
condition =
all_deps.size === this.dynamic_style_dependencies.size
? style_changed_var
: x`${style_changed_var} || ${condition}`;
} }
block.chunks.update.push(b` block.chunks.update.push(b`
if (${condition}) { if (${condition}) {

@ -105,14 +105,19 @@ export default class InlineComponentWrapper extends Wrapper {
this.slots.set(name, slot_definition); this.slots.set(name, slot_definition);
} }
warn_if_reactive() { warn_if_reactive() {
const { name } = this.node; let { name } = this.node;
const variable = this.renderer.component.var_lookup.get(name); const top = name.split('.')[0]; // <T.foo/> etc. should check for T instead of "T.foo"
const variable = this.renderer.component.var_lookup.get(top);
if (!variable) { if (!variable) {
return; return;
} }
const ignores = extract_ignores_above_node(this.node); const ignores = extract_ignores_above_node(this.node);
this.renderer.component.push_ignores(ignores); this.renderer.component.push_ignores(ignores);
if (variable.reassigned || variable.export_name || variable.is_reactive_dependency) { if (
variable.reassigned ||
variable.export_name || // or a prop
variable.mutated
) {
this.renderer.component.warn(this.node, compiler_warnings.reactive_component(name)); this.renderer.component.warn(this.node, compiler_warnings.reactive_component(name));
} }
this.renderer.component.pop_ignores(); this.renderer.component.pop_ignores();

@ -94,14 +94,14 @@ export default class WindowWrapper extends Wrapper {
bindings.scrollX && bindings.scrollY bindings.scrollX && bindings.scrollY
? x`"${bindings.scrollX}" in this._state || "${bindings.scrollY}" in this._state` ? x`"${bindings.scrollX}" in this._state || "${bindings.scrollY}" in this._state`
: x`"${bindings.scrollX || bindings.scrollY}" in this._state`; : x`"${bindings.scrollX || bindings.scrollY}" in this._state`;
const scrollX = bindings.scrollX && x`this._state.${bindings.scrollX}`; const scroll_x = bindings.scrollX && x`this._state.${bindings.scrollX}`;
const scrollY = bindings.scrollY && x`this._state.${bindings.scrollY}`; const scroll_y = bindings.scrollY && x`this._state.${bindings.scrollY}`;
renderer.meta_bindings.push(b` renderer.meta_bindings.push(b`
if (${condition}) { if (${condition}) {
@_scrollTo(${scrollX || '@_window.pageXOffset'}, ${scrollY || '@_window.pageYOffset'}); @_scrollTo(${scroll_x || '@_window.pageXOffset'}, ${scroll_y || '@_window.pageYOffset'});
} }
${scrollX && `${scrollX} = @_window.pageXOffset;`} ${scroll_x && `${scroll_x} = @_window.pageXOffset;`}
${scrollY && `${scrollY} = @_window.pageYOffset;`} ${scroll_y && `${scroll_y} = @_window.pageYOffset;`}
`); `);
block.event_listeners.push(x` block.event_listeners.push(x`
@listen(@_window, "${event}", () => { @listen(@_window, "${event}", () => {
@ -132,17 +132,17 @@ export default class WindowWrapper extends Wrapper {
// special case... might need to abstract this out if we add more special cases // special case... might need to abstract this out if we add more special cases
if (bindings.scrollX || bindings.scrollY) { if (bindings.scrollX || bindings.scrollY) {
const condition = renderer.dirty([bindings.scrollX, bindings.scrollY].filter(Boolean)); const condition = renderer.dirty([bindings.scrollX, bindings.scrollY].filter(Boolean));
const scrollX = bindings.scrollX const scroll_x = bindings.scrollX
? renderer.reference(bindings.scrollX) ? renderer.reference(bindings.scrollX)
: x`@_window.pageXOffset`; : x`@_window.pageXOffset`;
const scrollY = bindings.scrollY const scroll_y = bindings.scrollY
? renderer.reference(bindings.scrollY) ? renderer.reference(bindings.scrollY)
: x`@_window.pageYOffset`; : x`@_window.pageYOffset`;
block.chunks.update.push(b` block.chunks.update.push(b`
if (${condition} && !${scrolling}) { if (${condition} && !${scrolling}) {
${scrolling} = true; ${scrolling} = true;
@_clearTimeout(${scrolling_timeout}); @_clearTimeout(${scrolling_timeout});
@_scrollTo(${scrollX}, ${scrollY}); @_scrollTo(${scroll_x}, ${scroll_y});
${scrolling_timeout} = @_setTimeout(${clear_scrolling}, 100); ${scrolling_timeout} = @_setTimeout(${clear_scrolling}, 100);
} }
`); `);

@ -3,8 +3,11 @@ import { is_reserved_keyword } from '../../../utils/reserved_keywords.js';
/** @param {import('../../../../interfaces.js').Var} variable */ /** @param {import('../../../../interfaces.js').Var} variable */
export default function is_dynamic(variable) { export default function is_dynamic(variable) {
if (variable) { if (variable) {
if (variable.mutated || variable.reassigned) return true; // dynamic internal state // Only variables declared in the instance script tags should be considered dynamic
if (!variable.module && variable.writable && variable.export_name) return true; // writable props const is_declared_in_reactive_context = !variable.module && !variable.global;
if (is_declared_in_reactive_context && (variable.mutated || variable.reassigned)) return true; // dynamic internal state
if (is_declared_in_reactive_context && variable.writable && variable.export_name) return true; // writable props
if (is_reserved_keyword(variable.name)) return true; if (is_reserved_keyword(variable.name)) return true;
} }
return false; return false;

@ -144,9 +144,13 @@ export default function ssr(component, options) {
? b` ? b`
let $$settled; let $$settled;
let $$rendered; let $$rendered;
let #previous_head = $$result.head;
do { do {
$$settled = true; $$settled = true;
// $$result.head is mutated by the literal expression
// need to reset it if we're looping back to prevent duplication
$$result.head = #previous_head;
${reactive_declarations} ${reactive_declarations}

@ -8,7 +8,7 @@ import * as node from './node/index.js';
* *
* The new nodes are located in `./node`. * The new nodes are located in `./node`.
*/ */
const cqSyntax = fork({ const cq_syntax = fork({
atrule: { atrule: {
// extend or override at-rule dictionary // extend or override at-rule dictionary
container: { container: {
@ -16,8 +16,8 @@ const cqSyntax = fork({
prelude() { prelude() {
return this.createSingleNodeList(this.ContainerQuery()); return this.createSingleNodeList(this.ContainerQuery());
}, },
block(isStyleBlock = false) { block(is_style_block = false) {
return this.Block(isStyleBlock); return this.Block(is_style_block);
} }
} }
} }
@ -25,4 +25,4 @@ const cqSyntax = fork({
node node
}); });
export const parse = cqSyntax.parse; export const parse = cq_syntax.parse;

@ -16,7 +16,7 @@ export const structure = {
value: ['Identifier', 'Number', 'Comparison', 'Dimension', 'QueryCSSFunction', 'Ratio', null] value: ['Identifier', 'Number', 'Comparison', 'Dimension', 'QueryCSSFunction', 'Ratio', null]
}; };
function lookup_non_WS_type_and_value(offset, type, referenceStr) { function lookup_non_ws_type_and_value(offset, type, reference_str) {
let current_type; let current_type;
do { do {
@ -26,7 +26,7 @@ function lookup_non_WS_type_and_value(offset, type, referenceStr) {
} }
} while (current_type !== 0); // NULL -> 0 } while (current_type !== 0); // NULL -> 0
return current_type === type ? this.lookupValue(offset - 1, referenceStr) : false; return current_type === type ? this.lookupValue(offset - 1, reference_str) : false;
} }
export function parse() { export function parse() {
@ -40,7 +40,7 @@ export function parse() {
while (!this.eof && this.tokenType !== RightParenthesis) { while (!this.eof && this.tokenType !== RightParenthesis) {
switch (this.tokenType) { switch (this.tokenType) {
case Number: case Number:
if (lookup_non_WS_type_and_value.call(this, 1, Delim, '/')) { if (lookup_non_ws_type_and_value.call(this, 1, Delim, '/')) {
child = this.Ratio(); child = this.Ratio();
} else { } else {
child = this.Number(); child = this.Number();

@ -1,6 +1,6 @@
/** ---------------------------------------------------------------------- /** ----------------------------------------------------------------------
This file is automatically generated by `scripts/globals-extractor.js`. This file is automatically generated by `scripts/globals-extractor.js`.
Generated At: 2023-05-24T13:16:20.777Z Generated At: 2023-08-11T04:11:50.562Z
---------------------------------------------------------------------- */ ---------------------------------------------------------------------- */
export default new Set([ export default new Set([

@ -292,7 +292,18 @@ export function apply_preprocessor_sourcemap(filename, svelte_map, preprocessor_
toUrl: { toUrl: {
enumerable: false, enumerable: false,
value: function toUrl() { value: function toUrl() {
return 'data:application/json;charset=utf-8;base64,' + btoa(this.toString()); let b64 = '';
if (typeof window !== 'undefined' && window.btoa) {
// btoa doesn't support multi-byte characters
b64 = window.btoa(unescape(encodeURIComponent(this.toString())));
} else if (typeof Buffer !== 'undefined') {
b64 = Buffer.from(this.toString(), 'utf8').toString('base64');
} else {
throw new Error(
'Unsupported environment: `window.btoa` or `Buffer` should be present to use toUrl.'
);
}
return 'data:application/json;charset=utf-8;base64,' + b64;
} }
} }
}); });

@ -1,4 +1,4 @@
export function nodes_match(a, b) { export function nodes_match(a, b, ignoreKeys = []) {
if (!!a !== !!b) return false; if (!!a !== !!b) return false;
if (Array.isArray(a) !== Array.isArray(b)) return false; if (Array.isArray(a) !== Array.isArray(b)) return false;
@ -8,8 +8,12 @@ export function nodes_match(a, b) {
return a.every((child, i) => nodes_match(child, b[i])); return a.every((child, i) => nodes_match(child, b[i]));
} }
const a_keys = Object.keys(a).sort(); const a_keys = Object.keys(a)
const b_keys = Object.keys(b).sort(); .sort()
.filter((key) => !ignoreKeys.includes(key));
const b_keys = Object.keys(b)
.sort()
.filter((key) => !ignoreKeys.includes(key));
if (a_keys.length !== b_keys.length) return false; if (a_keys.length !== b_keys.length) return false;

@ -50,7 +50,7 @@ export interface ActionReturn<
* // ... * // ...
* } * }
* ``` * ```
* `Action<HTMLDivElement>` and `Action<HTMLDiveElement, undefined>` both signal that the action accepts no parameters. * `Action<HTMLDivElement>` and `Action<HTMLDivElement, undefined>` both signal that the action accepts no parameters.
* *
* You can return an object with methods `update` and `destroy` from the function and type which additional attributes and events it has. * You can return an object with methods `update` and `destroy` from the function and type which additional attributes and events it has.
* See interface `ActionReturn` for more details. * See interface `ActionReturn` for more details.

@ -84,7 +84,17 @@ function make_dirty(component, i) {
component.$$.dirty[(i / 31) | 0] |= 1 << i % 31; component.$$.dirty[(i / 31) | 0] |= 1 << i % 31;
} }
/** @returns {void} */ // TODO: Document the other params
/**
* @param {SvelteComponent} component
* @param {import('./public.js').ComponentConstructorOptions} options
*
* @param {import('./utils.js')['not_equal']} not_equal Used to compare props and state values.
* @param {(target: Element | ShadowRoot) => void} [append_styles] Function that appends styles to the DOM when the component is first initialised.
* This will be the `add_css` function from the compiled component.
*
* @returns {void}
*/
export function init( export function init(
component, component,
options, options,
@ -92,7 +102,7 @@ export function init(
create_fragment, create_fragment,
not_equal, not_equal,
props, props,
append_styles, append_styles = null,
dirty = [-1] dirty = [-1]
) { ) {
const parent_component = current_component; const parent_component = current_component;
@ -139,8 +149,9 @@ export function init(
if (options.target) { if (options.target) {
if (options.hydrate) { if (options.hydrate) {
start_hydrating(); start_hydrating();
// TODO: what is the correct type here?
// @ts-expect-error
const nodes = children(options.target); const nodes = children(options.target);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
$$.fragment && $$.fragment.l(nodes); $$.fragment && $$.fragment.l(nodes);
nodes.forEach(detach); nodes.forEach(detach);
} else { } else {
@ -283,7 +294,7 @@ if (typeof HTMLElement === 'function') {
'toAttribute' 'toAttribute'
); );
if (attribute_value == null) { if (attribute_value == null) {
this.removeAttribute(key); this.removeAttribute(this.$$p_d[key].attribute || key);
} else { } else {
this.setAttribute(this.$$p_d[key].attribute || key, attribute_value); this.setAttribute(this.$$p_d[key].attribute || key, attribute_value);
} }

@ -1,5 +1,7 @@
import { ResizeObserverSingleton } from './ResizeObserverSingleton.js';
import { contenteditable_truthy_values, has_prop } from './utils.js'; import { contenteditable_truthy_values, has_prop } from './utils.js';
import { ResizeObserverSingleton } from './ResizeObserverSingleton.js';
// Track which nodes are claimed during hydration. Unclaimed nodes can then be removed from the DOM // Track which nodes are claimed during hydration. Unclaimed nodes can then be removed from the DOM
// at the end of hydration without touching the remaining nodes. // at the end of hydration without touching the remaining nodes.
let is_hydrating = false; let is_hydrating = false;
@ -50,14 +52,14 @@ function init_hydrate(target) {
let children = /** @type {ArrayLike<NodeEx2>} */ (target.childNodes); let children = /** @type {ArrayLike<NodeEx2>} */ (target.childNodes);
// If target is <head>, there may be children without claim_order // If target is <head>, there may be children without claim_order
if (target.nodeName === 'HEAD') { if (target.nodeName === 'HEAD') {
const myChildren = []; const my_children = [];
for (let i = 0; i < children.length; i++) { for (let i = 0; i < children.length; i++) {
const node = children[i]; const node = children[i];
if (node.claim_order !== undefined) { if (node.claim_order !== undefined) {
myChildren.push(node); my_children.push(node);
} }
} }
children = myChildren; children = my_children;
} }
/* /*
* Reorder claimed children optimally. * Reorder claimed children optimally.
@ -87,15 +89,15 @@ function init_hydrate(target) {
// Find the largest subsequence length such that it ends in a value less than our current value // Find the largest subsequence length such that it ends in a value less than our current value
// upper_bound returns first greater value, so we subtract one // upper_bound returns first greater value, so we subtract one
// with fast path for when we are on the current longest subsequence // with fast path for when we are on the current longest subsequence
const seqLen = const seq_len =
(longest > 0 && children[m[longest]].claim_order <= current (longest > 0 && children[m[longest]].claim_order <= current
? longest + 1 ? longest + 1
: upper_bound(1, longest, (idx) => children[m[idx]].claim_order, current)) - 1; : upper_bound(1, longest, (idx) => children[m[idx]].claim_order, current)) - 1;
p[i] = m[seqLen] + 1; p[i] = m[seq_len] + 1;
const newLen = seqLen + 1; const new_len = seq_len + 1;
// We can guarantee that current is the smallest value. Otherwise, we would have generated a longer sequence. // We can guarantee that current is the smallest value. Otherwise, we would have generated a longer sequence.
m[newLen] = i; m[new_len] = i;
longest = Math.max(newLen, longest); longest = Math.max(new_len, longest);
} }
// The longest increasing subsequence of nodes (initially reversed) // The longest increasing subsequence of nodes (initially reversed)
@ -108,28 +110,28 @@ function init_hydrate(target) {
/** /**
* @type {NodeEx2[]} * @type {NodeEx2[]}
*/ */
const toMove = []; const to_move = [];
let last = children.length - 1; let last = children.length - 1;
for (let cur = m[longest] + 1; cur != 0; cur = p[cur - 1]) { for (let cur = m[longest] + 1; cur != 0; cur = p[cur - 1]) {
lis.push(children[cur - 1]); lis.push(children[cur - 1]);
for (; last >= cur; last--) { for (; last >= cur; last--) {
toMove.push(children[last]); to_move.push(children[last]);
} }
last--; last--;
} }
for (; last >= 0; last--) { for (; last >= 0; last--) {
toMove.push(children[last]); to_move.push(children[last]);
} }
lis.reverse(); lis.reverse();
// We sort the nodes being moved to guarantee that their insertion order matches the claim order // We sort the nodes being moved to guarantee that their insertion order matches the claim order
toMove.sort((a, b) => a.claim_order - b.claim_order); to_move.sort((a, b) => a.claim_order - b.claim_order);
// Finally, we move the nodes // Finally, we move the nodes
for (let i = 0, j = 0; i < toMove.length; i++) { for (let i = 0, j = 0; i < to_move.length; i++) {
while (j < lis.length && toMove[i].claim_order >= lis[j].claim_order) { while (j < lis.length && to_move[i].claim_order >= lis[j].claim_order) {
j++; j++;
} }
const anchor = j < lis.length ? lis[j] : null; const anchor = j < lis.length ? lis[j] : null;
target.insertBefore(toMove[i], anchor); target.insertBefore(to_move[i], anchor);
} }
} }
@ -478,7 +480,10 @@ export function set_custom_element_data_map(node, data_map) {
/** /**
* @returns {void} */ * @returns {void} */
export function set_custom_element_data(node, prop, value) { export function set_custom_element_data(node, prop, value) {
if (prop in node) { const lower = prop.toLowerCase(); // for backwards compatibility with existing behavior we do lowercase first
if (lower in node) {
node[lower] = typeof node[lower] === 'boolean' && value === '' ? true : value;
} else if (prop in node) {
node[prop] = typeof node[prop] === 'boolean' && value === '' ? true : value; node[prop] = typeof node[prop] === 'boolean' && value === '' ? true : value;
} else { } else {
attr(node, prop, value); attr(node, prop, value);
@ -624,26 +629,26 @@ function init_claim_info(nodes) {
* @template {ChildNodeEx} R * @template {ChildNodeEx} R
* @param {ChildNodeArray} nodes * @param {ChildNodeArray} nodes
* @param {(node: ChildNodeEx) => node is R} predicate * @param {(node: ChildNodeEx) => node is R} predicate
* @param {(node: ChildNodeEx) => ChildNodeEx | undefined} processNode * @param {(node: ChildNodeEx) => ChildNodeEx | undefined} process_node
* @param {() => R} createNode * @param {() => R} create_node
* @param {boolean} dontUpdateLastIndex * @param {boolean} dont_update_last_index
* @returns {R} * @returns {R}
*/ */
function claim_node(nodes, predicate, processNode, createNode, dontUpdateLastIndex = false) { function claim_node(nodes, predicate, process_node, create_node, dont_update_last_index = false) {
// Try to find nodes in an order such that we lengthen the longest increasing subsequence // Try to find nodes in an order such that we lengthen the longest increasing subsequence
init_claim_info(nodes); init_claim_info(nodes);
const resultNode = (() => { const result_node = (() => {
// We first try to find an element after the previous one // We first try to find an element after the previous one
for (let i = nodes.claim_info.last_index; i < nodes.length; i++) { for (let i = nodes.claim_info.last_index; i < nodes.length; i++) {
const node = nodes[i]; const node = nodes[i];
if (predicate(node)) { if (predicate(node)) {
const replacement = processNode(node); const replacement = process_node(node);
if (replacement === undefined) { if (replacement === undefined) {
nodes.splice(i, 1); nodes.splice(i, 1);
} else { } else {
nodes[i] = replacement; nodes[i] = replacement;
} }
if (!dontUpdateLastIndex) { if (!dont_update_last_index) {
nodes.claim_info.last_index = i; nodes.claim_info.last_index = i;
} }
return node; return node;
@ -654,13 +659,13 @@ function claim_node(nodes, predicate, processNode, createNode, dontUpdateLastInd
for (let i = nodes.claim_info.last_index - 1; i >= 0; i--) { for (let i = nodes.claim_info.last_index - 1; i >= 0; i--) {
const node = nodes[i]; const node = nodes[i];
if (predicate(node)) { if (predicate(node)) {
const replacement = processNode(node); const replacement = process_node(node);
if (replacement === undefined) { if (replacement === undefined) {
nodes.splice(i, 1); nodes.splice(i, 1);
} else { } else {
nodes[i] = replacement; nodes[i] = replacement;
} }
if (!dontUpdateLastIndex) { if (!dont_update_last_index) {
nodes.claim_info.last_index = i; nodes.claim_info.last_index = i;
} else if (replacement === undefined) { } else if (replacement === undefined) {
// Since we spliced before the last_index, we decrease it // Since we spliced before the last_index, we decrease it
@ -670,11 +675,11 @@ function claim_node(nodes, predicate, processNode, createNode, dontUpdateLastInd
} }
} }
// If we can't find any matching node, we create a new one // If we can't find any matching node, we create a new one
return createNode(); return create_node();
})(); })();
resultNode.claim_order = nodes.claim_info.total_claimed; result_node.claim_order = nodes.claim_info.total_claimed;
nodes.claim_info.total_claimed += 1; nodes.claim_info.total_claimed += 1;
return resultNode; return result_node;
} }
/** /**
@ -736,13 +741,13 @@ export function claim_text(nodes, data) {
(node) => node.nodeType === 3, (node) => node.nodeType === 3,
/** @param {Text} node */ /** @param {Text} node */
(node) => { (node) => {
const dataStr = '' + data; const data_str = '' + data;
if (node.data.startsWith(dataStr)) { if (node.data.startsWith(data_str)) {
if (node.data.length !== dataStr.length) { if (node.data.length !== data_str.length) {
return node.splitText(dataStr.length); return node.splitText(data_str.length);
} }
} else { } else {
node.data = dataStr; node.data = data_str;
} }
}, },
() => text(data), () => text(data),

@ -6,5 +6,5 @@
* https://svelte.dev/docs/svelte-compiler#svelte-version * https://svelte.dev/docs/svelte-compiler#svelte-version
* @type {string} * @type {string}
*/ */
export const VERSION = '4.1.2'; export const VERSION = '4.2.2';
export const PUBLIC_VERSION = '4'; export const PUBLIC_VERSION = '4';

@ -0,0 +1,252 @@
/// <reference lib="dom" />
// This file is deliberately not exposed through the exports map.
// It's meant to be loaded directly by the Svelte language server
/* eslint-disable @typescript-eslint/no-empty-interface */
import * as svelteElements from './elements.js';
/**
* @internal do not use
*/
type HTMLProps<Property extends string, Override> = Omit<
import('./elements.js').SvelteHTMLElements[Property],
keyof Override
> &
Override;
declare global {
/**
* This namespace does not exist in the runtime, it is only used for typings
*/
namespace svelteHTML {
// Every namespace eligible for use needs to implement the following two functions
/**
* @internal do not use
*/
function mapElementTag<K extends keyof ElementTagNameMap>(tag: K): ElementTagNameMap[K];
function mapElementTag<K extends keyof SVGElementTagNameMap>(tag: K): SVGElementTagNameMap[K];
function mapElementTag(tag: any): any; // needs to be any because used in context of <svelte:element>
/**
* @internal do not use
*/
function createElement<Elements extends IntrinsicElements, Key extends keyof Elements>(
// "undefined | null" because of <svelte:element>
element: Key | undefined | null,
attrs: string extends Key ? svelteElements.HTMLAttributes<any> : Elements[Key]
): Key extends keyof ElementTagNameMap
? ElementTagNameMap[Key]
: Key extends keyof SVGElementTagNameMap
? SVGElementTagNameMap[Key]
: any;
function createElement<Elements extends IntrinsicElements, Key extends keyof Elements, T>(
// "undefined | null" because of <svelte:element>
element: Key | undefined | null,
attrsEnhancers: T,
attrs: (string extends Key ? svelteElements.HTMLAttributes<any> : Elements[Key]) & T
): Key extends keyof ElementTagNameMap
? ElementTagNameMap[Key]
: Key extends keyof SVGElementTagNameMap
? SVGElementTagNameMap[Key]
: any;
// For backwards-compatibility and ease-of-use, in case someone enhanced the typings from import('svelte/elements').HTMLAttributes/SVGAttributes
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface HTMLAttributes<T extends EventTarget = any> {}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface SVGAttributes<T extends EventTarget = any> {}
/**
* Avoid using this interface directly. Instead use the `SvelteHTMLElements` interface exported by `svelte/elements`
* This should only be used if you need to extend the interface with custom elements
*/
interface IntrinsicElements extends svelteElements.SvelteHTMLElements {
a: HTMLProps<'a', HTMLAttributes>;
abbr: HTMLProps<'abbr', HTMLAttributes>;
address: HTMLProps<'address', HTMLAttributes>;
area: HTMLProps<'area', HTMLAttributes>;
article: HTMLProps<'article', HTMLAttributes>;
aside: HTMLProps<'aside', HTMLAttributes>;
audio: HTMLProps<'audio', HTMLAttributes>;
b: HTMLProps<'b', HTMLAttributes>;
base: HTMLProps<'base', HTMLAttributes>;
bdi: HTMLProps<'bdi', HTMLAttributes>;
bdo: HTMLProps<'bdo', HTMLAttributes>;
big: HTMLProps<'big', HTMLAttributes>;
blockquote: HTMLProps<'blockquote', HTMLAttributes>;
body: HTMLProps<'body', HTMLAttributes>;
br: HTMLProps<'br', HTMLAttributes>;
button: HTMLProps<'button', HTMLAttributes>;
canvas: HTMLProps<'canvas', HTMLAttributes>;
caption: HTMLProps<'caption', HTMLAttributes>;
cite: HTMLProps<'cite', HTMLAttributes>;
code: HTMLProps<'code', HTMLAttributes>;
col: HTMLProps<'col', HTMLAttributes>;
colgroup: HTMLProps<'colgroup', HTMLAttributes>;
data: HTMLProps<'data', HTMLAttributes>;
datalist: HTMLProps<'datalist', HTMLAttributes>;
dd: HTMLProps<'dd', HTMLAttributes>;
del: HTMLProps<'del', HTMLAttributes>;
details: HTMLProps<'details', HTMLAttributes>;
dfn: HTMLProps<'dfn', HTMLAttributes>;
dialog: HTMLProps<'dialog', HTMLAttributes>;
div: HTMLProps<'div', HTMLAttributes>;
dl: HTMLProps<'dl', HTMLAttributes>;
dt: HTMLProps<'dt', HTMLAttributes>;
em: HTMLProps<'em', HTMLAttributes>;
embed: HTMLProps<'embed', HTMLAttributes>;
fieldset: HTMLProps<'fieldset', HTMLAttributes>;
figcaption: HTMLProps<'figcaption', HTMLAttributes>;
figure: HTMLProps<'figure', HTMLAttributes>;
footer: HTMLProps<'footer', HTMLAttributes>;
form: HTMLProps<'form', HTMLAttributes>;
h1: HTMLProps<'h1', HTMLAttributes>;
h2: HTMLProps<'h2', HTMLAttributes>;
h3: HTMLProps<'h3', HTMLAttributes>;
h4: HTMLProps<'h4', HTMLAttributes>;
h5: HTMLProps<'h5', HTMLAttributes>;
h6: HTMLProps<'h6', HTMLAttributes>;
head: HTMLProps<'head', HTMLAttributes>;
header: HTMLProps<'header', HTMLAttributes>;
hgroup: HTMLProps<'hgroup', HTMLAttributes>;
hr: HTMLProps<'hr', HTMLAttributes>;
html: HTMLProps<'html', HTMLAttributes>;
i: HTMLProps<'i', HTMLAttributes>;
iframe: HTMLProps<'iframe', HTMLAttributes>;
img: HTMLProps<'img', HTMLAttributes>;
input: HTMLProps<'input', HTMLAttributes>;
ins: HTMLProps<'ins', HTMLAttributes>;
kbd: HTMLProps<'kbd', HTMLAttributes>;
keygen: HTMLProps<'keygen', HTMLAttributes>;
label: HTMLProps<'label', HTMLAttributes>;
legend: HTMLProps<'legend', HTMLAttributes>;
li: HTMLProps<'li', HTMLAttributes>;
link: HTMLProps<'link', HTMLAttributes>;
main: HTMLProps<'main', HTMLAttributes>;
map: HTMLProps<'map', HTMLAttributes>;
mark: HTMLProps<'mark', HTMLAttributes>;
menu: HTMLProps<'menu', HTMLAttributes>;
menuitem: HTMLProps<'menuitem', HTMLAttributes>;
meta: HTMLProps<'meta', HTMLAttributes>;
meter: HTMLProps<'meter', HTMLAttributes>;
nav: HTMLProps<'nav', HTMLAttributes>;
noscript: HTMLProps<'noscript', HTMLAttributes>;
object: HTMLProps<'object', HTMLAttributes>;
ol: HTMLProps<'ol', HTMLAttributes>;
optgroup: HTMLProps<'optgroup', HTMLAttributes>;
option: HTMLProps<'option', HTMLAttributes>;
output: HTMLProps<'output', HTMLAttributes>;
p: HTMLProps<'p', HTMLAttributes>;
param: HTMLProps<'param', HTMLAttributes>;
picture: HTMLProps<'picture', HTMLAttributes>;
pre: HTMLProps<'pre', HTMLAttributes>;
progress: HTMLProps<'progress', HTMLAttributes>;
q: HTMLProps<'q', HTMLAttributes>;
rp: HTMLProps<'rp', HTMLAttributes>;
rt: HTMLProps<'rt', HTMLAttributes>;
ruby: HTMLProps<'ruby', HTMLAttributes>;
s: HTMLProps<'s', HTMLAttributes>;
samp: HTMLProps<'samp', HTMLAttributes>;
slot: HTMLProps<'slot', HTMLAttributes>;
script: HTMLProps<'script', HTMLAttributes>;
section: HTMLProps<'section', HTMLAttributes>;
select: HTMLProps<'select', HTMLAttributes>;
small: HTMLProps<'small', HTMLAttributes>;
source: HTMLProps<'source', HTMLAttributes>;
span: HTMLProps<'span', HTMLAttributes>;
strong: HTMLProps<'strong', HTMLAttributes>;
style: HTMLProps<'style', HTMLAttributes>;
sub: HTMLProps<'sub', HTMLAttributes>;
summary: HTMLProps<'summary', HTMLAttributes>;
sup: HTMLProps<'sup', HTMLAttributes>;
table: HTMLProps<'table', HTMLAttributes>;
template: HTMLProps<'template', HTMLAttributes>;
tbody: HTMLProps<'tbody', HTMLAttributes>;
td: HTMLProps<'td', HTMLAttributes>;
textarea: HTMLProps<'textarea', HTMLAttributes>;
tfoot: HTMLProps<'tfoot', HTMLAttributes>;
th: HTMLProps<'th', HTMLAttributes>;
thead: HTMLProps<'thead', HTMLAttributes>;
time: HTMLProps<'time', HTMLAttributes>;
title: HTMLProps<'title', HTMLAttributes>;
tr: HTMLProps<'tr', HTMLAttributes>;
track: HTMLProps<'track', HTMLAttributes>;
u: HTMLProps<'u', HTMLAttributes>;
ul: HTMLProps<'ul', HTMLAttributes>;
var: HTMLProps<'var', HTMLAttributes>;
video: HTMLProps<'video', HTMLAttributes>;
wbr: HTMLProps<'wbr', HTMLAttributes>;
webview: HTMLProps<'webview', HTMLAttributes>;
// SVG
svg: HTMLProps<'svg', SVGAttributes>;
animate: HTMLProps<'animate', SVGAttributes>;
animateMotion: HTMLProps<'animateMotion', SVGAttributes>;
animateTransform: HTMLProps<'animateTransform', SVGAttributes>;
circle: HTMLProps<'circle', SVGAttributes>;
clipPath: HTMLProps<'clipPath', SVGAttributes>;
defs: HTMLProps<'defs', SVGAttributes>;
desc: HTMLProps<'desc', SVGAttributes>;
ellipse: HTMLProps<'ellipse', SVGAttributes>;
feBlend: HTMLProps<'feBlend', SVGAttributes>;
feColorMatrix: HTMLProps<'feColorMatrix', SVGAttributes>;
feComponentTransfer: HTMLProps<'feComponentTransfer', SVGAttributes>;
feComposite: HTMLProps<'feComposite', SVGAttributes>;
feConvolveMatrix: HTMLProps<'feConvolveMatrix', SVGAttributes>;
feDiffuseLighting: HTMLProps<'feDiffuseLighting', SVGAttributes>;
feDisplacementMap: HTMLProps<'feDisplacementMap', SVGAttributes>;
feDistantLight: HTMLProps<'feDistantLight', SVGAttributes>;
feDropShadow: HTMLProps<'feDropShadow', SVGAttributes>;
feFlood: HTMLProps<'feFlood', SVGAttributes>;
feFuncA: HTMLProps<'feFuncA', SVGAttributes>;
feFuncB: HTMLProps<'feFuncB', SVGAttributes>;
feFuncG: HTMLProps<'feFuncG', SVGAttributes>;
feFuncR: HTMLProps<'feFuncR', SVGAttributes>;
feGaussianBlur: HTMLProps<'feGaussianBlur', SVGAttributes>;
feImage: HTMLProps<'feImage', SVGAttributes>;
feMerge: HTMLProps<'feMerge', SVGAttributes>;
feMergeNode: HTMLProps<'feMergeNode', SVGAttributes>;
feMorphology: HTMLProps<'feMorphology', SVGAttributes>;
feOffset: HTMLProps<'feOffset', SVGAttributes>;
fePointLight: HTMLProps<'fePointLight', SVGAttributes>;
feSpecularLighting: HTMLProps<'feSpecularLighting', SVGAttributes>;
feSpotLight: HTMLProps<'feSpotLight', SVGAttributes>;
feTile: HTMLProps<'feTile', SVGAttributes>;
feTurbulence: HTMLProps<'feTurbulence', SVGAttributes>;
filter: HTMLProps<'filter', SVGAttributes>;
foreignObject: HTMLProps<'foreignObject', SVGAttributes>;
g: HTMLProps<'g', SVGAttributes>;
image: HTMLProps<'image', SVGAttributes>;
line: HTMLProps<'line', SVGAttributes>;
linearGradient: HTMLProps<'linearGradient', SVGAttributes>;
marker: HTMLProps<'marker', SVGAttributes>;
mask: HTMLProps<'mask', SVGAttributes>;
metadata: HTMLProps<'metadata', SVGAttributes>;
mpath: HTMLProps<'mpath', SVGAttributes>;
path: HTMLProps<'path', SVGAttributes>;
pattern: HTMLProps<'pattern', SVGAttributes>;
polygon: HTMLProps<'polygon', SVGAttributes>;
polyline: HTMLProps<'polyline', SVGAttributes>;
radialGradient: HTMLProps<'radialGradient', SVGAttributes>;
rect: HTMLProps<'rect', SVGAttributes>;
stop: HTMLProps<'stop', SVGAttributes>;
switch: HTMLProps<'switch', SVGAttributes>;
symbol: HTMLProps<'symbol', SVGAttributes>;
text: HTMLProps<'text', SVGAttributes>;
textPath: HTMLProps<'textPath', SVGAttributes>;
tspan: HTMLProps<'tspan', SVGAttributes>;
use: HTMLProps<'use', SVGAttributes>;
view: HTMLProps<'view', SVGAttributes>;
// Svelte specific
'svelte:window': HTMLProps<'svelte:window', HTMLAttributes>;
'svelte:body': HTMLProps<'svelte:body', HTMLAttributes>;
'svelte:document': HTMLProps<'svelte:document', HTMLAttributes>;
'svelte:fragment': { slot?: string };
'svelte:options': HTMLProps<'svelte:options', HTMLAttributes>;
'svelte:head': { [name: string]: any };
[name: string]: { [name: string]: any };
}
}
}

@ -1,6 +1,8 @@
{ {
"plugins": ["lube"],
"rules": { "rules": {
"no-console": "off", "no-console": "off",
"@typescript-eslint/no-var-requires": "off" "@typescript-eslint/no-var-requires": "off",
"lube/svelte-naming-convention": ["error", { "fixSameNames": true }]
} }
} }

@ -20,14 +20,14 @@ describe('compiler-errors', () => {
it_fn(dir, () => { it_fn(dir, () => {
const cwd = path.resolve(`${__dirname}/samples/${dir}`); const cwd = path.resolve(`${__dirname}/samples/${dir}`);
const compileOptions = Object.assign({}, config.compileOptions || {}, { const compile_options = Object.assign({}, config.compileOptions || {}, {
immutable: config.immutable, immutable: config.immutable,
accessors: 'accessors' in config ? config.accessors : true, accessors: 'accessors' in config ? config.accessors : true,
generate: 'dom' generate: 'dom'
}); });
try { try {
compile(fs.readFileSync(`${cwd}/main.svelte`, 'utf-8'), compileOptions); compile(fs.readFileSync(`${cwd}/main.svelte`, 'utf-8'), compile_options);
} catch (error) { } catch (error) {
if (typeof config.error === 'function') { if (typeof config.error === 'function') {
config.error(assert, error); config.error(assert, error);

@ -2,11 +2,11 @@ export default {
compileOptions: { compileOptions: {
filename: 'src/components/FooSwitcher.svelte', filename: 'src/components/FooSwitcher.svelte',
cssHash({ hash, css, name, filename }) { cssHash({ hash, css, name, filename }) {
const minFilename = filename const min_filename = filename
.split('/') .split('/')
.map((i) => i.charAt(0).toLowerCase()) .map((i) => i.charAt(0).toLowerCase())
.join(''); .join('');
return `sv-${name}-${minFilename}-${hash(css)}`; return `sv-${name}-${min_filename}-${hash(css)}`;
} }
} }
}; };

@ -1,10 +1,11 @@
import * as fs from 'node:fs'; import * as fs from 'node:fs';
import * as path from 'node:path'; import * as path from 'node:path';
import glob from 'tiny-glob/sync';
import colors from 'kleur';
import { assert } from 'vitest'; import { assert } from 'vitest';
import colors from 'kleur';
import { compile } from 'svelte/compiler'; import { compile } from 'svelte/compiler';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import glob from 'tiny-glob/sync';
export function try_load_json(file) { export function try_load_json(file) {
try { try {
@ -158,8 +159,8 @@ export function create_loader(compileOptions, cwd) {
) )
.replace( .replace(
/^import (\w+, )?{([^}]+)} from ['"](.+)['"];?/gm, /^import (\w+, )?{([^}]+)} from ['"](.+)['"];?/gm,
(_, default_, names, source) => { (_, _default, names, source) => {
const d = default_ ? `default: ${default_}` : ''; const d = _default ? `default: ${_default}` : '';
return `const { ${d} ${names.replaceAll( return `const { ${d} ${names.replaceAll(
' as ', ' as ',
': ' ': '

@ -18,12 +18,12 @@ describe('hydration', async () => {
it_fn(dir, async () => { it_fn(dir, async () => {
const cwd = path.resolve(`${__dirname}/samples/${dir}`); const cwd = path.resolve(`${__dirname}/samples/${dir}`);
const compileOptions = Object.assign({}, config.compileOptions, { const compile_options = Object.assign({}, config.compileOptions, {
accessors: 'accessors' in config ? config.accessors : true, accessors: 'accessors' in config ? config.accessors : true,
hydratable: true hydratable: true
}); });
const { default: SvelteComponent } = await create_loader(compileOptions, cwd)('main.svelte'); const { default: SvelteComponent } = await create_loader(compile_options, cwd)('main.svelte');
const target = window.document.body; const target = window.document.body;
const head = window.document.head; const head = window.document.head;

@ -2,12 +2,12 @@ export default {
props: {}, props: {},
snapshot(target) { snapshot(target) {
const nullText = target.querySelectorAll('p')[0].textContent; const null_text = target.querySelectorAll('p')[0].textContent;
const undefinedText = target.querySelectorAll('p')[1].textContent; const undefined_text = target.querySelectorAll('p')[1].textContent;
return { return {
nullText, nullText: null_text,
undefinedText undefinedText: undefined_text
}; };
} }
}; };

@ -53,11 +53,11 @@ function create_fragment(ctx) {
div7 = element("div"); div7 = element("div");
t7 = space(); t7 = space();
div8 = element("div"); div8 = element("div");
toggle_class(div0, "update1", reactiveModuleVar); toggle_class(div0, "update2", /*reactiveConst*/ ctx[0].x);
toggle_class(div1, "update2", /*reactiveConst*/ ctx[0].x); toggle_class(div1, "update3", nonReactiveGlobal && /*reactiveConst*/ ctx[0].x);
toggle_class(div2, "update3", nonReactiveGlobal && /*reactiveConst*/ ctx[0].x); toggle_class(div2, "update4", /*$reactiveStoreVal*/ ctx[2]);
toggle_class(div3, "update4", /*$reactiveStoreVal*/ ctx[2]); toggle_class(div3, "update5", /*$reactiveDeclaration*/ ctx[3]);
toggle_class(div4, "update5", /*$reactiveDeclaration*/ ctx[3]); toggle_class(div4, "update1", reassignedModuleVar);
toggle_class(div5, "static1", nonReactiveModuleVar); toggle_class(div5, "static1", nonReactiveModuleVar);
toggle_class(div6, "static2", nonReactiveGlobal); toggle_class(div6, "static2", nonReactiveGlobal);
toggle_class(div7, "static3", nonReactiveModuleVar && nonReactiveGlobal); toggle_class(div7, "static3", nonReactiveModuleVar && nonReactiveGlobal);
@ -83,24 +83,20 @@ function create_fragment(ctx) {
insert(target, div8, anchor); insert(target, div8, anchor);
}, },
p(ctx, [dirty]) { p(ctx, [dirty]) {
if (dirty & /*reactiveModuleVar*/ 0) {
toggle_class(div0, "update1", reactiveModuleVar);
}
if (dirty & /*reactiveConst*/ 1) { if (dirty & /*reactiveConst*/ 1) {
toggle_class(div1, "update2", /*reactiveConst*/ ctx[0].x); toggle_class(div0, "update2", /*reactiveConst*/ ctx[0].x);
} }
if (dirty & /*nonReactiveGlobal, reactiveConst*/ 1) { if (dirty & /*nonReactiveGlobal, reactiveConst*/ 1) {
toggle_class(div2, "update3", nonReactiveGlobal && /*reactiveConst*/ ctx[0].x); toggle_class(div1, "update3", nonReactiveGlobal && /*reactiveConst*/ ctx[0].x);
} }
if (dirty & /*$reactiveStoreVal*/ 4) { if (dirty & /*$reactiveStoreVal*/ 4) {
toggle_class(div3, "update4", /*$reactiveStoreVal*/ ctx[2]); toggle_class(div2, "update4", /*$reactiveStoreVal*/ ctx[2]);
} }
if (dirty & /*$reactiveDeclaration*/ 8) { if (dirty & /*$reactiveDeclaration*/ 8) {
toggle_class(div4, "update5", /*$reactiveDeclaration*/ ctx[3]); toggle_class(div3, "update5", /*$reactiveDeclaration*/ ctx[3]);
} }
}, },
i: noop, i: noop,
@ -130,7 +126,7 @@ function create_fragment(ctx) {
} }
let nonReactiveModuleVar = Math.random(); let nonReactiveModuleVar = Math.random();
let reactiveModuleVar = Math.random(); let reassignedModuleVar = Math.random();
function instance($$self, $$props, $$invalidate) { function instance($$self, $$props, $$invalidate) {
let reactiveDeclaration; let reactiveDeclaration;
@ -144,13 +140,13 @@ function instance($$self, $$props, $$invalidate) {
$$self.$$.on_destroy.push(() => $$unsubscribe_reactiveDeclaration()); $$self.$$.on_destroy.push(() => $$unsubscribe_reactiveDeclaration());
nonReactiveGlobal = Math.random(); nonReactiveGlobal = Math.random();
const reactiveConst = { x: Math.random() }; const reactiveConst = { x: Math.random() };
reactiveModuleVar += 1; reassignedModuleVar += 1;
if (Math.random()) { if (Math.random()) {
reactiveConst.x += 1; reactiveConst.x += 1;
} }
$: $$subscribe_reactiveDeclaration($$invalidate(1, reactiveDeclaration = reactiveModuleVar * 2)); $: $$subscribe_reactiveDeclaration($$invalidate(1, reactiveDeclaration = reassignedModuleVar * 2));
return [reactiveConst, reactiveDeclaration, $reactiveStoreVal, $reactiveDeclaration]; return [reactiveConst, reactiveDeclaration, $reactiveStoreVal, $reactiveDeclaration];
} }

@ -1,6 +1,6 @@
<script context="module"> <script context="module">
let nonReactiveModuleVar = Math.random(); let nonReactiveModuleVar = Math.random();
let reactiveModuleVar = Math.random(); let reassignedModuleVar = Math.random();
</script> </script>
<script> <script>
@ -9,22 +9,22 @@
nonReactiveGlobal = Math.random(); nonReactiveGlobal = Math.random();
const reactiveConst = {x: Math.random()}; const reactiveConst = {x: Math.random()};
$: reactiveDeclaration = reactiveModuleVar * 2; $: reactiveDeclaration = reassignedModuleVar * 2;
reactiveModuleVar += 1; reassignedModuleVar += 1;
if (Math.random()) { if (Math.random()) {
reactiveConst.x += 1; reactiveConst.x += 1;
} }
</script> </script>
<!--These should all get updaters because they have at least one reactive dependency--> <!--These should all get updaters because they have at least one reactive dependency-->
<div class:update1={reactiveModuleVar}></div>
<div class:update2={reactiveConst.x}></div> <div class:update2={reactiveConst.x}></div>
<div class:update3={nonReactiveGlobal && reactiveConst.x}></div> <div class:update3={nonReactiveGlobal && reactiveConst.x}></div>
<div class:update4={$reactiveStoreVal}></div> <div class:update4={$reactiveStoreVal}></div>
<div class:update5={$reactiveDeclaration}></div> <div class:update5={$reactiveDeclaration}></div>
<!--These shouldn't get updates because they're purely non-reactive--> <!--These shouldn't get updates because they're purely non-reactive-->
<div class:update1={reassignedModuleVar}></div>
<div class:static1={nonReactiveModuleVar}></div> <div class:static1={nonReactiveModuleVar}></div>
<div class:static2={nonReactiveGlobal}></div> <div class:static2={nonReactiveGlobal}></div>
<div class:static3={nonReactiveModuleVar && nonReactiveGlobal}></div> <div class:static3={nonReactiveModuleVar && nonReactiveGlobal}></div>

@ -21,7 +21,7 @@ function create_dynamic_element_3(ctx) {
return { return {
c() { c() {
svelte_element = element(static_value); svelte_element = element(static_value);
set_dynamic_element_data(static_value)(svelte_element, { static_value, ...static_obj }); set_dynamic_element_data(static_value)(svelte_element, { static_value, .../*static_obj*/ ctx[2] });
toggle_class(svelte_element, "foo", static_value); toggle_class(svelte_element, "foo", static_value);
}, },
m(target, anchor) { m(target, anchor) {
@ -43,7 +43,7 @@ function create_dynamic_element_2(ctx) {
return { return {
c() { c() {
svelte_element = element(/*dynamic_value*/ ctx[0]); svelte_element = element(/*dynamic_value*/ ctx[0]);
set_dynamic_element_data(/*dynamic_value*/ ctx[0])(svelte_element, { static_value, ...static_obj }); set_dynamic_element_data(/*dynamic_value*/ ctx[0])(svelte_element, { static_value, .../*static_obj*/ ctx[2] });
toggle_class(svelte_element, "foo", static_value); toggle_class(svelte_element, "foo", static_value);
}, },
m(target, anchor) { m(target, anchor) {

@ -26,8 +26,8 @@ describe('parse', () => {
.trimEnd() .trimEnd()
.replace(/\r/g, ''); .replace(/\r/g, '');
const expectedOutput = try_load_json(`${__dirname}/samples/${dir}/output.json`); const expected_output = try_load_json(`${__dirname}/samples/${dir}/output.json`);
const expectedError = try_load_json(`${__dirname}/samples/${dir}/error.json`); const expected_error = try_load_json(`${__dirname}/samples/${dir}/error.json`);
try { try {
const { ast } = svelte.compile( const { ast } = svelte.compile(
@ -42,16 +42,16 @@ describe('parse', () => {
JSON.stringify(ast, null, '\t') JSON.stringify(ast, null, '\t')
); );
assert.deepEqual(ast.html, expectedOutput.html); assert.deepEqual(ast.html, expected_output.html);
assert.deepEqual(ast.css, expectedOutput.css); assert.deepEqual(ast.css, expected_output.css);
assert.deepEqual(ast.instance, expectedOutput.instance); assert.deepEqual(ast.instance, expected_output.instance);
assert.deepEqual(ast.module, expectedOutput.module); assert.deepEqual(ast.module, expected_output.module);
} catch (err) { } catch (err) {
if (err.name !== 'ParseError') throw err; if (err.name !== 'ParseError') throw err;
if (!expectedError) throw err; if (!expected_error) throw err;
const { code, message, pos, start } = err; const { code, message, pos, start } = err;
assert.deepEqual({ code, message, pos, start }, expectedError); assert.deepEqual({ code, message, pos, start }, expected_error);
} }
}); });
}); });

@ -33,10 +33,10 @@ export function ok(condition, message) {
} }
export function htmlEqual(actual, expected, message) { export function htmlEqual(actual, expected, message) {
return deepEqual(normalizeHtml(window, actual), normalizeHtml(window, expected), message); return deepEqual(normalize_html(window, actual), normalize_html(window, expected), message);
} }
function normalizeHtml(window, html) { function normalize_html(window, html) {
try { try {
const node = window.document.createElement('div'); const node = window.document.createElement('div');
node.innerHTML = html node.innerHTML = html
@ -44,7 +44,7 @@ function normalizeHtml(window, html) {
.replace(/>[\s\r\n]+</g, '><') .replace(/>[\s\r\n]+</g, '><')
.trim(); .trim();
normalizeStyles(node); normalize_styles(node);
return node.innerHTML.replace(/<\/?noscript\/?>/g, ''); return node.innerHTML.replace(/<\/?noscript\/?>/g, '');
} catch (err) { } catch (err) {
@ -52,14 +52,14 @@ function normalizeHtml(window, html) {
} }
} }
function normalizeStyles(node) { function normalize_styles(node) {
if (node.nodeType === 1) { if (node.nodeType === 1) {
if (node.hasAttribute('style')) { if (node.hasAttribute('style')) {
node.style = node.style.cssText; node.style = node.style.cssText;
} }
for (const child of node.childNodes) { for (const child of node.childNodes) {
normalizeStyles(child); normalize_styles(child);
} }
} }
} }

@ -93,7 +93,7 @@ async function run_browser_test(dir) {
globalName: 'test' globalName: 'test'
}); });
function assertWarnings() { function assert_warnings() {
if (config.warnings) { if (config.warnings) {
assert.deepStrictEqual( assert.deepStrictEqual(
warnings.map((w) => ({ warnings.map((w) => ({
@ -112,7 +112,7 @@ async function run_browser_test(dir) {
} }
} }
assertWarnings(); assert_warnings();
try { try {
const page = await browser.newPage(); const page = await browser.newPage();
@ -191,7 +191,7 @@ async function run_custom_elements_test(dir) {
globalName: 'test' globalName: 'test'
}); });
function assertWarnings() { function assert_warnings() {
if (expected_warnings) { if (expected_warnings) {
assert.deepStrictEqual( assert.deepStrictEqual(
warnings.map((w) => ({ warnings.map((w) => ({
@ -205,7 +205,7 @@ async function run_custom_elements_test(dir) {
); );
} }
} }
assertWarnings(); assert_warnings();
const page = await browser.newPage(); const page = await browser.newPage();
page.on('console', (type) => { page.on('console', (type) => {

@ -0,0 +1,23 @@
<svelte:options
customElement={{
tag: 'custom-element',
props: {
expanded: { reflect: true, type: 'Boolean', attribute: 'aria-expanded' }
}
}}
/>
<script>
export let expanded = false;
</script>
<div>
<button on:click={() => (expanded = !expanded)}>Toggle</button>
<div class:hidden={!expanded}>Hidden Text</div>
</div>
<style>
.hidden {
display: none;
}
</style>

@ -0,0 +1,19 @@
import * as assert from 'assert.js';
import { tick } from 'svelte';
import './main.svelte';
export default async function (target) {
const element = document.createElement('custom-element');
target.appendChild(element);
await tick();
const el = target.querySelector('custom-element');
el.shadowRoot.querySelector('button').click();
await tick();
assert.equal(el.getAttribute('aria-expanded'), '');
el.shadowRoot.querySelector('button').click();
await tick();
assert.equal(el.getAttribute('aria-expanded'), null);
}

@ -6,25 +6,25 @@ export default async function (target) {
target.innerHTML = '<custom-element red white></custom-element>'; target.innerHTML = '<custom-element red white></custom-element>';
await tick(); await tick();
await tick(); await tick();
const ceRoot = target.querySelector('custom-element').shadowRoot; const ce_root = target.querySelector('custom-element').shadowRoot;
const div = ceRoot.querySelector('div'); const div = ce_root.querySelector('div');
const p = ceRoot.querySelector('p'); const p = ce_root.querySelector('p');
const button = ceRoot.querySelector('button'); const button = ce_root.querySelector('button');
assert.equal(getComputedStyle(div).color, 'rgb(255, 0, 0)'); assert.equal(getComputedStyle(div).color, 'rgb(255, 0, 0)');
assert.equal(getComputedStyle(p).color, 'rgb(255, 255, 255)'); assert.equal(getComputedStyle(p).color, 'rgb(255, 255, 255)');
const innerRoot = ceRoot.querySelector('my-widget').shadowRoot; const inner_root = ce_root.querySelector('my-widget').shadowRoot;
const innerDiv = innerRoot.querySelector('div'); const inner_div = inner_root.querySelector('div');
const innerP = innerRoot.querySelector('p'); const inner_p = inner_root.querySelector('p');
assert.equal(getComputedStyle(innerDiv).color, 'rgb(255, 0, 0)'); assert.equal(getComputedStyle(inner_div).color, 'rgb(255, 0, 0)');
assert.equal(getComputedStyle(innerP).color, 'rgb(255, 255, 255)'); assert.equal(getComputedStyle(inner_p).color, 'rgb(255, 255, 255)');
button.click(); button.click();
await tick(); await tick();
await tick(); await tick();
assert.equal(getComputedStyle(div).color, 'rgb(0, 0, 0)'); assert.equal(getComputedStyle(div).color, 'rgb(0, 0, 0)');
assert.equal(getComputedStyle(innerDiv).color, 'rgb(0, 0, 0)'); assert.equal(getComputedStyle(inner_div).color, 'rgb(0, 0, 0)');
} }

@ -30,7 +30,7 @@ export default async function (target) {
const component = new SvelteComponent(options); const component = new SvelteComponent(options);
const waitUntil = async (fn, ms = 500) => { const wait_until = async (fn, ms = 500) => {
const start = new Date().getTime(); const start = new Date().getTime();
do { do {
if (fn()) return; if (fn()) return;
@ -48,7 +48,7 @@ export default async function (target) {
component, component,
target, target,
window, window,
waitUntil waitUntil: wait_until
}); });
component.$destroy(); component.$destroy();

@ -45,14 +45,14 @@ export default {
` `
); );
const circleColor1 = target.querySelector('#svg-1 circle'); const circle_color1 = target.querySelector('#svg-1 circle');
const rectColor1 = target.querySelector('#svg-1 rect'); const rect_color1 = target.querySelector('#svg-1 rect');
const circleColor2 = target.querySelector('#svg-2 circle'); const circle_color2 = target.querySelector('#svg-2 circle');
const rectColor2 = target.querySelector('#svg-2 rect'); const rect_color2 = target.querySelector('#svg-2 rect');
assert.htmlEqual(window.getComputedStyle(circleColor1).fill, 'rgb(255, 0, 0)'); assert.htmlEqual(window.getComputedStyle(circle_color1).fill, 'rgb(255, 0, 0)');
assert.htmlEqual(window.getComputedStyle(rectColor1).fill, 'rgb(255, 255, 0)'); assert.htmlEqual(window.getComputedStyle(rect_color1).fill, 'rgb(255, 255, 0)');
assert.htmlEqual(window.getComputedStyle(circleColor2).fill, 'rgb(0, 255, 255)'); assert.htmlEqual(window.getComputedStyle(circle_color2).fill, 'rgb(0, 255, 255)');
assert.htmlEqual(window.getComputedStyle(rectColor2).fill, 'rgb(0, 0, 0)'); assert.htmlEqual(window.getComputedStyle(rect_color2).fill, 'rgb(0, 0, 0)');
} }
}; };

@ -14,14 +14,14 @@ export default {
</div> </div>
`, `,
test({ target, window, assert }) { test({ target, window, assert }) {
const railColor1 = target.querySelector('#slider-1 p'); const rail_color1 = target.querySelector('#slider-1 p');
const trackColor1 = target.querySelector('#slider-1 span'); const track_color1 = target.querySelector('#slider-1 span');
const railColor2 = target.querySelector('#slider-2 p'); const rail_color2 = target.querySelector('#slider-2 p');
const trackColor2 = target.querySelector('#slider-2 span'); const track_color2 = target.querySelector('#slider-2 span');
assert.htmlEqual(window.getComputedStyle(railColor1).color, 'rgb(0, 0, 0)'); assert.htmlEqual(window.getComputedStyle(rail_color1).color, 'rgb(0, 0, 0)');
assert.htmlEqual(window.getComputedStyle(trackColor1).color, 'rgb(255, 0, 0)'); assert.htmlEqual(window.getComputedStyle(track_color1).color, 'rgb(255, 0, 0)');
assert.htmlEqual(window.getComputedStyle(railColor2).color, 'rgb(0, 255, 0)'); assert.htmlEqual(window.getComputedStyle(rail_color2).color, 'rgb(0, 255, 0)');
assert.htmlEqual(window.getComputedStyle(trackColor2).color, 'rgb(0, 0, 255)'); assert.htmlEqual(window.getComputedStyle(track_color2).color, 'rgb(0, 0, 255)');
} }
}; };

@ -18,31 +18,31 @@ export default {
`, `,
test({ target, window, assert, component }) { test({ target, window, assert, component }) {
function assert_slider_1() { function assert_slider_1() {
const railColor1 = target.querySelector('#component1 p'); const rail_color1 = target.querySelector('#component1 p');
const trackColor1 = target.querySelector('#component1 span'); const track_color1 = target.querySelector('#component1 span');
const railColor2 = target.querySelector('#component2 p'); const rail_color2 = target.querySelector('#component2 p');
const trackColor2 = target.querySelector('#component2 span'); const track_color2 = target.querySelector('#component2 span');
assert.equal(window.getComputedStyle(railColor1).color, 'rgb(0, 0, 0)'); assert.equal(window.getComputedStyle(rail_color1).color, 'rgb(0, 0, 0)');
assert.equal(window.getComputedStyle(trackColor1).color, 'rgb(255, 0, 0)'); assert.equal(window.getComputedStyle(track_color1).color, 'rgb(255, 0, 0)');
assert.equal(window.getComputedStyle(railColor2).color, 'rgb(0, 255, 0)'); assert.equal(window.getComputedStyle(rail_color2).color, 'rgb(0, 255, 0)');
assert.equal(window.getComputedStyle(trackColor2).color, 'rgb(0, 0, 255)'); assert.equal(window.getComputedStyle(track_color2).color, 'rgb(0, 0, 255)');
assert.equal(railColor1.textContent, 'Slider1'); assert.equal(rail_color1.textContent, 'Slider1');
assert.equal(railColor2.textContent, 'Slider1'); assert.equal(rail_color2.textContent, 'Slider1');
} }
function assert_slider_2() { function assert_slider_2() {
const railColor1 = target.querySelector('#component1 p'); const rail_color1 = target.querySelector('#component1 p');
const trackColor1 = target.querySelector('#component1 span'); const track_color1 = target.querySelector('#component1 span');
const railColor2 = target.querySelector('#component2 p'); const rail_color2 = target.querySelector('#component2 p');
const trackColor2 = target.querySelector('#component2 span'); const track_color2 = target.querySelector('#component2 span');
assert.equal(window.getComputedStyle(railColor1).color, 'rgb(0, 0, 0)'); assert.equal(window.getComputedStyle(rail_color1).color, 'rgb(0, 0, 0)');
assert.equal(window.getComputedStyle(trackColor1).color, 'rgb(255, 0, 0)'); assert.equal(window.getComputedStyle(track_color1).color, 'rgb(255, 0, 0)');
assert.equal(window.getComputedStyle(railColor2).color, 'rgb(0, 255, 0)'); assert.equal(window.getComputedStyle(rail_color2).color, 'rgb(0, 255, 0)');
assert.equal(window.getComputedStyle(trackColor2).color, 'rgb(0, 0, 255)'); assert.equal(window.getComputedStyle(track_color2).color, 'rgb(0, 0, 255)');
assert.equal(railColor1.textContent, 'Slider2'); assert.equal(rail_color1.textContent, 'Slider2');
assert.equal(railColor2.textContent, 'Slider2'); assert.equal(rail_color2.textContent, 'Slider2');
} }
assert_slider_1(); assert_slider_1();

@ -20,31 +20,31 @@ export default {
`, `,
test({ target, window, assert, component }) { test({ target, window, assert, component }) {
function assert_slider_1() { function assert_slider_1() {
const railColor1 = target.querySelector('#component1 p'); const rail_color1 = target.querySelector('#component1 p');
const trackColor1 = target.querySelector('#component1 span'); const track_color1 = target.querySelector('#component1 span');
const railColor2 = target.querySelector('#component2 p'); const rail_color2 = target.querySelector('#component2 p');
const trackColor2 = target.querySelector('#component2 span'); const track_color2 = target.querySelector('#component2 span');
assert.equal(window.getComputedStyle(railColor1).color, 'rgb(0, 0, 0)'); assert.equal(window.getComputedStyle(rail_color1).color, 'rgb(0, 0, 0)');
assert.equal(window.getComputedStyle(trackColor1).color, 'rgb(255, 0, 0)'); assert.equal(window.getComputedStyle(track_color1).color, 'rgb(255, 0, 0)');
assert.equal(window.getComputedStyle(railColor2).color, 'rgb(0, 255, 0)'); assert.equal(window.getComputedStyle(rail_color2).color, 'rgb(0, 255, 0)');
assert.equal(window.getComputedStyle(trackColor2).color, 'rgb(0, 0, 255)'); assert.equal(window.getComputedStyle(track_color2).color, 'rgb(0, 0, 255)');
assert.equal(railColor1.textContent, 'Slider1'); assert.equal(rail_color1.textContent, 'Slider1');
assert.equal(railColor2.textContent, 'Slider1'); assert.equal(rail_color2.textContent, 'Slider1');
} }
function assert_slider_2() { function assert_slider_2() {
const railColor1 = target.querySelector('#component1 p'); const rail_color1 = target.querySelector('#component1 p');
const trackColor1 = target.querySelector('#component1 span'); const track_color1 = target.querySelector('#component1 span');
const railColor2 = target.querySelector('#component2 p'); const rail_color2 = target.querySelector('#component2 p');
const trackColor2 = target.querySelector('#component2 span'); const track_color2 = target.querySelector('#component2 span');
assert.equal(window.getComputedStyle(railColor1).color, 'rgb(0, 0, 0)'); assert.equal(window.getComputedStyle(rail_color1).color, 'rgb(0, 0, 0)');
assert.equal(window.getComputedStyle(trackColor1).color, 'rgb(255, 0, 0)'); assert.equal(window.getComputedStyle(track_color1).color, 'rgb(255, 0, 0)');
assert.equal(window.getComputedStyle(railColor2).color, 'rgb(0, 255, 0)'); assert.equal(window.getComputedStyle(rail_color2).color, 'rgb(0, 255, 0)');
assert.equal(window.getComputedStyle(trackColor2).color, 'rgb(0, 0, 255)'); assert.equal(window.getComputedStyle(track_color2).color, 'rgb(0, 0, 255)');
assert.equal(railColor1.textContent, 'Slider2'); assert.equal(rail_color1.textContent, 'Slider2');
assert.equal(railColor2.textContent, 'Slider2'); assert.equal(rail_color2.textContent, 'Slider2');
} }
assert_slider_1(); assert_slider_1();

@ -22,26 +22,26 @@ export default {
</div> </div>
`, `,
test({ target, window, assert }) { test({ target, window, assert }) {
const railColor1 = target.querySelector('#component1 p'); const rail_color1 = target.querySelector('#component1 p');
const trackColor1 = target.querySelector('#component1 span'); const track_color1 = target.querySelector('#component1 span');
const railColor2 = target.querySelector('#component2 p'); const rail_color2 = target.querySelector('#component2 p');
const trackColor2 = target.querySelector('#component2 span'); const track_color2 = target.querySelector('#component2 span');
const nestRailColor1 = target.querySelector('#nest-component1 p'); const nest_rail_color1 = target.querySelector('#nest-component1 p');
const nestTrackColor1 = target.querySelector('#nest-component1 span'); const nest_track_color1 = target.querySelector('#nest-component1 span');
const nestRailColor2 = target.querySelector('#nest-component2 p'); const nest_rail_color2 = target.querySelector('#nest-component2 p');
const nestTrackColor2 = target.querySelector('#nest-component2 span'); const nest_track_color2 = target.querySelector('#nest-component2 span');
assert.equal(window.getComputedStyle(railColor1).color, 'rgb(0, 0, 0)'); assert.equal(window.getComputedStyle(rail_color1).color, 'rgb(0, 0, 0)');
assert.equal(window.getComputedStyle(trackColor1).color, 'rgb(255, 0, 0)'); assert.equal(window.getComputedStyle(track_color1).color, 'rgb(255, 0, 0)');
assert.equal(window.getComputedStyle(railColor2).color, 'rgb(0, 255, 0)'); assert.equal(window.getComputedStyle(rail_color2).color, 'rgb(0, 255, 0)');
assert.equal(window.getComputedStyle(trackColor2).color, 'rgb(0, 0, 255)'); assert.equal(window.getComputedStyle(track_color2).color, 'rgb(0, 0, 255)');
assert.equal(window.getComputedStyle(nestRailColor1).color, 'rgb(255, 255, 0)'); assert.equal(window.getComputedStyle(nest_rail_color1).color, 'rgb(255, 255, 0)');
assert.equal(window.getComputedStyle(nestTrackColor1).color, 'rgb(255, 0, 255)'); assert.equal(window.getComputedStyle(nest_track_color1).color, 'rgb(255, 0, 255)');
assert.equal(window.getComputedStyle(nestRailColor2).color, 'rgb(0, 255, 255)'); assert.equal(window.getComputedStyle(nest_rail_color2).color, 'rgb(0, 255, 255)');
assert.equal(window.getComputedStyle(nestTrackColor2).color, 'rgb(255, 255, 255)'); assert.equal(window.getComputedStyle(nest_track_color2).color, 'rgb(255, 255, 255)');
assert.equal(railColor1.textContent, 'Slider1'); assert.equal(rail_color1.textContent, 'Slider1');
assert.equal(railColor2.textContent, 'Slider2'); assert.equal(rail_color2.textContent, 'Slider2');
assert.equal(nestRailColor1.textContent, 'Slider1'); assert.equal(nest_rail_color1.textContent, 'Slider1');
assert.equal(nestRailColor2.textContent, 'Slider2'); assert.equal(nest_rail_color2.textContent, 'Slider2');
} }
}; };

@ -25,51 +25,51 @@ export default {
`, `,
test({ target, window, assert, component }) { test({ target, window, assert, component }) {
function assert_slider_1() { function assert_slider_1() {
const railColor1 = target.querySelector('#component1 p'); const rail_color1 = target.querySelector('#component1 p');
const trackColor1 = target.querySelector('#component1 span'); const track_color1 = target.querySelector('#component1 span');
const railColor2 = target.querySelector('#component2 p'); const rail_color2 = target.querySelector('#component2 p');
const trackColor2 = target.querySelector('#component2 span'); const track_color2 = target.querySelector('#component2 span');
const nestRailColor1 = target.querySelector('#nest-component1 p'); const nest_rail_color1 = target.querySelector('#nest-component1 p');
const nestTrackColor1 = target.querySelector('#nest-component1 span'); const nest_track_color1 = target.querySelector('#nest-component1 span');
const nestRailColor2 = target.querySelector('#nest-component2 p'); const nest_rail_color2 = target.querySelector('#nest-component2 p');
const nestTrackColor2 = target.querySelector('#nest-component2 span'); const nest_track_color2 = target.querySelector('#nest-component2 span');
assert.equal(window.getComputedStyle(railColor1).color, 'rgb(0, 0, 0)'); assert.equal(window.getComputedStyle(rail_color1).color, 'rgb(0, 0, 0)');
assert.equal(window.getComputedStyle(trackColor1).color, 'rgb(255, 0, 0)'); assert.equal(window.getComputedStyle(track_color1).color, 'rgb(255, 0, 0)');
assert.equal(window.getComputedStyle(railColor2).color, 'rgb(0, 255, 0)'); assert.equal(window.getComputedStyle(rail_color2).color, 'rgb(0, 255, 0)');
assert.equal(window.getComputedStyle(trackColor2).color, 'rgb(0, 0, 255)'); assert.equal(window.getComputedStyle(track_color2).color, 'rgb(0, 0, 255)');
assert.equal(window.getComputedStyle(nestRailColor1).color, 'rgb(255, 255, 0)'); assert.equal(window.getComputedStyle(nest_rail_color1).color, 'rgb(255, 255, 0)');
assert.equal(window.getComputedStyle(nestTrackColor1).color, 'rgb(255, 0, 255)'); assert.equal(window.getComputedStyle(nest_track_color1).color, 'rgb(255, 0, 255)');
assert.equal(window.getComputedStyle(nestRailColor2).color, 'rgb(255, 255, 0)'); assert.equal(window.getComputedStyle(nest_rail_color2).color, 'rgb(255, 255, 0)');
assert.equal(window.getComputedStyle(nestTrackColor2).color, 'rgb(255, 0, 255)'); assert.equal(window.getComputedStyle(nest_track_color2).color, 'rgb(255, 0, 255)');
assert.equal(railColor1.textContent, 'Slider1'); assert.equal(rail_color1.textContent, 'Slider1');
assert.equal(railColor2.textContent, 'Slider1'); assert.equal(rail_color2.textContent, 'Slider1');
assert.equal(nestRailColor1.textContent, 'Slider1'); assert.equal(nest_rail_color1.textContent, 'Slider1');
assert.equal(nestRailColor2.textContent, 'Slider1'); assert.equal(nest_rail_color2.textContent, 'Slider1');
} }
function assert_slider_2() { function assert_slider_2() {
const railColor1 = target.querySelector('#component1 p'); const rail_color1 = target.querySelector('#component1 p');
const trackColor1 = target.querySelector('#component1 span'); const track_color1 = target.querySelector('#component1 span');
const railColor2 = target.querySelector('#component2 p'); const rail_color2 = target.querySelector('#component2 p');
const trackColor2 = target.querySelector('#component2 span'); const track_color2 = target.querySelector('#component2 span');
const nestRailColor1 = target.querySelector('#nest-component1 p'); const nest_rail_color1 = target.querySelector('#nest-component1 p');
const nestTrackColor1 = target.querySelector('#nest-component1 span'); const nest_track_color1 = target.querySelector('#nest-component1 span');
const nestRailColor2 = target.querySelector('#nest-component2 p'); const nest_rail_color2 = target.querySelector('#nest-component2 p');
const nestTrackColor2 = target.querySelector('#nest-component2 span'); const nest_track_color2 = target.querySelector('#nest-component2 span');
assert.equal(window.getComputedStyle(railColor1).color, 'rgb(0, 0, 0)'); assert.equal(window.getComputedStyle(rail_color1).color, 'rgb(0, 0, 0)');
assert.equal(window.getComputedStyle(trackColor1).color, 'rgb(255, 0, 0)'); assert.equal(window.getComputedStyle(track_color1).color, 'rgb(255, 0, 0)');
assert.equal(window.getComputedStyle(railColor2).color, 'rgb(0, 255, 0)'); assert.equal(window.getComputedStyle(rail_color2).color, 'rgb(0, 255, 0)');
assert.equal(window.getComputedStyle(trackColor2).color, 'rgb(0, 0, 255)'); assert.equal(window.getComputedStyle(track_color2).color, 'rgb(0, 0, 255)');
assert.equal(window.getComputedStyle(nestRailColor1).color, 'rgb(0, 255, 255)'); assert.equal(window.getComputedStyle(nest_rail_color1).color, 'rgb(0, 255, 255)');
assert.equal(window.getComputedStyle(nestTrackColor1).color, 'rgb(255, 255, 255)'); assert.equal(window.getComputedStyle(nest_track_color1).color, 'rgb(255, 255, 255)');
assert.equal(window.getComputedStyle(nestRailColor2).color, 'rgb(0, 255, 255)'); assert.equal(window.getComputedStyle(nest_rail_color2).color, 'rgb(0, 255, 255)');
assert.equal(window.getComputedStyle(nestTrackColor2).color, 'rgb(255, 255, 255)'); assert.equal(window.getComputedStyle(nest_track_color2).color, 'rgb(255, 255, 255)');
assert.equal(railColor1.textContent, 'Slider2'); assert.equal(rail_color1.textContent, 'Slider2');
assert.equal(railColor2.textContent, 'Slider2'); assert.equal(rail_color2.textContent, 'Slider2');
assert.equal(nestRailColor1.textContent, 'Slider2'); assert.equal(nest_rail_color1.textContent, 'Slider2');
assert.equal(nestRailColor2.textContent, 'Slider2'); assert.equal(nest_rail_color2.textContent, 'Slider2');
} }
assert_slider_1(); assert_slider_1();

@ -55,18 +55,18 @@ async function run_test(dir) {
const cwd = path.resolve(`${__dirname}/samples/${dir}`); const cwd = path.resolve(`${__dirname}/samples/${dir}`);
const compileOptions = Object.assign({}, config.compileOptions || {}, { const compile_options = Object.assign({}, config.compileOptions || {}, {
hydratable: hydrate, hydratable: hydrate,
immutable: config.immutable, immutable: config.immutable,
accessors: 'accessors' in config ? config.accessors : true accessors: 'accessors' in config ? config.accessors : true
}); });
const load = create_loader(compileOptions, cwd); const load = create_loader(compile_options, cwd);
let mod; let mod;
let SvelteComponent; let SvelteComponent;
let unintendedError = null; let unintended_error = null;
if (config.expect_unhandled_rejections) { if (config.expect_unhandled_rejections) {
listeners.forEach((listener) => { listeners.forEach((listener) => {
@ -111,7 +111,7 @@ async function run_test(dir) {
let snapshot = undefined; let snapshot = undefined;
if (hydrate && from_ssr_html) { if (hydrate && from_ssr_html) {
const load_ssr = create_loader({ ...compileOptions, generate: 'ssr' }, cwd); const load_ssr = create_loader({ ...compile_options, generate: 'ssr' }, cwd);
// ssr into target // ssr into target
if (config.before_test) config.before_test(); if (config.before_test) config.before_test();
@ -152,14 +152,14 @@ async function run_test(dir) {
console.warn = warn; console.warn = warn;
if (config.error) { if (config.error) {
unintendedError = true; unintended_error = true;
assert.fail('Expected a runtime error'); assert.fail('Expected a runtime error');
} }
if (config.warnings) { if (config.warnings) {
assert.deepEqual(warnings, config.warnings); assert.deepEqual(warnings, config.warnings);
} else if (warnings.length) { } else if (warnings.length) {
unintendedError = true; unintended_error = true;
assert.fail('Received unexpected warnings'); assert.fail('Received unexpected warnings');
} }
@ -183,7 +183,7 @@ async function run_test(dir) {
snapshot, snapshot,
window, window,
raf, raf,
compileOptions, compileOptions: compile_options,
load load
}); });
} }
@ -201,7 +201,7 @@ async function run_test(dir) {
await test() await test()
.catch((err) => { .catch((err) => {
if (config.error && !unintendedError) { if (config.error && !unintended_error) {
if (typeof config.error === 'function') { if (typeof config.error === 'function') {
config.error(assert, err); config.error(assert, err);
} else { } else {
@ -217,7 +217,7 @@ async function run_test(dir) {
mkdirp(path.dirname(out)); // file could be in subdirectory, therefore don't use dir mkdirp(path.dirname(out)); // file could be in subdirectory, therefore don't use dir
const { js } = compile(fs.readFileSync(`${cwd}/${file}`, 'utf-8').replace(/\r/g, ''), { const { js } = compile(fs.readFileSync(`${cwd}/${file}`, 'utf-8').replace(/\r/g, ''), {
...compileOptions, ...compile_options,
filename: file filename: file
}); });
fs.writeFileSync(out, js.code); fs.writeFileSync(out, js.code);

@ -10,9 +10,9 @@ export default {
`, `,
async test({ assert, target, window }) { async test({ assert, target, window }) {
const [btn1, btn2, btn3, btn4] = target.querySelectorAll('button'); const [btn1, btn2, btn3, btn4] = target.querySelectorAll('button');
const clickEvent = new window.MouseEvent('click'); const click_event = new window.MouseEvent('click');
await btn1.dispatchEvent(clickEvent); await btn1.dispatchEvent(click_event);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
@ -24,7 +24,7 @@ export default {
` `
); );
await btn2.dispatchEvent(clickEvent); await btn2.dispatchEvent(click_event);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
@ -36,7 +36,7 @@ export default {
` `
); );
await btn3.dispatchEvent(clickEvent); await btn3.dispatchEvent(click_event);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
@ -48,7 +48,7 @@ export default {
` `
); );
await btn4.dispatchEvent(clickEvent); await btn4.dispatchEvent(click_event);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,

@ -12,9 +12,9 @@ export default {
async test({ assert, target, window }) { async test({ assert, target, window }) {
const [btn1, btn2, btn3, btn4] = target.querySelectorAll('button'); const [btn1, btn2, btn3, btn4] = target.querySelectorAll('button');
const clickEvent = new window.MouseEvent('click'); const click_event = new window.MouseEvent('click');
await btn1.dispatchEvent(clickEvent); await btn1.dispatchEvent(click_event);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
@ -27,7 +27,7 @@ export default {
` `
); );
await btn2.dispatchEvent(clickEvent); await btn2.dispatchEvent(click_event);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
@ -40,7 +40,7 @@ export default {
` `
); );
await btn3.dispatchEvent(clickEvent); await btn3.dispatchEvent(click_event);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
@ -53,7 +53,7 @@ export default {
` `
); );
await btn4.dispatchEvent(clickEvent); await btn4.dispatchEvent(click_event);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,

@ -5,10 +5,10 @@ export default {
async test({ assert, target, window }) { async test({ assert, target, window }) {
const button = target.querySelector('button'); const button = target.querySelector('button');
const eventEnter = new window.MouseEvent('mouseenter'); const event_enter = new window.MouseEvent('mouseenter');
const eventLeave = new window.MouseEvent('mouseleave'); const event_leave = new window.MouseEvent('mouseleave');
await button.dispatchEvent(eventEnter); await button.dispatchEvent(event_enter);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
` `
@ -17,7 +17,7 @@ export default {
` `
); );
await button.dispatchEvent(eventLeave); await button.dispatchEvent(event_leave);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
` `

@ -7,7 +7,7 @@ export default {
const button = target.querySelector('button'); const button = target.querySelector('button');
const enter = new window.MouseEvent('mouseenter'); const enter = new window.MouseEvent('mouseenter');
const leave = new window.MouseEvent('mouseleave'); const leave = new window.MouseEvent('mouseleave');
const ctrlPress = new window.KeyboardEvent('keydown', { ctrlKey: true }); const ctrl_press = new window.KeyboardEvent('keydown', { ctrlKey: true });
await button.dispatchEvent(enter); await button.dispatchEvent(enter);
assert.htmlEqual( assert.htmlEqual(
@ -18,7 +18,7 @@ export default {
` `
); );
await window.dispatchEvent(ctrlPress); await window.dispatchEvent(ctrl_press);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
` `

@ -5,10 +5,10 @@ export default {
async test({ assert, target, window }) { async test({ assert, target, window }) {
const button = target.querySelector('button'); const button = target.querySelector('button');
const eventEnter = new window.MouseEvent('mouseenter'); const event_enter = new window.MouseEvent('mouseenter');
const eventLeave = new window.MouseEvent('mouseleave'); const event_leave = new window.MouseEvent('mouseleave');
await button.dispatchEvent(eventEnter); await button.dispatchEvent(event_enter);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
` `
@ -17,7 +17,7 @@ export default {
` `
); );
await button.dispatchEvent(eventLeave); await button.dispatchEvent(event_leave);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
` `

@ -0,0 +1,7 @@
export default {
skip_if_ssr: true,
skip_if_hydrate: true,
html: `
<my-custom-element>Hello World!</my-custom-element>
`
};

@ -0,0 +1,25 @@
<script>
class MyCustomElement extends HTMLElement {
constructor() {
super();
this._obj = null;
}
set camelCase(obj) {
this._obj = obj;
this.render();
}
connectedCallback() {
this.render();
}
render() {
this.innerHTML = 'Hello ' + this._obj.text + '!';
}
}
window.customElements.define('my-custom-element', MyCustomElement);
</script>
<my-custom-element camelCase={{ text: 'World' }} />

@ -0,0 +1,7 @@
export default {
skip_if_ssr: true,
skip_if_hydrate: true,
html: `
<my-custom-inheritance-element>Hello World!</my-custom-inheritance-element>
`
};

@ -0,0 +1,33 @@
<script>
class MyCustomElement extends HTMLElement {
constructor() {
super();
this._obj = null;
this._text = null;
}
set text(text) {
this._text = text;
this.render();
}
set camelCase(obj) {
this._obj = obj;
this.render();
}
connectedCallback() {
this.render();
}
render() {
this.innerHTML = 'Hello ' + this._obj.text + this._text;
}
}
class Extended extends MyCustomElement {}
window.customElements.define('my-custom-inheritance-element', Extended);
</script>
<my-custom-inheritance-element camelCase={{ text: 'World' }} text="!" />

@ -1,8 +1,8 @@
const realPromise = Promise.resolve(42); const real_promise = Promise.resolve(42);
const promise = () => {}; const promise = () => {};
promise.then = realPromise.then.bind(realPromise); promise.then = real_promise.then.bind(real_promise);
promise.catch = realPromise.catch.bind(realPromise); promise.catch = real_promise.catch.bind(real_promise);
export default { export default {
get props() { get props() {

@ -1,13 +1,13 @@
let fulfil; let fulfil;
const thePromise = new Promise((f) => { const the_promise = new Promise((f) => {
fulfil = f; fulfil = f;
}); });
const items = [ const items = [
{ {
title: 'a title', title: 'a title',
data: thePromise data: the_promise
} }
]; ];
@ -23,7 +23,7 @@ export default {
test({ assert, target }) { test({ assert, target }) {
fulfil(42); fulfil(42);
return thePromise.then(() => { return the_promise.then(() => {
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
` `

@ -28,10 +28,10 @@ export default {
assert.equal(component.clicked, 42); assert.equal(component.clicked, 42);
const thePromise = Promise.resolve(43); const the_promise = Promise.resolve(43);
component.thePromise = thePromise; component.thePromise = the_promise;
return thePromise; return the_promise;
}) })
.then(() => { .then(() => {
const { button } = component; const { button } = component;

@ -1,12 +1,12 @@
let fulfil; let fulfil;
const thePromise = new Promise((f) => { const the_promise = new Promise((f) => {
fulfil = f; fulfil = f;
}); });
export default { export default {
get props() { get props() {
return { show: true, thePromise }; return { show: true, thePromise: the_promise };
}, },
html: ` html: `
@ -16,7 +16,7 @@ export default {
test({ assert, component, target }) { test({ assert, component, target }) {
fulfil(42); fulfil(42);
return thePromise.then(() => { return the_promise.then(() => {
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
` `
@ -35,7 +35,7 @@ export default {
component.show = true; component.show = true;
return thePromise.then(() => { return the_promise.then(() => {
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
` `

@ -1,12 +1,12 @@
let fulfil; let fulfil;
const thePromise = new Promise((f) => { const the_promise = new Promise((f) => {
fulfil = f; fulfil = f;
}); });
export default { export default {
get props() { get props() {
return { thePromise }; return { thePromise: the_promise };
}, },
html: ` html: `
@ -16,7 +16,7 @@ export default {
test({ assert, target }) { test({ assert, target }) {
fulfil(42); fulfil(42);
return thePromise.then(() => { return the_promise.then(() => {
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
` `

@ -1,12 +1,12 @@
let fulfil; let fulfil;
const thePromise = new Promise((f) => { const the_promise = new Promise((f) => {
fulfil = f; fulfil = f;
}); });
export default { export default {
get props() { get props() {
return { thePromise }; return { thePromise: the_promise };
}, },
html: ` html: `
@ -16,7 +16,7 @@ export default {
async test({ assert, target }) { async test({ assert, target }) {
fulfil([]); fulfil([]);
await thePromise; await the_promise;
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,

@ -15,12 +15,12 @@ export default {
test({ assert, component, target, window }) { test({ assert, component, target, window }) {
const { cats } = component; const { cats } = component;
const newCats = cats.slice(); const new_cats = cats.slice();
newCats.push({ new_cats.push({
name: 'cat ' + cats.length, name: 'cat ' + cats.length,
checked: false checked: false
}); });
component.cats = newCats; component.cats = new_cats;
let inputs = target.querySelectorAll('input'); let inputs = target.querySelectorAll('input');
assert.equal(inputs.length, 3); assert.equal(inputs.length, 3);

@ -23,7 +23,7 @@ export default {
async test({ assert, target, window }) { async test({ assert, target, window }) {
const inputs = target.querySelectorAll('input'); const inputs = target.querySelectorAll('input');
const checked = new Set(); const checked = new Set();
const checkInbox = async (i) => { const check_inbox = async (i) => {
checked.add(i); checked.add(i);
inputs[i].checked = true; inputs[i].checked = true;
await inputs[i].dispatchEvent(event); await inputs[i].dispatchEvent(event);
@ -35,17 +35,17 @@ export default {
const event = new window.Event('change'); const event = new window.Event('change');
await checkInbox(2); await check_inbox(2);
for (let i = 0; i < 18; i++) { for (let i = 0; i < 18; i++) {
assert.equal(inputs[i].checked, checked.has(i)); assert.equal(inputs[i].checked, checked.has(i));
} }
await checkInbox(12); await check_inbox(12);
for (let i = 0; i < 18; i++) { for (let i = 0; i < 18; i++) {
assert.equal(inputs[i].checked, checked.has(i)); assert.equal(inputs[i].checked, checked.has(i));
} }
await checkInbox(8); await check_inbox(8);
for (let i = 0; i < 18; i++) { for (let i = 0; i < 18; i++) {
assert.equal(inputs[i].checked, checked.has(i)); assert.equal(inputs[i].checked, checked.has(i));
} }

@ -1,8 +1,8 @@
export default { export default {
async test({ assert, target, component, window }) { async test({ assert, target, component, window }) {
const button = target.querySelector('button'); const button = target.querySelector('button');
const clickEvent = new window.Event('click'); const click_event = new window.Event('click');
const changeEvent = new window.Event('change'); const change_event = new window.Event('change');
const [input1, input2] = target.querySelectorAll('input[type="checkbox"]'); const [input1, input2] = target.querySelectorAll('input[type="checkbox"]');
function validate_inputs(v1, v2) { function validate_inputs(v1, v2) {
@ -17,24 +17,24 @@ export default {
validate_inputs(true, true); validate_inputs(true, true);
input1.checked = false; input1.checked = false;
await input1.dispatchEvent(changeEvent); await input1.dispatchEvent(change_event);
assert.deepEqual(component.test, ['b']); assert.deepEqual(component.test, ['b']);
input2.checked = false; input2.checked = false;
await input2.dispatchEvent(changeEvent); await input2.dispatchEvent(change_event);
assert.deepEqual(component.test, []); assert.deepEqual(component.test, []);
input1.checked = true; input1.checked = true;
input2.checked = true; input2.checked = true;
await input1.dispatchEvent(changeEvent); await input1.dispatchEvent(change_event);
await input2.dispatchEvent(changeEvent); await input2.dispatchEvent(change_event);
assert.deepEqual(component.test, ['b', 'a']); assert.deepEqual(component.test, ['b', 'a']);
await button.dispatchEvent(clickEvent); await button.dispatchEvent(click_event);
assert.deepEqual(component.test, ['b', 'a']); // should it be ['a'] only? valid arguments for both outcomes assert.deepEqual(component.test, ['b', 'a']); // should it be ['a'] only? valid arguments for both outcomes
input1.checked = false; input1.checked = false;
await input1.dispatchEvent(changeEvent); await input1.dispatchEvent(change_event);
assert.deepEqual(component.test, []); assert.deepEqual(component.test, []);
} }
}; };

@ -1,8 +1,8 @@
export default { export default {
async test({ assert, target, component, window }) { async test({ assert, target, component, window }) {
const button = target.querySelector('button'); const button = target.querySelector('button');
const clickEvent = new window.Event('click'); const click_event = new window.Event('click');
const changeEvent = new window.Event('change'); const change_event = new window.Event('change');
const [input1, input2] = target.querySelectorAll('input[type="radio"]'); const [input1, input2] = target.querySelectorAll('input[type="radio"]');
function validate_inputs(v1, v2) { function validate_inputs(v1, v2) {
@ -17,18 +17,18 @@ export default {
validate_inputs(false, true); validate_inputs(false, true);
input1.checked = true; input1.checked = true;
await input1.dispatchEvent(changeEvent); await input1.dispatchEvent(change_event);
assert.deepEqual(component.test, 'a'); assert.deepEqual(component.test, 'a');
input2.checked = true; input2.checked = true;
await input2.dispatchEvent(changeEvent); await input2.dispatchEvent(change_event);
assert.deepEqual(component.test, 'b'); assert.deepEqual(component.test, 'b');
await button.dispatchEvent(clickEvent); await button.dispatchEvent(click_event);
assert.deepEqual(component.test, 'b'); // should it be undefined? valid arguments for both outcomes assert.deepEqual(component.test, 'b'); // should it be undefined? valid arguments for both outcomes
input1.checked = true; input1.checked = true;
await input1.dispatchEvent(changeEvent); await input1.dispatchEvent(change_event);
assert.deepEqual(component.test, 'a'); assert.deepEqual(component.test, 'a');
} }
}; };

@ -1,25 +1,25 @@
export default { export default {
test({ assert, target, window, component }) { test({ assert, target, window, component }) {
const input = target.querySelector('input'); const input = target.querySelector('input');
const inputEvent = new window.InputEvent('input'); const input_event = new window.InputEvent('input');
assert.equal(component.value, 5); assert.equal(component.value, 5);
assert.equal(input.value, '5'); assert.equal(input.value, '5');
input.value = '5.'; input.value = '5.';
input.dispatchEvent(inputEvent); input.dispatchEvent(input_event);
// input type number has value === "" if ends with dot/comma // input type number has value === "" if ends with dot/comma
assert.equal(component.value, undefined); assert.equal(component.value, undefined);
assert.equal(input.value, ''); assert.equal(input.value, '');
input.value = '5.5'; input.value = '5.5';
input.dispatchEvent(inputEvent); input.dispatchEvent(input_event);
assert.equal(component.value, 5.5); assert.equal(component.value, 5.5);
assert.equal(input.value, '5.5'); assert.equal(input.value, '5.5');
input.value = '5.50'; input.value = '5.50';
input.dispatchEvent(inputEvent); input.dispatchEvent(input_event);
assert.equal(component.value, 5.5); assert.equal(component.value, 5.5);
assert.equal(input.value, '5.50'); assert.equal(input.value, '5.50');

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

Loading…
Cancel
Save