Merge remote-tracking branch 'upstream/master' into hmr-capture-state

pull/3822/head
rixo 6 years ago
commit 16398f9581

@ -35,13 +35,9 @@ module.exports = {
argsIgnorePattern: '^_' argsIgnorePattern: '^_'
} }
], ],
'@typescript-eslint/no-object-literal-type-assertion': [ '@typescript-eslint/no-object-literal-type-assertion': 'off',
'error', '@typescript-eslint/no-unused-vars': 'off',
{ '@typescript-eslint/prefer-interface': 'off'
allowAsParameter: true
}
],
'@typescript-eslint/no-unused-vars': 'off'
}, },
globals: { globals: {
globalThis: false globalThis: false

1
.gitignore vendored

@ -16,6 +16,7 @@ node_modules
/scratch/ /scratch/
/coverage/ /coverage/
/coverage.lcov /coverage.lcov
/test/*/samples/_
/test/sourcemaps/samples/*/output.js /test/sourcemaps/samples/*/output.js
/test/sourcemaps/samples/*/output.js.map /test/sourcemaps/samples/*/output.js.map
/test/sourcemaps/samples/*/output.css /test/sourcemaps/samples/*/output.css

@ -1,6 +1,40 @@
# Svelte changelog # Svelte changelog
## Unreleased ## 3.16.0
* Use bitmasks to track changes ([#3945](https://github.com/sveltejs/svelte/pull/3945))
* Fix heisenbug with component styles ([#3977](https://github.com/sveltejs/svelte/issues/3977))
* Do not warn about missing expected props for `export function foo() {}` ([#3954](https://github.com/sveltejs/svelte/issues/3954))
* Fix `context="module"` exports with the same name as an instance variable ([#3983](https://github.com/sveltejs/svelte/issues/3983))
* Fix binding to contextual values from `{#each}` blocks referring to global variables ([#3992](https://github.com/sveltejs/svelte/issues/3992))
* Use `requestAnimationFrame` callback argument for smoother transitions ([#4014](https://github.com/sveltejs/svelte/pull/4014))
* Fix `listen_dev` argument order ([#4016](https://github.com/sveltejs/svelte/pull/4016))
## 3.15.0
* Hide commented sections from preprocessors ([#3894](https://github.com/sveltejs/svelte/pull/3894))
* Add `seeking` and `ended` bindings to media elements ([#3650](https://github.com/sveltejs/svelte/pull/3650))
* Add `videoWidth` and `videoHeight` bindings to video elements ([#3927](https://github.com/sveltejs/svelte/pull/3927))
* Fix for dynamic event handlers ([#3934](https://github.com/sveltejs/svelte/pull/3934))
* Handle scale transforms when using the `flip` animation ([#3555](https://github.com/sveltejs/svelte/issues/3555))
* Fix some code generation bugs ([#3929](https://github.com/sveltejs/svelte/issues/3929), [#3939](https://github.com/sveltejs/svelte/issues/3939))
* Add `aria-hidden="true"` to objects generated when adding resize-listeners, to improve accessibility ([#3948](https://github.com/sveltejs/svelte/issues/3948))
## 3.14.1
* Deconflict block method names with other variables ([#3900](https://github.com/sveltejs/svelte/issues/3900))
* Fix entity encoding issue in text nodes with constant expressions ([#3911](https://github.com/sveltejs/svelte/issues/3911))
* Make code for unknown prop warnings compatible with older js engines ([#3914](https://github.com/sveltejs/svelte/issues/3914))
## 3.14.0
* Add `loopGuardTimeout` option that augments `for`/`while` loops to prevent infinite loops, primarily for use in the REPL ([#3887](https://github.com/sveltejs/svelte/pull/3887))
* Keep component bindings in sync when changed in reactive statements ([#3382](https://github.com/sveltejs/svelte/issues/3382))
* Update attributes before bindings ([#3857](https://github.com/sveltejs/svelte/issues/3857))
* Prevent variable naming conflict ([#3899](https://github.com/sveltejs/svelte/issues/3899))
## 3.13.0
* New structured code generation, which eliminates a number of edge cases and obscure bugs ([#3539](https://github.com/sveltejs/svelte/pull/3539)) * New structured code generation, which eliminates a number of edge cases and obscure bugs ([#3539](https://github.com/sveltejs/svelte/pull/3539))
@ -25,6 +59,23 @@ Also:
* Throw exception immediately when calling `createEventDispatcher()` after component instantiation ([#3667](https://github.com/sveltejs/svelte/pull/3667)) * Throw exception immediately when calling `createEventDispatcher()` after component instantiation ([#3667](https://github.com/sveltejs/svelte/pull/3667))
* Fix globals shadowing contextual template scope ([#3674](https://github.com/sveltejs/svelte/issues/3674)) * Fix globals shadowing contextual template scope ([#3674](https://github.com/sveltejs/svelte/issues/3674))
* Fix `<svelte:window>` bindings to stores ([#3832](https://github.com/sveltejs/svelte/issues/3832)) * Fix `<svelte:window>` bindings to stores ([#3832](https://github.com/sveltejs/svelte/issues/3832))
* Deconflict generated var names with builtins ([#3724](https://github.com/sveltejs/svelte/issues/3724))
* Allow spring/tweened values to be initially undefined ([#3761](https://github.com/sveltejs/svelte/issues/3761))
* Warn if using `<svelte:options tag="...">` without `customElement: true` option ([#3782](https://github.com/sveltejs/svelte/pull/3782))
* Add `Event` to list of known globals ([#3810](https://github.com/sveltejs/svelte/pull/3810))
* Throw helpful error on empty CSS declaration ([#3801](https://github.com/sveltejs/svelte/issues/3801))
* Support `easing` param on `fade` transition ([#3823](https://github.com/sveltejs/svelte/pull/3823))
* Generate valid names from filenames with unicode characters ([#3845](https://github.com/sveltejs/svelte/issues/3845))
* Don't generate any code for markup-less components ([#2200](https://github.com/sveltejs/svelte/issues/2200))
* Deconflict with internal name `block` ([#3854](https://github.com/sveltejs/svelte/issues/3854))
* Set attributes before bindings, to prevent erroneous assignments to `input.files` ([#3828](https://github.com/sveltejs/svelte/issues/3828))
* Smarter unused CSS detection ([#3825](https://github.com/sveltejs/svelte/pull/3825))
* Allow dynamic event handlers ([#3040](https://github.com/sveltejs/svelte/issues/3040))
* Prevent erroneous `"undefined"` class name ([#3876](https://github.com/sveltejs/svelte/pull/3876))
* Prevent resetting of `src` attribute unless changed ([#3579](https://github.com/sveltejs/svelte/pull/3579))
* Prevent hydration of void element 'children' ([#3882](https://github.com/sveltejs/svelte/issues/3882))
* Hoist globals even if mentioned in `<script>` block ([#3745](https://github.com/sveltejs/svelte/pull/3745))
## 3.12.1 ## 3.12.1

@ -2,7 +2,7 @@
Svelte is a new way to build web applications. It's a compiler that takes your declarative components and converts them into efficient JavaScript that surgically updates the DOM. Svelte is a new way to build web applications. It's a compiler that takes your declarative components and converts them into efficient JavaScript that surgically updates the DOM.
The [Open Source Guides](https://opensource.guide/) website has a collection of resources for individuals, communities, and companies who want to learn how to run and contribute to an open source project. Contributors and people new to open source alike will find the following guides especially useful: The [Open Source Guides](https://opensource.guide/) website has a collection of resources for individuals, communities, and companies. These resources help people who want to learn how to run and contribute to open source projects. Contributors and people new to open source alike will find the following guides especially useful:
* [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/) * [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
* [Building Welcoming Communities](https://opensource.guide/building-community/) * [Building Welcoming Communities](https://opensource.guide/building-community/)
@ -30,7 +30,7 @@ One great way you can contribute to the project without writing any code is to h
## Bugs ## Bugs
We use [GitHub issues](https://github.com/sveltejs/svelte/issues) for our public bugs. If you would like to report a problem, take a look around and see if someone already opened an issue about it. If you a are certain this is a new, unreported bug, you can submit a [bug report](#reporting-new-issues). We use [GitHub issues](https://github.com/sveltejs/svelte/issues) for our public bugs. If you would like to report a problem, take a look around and see if someone already opened an issue about it. If you are certain this is a new unreported bug, you can submit a [bug report](#reporting-new-issues).
If you have questions about using Svelte, contact us on Discord at [svelte.dev/chat](https://svelte.dev/chat), and we will do our best to answer your questions. If you have questions about using Svelte, contact us on Discord at [svelte.dev/chat](https://svelte.dev/chat), and we will do our best to answer your questions.
@ -64,7 +64,7 @@ Working on your first Pull Request? You can learn how from this free video serie
If you would like to request a new feature or enhancement but are not yet thinking about opening a pull request, you can also file an issue with [feature template](https://github.com/sveltejs/svelte/issues/new?template=feature_request.md). If you would like to request a new feature or enhancement but are not yet thinking about opening a pull request, you can also file an issue with [feature template](https://github.com/sveltejs/svelte/issues/new?template=feature_request.md).
If you're only fixing a bug, it's fine to submit a pull request right away but we still recommend to file an issue detailing what you're fixing. This is helpful in case we don't accept that specific fix but want to keep track of the issue. If you're only fixing a bug, it's fine to submit a pull request right away but we still recommend that you file an issue detailing what you're fixing. This is helpful in case we don't accept that specific fix but want to keep track of the issue.
### Sending a pull request ### Sending a pull request
@ -96,6 +96,17 @@ Test samples are kept in `/test/xxx/samples` folder.
1. To run test, run `npm run test` 1. To run test, run `npm run test`
1. To run test for a specific feature, you can use the `-g` (aka `--grep`) option. For example, to only run test involving transitions, run `npm run test -- -g transition`. 1. To run test for a specific feature, you can use the `-g` (aka `--grep`) option. For example, to only run test involving transitions, run `npm run test -- -g transition`.
##### Running solo test
1. To run only one test, rename the test sample folder to end with `.solo`. For example, to run the `test/js/samples/action` only, rename it to `test/js/samples/action.solo`.
1. To run only one test suite, rename the test suite folder to end with `.solo`. For example, to run the `test/js` test suite only, rename it to `test/js.solo`.
1. Remember to rename the test folder back. The CI will fail if there's a solo test.
##### Updating `.expected` files
1. Tests suites like `css`, `js`, `server-side-rendering` asserts that the generated output has to match the content in the `.expected` file. For example, in the `js` test suites, the generated js code is compared against the content in `expected.js`.
1. To update the content of the `.expected` file, run the test with `--update` flag. (`npm run test --update`)
#### Breaking changes #### Breaking changes
When adding a new breaking change, follow this template in your pull request: When adding a new breaking change, follow this template in your pull request:

42
package-lock.json generated

@ -1,6 +1,6 @@
{ {
"name": "svelte", "name": "svelte",
"version": "3.13.0-alpha.2", "version": "3.15.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -30,6 +30,16 @@
"integrity": "sha512-KioOCsSvSvXx6xUNLiJz+P+VMb7NRcePjoefOr74Y5P6lEKsiOn35eZyZzgpK4XCNJdXTDR7+zykj0lwxRvZ2g==", "integrity": "sha512-KioOCsSvSvXx6xUNLiJz+P+VMb7NRcePjoefOr74Y5P6lEKsiOn35eZyZzgpK4XCNJdXTDR7+zykj0lwxRvZ2g==",
"dev": true "dev": true
}, },
"@rollup/plugin-replace": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.2.1.tgz",
"integrity": "sha512-dgq5ijT8fK18KTb1inenZ61ivTayV7pvbz2+ivT+VN20BOgJVM1fqoBETqGHKgFVm/J9BhR82mQyAtxfpPv1lQ==",
"dev": true,
"requires": {
"magic-string": "^0.25.2",
"rollup-pluginutils": "^2.6.0"
}
},
"@types/eslint-visitor-keys": { "@types/eslint-visitor-keys": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz",
@ -490,14 +500,14 @@
"dev": true "dev": true
}, },
"code-red": { "code-red": {
"version": "0.0.18", "version": "0.0.26",
"resolved": "https://registry.npmjs.org/code-red/-/code-red-0.0.18.tgz", "resolved": "https://registry.npmjs.org/code-red/-/code-red-0.0.26.tgz",
"integrity": "sha512-g7W6RwRqBbQTtMaUqrNWDyyl2GK0Uulk/uZPzGdgTXpOGX/LA8bW67EKQLdQgpYfd6APhZVwoX2lrL7mnJOWkA==", "integrity": "sha512-W4t68vk3xJjmkbuAKfEtaj7E+K82BtV+A4VjBlxHA6gDoSLc+sTB643JdJMSk27vpp5iEqHFuGnHieQGy/GmUQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"acorn": "^7.1.0", "acorn": "^7.1.0",
"is-reference": "^1.1.4", "is-reference": "^1.1.4",
"periscopic": "^1.0.2", "periscopic": "^2.0.1",
"sourcemap-codec": "^1.4.6" "sourcemap-codec": "^1.4.6"
} }
}, },
@ -1180,9 +1190,9 @@
"dev": true "dev": true
}, },
"estree-walker": { "estree-walker": {
"version": "0.8.1", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.8.1.tgz", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.0.tgz",
"integrity": "sha512-H6cJORkqvrNziu0KX2hqOMAlA2CiuAxHeGJXSIoKA/KLv229Dw806J3II6mKTm5xiDX1At1EXCfsOQPB+tMB+g==", "integrity": "sha512-vY6xMN2j47HfQfVWGRqHshr1olf+XS1Y488NoqjDP1c8E1TcoZr/D3eSGa6akBs76WL1X9nluWjgBsCKdt/qKg==",
"dev": true "dev": true
}, },
"esutils": { "esutils": {
@ -2713,9 +2723,9 @@
"dev": true "dev": true
}, },
"periscopic": { "periscopic": {
"version": "1.0.2", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/periscopic/-/periscopic-1.0.2.tgz", "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-2.0.1.tgz",
"integrity": "sha512-KpKBKadLf8THXOxswQBhOY8E1lVVhfUidacPtQBrq7KDXaNkQLUPiTmXagzqpJGECP3/0gDXYFO6CZHVbGvOSw==", "integrity": "sha512-twJ8e4RatllMAcbmBqKj8cvZ94HtqSzbb8hJoGj4iSCcCHXxKb06HRxOq4heyq2x/6mKynJDvTTreHCz+m6lJw==",
"dev": true, "dev": true,
"requires": { "requires": {
"is-reference": "^1.1.4" "is-reference": "^1.1.4"
@ -3109,16 +3119,6 @@
"rollup-pluginutils": "^2.8.1" "rollup-pluginutils": "^2.8.1"
} }
}, },
"rollup-plugin-replace": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/rollup-plugin-replace/-/rollup-plugin-replace-2.2.0.tgz",
"integrity": "sha512-/5bxtUPkDHyBJAKketb4NfaeZjL5yLZdeUihSfbF2PQMz+rSTEb8ARKoOl3UBT4m7/X+QOXJo3sLTcq+yMMYTA==",
"dev": true,
"requires": {
"magic-string": "^0.25.2",
"rollup-pluginutils": "^2.6.0"
}
},
"rollup-plugin-sucrase": { "rollup-plugin-sucrase": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/rollup-plugin-sucrase/-/rollup-plugin-sucrase-2.1.0.tgz", "resolved": "https://registry.npmjs.org/rollup-plugin-sucrase/-/rollup-plugin-sucrase-2.1.0.tgz",

@ -1,6 +1,6 @@
{ {
"name": "svelte", "name": "svelte",
"version": "3.13.0-alpha.2", "version": "3.16.0",
"description": "Cybernetically enhanced web apps", "description": "Cybernetically enhanced web apps",
"module": "index.mjs", "module": "index.mjs",
"main": "index", "main": "index",
@ -56,6 +56,7 @@
}, },
"homepage": "https://github.com/sveltejs/svelte#README", "homepage": "https://github.com/sveltejs/svelte#README",
"devDependencies": { "devDependencies": {
"@rollup/plugin-replace": "^2.2.1",
"@types/mocha": "^5.2.7", "@types/mocha": "^5.2.7",
"@types/node": "^8.10.53", "@types/node": "^8.10.53",
"@typescript-eslint/eslint-plugin": "^1.13.0", "@typescript-eslint/eslint-plugin": "^1.13.0",
@ -63,25 +64,25 @@
"acorn": "^7.1.0", "acorn": "^7.1.0",
"agadoo": "^1.1.0", "agadoo": "^1.1.0",
"c8": "^5.0.1", "c8": "^5.0.1",
"code-red": "0.0.18", "code-red": "0.0.26",
"codecov": "^3.5.0", "codecov": "^3.5.0",
"css-tree": "1.0.0-alpha22", "css-tree": "1.0.0-alpha22",
"eslint": "^6.3.0", "eslint": "^6.3.0",
"eslint-plugin-import": "^2.18.2", "eslint-plugin-import": "^2.18.2",
"eslint-plugin-svelte3": "^2.7.3", "eslint-plugin-svelte3": "^2.7.3",
"estree-walker": "^0.8.1", "estree-walker": "^1.0.0",
"is-reference": "^1.1.4", "is-reference": "^1.1.4",
"jsdom": "^15.1.1", "jsdom": "^15.1.1",
"kleur": "^3.0.3", "kleur": "^3.0.3",
"locate-character": "^2.0.5", "locate-character": "^2.0.5",
"magic-string": "^0.25.3", "magic-string": "^0.25.3",
"mocha": "^6.2.0", "mocha": "^6.2.0",
"periscopic": "^2.0.1",
"puppeteer": "^1.19.0", "puppeteer": "^1.19.0",
"rollup": "^1.21.4", "rollup": "^1.21.4",
"rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-json": "^4.0.0", "rollup-plugin-json": "^4.0.0",
"rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-replace": "^2.2.0",
"rollup-plugin-sucrase": "^2.1.0", "rollup-plugin-sucrase": "^2.1.0",
"rollup-plugin-typescript": "^1.0.1", "rollup-plugin-typescript": "^1.0.1",
"rollup-plugin-virtual": "^1.0.1", "rollup-plugin-virtual": "^1.0.1",

@ -1,5 +1,5 @@
import fs from 'fs'; import fs from 'fs';
import replace from 'rollup-plugin-replace'; import replace from '@rollup/plugin-replace';
import resolve from 'rollup-plugin-node-resolve'; import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs'; import commonjs from 'rollup-plugin-commonjs';
import json from 'rollup-plugin-json'; import json from 'rollup-plugin-json';

@ -6,7 +6,9 @@ authorURL: https://twitter.com/Rich_Harris
draft: true draft: true
--- ---
*Coming soon* This post will walk you through setting up your editor so that recognises Svelte files: *__Coming soon__*
This post will walk you through setting up your editor so that recognises Svelte files:
* eslint-plugin-svelte3 * eslint-plugin-svelte3
* svelte-vscode * svelte-vscode
@ -14,7 +16,7 @@ draft: true
## Atom ## Atom
To treat `*.svelte` files as HTML, open Edit → Config... and add the following lines to your `core` section: To treat `*.svelte` files as HTML, open *__Edit → Config...__* and add the following lines to your `core` section:
```cson ```cson
"*": "*":
@ -45,3 +47,23 @@ To set the filetype for a single file, use a [modeline](https://vim.fandom.com/w
``` ```
<!-- vim: set ft=html :--> <!-- vim: set ft=html :-->
``` ```
## Visual Studio Code
To treat `*.svelte` files as HTML, add the following lines to your `settings.json` file:
```cson
"files.associations": {
"*.svelte": "html"
}
```
## JetBrains WebStorm
To treat `*.svelte` files as HTML in WebStorm, you need to create a new file type association. Please refer to the [JetBrains website](https://www.jetbrains.com/help/webstorm/creating-and-registering-file-types.html) to see how.
## Sublime Text 3
Open any `.svelte` file.
Go to *__View → Syntax → Open all with current extension as... → HTML__*.

@ -3,7 +3,109 @@ title: Svelte for new developers
description: Never used Node.js or the command line? No problem description: Never used Node.js or the command line? No problem
author: Rich Harris author: Rich Harris
authorURL: https://twitter.com/Rich_Harris authorURL: https://twitter.com/Rich_Harris
draft: true
--- ---
*Coming soon* This blog post will walk you through installing Node.js and git and using Terminal.app to clone a project template and start developing with Svelte This short guide is designed to help you — someone who has looked at the [tutorial](/tutorial) and wants to start creating Svelte apps, but doesn't have a ton of experience using JavaScript build tooling — get up and running.
If there are things that don't make sense, or that we're glossing over, feel free to [raise an issue](https://github.com/sveltejs/svelte/issues) or [suggest edits to this page](https://github.com/sveltejs/svelte/blob/master/site/content/blog/2019-04-16-svelte-for-new-developers.md) that will help us help more people.
If you get stuck at any point following this guide, the best place to ask for help is in the [chatroom](https://svelte.dev/chat).
## First things first
You'll be using the *command line*, also known as the terminal. On Windows, you can access it by running **Command Prompt** from the Start menu; on a Mac, hit `Cmd` and `Space` together to bring up **Spotlight**, then start typing `Terminal.app`. On most Linux systems, `Ctrl-Alt-T` brings up the command line.
The command line is a way to interact with your computer (or another computer! but that's a topic for another time) with more power and control than the GUI (graphical user interface) that most people use day-to-day.
Once on the command line, you can navigate the filesystem using `ls` to list the contents of your current directory, and `cd` to change the current directory. For example, if you had a `Development` directory of your projects inside your home directory, you would type
```bash
cd Development
```
to go to it. From there, you could create a new project directory with the `mkdir` command:
```bash
mkdir svelte-projects
cd svelte-projects
```
A full introduction to the command line is out of the scope of this guide, but here are a few more useful commands:
* `cd ..` — navigates to the parent of the current directory
* `cat my-file.txt` — on Mac/Linux, lists the contents of `my-file.txt`
* `open .` (or `start .` on Windows) — opens the current directory in Finder or File Explorer
## Installing Node.js
[Node](https://nodejs.org/en/) is a way to run JavaScript on the command line. It's used by many tools, including Svelte. If you don't yet have it installed, the easiest way is to download the latest version straight from the [website](https://nodejs.org/en/).
Once installed, you'll have access to three new commands:
* `node my-file.js` — runs the JavaScript in `my-file.js`
* `npm [subcommand]` — [npm](https://www.npmjs.com/) is a way to install 'packages' that your application depends on, such as the [svelte](https://www.npmjs.com/) package
* `npx [subcommand]` — a convenient way to run programs available on npm without permanently installing them
## Installing a text editor
To write code, you need a good editor. The most popular choice is [Visual Studio Code](https://code.visualstudio.com/) or VSCode, and justifiably so — it's well-designed and fully-featured, and has a wealth of extensions ([including one for Svelte](https://marketplace.visualstudio.com/items?itemName=JamesBirtles.svelte-vscode), which provides syntax highlighting and diagnostic messages when you're writing components).
## Creating a project
We're going to follow the instructions in part two of [The easiest way to get started with Svelte](/blog/the-easiest-way-to-get-started).
First, we'll use npx to run [degit](https://github.com/Rich-Harris/degit), a program for cloning project templates from [GitHub](https://github.com) and other code storage websites. You don't have to use a project template, but it means you have to do a lot less setup work.
(Eventually you'll probably have to learn [git](https://git-scm.com/), which most programmers use to manage their projects. But you don't need to worry about it just yet.)
On the command line, navigate to where you want to create a new project, then type the following lines (you can paste the whole lot, but you'll develop better muscle memory if you get into the habit of writing each line out one at a time then running it):
```bash
npx degit sveltejs/template my-svelte-project
cd my-svelte-project
npm install
```
This creates a new directory, `my-svelte-project`, adds files from the [sveltejs/template](https://github.com/sveltejs/template) code repository, and installs a number of packages from npm. Open the directory in your text editor and take a look around. The app's 'source code' lives in the `src` directory, while the files your app can load are in `public`.
In the `package.json` file, there is a section called `"scripts"`. These scripts define shortcuts for working with your application — `dev`, `build` and `start`. To launch your app in development mode, type the following:
> TODO update the template, it needs... some work
```bash
npm run dev
```
Running the `dev` script starts a program called [Rollup](https://rollupjs.org/guide/en/). Rollup's job is to take your application's source files (so far, just `src/main.js` and `src/App.svelte`), pass them to other programs (including Svelte, in our case) and convert them into the code that will actually run when you open the application in a browser.
Speaking of which, open a browser and navigate to http://localhost:5000. This is your application running on a local *web server* (hence 'localhost') on port 5000.
Try changing `src/App.svelte` and saving it. The application will reload with your changes.
## Building your app
In the last step, we were running the app in 'development mode'. In dev mode, Svelte adds extra code that helps with debugging, and Rollup skips the final step where your app's JavaScript is compressed using [Terser](https://terser.org/).
When you share your app with the world, you want to build it in 'production mode', so that it's as small and efficient as possible for end users. To do that, use the `build` command:
```bash
npm run build
```
Your `public` directory now contains a compressed `bundle.js` file containing your app's JavaScript. You can run it like so:
```bash
npm run start
```
This will run the app on http://localhost:5000.
## Next steps
To share your app with the world you'll need to *deploy* it. There are many ways to do so — some are listed in the `README.md` file inside your project.

@ -563,12 +563,14 @@ Elements with the `contenteditable` attribute support `innerHTML` and `textConte
--- ---
Media elements (`<audio>` and `<video>`) have their own set of bindings — four *readonly* ones... Media elements (`<audio>` and `<video>`) have their own set of bindings — six *readonly* ones...
* `duration` (readonly) — the total duration of the video, in seconds * `duration` (readonly) — the total duration of the video, in seconds
* `buffered` (readonly) — an array of `{start, end}` objects * `buffered` (readonly) — an array of `{start, end}` objects
* `seekable` (readonly) — ditto * `seekable` (readonly) — ditto
* `played` (readonly) — ditto * `played` (readonly) — ditto
* `seeking` (readonly) — boolean
* `ended` (readonly) — boolean
...and four *two-way* bindings: ...and four *two-way* bindings:
@ -577,16 +579,22 @@ Media elements (`<audio>` and `<video>`) have their own set of bindings — four
* `paused` — this one should be self-explanatory * `paused` — this one should be self-explanatory
* `volume` — a value between 0 and 1 * `volume` — a value between 0 and 1
Videos additionally have readonly `videoWidth` and `videoHeight` bindings.
```html ```html
<video <video
src={clip} src={clip}
bind:duration bind:duration
bind:buffered bind:buffered
bind:seekable bind:seekable
bind:seeking
bind:played bind:played
bind:ended
bind:currentTime bind:currentTime
bind:paused bind:paused
bind:volume bind:volume
bind:videoWidth
bind:videoHeight
></video> ></video>
``` ```
@ -1146,7 +1154,7 @@ bind:property={variable}
--- ---
You can bind to component props using the same mechanism. You can bind to component props using the same syntax as for elements.
```html ```html
<Keypad bind:value={pin}/> <Keypad bind:value={pin}/>

@ -4,9 +4,9 @@ title: Compile time
Typically, you won't interact with the Svelte compiler directly, but will instead integrate it into your build system using a bundler plugin: Typically, you won't interact with the Svelte compiler directly, but will instead integrate it into your build system using a bundler plugin:
* [rollup-plugin-svelte](https://github.com/rollup/rollup-plugin-svelte) for users of [Rollup](https://rollupjs.org) * [rollup-plugin-svelte](https://github.com/sveltejs/rollup-plugin-svelte) for users of [Rollup](https://rollupjs.org)
* [svelte-loader](https://github.com/sveltejs/svelte-loader) for users of [webpack](https://webpack.js.org) * [svelte-loader](https://github.com/sveltejs/svelte-loader) for users of [webpack](https://webpack.js.org)
* [parcel-plugin-svelte](https://github.com/DeMoorJasper/parcel-plugin-svelte) for users of [Parcel](https://parceljs.org/) * or one of the [community-maintained plugins](https://github.com/sveltejs/integrations#bundler-plugins)
Nonetheless, it's useful to understand how to use the compiler, since bundler plugins generally expose compiler options to you. Nonetheless, it's useful to understand how to use the compiler, since bundler plugins generally expose compiler options to you.
@ -53,6 +53,7 @@ The following options can be passed to the compiler. None are required:
| `tag` | string | null | `tag` | string | null
| `accessors` | boolean | `false` | `accessors` | boolean | `false`
| `css` | boolean | `true` | `css` | boolean | `true`
| `loopGuardTimeout` | number | 0
| `preserveComments` | boolean | `false` | `preserveComments` | boolean | `false`
| `preserveWhitespace` | boolean | `false` | `preserveWhitespace` | boolean | `false`
| `outputFilename` | string | `null` | `outputFilename` | string | `null`
@ -73,6 +74,7 @@ The following options can be passed to the compiler. None are required:
| `customElement` | `false` | If `true`, tells the compiler to generate a custom element constructor instead of a regular Svelte component. | `customElement` | `false` | If `true`, tells the compiler to generate a custom element constructor instead of a regular Svelte component.
| `tag` | `null` | A `string` that tells Svelte what tag name to register the custom element with. It must be a lowercase alphanumeric string with at least one hyphen, e.g. `"my-element"`. | `tag` | `null` | A `string` that tells Svelte what tag name to register the custom element with. It must be a lowercase alphanumeric string with at least one hyphen, e.g. `"my-element"`.
| `css` | `true` | If `true`, styles will be included in the JavaScript class and injected at runtime. It's recommended that you set this to `false` and use the CSS that is statically generated, as it will result in smaller JavaScript bundles and better performance. | `css` | `true` | If `true`, styles will be included in the JavaScript class and injected at runtime. It's recommended that you set this to `false` and use the CSS that is statically generated, as it will result in smaller JavaScript bundles and better performance.
| `loopGuardTimeout` | 0 | A `number` that tells Svelte to break the loop if it blocks the thread for more than `loopGuardTimeout` ms. This is useful to prevent infinite loops. **Only available when `dev: true`**
| `preserveComments` | `false` | If `true`, your HTML comments will be preserved during server-side rendering. By default, they are stripped out. | `preserveComments` | `false` | If `true`, your HTML comments will be preserved during server-side rendering. By default, they are stripped out.
| `preserveWhitespace` | `false` | If `true`, whitespace inside and between elements is kept as you typed it, rather than optimised by Svelte. | `preserveWhitespace` | `false` | If `true`, whitespace inside and between elements is kept as you typed it, rather than optimised by Svelte.
| `outputFilename` | `null` | A `string` used for your JavaScript sourcemap. | `outputFilename` | `null` | A `string` used for your JavaScript sourcemap.
@ -235,7 +237,7 @@ const { code } = svelte.preprocess(source, {
The `script` and `style` functions receive the contents of `<script>` and `<style>` elements respectively. In addition to `filename`, they get an object of the element's attributes. The `script` and `style` functions receive the contents of `<script>` and `<style>` elements respectively. In addition to `filename`, they get an object of the element's attributes.
If a `dependencies` array is returned, it will be included in the result object. This is used by packages like [rollup-plugin-svelte](https://github.com/rollup/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 [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).
```js ```js
const svelte = require('svelte/compiler'); const svelte = require('svelte/compiler');

@ -1,7 +1,7 @@
<style> <style>
p { p {
color: purple; color: purple;
font-family: 'Comic Sans MS'; font-family: 'Comic Sans MS', cursive;
font-size: 2em; font-size: 2em;
} }
</style> </style>

@ -5,7 +5,7 @@
<style> <style>
p { p {
color: purple; color: purple;
font-family: 'Comic Sans MS'; font-family: 'Comic Sans MS', cursive;
font-size: 2em; font-size: 2em;
} }
</style> </style>

@ -1,6 +1,6 @@
<style> <style>
button { button {
font-family: 'Comic Sans MS'; font-family: 'Comic Sans MS', cursive;
font-size: 2em; font-size: 2em;
padding: 0.5em 1em; padding: 0.5em 1em;
color: royalblue; color: royalblue;

@ -43,8 +43,8 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: #666; background-color: #666;
-webkit-mask: url(logo-mask.svg) 50% 50% no-repeat; -webkit-mask: url(svelte-logo-mask.svg) 50% 50% no-repeat;
mask: url(logo-mask.svg) 50% 50% no-repeat; mask: url(svelte-logo-mask.svg) 50% 50% no-repeat;
} }
</style> </style>

@ -33,7 +33,7 @@
font-family: 'Overpass'; font-family: 'Overpass';
letter-spacing: 0.12em; letter-spacing: 0.12em;
color: #676778; color: #676778;
font-weight: 100; font-weight: 400;
} }
.centered span { .centered span {
@ -71,4 +71,4 @@
toggle me toggle me
</label> </label>
<link href="https://fonts.googleapis.com/css?family=Overpass:100" rel="stylesheet"> <link href="https://fonts.googleapis.com/css?family=Overpass:100,400" rel="stylesheet">

@ -1,70 +1,20 @@
<script> <script>
let language = "english"; import { longpress } from './longpress.js';
const translations = { let pressed = false;
english: { let duration = 2000;
tooltip: "Switch Languages", </script>
},
latin: {
tooltip: "Itchsway Anguageslay",
}
};
function tooltip(node, text) {
const tooltip = document.createElement('div');
tooltip.textContent = text;
Object.assign(tooltip.style, {
position: 'absolute',
background: 'black',
color: 'white',
padding: '0.5em 1em',
fontSize: '12px',
pointerEvents: 'none',
transform: 'translate(5px, -50%)',
borderRadius: '2px',
transition: 'opacity 0.4s'
});
function position() {
const { top, right, bottom } = node.getBoundingClientRect();
tooltip.style.top = `${(top + bottom) / 2}px`;
tooltip.style.left = `${right}px`;
}
function append() {
document.body.appendChild(tooltip);
tooltip.style.opacity = 0;
setTimeout(() => tooltip.style.opacity = 1);
position();
}
function remove() {
tooltip.remove();
}
node.addEventListener('mouseenter', append);
node.addEventListener('mouseleave', remove);
return {
update(text) {
tooltip.textContent = text;
position();
},
destroy() { <label>
tooltip.remove(); <input type=range bind:value={duration} max={2000} step={100}>
node.removeEventListener('mouseenter', append); {duration}ms
node.removeEventListener('mouseleave', remove); </label>
}
};
}
function toggleLanguage() { <button use:longpress={duration}
language = language === 'english' ? 'latin' : 'english' on:longpress="{() => pressed = true}"
} on:mouseenter="{() => pressed = false}"
</script> >press and hold</button>
<button on:click={toggleLanguage} use:tooltip={translations[language].tooltip}> {#if pressed}
{language} <p>congratulations, you pressed and held for {duration}ms</p>
</button> {/if}

@ -0,0 +1,28 @@
export function longpress(node, duration) {
let timer;
const handleMousedown = () => {
timer = setTimeout(() => {
node.dispatchEvent(
new CustomEvent('longpress')
);
}, duration);
};
const handleMouseup = () => {
clearTimeout(timer)
};
node.addEventListener('mousedown', handleMousedown);
node.addEventListener('mouseup', handleMouseup);
return {
update(newDuration) {
duration = newDuration;
},
destroy() {
node.removeEventListener('mousedown', handleMousedown);
node.removeEventListener('mouseup', handleMouseup);
}
};
}

@ -2,18 +2,9 @@
<script> <script>
let people = [ let people = [
{ { first: 'Hans', last: 'Emil' },
first: 'Hans', { first: 'Max', last: 'Mustermann' },
last: 'Emil' { first: 'Roman', last: 'Tisch' }
},
{
first: 'Max',
last: 'Mustermann'
},
{
first: 'Roman',
last: 'Tisch'
}
]; ];
let prefix = ''; let prefix = '';
@ -39,7 +30,9 @@
} }
function update() { function update() {
people[i] = { first, last }; selected.first = first;
selected.last = last;
people = people;
} }
function remove() { function remove() {

@ -11,7 +11,7 @@ export const title = css`
`; `;
export const comicSans = css` export const comicSans = css`
font-family: 'Comic Sans MS'; font-family: 'Comic Sans MS', cursive;
`; `;
export const box = css` export const box = css`

@ -1,7 +1,7 @@
<style> <style>
p { p {
color: purple; color: purple;
font-family: 'Comic Sans MS'; font-family: 'Comic Sans MS', cursive;
font-size: 2em; font-size: 2em;
} }
</style> </style>

@ -8,7 +8,7 @@ Just like in HTML, you can add a `<style>` tag to your component. Let's add some
<style> <style>
p { p {
color: purple; color: purple;
font-family: 'Comic Sans MS'; font-family: 'Comic Sans MS', cursive;
font-size: 2em; font-size: 2em;
} }
</style> </style>

@ -1,7 +1,7 @@
<style> <style>
p { p {
color: purple; color: purple;
font-family: 'Comic Sans MS'; font-family: 'Comic Sans MS', cursive;
font-size: 2em; font-size: 2em;
} }
</style> </style>

@ -5,7 +5,7 @@
<style> <style>
p { p {
color: purple; color: purple;
font-family: 'Comic Sans MS'; font-family: 'Comic Sans MS', cursive;
font-size: 2em; font-size: 2em;
} }
</style> </style>

@ -4,11 +4,12 @@ title: Making an app
This tutorial is designed to get you familiar with the process of writing components. But at some point, you'll want to start writing components in the comfort of your own text editor. This tutorial is designed to get you familiar with the process of writing components. But at some point, you'll want to start writing components in the comfort of your own text editor.
First, you'll need to integrate Svelte with a build tool. Popular choices are: First, you'll need to integrate Svelte with a build tool. There are officially maintained plugins for [Rollup](https://rollupjs.org) and [webpack](https://webpack.js.org/)...
* [Rollup](https://rollupjs.org) / [rollup-plugin-svelte](https://github.com/rollup/rollup-plugin-svelte) * [rollup-plugin-svelte](https://github.com/sveltejs/rollup-plugin-svelte)
* [webpack](https://webpack.js.org/) / [svelte-loader](https://github.com/sveltejs/svelte-loader) * [svelte-loader](https://github.com/sveltejs/svelte-loader)
* [Parcel](https://parceljs.org/) / [parcel-plugin-svelte](https://github.com/DeMoorJasper/parcel-plugin-svelte)
...and a variety of [community-maintained ones](https://github.com/sveltejs/integrations#bundler-plugins).
Don't worry if you're relatively new to web development and haven't used these tools before. We've prepared a simple step-by-step guide, [Svelte for new developers](blog/svelte-for-new-developers), which walks you through the process. Don't worry if you're relatively new to web development and haven't used these tools before. We've prepared a simple step-by-step guide, [Svelte for new developers](blog/svelte-for-new-developers), which walks you through the process.

@ -11,7 +11,7 @@ let count = 0;
$: doubled = count * 2; $: doubled = count * 2;
``` ```
> Don't worry if this looks a little alien. It's valid (if unconventional) JavaScript, which Svelte interprets to mean 're-run this code whenever any of the referenced values change'. Once you get used to it, there's no going back. > Don't worry if this looks a little alien. It's [valid](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/label) (if unconventional) JavaScript, which Svelte interprets to mean 're-run this code whenever any of the referenced values change'. Once you get used to it, there's no going back.
Let's use `doubled` in our markup: Let's use `doubled` in our markup:

@ -30,3 +30,12 @@ function addNumber() {
numbers[numbers.length] = numbers.length + 1; numbers[numbers.length] = numbers.length + 1;
} }
``` ```
A simple rule of thumb: the name of the updated variable must appear on the left hand side of the assignment. For example this...
```js
const foo = obj.foo;
foo.bar = 'baz';
```
...won't update references to `obj.foo.bar`, unless you follow it up with `obj = obj`.

@ -1,6 +1,6 @@
<style> <style>
button { button {
font-family: 'Comic Sans MS'; font-family: 'Comic Sans MS', cursive;
font-size: 2em; font-size: 2em;
padding: 0.5em 1em; padding: 0.5em 1em;
color: royalblue; color: royalblue;

@ -1,6 +1,6 @@
<style> <style>
button { button {
font-family: 'Comic Sans MS'; font-family: 'Comic Sans MS', cursive;
font-size: 2em; font-size: 2em;
padding: 0.5em 1em; padding: 0.5em 1em;
color: royalblue; color: royalblue;

@ -24,12 +24,14 @@ Now, when you click on the video, it will update `time`, `duration` and `paused`
> Ordinarily on the web, you would track `currentTime` by listening for `timeupdate` events. But these events fire too infrequently, resulting in choppy UI. Svelte does better — it checks `currentTime` using `requestAnimationFrame`. > Ordinarily on the web, you would track `currentTime` by listening for `timeupdate` events. But these events fire too infrequently, resulting in choppy UI. Svelte does better — it checks `currentTime` using `requestAnimationFrame`.
The complete set of bindings for `<audio>` and `<video>` is as follows — four *readonly* bindings... The complete set of bindings for `<audio>` and `<video>` is as follows — six *readonly* bindings...
* `duration` (readonly) — the total duration of the video, in seconds * `duration` (readonly) — the total duration of the video, in seconds
* `buffered` (readonly) — an array of `{start, end}` objects * `buffered` (readonly) — an array of `{start, end}` objects
* `seekable` (readonly) — ditto * `seekable` (readonly) — ditto
* `played` (readonly) — ditto * `played` (readonly) — ditto
* `seeking` (readonly) — boolean
* `ended` (readonly) — boolean
...and four *two-way* bindings: ...and four *two-way* bindings:
@ -37,3 +39,5 @@ The complete set of bindings for `<audio>` and `<video>` is as follows — four
* `playbackRate` — how fast to play the video, where `1` is 'normal' * `playbackRate` — how fast to play the video, where `1` is 'normal'
* `paused` — this one should be self-explanatory * `paused` — this one should be self-explanatory
* `volume` — a value between 0 and 1 * `volume` — a value between 0 and 1
Videos additionally have readonly `videoWidth` and `videoHeight` bindings.

@ -43,8 +43,8 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: #666; background-color: #666;
-webkit-mask: url(logo-mask.svg) 50% 50% no-repeat; -webkit-mask: url(svelte-logo-mask.svg) 50% 50% no-repeat;
mask: url(logo-mask.svg) 50% 50% no-repeat; mask: url(svelte-logo-mask.svg) 50% 50% no-repeat;
} }
</style> </style>

@ -43,8 +43,8 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: #666; background-color: #666;
-webkit-mask: url(logo-mask.svg) 50% 50% no-repeat; -webkit-mask: url(svelte-logo-mask.svg) 50% 50% no-repeat;
mask: url(logo-mask.svg) 50% 50% no-repeat; mask: url(svelte-logo-mask.svg) 50% 50% no-repeat;
} }
</style> </style>

@ -28,7 +28,7 @@ Clicking the buttons causes the progress bar to animate to its new value. It's a
</script> </script>
``` ```
> The `svelte/easing` module contains the [Penner easing equations](http://robertpenner.com/easing/), or you can supply your own `p => t` function where `p` and `t` are both values between 0 and 1. > The `svelte/easing` module contains the [Penner easing equations](https://web.archive.org/web/20190805215728/http://robertpenner.com/easing/), or you can supply your own `p => t` function where `p` and `t` are both values between 0 and 1.
The full set of options available to `tweened`: The full set of options available to `tweened`:

@ -1,70 +1,20 @@
<script> <script>
let language = "english"; import { longpress } from './longpress.js';
const translations = { let pressed = false;
english: { let duration = 2000;
tooltip: "Switch Languages", </script>
},
latin: {
tooltip: "Itchsway Anguageslay",
}
};
function tooltip(node, text) {
const tooltip = document.createElement('div');
tooltip.textContent = text;
Object.assign(tooltip.style, {
position: 'absolute',
background: 'black',
color: 'white',
padding: '0.5em 1em',
fontSize: '12px',
pointerEvents: 'none',
transform: 'translate(5px, -50%)',
borderRadius: '2px',
transition: 'opacity 0.4s'
});
function position() {
const { top, right, bottom } = node.getBoundingClientRect();
tooltip.style.top = `${(top + bottom) / 2}px`;
tooltip.style.left = `${right}px`;
}
function append() {
document.body.appendChild(tooltip);
tooltip.style.opacity = 0;
setTimeout(() => tooltip.style.opacity = 1);
position();
}
function remove() {
tooltip.remove();
}
node.addEventListener('mouseenter', append);
node.addEventListener('mouseleave', remove);
return {
update(text) {
tooltip.textContent = text;
position();
},
destroy() { <label>
tooltip.remove(); <input type=range bind:value={duration} max={2000} step={100}>
node.removeEventListener('mouseenter', append); {duration}ms
node.removeEventListener('mouseleave', remove); </label>
}
};
}
function toggleLanguage() { <button use:longpress
language = language === 'english' ? 'latin' : 'english' on:longpress="{() => pressed = true}"
} on:mouseenter="{() => pressed = false}"
</script> >press and hold</button>
<button on:click={toggleLanguage} use:tooltip={translations[language].tooltip}> {#if pressed}
{language} <p>congratulations, you pressed and held for {duration}ms</p>
</button> {/if}

@ -0,0 +1,25 @@
export function longpress(node, duration) {
let timer;
const handleMousedown = () => {
timer = setTimeout(() => {
node.dispatchEvent(
new CustomEvent('longpress')
);
}, 500);
};
const handleMouseup = () => {
clearTimeout(timer)
};
node.addEventListener('mousedown', handleMousedown);
node.addEventListener('mouseup', handleMouseup);
return {
destroy() {
node.removeEventListener('mousedown', handleMousedown);
node.removeEventListener('mouseup', handleMouseup);
}
};
}

@ -1,70 +1,20 @@
<script> <script>
let language = "english"; import { longpress } from './longpress.js';
const translations = { let pressed = false;
english: { let duration = 2000;
tooltip: "Switch Languages", </script>
},
latin: {
tooltip: "Itchsway Anguageslay",
}
};
function tooltip(node, text) {
const tooltip = document.createElement('div');
tooltip.textContent = text;
Object.assign(tooltip.style, {
position: 'absolute',
background: 'black',
color: 'white',
padding: '0.5em 1em',
fontSize: '12px',
pointerEvents: 'none',
transform: 'translate(5px, -50%)',
borderRadius: '2px',
transition: 'opacity 0.4s'
});
function position() {
const { top, right, bottom } = node.getBoundingClientRect();
tooltip.style.top = `${(top + bottom) / 2}px`;
tooltip.style.left = `${right}px`;
}
function append() {
document.body.appendChild(tooltip);
tooltip.style.opacity = 0;
setTimeout(() => tooltip.style.opacity = 1);
position();
}
function remove() {
tooltip.remove();
}
node.addEventListener('mouseenter', append);
node.addEventListener('mouseleave', remove);
return {
update(text) {
tooltip.textContent = text;
position();
},
destroy() { <label>
tooltip.remove(); <input type=range bind:value={duration} max={2000} step={100}>
node.removeEventListener('mouseenter', append); {duration}ms
node.removeEventListener('mouseleave', remove); </label>
}
};
}
function toggleLanguage() { <button use:longpress={duration}
language = language === 'english' ? 'latin' : 'english' on:longpress="{() => pressed = true}"
} on:mouseenter="{() => pressed = false}"
</script> >press and hold</button>
<button on:click={toggleLanguage} use:tooltip={translations[language].tooltip}> {#if pressed}
{language} <p>congratulations, you pressed and held for {duration}ms</p>
</button> {/if}

@ -0,0 +1,28 @@
export function longpress(node, duration) {
let timer;
const handleMousedown = () => {
timer = setTimeout(() => {
node.dispatchEvent(
new CustomEvent('longpress')
);
}, duration);
};
const handleMouseup = () => {
clearTimeout(timer)
};
node.addEventListener('mousedown', handleMousedown);
node.addEventListener('mouseup', handleMouseup);
return {
update(newDuration) {
duration = newDuration;
},
destroy() {
node.removeEventListener('mousedown', handleMousedown);
node.removeEventListener('mouseup', handleMouseup);
}
};
}

@ -2,4 +2,45 @@
title: Adding parameters title: Adding parameters
--- ---
TODO come up with a better example Like transitions and animations, an action can take an argument, which the action function will be called with alongside the element it belongs to.
Here, we're using a `longpress` action that fires an event with the same name whenever the user presses and holds the button for a given duration. Right now, if you switch over to the `longpress.js` file, you'll see it's hardcoded to 500ms.
We can change the action function to accept a `duration` as a second argument, and pass that `duration` to the `setTimeout` call:
```js
export function longpress(node, duration) {
// ...
const handleMousedown = () => {
timer = setTimeout(() => {
node.dispatchEvent(
new CustomEvent('longpress')
);
}, duration);
};
// ...
}
```
Back in `App.svelte`, we can pass the `duration` value to the action:
```html
<button use:longpress={duration}
```
This *almost* works — the event now only fires after 2 seconds. But if you slide the duration down, it will still take two seconds.
To change that, we can add an `update` method in `longpress.js`. This will be called whenever the argument changes:
```js
return {
update(newDuration) {
duration = newDuration;
},
// ...
};
```
> If you need to pass multiple arguments to an action, combine them into a single object, as in `use:longpress={{duration, spiciness}}`

@ -1256,19 +1256,19 @@
} }
}, },
"@polka/redirect": { "@polka/redirect": {
"version": "1.0.0-next.0", "version": "1.0.0-next.7",
"resolved": "https://registry.npmjs.org/@polka/redirect/-/redirect-1.0.0-next.0.tgz", "resolved": "https://registry.npmjs.org/@polka/redirect/-/redirect-1.0.0-next.7.tgz",
"integrity": "sha512-ym6ooqMr09+cV+y52p5kszJ0jYcX+nJfm8POrQb7QYowvpPPuneZ71EclHrQSB7a50lcytgR/xtL6AUFdvyEkg==" "integrity": "sha512-sHh1oVy9VBVhn41fOlrUdlxFkcbKrYdBcqePTSxvf2NqKu7UcMPZse/wDeQZk17A8cqDArKsR4m0MXd+3/Q83g=="
}, },
"@polka/send": { "@polka/send": {
"version": "1.0.0-next.6", "version": "1.0.0-next.7",
"resolved": "https://registry.npmjs.org/@polka/send/-/send-1.0.0-next.6.tgz", "resolved": "https://registry.npmjs.org/@polka/send/-/send-1.0.0-next.7.tgz",
"integrity": "sha512-4ON4Yf/QcP9I6HmFvn8rspRdBQ6NupSkUvAkUKo4gT2SSSWrrHMqDVQJsvDr4BRGRIVZSg+gr6W3M9Xj3V3JSQ==" "integrity": "sha512-X/7sxWMfzuHuMXSls3SuOgfwcoNakuaNWciVS/KX3RML8NIPKQYgbhpqYPCujtXK9Z/KvW3/gzr2TUpabIJRMg=="
}, },
"@polka/url": { "@polka/url": {
"version": "1.0.0-next.3", "version": "1.0.0-next.9",
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.3.tgz", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.9.tgz",
"integrity": "sha512-Uom7l6OeP6vcf85lMImelYu5WKVWjXyhkpi9WsRdRzlJFJFPVhjBtBCktgDUj7dk1N5FURUdegSZ5XOjxf8JZg==" "integrity": "sha512-VZqSaulg2kVQYMulmuZcvapPwH5/y81YHANiFIKz1GNZoG/F4o1JSeLlrvXJ8tC+RPUjxdrebfT3Qn+bnMi0bA=="
}, },
"@sindresorhus/slugify": { "@sindresorhus/slugify": {
"version": "0.9.1", "version": "0.9.1",
@ -1291,17 +1291,24 @@
} }
}, },
"@sveltejs/svelte-repl": { "@sveltejs/svelte-repl": {
"version": "0.1.9", "version": "0.1.17",
"resolved": "https://registry.npmjs.org/@sveltejs/svelte-repl/-/svelte-repl-0.1.9.tgz", "resolved": "https://registry.npmjs.org/@sveltejs/svelte-repl/-/svelte-repl-0.1.17.tgz",
"integrity": "sha512-OXDfHwT5O7UXVYnf4ndTk3dKMITTmWcMty4/lOFte80ui01i47QiVy3GEe9G8FkcU1YBe+c06MMnIgm7j0Ln7Q==", "integrity": "sha512-rM0DC+pZnqwH6PiuxXUmFRwYZ9XNkexxTNt+prR91Qs7ssxGgf0QkH6kGivSNLbrOtOvcgJbt1nUDybWra5HKA==",
"dev": true, "dev": true,
"requires": { "requires": {
"codemirror": "^5.48.4", "codemirror": "^5.49.2",
"estree-walker": "^0.6.1", "estree-walker": "^0.9.0",
"sourcemap-codec": "^1.4.6", "sourcemap-codec": "^1.4.6",
"svelte-json-tree": "0.0.5",
"yootils": "0.0.16" "yootils": "0.0.16"
}, },
"dependencies": { "dependencies": {
"estree-walker": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.9.0.tgz",
"integrity": "sha512-12U47o7XHUX329+x3FzNVjCx3SHEzMF0nkDv7r/HnBzX/xNTKxajBk6gyygaxrAFtLj39219oMfbtxv4KpaOiA==",
"dev": true
},
"sourcemap-codec": { "sourcemap-codec": {
"version": "1.4.6", "version": "1.4.6",
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.6.tgz", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.6.tgz",
@ -1587,9 +1594,9 @@
"dev": true "dev": true
}, },
"codemirror": { "codemirror": {
"version": "5.48.4", "version": "5.49.2",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.48.4.tgz", "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.49.2.tgz",
"integrity": "sha512-pUhZXDQ6qXSpWdwlgAwHEkd4imA0kf83hINmUEzJpmG80T/XLtDDEzZo8f6PQLuRCcUQhmzqqIo3ZPTRaWByRA==", "integrity": "sha512-dwJ2HRPHm8w51WB5YTF9J7m6Z5dtkqbU9ntMZ1dqXyFB9IpjoUFDj80ahRVEoVanfIp6pfASJbOlbWdEf8FOzQ==",
"dev": true "dev": true
}, },
"color-convert": { "color-convert": {
@ -2510,9 +2517,9 @@
} }
}, },
"mime": { "mime": {
"version": "2.4.3", "version": "2.4.4",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.4.3.tgz", "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz",
"integrity": "sha512-QgrPRJfE+riq5TPZMcHZOtm8c6K/yYrMbKIoRfapfiGLxS8OTeIfRhUGW5LU7MlRa52KOAGCfUNruqLrIBvWZw==" "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA=="
}, },
"mimic-fn": { "mimic-fn": {
"version": "2.1.0", "version": "2.1.0",
@ -3119,11 +3126,11 @@
"dev": true "dev": true
}, },
"polka": { "polka": {
"version": "1.0.0-next.6", "version": "1.0.0-next.9",
"resolved": "https://registry.npmjs.org/polka/-/polka-1.0.0-next.6.tgz", "resolved": "https://registry.npmjs.org/polka/-/polka-1.0.0-next.9.tgz",
"integrity": "sha512-e3vZm2cMmPPrgn+0J5DO0rrSTfsCHGyh+YS6jjrqYP8BHJkPq8nCVSDxHkaiEN4f0c2dtR6FB+snDmLE/sRz7A==", "integrity": "sha512-oAWH5O3CIPTzPKNx9KF9NDfy3KRyy9NtUhDEJGmMRCDT6s3CZaGDm7xafcKtm0uK6g0CBiNtoeGWpPFSLUXeaw==",
"requires": { "requires": {
"@polka/url": "^1.0.0-next.3", "@polka/url": "^1.0.0-next.9",
"trouter": "^3.1.0" "trouter": "^3.1.0"
} }
}, },
@ -3368,12 +3375,6 @@
"requires": { "requires": {
"@babel/helper-module-imports": "^7.0.0", "@babel/helper-module-imports": "^7.0.0",
"rollup-pluginutils": "^2.8.1" "rollup-pluginutils": "^2.8.1"
},
"dependencies": {
"estree-walker": {
"version": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz",
"integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w=="
}
} }
}, },
"rollup-plugin-commonjs": { "rollup-plugin-commonjs": {
@ -3411,10 +3412,6 @@
"rollup-pluginutils": "^2.8.1" "rollup-pluginutils": "^2.8.1"
}, },
"dependencies": { "dependencies": {
"estree-walker": {
"version": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz",
"integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w=="
},
"resolve": { "resolve": {
"version": "1.11.1", "version": "1.11.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz",
@ -3458,12 +3455,6 @@
"rollup-pluginutils": "^2.8.1", "rollup-pluginutils": "^2.8.1",
"serialize-javascript": "^1.7.0", "serialize-javascript": "^1.7.0",
"terser": "^4.1.0" "terser": "^4.1.0"
},
"dependencies": {
"estree-walker": {
"version": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz",
"integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w=="
}
} }
}, },
"rollup-pluginutils": { "rollup-pluginutils": {
@ -3473,14 +3464,6 @@
"dev": true, "dev": true,
"requires": { "requires": {
"estree-walker": "^0.6.1" "estree-walker": "^0.6.1"
},
"dependencies": {
"estree-walker": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz",
"integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==",
"dev": true
}
} }
}, },
"safe-buffer": { "safe-buffer": {
@ -3600,19 +3583,13 @@
"dev": true "dev": true
}, },
"sirv": { "sirv": {
"version": "0.4.2", "version": "1.0.0-next.2",
"resolved": "https://registry.npmjs.org/sirv/-/sirv-0.4.2.tgz", "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.0-next.2.tgz",
"integrity": "sha512-dQbZnsMaIiTQPZmbGmktz+c74zt/hyrJEB4tdp2Jj0RNv9J6B/OWR5RyrZEvIn9fyh9Zlg2OlE2XzKz6wMKGAw==", "integrity": "sha512-hWp0todr4jSb1BFBiANRmqYRXzX02l36/X4tyHPYKqMZ+e1hrDZKUjIIXrAOBRWlAE/G5cGImUciMrUcU8DeOg==",
"requires": { "requires": {
"@polka/url": "^0.5.0", "@polka/url": "^1.0.0-next.9",
"mime": "^2.3.1" "mime": "^2.3.1",
}, "totalist": "^1.0.0"
"dependencies": {
"@polka/url": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@polka/url/-/url-0.5.0.tgz",
"integrity": "sha512-oZLYFEAzUKyi3SKnXvj32ZCEGH6RDnao7COuCVhDydMS9NrCSVXhM79VaKyP5+Zc33m0QXEd2DN3UkU7OsHcfw=="
}
} }
}, },
"source-map": { "source-map": {
@ -3780,6 +3757,12 @@
"integrity": "sha512-9/broj3bjShrsk3FuVDH0Bho2BchPKT8ubAqRcTqwkqrO9npOS3Vi98Yb5mBaa4bYV+ELTuvPvaQZbCJYMFRdg==", "integrity": "sha512-9/broj3bjShrsk3FuVDH0Bho2BchPKT8ubAqRcTqwkqrO9npOS3Vi98Yb5mBaa4bYV+ELTuvPvaQZbCJYMFRdg==",
"dev": true "dev": true
}, },
"svelte-json-tree": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/svelte-json-tree/-/svelte-json-tree-0.0.5.tgz",
"integrity": "sha512-kTcOVlsldI2neszYNQAfFCt+u62OWWAZgpeoW9RN3hjtJCWI5bkVj0gtljZWUlyEWTfgpmag5L5AHDKg8w8ZmQ==",
"dev": true
},
"tar": { "tar": {
"version": "4.4.10", "version": "4.4.10",
"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.10.tgz", "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.10.tgz",
@ -3843,6 +3826,11 @@
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
"dev": true "dev": true
}, },
"totalist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/totalist/-/totalist-1.0.1.tgz",
"integrity": "sha512-HuAt9bWDCdLkebrIQr+i63NgQSvjeD2VTNUIEBqof/4pG4Gb6omuBOMUX0vF371cbfImXQzmb4Ue/0c9MUWGew=="
},
"trim-right": { "trim-right": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz",

@ -13,8 +13,8 @@
"deploy": "make deploy" "deploy": "make deploy"
}, },
"dependencies": { "dependencies": {
"@polka/redirect": "^1.0.0-next.0", "@polka/redirect": "^1.0.0-next.7",
"@polka/send": "^1.0.0-next.6", "@polka/send": "^1.0.0-next.7",
"cookie": "^0.4.0", "cookie": "^0.4.0",
"devalue": "^2.0.0", "devalue": "^2.0.0",
"do-not-zip": "^1.0.0", "do-not-zip": "^1.0.0",
@ -23,9 +23,9 @@
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"marked": "^0.7.0", "marked": "^0.7.0",
"pg": "^7.12.1", "pg": "^7.12.1",
"polka": "^1.0.0-next.6", "polka": "^1.0.0-next.9",
"prismjs": "^1.17.1", "prismjs": "^1.17.1",
"sirv": "^0.4.2", "sirv": "^1.0.0-next.2",
"yootils": "0.0.16" "yootils": "0.0.16"
}, },
"devDependencies": { "devDependencies": {
@ -36,7 +36,7 @@
"@babel/runtime": "^7.6.0", "@babel/runtime": "^7.6.0",
"@sindresorhus/slugify": "^0.9.1", "@sindresorhus/slugify": "^0.9.1",
"@sveltejs/site-kit": "^1.1.4", "@sveltejs/site-kit": "^1.1.4",
"@sveltejs/svelte-repl": "^0.1.9", "@sveltejs/svelte-repl": "^0.1.17",
"degit": "^2.1.4", "degit": "^2.1.4",
"dotenv": "^8.1.0", "dotenv": "^8.1.0",
"esm": "^3.2.25", "esm": "^3.2.25",

@ -13,6 +13,9 @@ const mode = process.env.NODE_ENV;
const dev = mode === 'development'; const dev = mode === 'development';
const legacy = !!process.env.SAPPER_LEGACY_BUILD; const legacy = !!process.env.SAPPER_LEGACY_BUILD;
const onwarn = (warning, onwarn) => (warning.code === 'CIRCULAR_DEPENDENCY' && /[/\\]@sapper[/\\]/.test(warning.message)) || onwarn(warning);
const dedupe = importee => importee === 'svelte' || importee.startsWith('svelte/');
export default { export default {
client: { client: {
input: config.client.input(), input: config.client.input(),
@ -28,7 +31,10 @@ export default {
hydratable: true, hydratable: true,
emitCss: true emitCss: true
}), }),
resolve(), resolve({
browser: true,
dedupe
}),
commonjs(), commonjs(),
json(), json(),
@ -53,6 +59,7 @@ export default {
module: true module: true
}) })
], ],
onwarn
}, },
server: { server: {
@ -67,7 +74,9 @@ export default {
generate: 'ssr', generate: 'ssr',
dev dev
}), }),
resolve(), resolve({
dedupe
}),
commonjs(), commonjs(),
json() json()
], ],
@ -78,6 +87,7 @@ export default {
require('module').builtinModules || Object.keys(process.binding('natives')) require('module').builtinModules || Object.keys(process.binding('natives'))
) )
], ],
onwarn
}, },
serviceworker: { serviceworker: {

@ -63,6 +63,8 @@
<a target="_blank" rel="noopener" href="http://healthtree.org/"><img src="organisations/healthtree.png" alt="HealthTree logo" loading="lazy"></a> <a target="_blank" rel="noopener" href="http://healthtree.org/"><img src="organisations/healthtree.png" alt="HealthTree logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://itslearning.com"><img src="organisations/itslearning.svg" alt="itslearning logo" loading="lazy"></a> <a target="_blank" rel="noopener" href="https://itslearning.com"><img src="organisations/itslearning.svg" alt="itslearning logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://jacoux.com"><img src="organisations/jacoux.png" alt="Jacoux logo" loading="lazy"></a> <a target="_blank" rel="noopener" href="https://jacoux.com"><img src="organisations/jacoux.png" alt="Jacoux logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://jingmnt.co.za"><img src="organisations/jingmnt.png" alt="Jingmnt logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://www.mentorcv.com"><img src="organisations/mentorcv.png" alt="Mentor CV logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://www.metrovias.com.ar/"><img src="organisations/metrovias.svg" alt="Metrovias logo" loading="lazy"></a> <a target="_blank" rel="noopener" href="https://www.metrovias.com.ar/"><img src="organisations/metrovias.svg" alt="Metrovias logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="http://mustlab.ru"><img src="organisations/mustlab.png" alt="Mustlab logo" loading="lazy"></a> <a target="_blank" rel="noopener" href="http://mustlab.ru"><img src="organisations/mustlab.png" alt="Mustlab logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://www.nesta.org.uk"><img src="organisations/nesta.svg" alt="Nesta logo" loading="lazy"></a> <a target="_blank" rel="noopener" href="https://www.nesta.org.uk"><img src="organisations/nesta.svg" alt="Nesta logo" loading="lazy"></a>
@ -74,12 +76,14 @@
<a target="_blank" rel="noopener" href="https://openstate.eu"><img src="organisations/open-state-foundation.svg" alt="Open State Foundation logo" loading="lazy"></a> <a target="_blank" rel="noopener" href="https://openstate.eu"><img src="organisations/open-state-foundation.svg" alt="Open State Foundation logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://panascais.net"><img src="organisations/panascais.svg" alt="Panascais logo" loading="lazy"></a> <a target="_blank" rel="noopener" href="https://panascais.net"><img src="organisations/panascais.svg" alt="Panascais logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://pankod.com"><img src="organisations/pankod.svg" alt="Pankod logo" loading="lazy"></a> <a target="_blank" rel="noopener" href="https://pankod.com"><img src="organisations/pankod.svg" alt="Pankod logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://paperform.co"><img src="organisations/paperform.svg" alt="Paperform logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://razorpay.com"><img src="organisations/razorpay.svg" alt="Razorpay logo" loading="lazy"></a> <a target="_blank" rel="noopener" href="https://razorpay.com"><img src="organisations/razorpay.svg" alt="Razorpay logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://sp.nl"><img src="organisations/socialist-party.svg" alt="Socialist Party logo" loading="lazy"></a> <a target="_blank" rel="noopener" href="https://sp.nl"><img src="organisations/socialist-party.svg" alt="Socialist Party logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://sqltribe.com"><img src="organisations/sqltribe.svg" alt="SQL Tribe logo" loading="lazy"></a> <a target="_blank" rel="noopener" href="https://sqltribe.com"><img src="organisations/sqltribe.svg" alt="SQL Tribe logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://www.stone.co"><img src="organisations/stone.svg" alt="Stone Payments logo" loading="lazy"></a> <a target="_blank" rel="noopener" href="https://www.stone.co"><img src="organisations/stone.svg" alt="Stone Payments logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://www.strixengine.com"><img src="organisations/strixcloud.svg" alt="Strix Cloud logo" loading="lazy"><span>Strix Cloud</span></a> <a target="_blank" rel="noopener" href="https://www.strixengine.com"><img src="organisations/strixcloud.svg" alt="Strix Cloud logo" loading="lazy"><span>Strix Cloud</span></a>
<a target="_blank" rel="noopener" href="https://sucuri.net" style="background-color: rgb(93, 93, 93);"><img src="organisations/sucuri.png" alt="Sucuri logo" loading="lazy"></a> <a target="_blank" rel="noopener" href="https://sucuri.net" style="background-color: rgb(93, 93, 93);"><img src="organisations/sucuri.png" alt="Sucuri logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://tsh.io"><img src="organisations/tsh.svg" alt="The Software House logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://thunderdome.dev"><img src="organisations/thunderdome.svg" alt="Thunderdome logo" loading="lazy"></a> <a target="_blank" rel="noopener" href="https://thunderdome.dev"><img src="organisations/thunderdome.svg" alt="Thunderdome logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://m.tokopedia.com"><img src="organisations/tokopedia.svg" alt="Tokopedia logo" loading="lazy"></a> <a target="_blank" rel="noopener" href="https://m.tokopedia.com"><img src="organisations/tokopedia.svg" alt="Tokopedia logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://webdesq.net"><img src="organisations/webdesq.svg" alt="Webdesq logo" loading="lazy"></a> <a target="_blank" rel="noopener" href="https://webdesq.net"><img src="organisations/webdesq.svg" alt="Webdesq logo" loading="lazy"></a>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 103 124">
<path style="fill:black" d='M96.33,20.61C85.38,4.93,63.74.28,48.09,10.25L20.61,27.77A31.46,31.46,0,0,0,6.37,48.88,33.22,33.22,0,0,0,9.64,70.2,31.52,31.52,0,0,0,4.93,82a33.61,33.61,0,0,0,5.73,25.41c11,15.68,32.6,20.33,48.25,10.36l27.48-17.52a31.48,31.48,0,0,0,14.24-21.11A33.22,33.22,0,0,0,97.36,57.8,31.52,31.52,0,0,0,102.07,46a33.57,33.57,0,0,0-5.74-25.41
M45.41,108.86A21.81,21.81,0,0,1,22,100.18,20.2,20.2,0,0,1,18.53,84.9a19,19,0,0,1,.65-2.57l.52-1.58,1.41,1a35.32,35.32,0,0,0,10.75,5.37l1,.31-.1,1a6.2,6.2,0,0,0,1.11,4.08A6.57,6.57,0,0,0,41,95.19a6,6,0,0,0,1.68-.74L70.11,76.94a5.76,5.76,0,0,0,2.59-3.83,6.09,6.09,0,0,0-1-4.6,6.58,6.58,0,0,0-7.06-2.62,6.21,6.21,0,0,0-1.69.74L52.43,73.31a19.88,19.88,0,0,1-5.58,2.45,21.82,21.82,0,0,1-23.43-8.68A20.2,20.2,0,0,1,20,51.8a19,19,0,0,1,8.56-12.7L56,21.59a19.88,19.88,0,0,1,5.58-2.45A21.81,21.81,0,0,1,85,27.82,20.2,20.2,0,0,1,88.47,43.1a19,19,0,0,1-.65,2.57l-.52,1.58-1.41-1a35.32,35.32,0,0,0-10.75-5.37l-1-.31.1-1a6.2,6.2,0,0,0-1.11-4.08,6.57,6.57,0,0,0-7.06-2.62,6,6,0,0,0-1.68.74L36.89,51.06a5.71,5.71,0,0,0-2.58,3.83,6,6,0,0,0,1,4.6,6.58,6.58,0,0,0,7.06,2.62,6.21,6.21,0,0,0,1.69-.74l10.48-6.68a19.88,19.88,0,0,1,5.58-2.45,21.82,21.82,0,0,1,23.43,8.68A20.2,20.2,0,0,1,87,76.2a19,19,0,0,1-8.56,12.7L51,106.41a19.88,19.88,0,0,1-5.58,2.45' />
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="275px" height="424px" viewBox="0 0 275 424" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
<title>Logo--gradient--shadow</title>
<desc>Created with Sketch.</desc>
<defs>
<linearGradient x1="93.6192181%" y1="19.8016827%" x2="3.21017795%" y2="50%" id="linearGradient-1">
<stop stop-color="#5EB3C0" offset="0%"></stop>
<stop stop-color="#2F7EC4" offset="100%"></stop>
</linearGradient>
<path d="M326,231.945968 L351,231.945968 C362.045695,231.945968 371,240.900273 371,251.945968 L371,588.945968 C371,599.991663 362.045695,608.945968 351,608.945968 L326,608.945968 C314.954305,608.945968 306,599.991663 306,588.945968 L306,251.945968 C306,240.900273 314.954305,231.945968 326,231.945968 Z M425.789423,233.983664 L522.789423,292.073947 C529.123237,295.867071 533,302.707533 533,310.090282 L533,385.015414 C533,392.407317 529.113693,399.254857 522.767388,403.044928 L425.767388,460.974199 C415.809962,466.920863 402.91715,463.669498 396.970486,453.712073 C395.026473,450.456908 394,446.73616 394,442.944685 L394,252 C394,240.40202 403.40202,231 415,231 C418.800191,231 422.529161,232.031195 425.789423,233.983664 Z" id="path-2"></path>
<filter x="-18.3%" y="-8.1%" width="136.6%" height="122.0%" filterUnits="objectBoundingBox" id="filter-3">
<feOffset dx="0" dy="11" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="12" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
</filter>
</defs>
<g id="Logo" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Logo--gradient--shadow" transform="translate(-282.000000, -219.000000)">
<g>
<use fill="black" fill-opacity="1" filter="url(#filter-3)" xlink:href="#path-2"></use>
<use fill="url(#linearGradient-1)" fill-rule="evenodd" xlink:href="#path-2"></use>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 97 44"><path d="M39.1 6.9h-2.3v9h-2.2v-9h-2.3V5.1h6.8v1.8zm3.1-1.8v4.1h2.5V5.1h2.2v10.7h-2.2V11h-2.5v4.8H40V5.1h2.2zm12.1 1.8h-3.8v2.5h3.6v1.8h-3.6v2.9h4v1.8h-6.1V5.1h6v1.8zM34.5 26.7c0 .3 0 .5.1.7 0 .2.1.4.2.5.1.1.3.3.4.3.2.1.4.1.7.1.3 0 .6-.1.9-.3.3-.2.4-.5.4-1 0-.2 0-.4-.1-.6-.1-.2-.2-.3-.3-.5-.1-.1-.3-.3-.6-.4-.2-.1-.5-.2-.9-.4-.5-.2-.9-.3-1.2-.5-.4-.2-.6-.4-.9-.7-.2-.3-.4-.6-.5-.9-.1-.3-.2-.7-.2-1.2 0-1.1.3-1.9.9-2.4.6-.5 1.4-.8 2.4-.8.5 0 .9.1 1.3.2s.8.3 1 .5c.3.2.5.5.7.9.2.4.2.8.2 1.3v.3h-2.1c0-.5-.1-.9-.3-1.2-.2-.3-.5-.4-.9-.4-.2 0-.4 0-.6.1-.2.1-.3.2-.4.3-.1.1-.2.2-.2.4s-.1.3-.1.5c0 .3.1.6.2.8.1.2.4.4.9.6l1.7.7c.4.2.7.4 1 .6.3.2.5.4.6.6.2.2.3.5.3.7.1.3.1.6.1.9 0 1.1-.3 2-1 2.5s-1.6.8-2.7.8c-1.2 0-2.1-.3-2.6-.8s-.8-1.3-.8-2.3v-.4h2.2v.5zm6-4.6c.1-.7.2-1.3.5-1.8s.6-.9 1.1-1.1c.5-.3 1.1-.4 1.9-.4.8 0 1.5.1 1.9.4.5.3.8.7 1.1 1.1.3.5.4 1.1.5 1.8.1.7.1 1.4.1 2.2 0 .8 0 1.6-.1 2.3-.1.7-.2 1.3-.5 1.8s-.6.9-1.1 1.1c-.5.3-1.1.4-1.9.4-.8 0-1.5-.1-1.9-.4-.5-.3-.8-.6-1.1-1.1-.3-.5-.4-1.1-.5-1.8-.1-.7-.1-1.4-.1-2.3 0-.8 0-1.5.1-2.2zm2.1 4.1c0 .5.1.9.2 1.2.1.3.3.5.4.7.2.1.5.2.8.2.3 0 .6-.1.8-.2.2-.1.3-.4.4-.7.1-.3.2-.7.2-1.2s.1-1.1.1-1.8 0-1.3-.1-1.8c0-.5-.1-.9-.2-1.2-.1-.3-.3-.6-.4-.7-.2-.1-.5-.2-.8-.2-.3 0-.6.1-.8.2-.2.1-.3.4-.4.7-.1.3-.2.7-.2 1.2s-.1 1.1-.1 1.8 0 1.3.1 1.8zM54.9 19v1.8h-3.8v2.5h3.6V25h-3.6v4.7h-2.2V19h6zm7.2 1.7h-2.3v9h-2.2v-9h-2.3V19h6.8v1.7zm2.4-1.7l1.2 7.9L67 19h2l1.3 7.9 1.2-7.9h2.1l-1.9 10.7h-2.4L68 21.9l-1.2 7.8h-2.4l-2-10.7h2.1zm14.4 0l2.8 10.7h-2.2l-.5-2.3h-2.8l-.5 2.3h-2.2L76.3 19h2.6zm-.2 6.7l-1-4.8-1 4.8h2zm7.8-6.7c.9 0 1.6.2 2.1.7.5.5.7 1.1.7 2.1 0 .7-.1 1.3-.4 1.8-.3.5-.7.7-1.3.9.5.1.9.3 1.2.5.2.3.4.7.5 1.3v1.5c0 .6 0 1 .1 1.3.1.3.2.5.4.6v.1h-2.3c-.1-.2-.2-.3-.2-.5s-.1-.4-.1-.6l-.1-2.1c0-.4-.1-.8-.3-1-.2-.2-.5-.4-1-.4h-1.2v4.5h-2.2V19h4.1zm-.9 4.7c.5 0 .9-.1 1.2-.4.3-.2.4-.7.4-1.2 0-1-.5-1.5-1.5-1.5h-1v3.1h.9zm11.3-3H93v2.5h3.6V25H93v2.9h4v1.8h-6.1V19h6v1.7zM35 32.8v4.1h2.5v-4.1h2.2v10.7h-2.2v-4.8H35v4.8h-2.2V32.8H35zm6 3.2c.1-.7.2-1.3.5-1.8s.6-.9 1.1-1.1c.5-.3 1.1-.4 1.9-.4.8 0 1.5.1 1.9.4.5.3.8.7 1.1 1.1.3.5.4 1.1.5 1.8.1.7.1 1.4.1 2.2 0 .8 0 1.6-.1 2.3-.1.7-.2 1.3-.5 1.8s-.6.9-1.1 1.1c-.5.3-1.1.4-1.9.4-.8 0-1.5-.1-1.9-.4-.5-.3-.8-.6-1.1-1.1-.3-.5-.4-1.1-.5-1.8-.1-.7-.1-1.4-.1-2.3 0-.8.1-1.5.1-2.2zm2.1 4.1c0 .5.1.9.2 1.2.1.3.3.5.4.7.2.1.5.2.8.2.3 0 .6-.1.8-.2.2-.1.3-.4.4-.7.1-.3.2-.7.2-1.2s.1-1.1.1-1.8 0-1.3-.1-1.8c0-.5-.1-.9-.2-1.2-.1-.3-.3-.6-.4-.7-.2-.1-.5-.2-.8-.2-.3 0-.6.1-.8.2-.2.1-.3.4-.4.7-.1.3-.2.7-.2 1.2s-.1 1.1-.1 1.8c.1.6.1 1.2.1 1.8zm8.4.2c0 .3 0 .5.1.7 0 .2.1.4.2.6.1.2.2.3.4.4.2.1.4.1.6.1.3 0 .5 0 .6-.1.2-.1.3-.2.4-.4.1-.2.2-.4.2-.6 0-.2.1-.5.1-.7v-7.5h2.2v7.5c0 .7-.1 1.2-.3 1.7-.2.4-.4.8-.8 1.1-.3.3-.7.4-1.1.5-.4.1-.8.1-1.3.1-.4 0-.9 0-1.3-.1s-.8-.2-1.1-.5c-.3-.2-.6-.6-.8-1-.2-.5-.3-1-.3-1.8v-7.5h2.2v7.5zm7.8.2c0 .3 0 .5.1.7 0 .2.1.4.2.5.1.1.3.3.4.3.2.1.4.1.7.1.3 0 .6-.1.9-.3.3-.2.4-.5.4-1 0-.2 0-.4-.1-.6-.1-.2-.2-.3-.3-.5-.1-.1-.3-.3-.6-.4-.2-.1-.5-.2-.9-.4-.5-.2-.9-.3-1.2-.5-.4-.2-.6-.4-.9-.7-.2-.3-.4-.6-.5-.9-.1-.3-.2-.7-.2-1.2 0-1.1.3-1.9.9-2.4.6-.5 1.4-.8 2.4-.8.5 0 .9.1 1.3.2s.8.3 1 .5c.3.2.5.5.7.9.2.4.2.8.2 1.3v.3h-2.1c0-.5-.1-.9-.3-1.2-.2-.3-.5-.4-.9-.4-.2 0-.4 0-.6.1-.2.1-.3.2-.4.3-.1.1-.2.2-.2.4s-.1.3-.1.5c0 .3.1.6.2.8.1.2.4.4.9.6l1.7.7c.4.2.7.4 1 .6.3.2.5.4.6.6.2.2.3.5.3.7.1.3.1.6.1.9 0 1.1-.3 2-1 2.5s-1.6.8-2.7.8c-1.2 0-2.1-.3-2.6-.8s-.8-1.3-.8-2.3V40h2.2v.5zm12-5.9h-3.8v2.5h3.6v1.8h-3.6v2.9h4v1.8h-6.1V32.8h6v1.8zM30.1 44L30 4.6 0 0v34.8l10.2 3.1V21.6l8.5 1.9v11.1L10 37.9l8.7 2.6z"/></svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

@ -1,4 +1,4 @@
import { walk, childKeys } from 'estree-walker'; import { walk } from 'estree-walker';
import { getLocator } from 'locate-character'; import { getLocator } from 'locate-character';
import Stats from '../Stats'; import Stats from '../Stats';
import { globals, reserved, is_valid } from '../utils/names'; import { globals, reserved, is_valid } from '../utils/names';
@ -27,7 +27,7 @@ import Slot from './nodes/Slot';
import { Node, ImportDeclaration, Identifier, Program, ExpressionStatement, AssignmentExpression, Literal } from 'estree'; import { Node, ImportDeclaration, Identifier, Program, ExpressionStatement, AssignmentExpression, Literal } from 'estree';
import add_to_set from './utils/add_to_set'; import add_to_set from './utils/add_to_set';
import check_graph_for_cycles from './utils/check_graph_for_cycles'; import check_graph_for_cycles from './utils/check_graph_for_cycles';
import { print, x } from 'code-red'; import { print, x, b } from 'code-red';
interface ComponentOptions { interface ComponentOptions {
namespace?: string; namespace?: string;
@ -37,13 +37,6 @@ interface ComponentOptions {
preserveWhitespace?: boolean; preserveWhitespace?: boolean;
} }
// We need to tell estree-walker that it should always
// look for an `else` block, otherwise it might get
// the wrong idea about the shape of each/if blocks
childKeys.EachBlock = childKeys.IfBlock = ['children', 'else'];
childKeys.Attribute = ['value'];
childKeys.ExportNamedDeclaration = ['declaration', 'specifiers'];
export default class Component { export default class Component {
stats: Stats; stats: Stats;
warnings: Warning[]; warnings: Warning[];
@ -169,13 +162,12 @@ export default class Component {
this.tag = this.name.name; this.tag = this.name.name;
} }
this.walk_module_js_pre_template(); this.walk_module_js();
this.walk_instance_js_pre_template(); this.walk_instance_js_pre_template();
this.fragment = new Fragment(this, ast.html); this.fragment = new Fragment(this, ast.html);
this.name = this.get_unique_name(name); this.name = this.get_unique_name(name);
this.walk_module_js_post_template();
this.walk_instance_js_post_template(); this.walk_instance_js_post_template();
if (!compile_options.customElement) this.stylesheet.reify(); if (!compile_options.customElement) this.stylesheet.reify();
@ -209,7 +201,6 @@ export default class Component {
}); });
const subscribable_name = name.slice(1); const subscribable_name = name.slice(1);
this.add_reference(subscribable_name);
const variable = this.var_lookup.get(subscribable_name); const variable = this.var_lookup.get(subscribable_name);
if (variable) variable.subscribable = true; if (variable) variable.subscribable = true;
@ -240,8 +231,7 @@ export default class Component {
const { compile_options, name } = this; const { compile_options, name } = this;
const { format = 'esm' } = compile_options; const { format = 'esm' } = compile_options;
// TODO reinstate banner (along with fragment marker comments) const banner = `${this.file ? `${this.file} ` : ``}generated by Svelte v${'__VERSION__'}`;
const banner = `/* ${this.file ? `${this.file} ` : ``}generated by Svelte v${'__VERSION__'} */`;
const program: any = { type: 'Program', body: result }; const program: any = { type: 'Program', body: result };
@ -526,7 +516,7 @@ export default class Component {
}); });
} }
walk_module_js_pre_template() { walk_module_js() {
const component = this; const component = this;
const script = this.ast.module; const script = this.ast.module;
if (!script) return; if (!script) return;
@ -577,6 +567,25 @@ export default class Component {
}); });
} }
}); });
const { body } = script.content;
let i = body.length;
while (--i >= 0) {
const node = body[i];
if (node.type === 'ImportDeclaration') {
this.extract_imports(node);
body.splice(i, 1);
}
if (/^Export/.test(node.type)) {
const replacement = this.extract_exports(node);
if (replacement) {
body[i] = replacement;
} else {
body.splice(i, 1);
}
}
}
} }
walk_instance_js_pre_template() { walk_instance_js_pre_template() {
@ -674,30 +683,6 @@ export default class Component {
this.track_references_and_mutations(); this.track_references_and_mutations();
} }
walk_module_js_post_template() {
const script = this.ast.module;
if (!script) return;
const { body } = script.content;
let i = body.length;
while (--i >= 0) {
const node = body[i];
if (node.type === 'ImportDeclaration') {
this.extract_imports(node);
body.splice(i, 1);
}
if (/^Export/.test(node.type)) {
const replacement = this.extract_exports(node);
if (replacement) {
body[i] = replacement;
} else {
body.splice(i, 1);
}
}
}
}
walk_instance_js_post_template() { walk_instance_js_post_template() {
const script = this.ast.instance; const script = this.ast.instance;
if (!script) return; if (!script) return;
@ -718,11 +703,13 @@ export default class Component {
let scope = instance_scope; let scope = instance_scope;
const toRemove = []; const to_remove = [];
const remove = (parent, prop, index) => { const remove = (parent, prop, index) => {
toRemove.unshift([parent, prop, index]); to_remove.unshift([parent, prop, index]);
}; };
const to_insert = new Map();
walk(content, { walk(content, {
enter(node, parent, prop, index) { enter(node, parent, prop, index) {
if (map.has(node)) { if (map.has(node)) {
@ -748,16 +735,41 @@ export default class Component {
} }
component.warn_on_undefined_store_value_references(node, parent, scope); component.warn_on_undefined_store_value_references(node, parent, scope);
if (component.compile_options.dev && component.compile_options.loopGuardTimeout > 0) {
const to_insert_for_loop_protect = component.loop_protect(node, prop, index, component.compile_options.loopGuardTimeout);
if (to_insert_for_loop_protect) {
if (!Array.isArray(parent[prop])) {
parent[prop] = {
type: 'BlockStatement',
body: [to_insert_for_loop_protect.node, node],
};
} else {
// can't insert directly, will screw up the index in the for-loop of estree-walker
if (!to_insert.has(parent)) {
to_insert.set(parent, []);
}
to_insert.get(parent).push(to_insert_for_loop_protect);
}
}
}
}, },
leave(node) { leave(node) {
if (map.has(node)) { if (map.has(node)) {
scope = scope.parent; scope = scope.parent;
} }
if (to_insert.has(node)) {
const nodes_to_insert = to_insert.get(node);
for (const { index, prop, node: node_to_insert } of nodes_to_insert.reverse()) {
node[prop].splice(index, 0, node_to_insert);
}
to_insert.delete(node);
}
}, },
}); });
for (const [parent, prop, index] of toRemove) { for (const [parent, prop, index] of to_remove) {
if (parent) { if (parent) {
if (index !== null) { if (index !== null) {
parent[prop].splice(index, 1); parent[prop].splice(index, 1);
@ -837,48 +849,30 @@ export default class Component {
} }
} }
invalidate(name, value?) { loop_protect(node, prop, index, timeout) {
const variable = this.var_lookup.get(name); if (node.type === 'WhileStatement' ||
node.type === 'ForStatement' ||
if (variable && (variable.subscribable && (variable.reassigned || variable.export_name))) { node.type === 'DoWhileStatement') {
return x`${`$$subscribe_${name}`}($$invalidate('${name}', ${value || name}))`; const guard = this.get_unique_name('guard');
} this.add_var({
name: guard.name,
if (name[0] === '$' && name[1] !== '$') { internal: true,
return x`${name.slice(1)}.set(${value || name})`; });
}
if ( const before = b`const ${guard} = @loop_guard(${timeout})`;
variable && const inside = b`${guard}();`;
!variable.referenced &&
!variable.is_reactive_dependency &&
!variable.export_name &&
!name.startsWith('$$')
) {
return value || name;
}
if (value) { // wrap expression statement with BlockStatement
return x`$$invalidate('${name}', ${value})`; if (node.body.type !== 'BlockStatement') {
node.body = {
type: 'BlockStatement',
body: [node.body],
};
}
node.body.body.push(inside[0]);
return { index, prop, node: before[0] };
} }
return null;
// if this is a reactive declaration, invalidate dependencies recursively
const deps = new Set([name]);
deps.forEach(name => {
const reactive_declarations = this.reactive_declarations.filter(x =>
x.assignees.has(name)
);
reactive_declarations.forEach(declaration => {
declaration.dependencies.forEach(name => {
deps.add(name);
});
});
});
return Array.from(deps)
.map(n => x`$$invalidate('${n}', ${n})`)
.reduce((lhs, rhs) => x`${lhs}, ${rhs}}`);
} }
rewrite_props(get_insert: (variable: Var) => Node[]) { rewrite_props(get_insert: (variable: Var) => Node[]) {
@ -1002,6 +996,10 @@ export default class Component {
if (!d.init) return false; if (!d.init) return false;
if (d.init.type !== 'Literal') return false; if (d.init.type !== 'Literal') return false;
// everything except const values can be changed by e.g. svelte devtools
// which means we can't hoist it
if (node.kind !== 'const' && this.compile_options.dev) return false;
const { name } = d.id as Identifier; const { name } = d.id as Identifier;
const v = this.var_lookup.get(name); const v = this.var_lookup.get(name);
@ -1273,25 +1271,6 @@ export default class Component {
}); });
} }
qualify(name) {
if (name === `$$props`) return x`#ctx.$$props`;
let [head, ...tail] = name.split('.');
const variable = this.var_lookup.get(head);
if (variable) {
this.add_reference(name); // TODO we can probably remove most other occurrences of this
if (!variable.hoistable) {
tail.unshift(head);
head = '#ctx';
}
}
return [head, ...tail].reduce((lhs, rhs) => x`${lhs}.${rhs}`);
}
warn_if_undefined(name: string, node, template_scope: TemplateScope) { warn_if_undefined(name: string, node, template_scope: TemplateScope) {
if (name[0] === '$') { if (name[0] === '$') {
if (name === '$' || name[1] === '$' && name !== '$$props') { if (name === '$' || name[1] === '$' && name !== '$$props') {

@ -41,28 +41,11 @@ function edit_source(source, sveltePath) {
: source; : source;
} }
function esm( function get_internal_globals(
program: any,
name: Identifier,
_banner: string,
sveltePath: string,
internal_path: string,
helpers: Array<{ name: string; alias: Identifier }>,
globals: Array<{ name: string; alias: Identifier }>, globals: Array<{ name: string; alias: Identifier }>,
imports: ImportDeclaration[], helpers: Array<{ name: string; alias: Identifier }>
module_exports: Export[]
) { ) {
const import_declaration = { return globals.length > 0 && {
type: 'ImportDeclaration',
specifiers: helpers.map(h => ({
type: 'ImportSpecifier',
local: h.alias,
imported: { type: 'Identifier', name: h.name }
})),
source: { type: 'Literal', value: internal_path }
};
const internal_globals = globals.length > 0 && {
type: 'VariableDeclaration', type: 'VariableDeclaration',
kind: 'const', kind: 'const',
declarations: [{ declarations: [{
@ -82,6 +65,30 @@ function esm(
init: helpers.find(({ name }) => name === 'globals').alias init: helpers.find(({ name }) => name === 'globals').alias
}] }]
}; };
}
function esm(
program: any,
name: Identifier,
banner: string,
sveltePath: string,
internal_path: string,
helpers: Array<{ name: string; alias: Identifier }>,
globals: Array<{ name: string; alias: Identifier }>,
imports: ImportDeclaration[],
module_exports: Export[]
) {
const import_declaration = {
type: 'ImportDeclaration',
specifiers: helpers.map(h => ({
type: 'ImportSpecifier',
local: h.alias,
imported: { type: 'Identifier', name: h.name }
})),
source: { type: 'Literal', value: internal_path }
};
const internal_globals = get_internal_globals(globals, helpers);
// edit user imports // edit user imports
imports.forEach(node => { imports.forEach(node => {
@ -98,6 +105,8 @@ function esm(
}; };
program.body = b` program.body = b`
/* ${banner} */
${import_declaration} ${import_declaration}
${internal_globals} ${internal_globals}
${imports} ${imports}
@ -112,7 +121,7 @@ function esm(
function cjs( function cjs(
program: any, program: any,
name: Identifier, name: Identifier,
_banner: string, banner: string,
sveltePath: string, sveltePath: string,
internal_path: string, internal_path: string,
helpers: Array<{ name: string; alias: Identifier }>, helpers: Array<{ name: string; alias: Identifier }>,
@ -141,26 +150,7 @@ function cjs(
}] }]
}; };
const internal_globals = globals.length > 0 && { const internal_globals = get_internal_globals(globals, helpers);
type: 'VariableDeclaration',
kind: 'const',
declarations: [{
type: 'VariableDeclarator',
id: {
type: 'ObjectPattern',
properties: globals.map(g => ({
type: 'Property',
method: false,
shorthand: false,
computed: false,
key: { type: 'Identifier', name: g.name },
value: g.alias,
kind: 'init'
}))
},
init: helpers.find(({ name }) => name === 'globals').alias
}]
};
const user_requires = imports.map(node => ({ const user_requires = imports.map(node => ({
type: 'VariableDeclaration', type: 'VariableDeclaration',
@ -188,6 +178,8 @@ function cjs(
const exports = module_exports.map(x => b`exports.${{ type: 'Identifier', name: x.as }} = ${{ type: 'Identifier', name: x.name }};`); const exports = module_exports.map(x => b`exports.${{ type: 'Identifier', name: x.as }} = ${{ type: 'Identifier', name: x.name }};`);
program.body = b` program.body = b`
/* ${banner} */
"use strict"; "use strict";
${internal_requires} ${internal_requires}
${internal_globals} ${internal_globals}

@ -3,6 +3,7 @@ import Stylesheet from './Stylesheet';
import { gather_possible_values, UNKNOWN } from './gather_possible_values'; import { gather_possible_values, UNKNOWN } from './gather_possible_values';
import { CssNode } from './interfaces'; import { CssNode } from './interfaces';
import Component from '../Component'; import Component from '../Component';
import Element from '../nodes/Element';
enum BlockAppliesToNode { enum BlockAppliesToNode {
NotPossible, NotPossible,
@ -34,8 +35,8 @@ export default class Selector {
this.used = this.blocks[0].global; this.used = this.blocks[0].global;
} }
apply(node: CssNode, stack: CssNode[]) { apply(node: Element, stack: Element[]) {
const to_encapsulate: CssNode[] = []; const to_encapsulate: any[] = [];
apply_selector(this.local_blocks.slice(), node, stack.slice(), to_encapsulate); apply_selector(this.local_blocks.slice(), node, stack.slice(), to_encapsulate);
@ -132,7 +133,7 @@ export default class Selector {
} }
} }
function apply_selector(blocks: Block[], node: CssNode, stack: CssNode[], to_encapsulate: any[]): boolean { function apply_selector(blocks: Block[], node: Element, stack: Element[], to_encapsulate: any[]): boolean {
const block = blocks.pop(); const block = blocks.pop();
if (!block) return false; if (!block) return false;
@ -259,16 +260,84 @@ function attribute_matches(node: CssNode, name: string, expected_value: string,
const attr = node.attributes.find((attr: CssNode) => attr.name === name); const attr = node.attributes.find((attr: CssNode) => attr.name === name);
if (!attr) return false; if (!attr) return false;
if (attr.is_true) return operator === null; if (attr.is_true) return operator === null;
if (attr.chunks.length > 1) return true;
if (!expected_value) return true; if (!expected_value) return true;
const value = attr.chunks[0]; if (attr.chunks.length === 1) {
const value = attr.chunks[0];
if (!value) return false; if (!value) return false;
if (value.type === 'Text') return test_attribute(operator, expected_value, case_insensitive, value.data); if (value.type === 'Text') return test_attribute(operator, expected_value, case_insensitive, value.data);
}
const possible_values = new Set(); const possible_values = new Set();
gather_possible_values(value.node, possible_values);
let prev_values = [];
for (const chunk of attr.chunks) {
const current_possible_values = new Set();
if (chunk.type === 'Text') {
current_possible_values.add(chunk.data);
} else {
gather_possible_values(chunk.node, current_possible_values);
}
// impossible to find out all combinations
if (current_possible_values.has(UNKNOWN)) return true;
if (prev_values.length > 0) {
const start_with_space = [];
const remaining = [];
current_possible_values.forEach((current_possible_value: string) => {
if (/^\s/.test(current_possible_value)) {
start_with_space.push(current_possible_value);
} else {
remaining.push(current_possible_value);
}
});
if (remaining.length > 0) {
if (start_with_space.length > 0) {
prev_values.forEach(prev_value => possible_values.add(prev_value));
}
const combined = [];
prev_values.forEach((prev_value: string) => {
remaining.forEach((value: string) => {
combined.push(prev_value + value);
});
});
prev_values = combined;
start_with_space.forEach((value: string) => {
if (/\s$/.test(value)) {
possible_values.add(value);
} else {
prev_values.push(value);
}
});
continue;
} else {
prev_values.forEach(prev_value => possible_values.add(prev_value));
prev_values = [];
}
}
current_possible_values.forEach((current_possible_value: string) => {
if (/\s$/.test(current_possible_value)) {
possible_values.add(current_possible_value);
} else {
prev_values.push(current_possible_value);
}
});
if (prev_values.length < current_possible_values.size) {
prev_values.push(' ');
}
if (prev_values.length > 20) {
// might grow exponentially, bail out
return true;
}
}
prev_values.forEach(prev_value => possible_values.add(prev_value));
if (possible_values.has(UNKNOWN)) return true; if (possible_values.has(UNKNOWN)) return true;
for (const value of possible_values) { for (const value of possible_values) {

@ -24,12 +24,13 @@ const valid_options = [
'customElement', 'customElement',
'tag', 'tag',
'css', 'css',
'loopGuardTimeout',
'preserveComments', 'preserveComments',
'preserveWhitespace' 'preserveWhitespace'
]; ];
function validate_options(options: CompileOptions, warnings: Warning[]) { function validate_options(options: CompileOptions, warnings: Warning[]) {
const { name, filename } = options; const { name, filename, loopGuardTimeout, dev } = options;
Object.keys(options).forEach(key => { Object.keys(options).forEach(key => {
if (valid_options.indexOf(key) === -1) { if (valid_options.indexOf(key) === -1) {
@ -54,6 +55,16 @@ function validate_options(options: CompileOptions, warnings: Warning[]) {
toString: () => message, toString: () => message,
}); });
} }
if (loopGuardTimeout && !dev) {
const message = 'options.loopGuardTimeout is for options.dev = true only';
warnings.push({
code: `options-loop-guard-timeout`,
message,
filename,
toString: () => message,
});
}
} }
export default function compile(source: string, options: CompileOptions = {}) { export default function compile(source: string, options: CompileOptions = {}) {

@ -14,7 +14,7 @@ export default class Action extends Node {
component.warn_if_undefined(info.name, info, scope); component.warn_if_undefined(info.name, info, scope);
this.name = info.name; this.name = info.name;
component.qualify(info.name); component.add_reference(info.name.split('.')[0]);
this.expression = info.expression this.expression = info.expression
? new Expression(component, this, scope, info.expression) ? new Expression(component, this, scope, info.expression)

@ -13,7 +13,7 @@ export default class Animation extends Node {
component.warn_if_undefined(info.name, info, scope); component.warn_if_undefined(info.name, info, scope);
this.name = info.name; this.name = info.name;
component.qualify(info.name); component.add_reference(info.name.split('.')[0]);
if (parent.animation) { if (parent.animation) {
component.error(this, { component.error(this, {

@ -11,7 +11,11 @@ const read_only_media_attributes = new Set([
'duration', 'duration',
'buffered', 'buffered',
'seekable', 'seekable',
'played' 'played',
'seeking',
'ended',
'videoHeight',
'videoWidth'
]); ]);
export default class Binding extends Node { export default class Binding extends Node {
@ -48,7 +52,9 @@ export default class Binding extends Node {
} else if (this.is_contextual) { } else if (this.is_contextual) {
scope.dependencies_for_name.get(name).forEach(name => { scope.dependencies_for_name.get(name).forEach(name => {
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);
variable[this.expression.node.type === 'MemberExpression' ? 'mutated' : 'reassigned'] = true; if (variable) {
variable[this.expression.node.type === 'MemberExpression' ? 'mutated' : 'reassigned'] = true;
}
}); });
} else { } else {
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);

@ -151,6 +151,10 @@ export default class Element extends Node {
} }
} }
// Binding relies on Attribute, defer its evaluation
const order = ['Binding']; // everything else is -1
info.attributes.sort((a, b) => order.indexOf(a.type) - order.indexOf(b.type));
info.attributes.forEach(node => { info.attributes.forEach(node => {
switch (node.type) { switch (node.type) {
case 'Action': case 'Action':
@ -592,7 +596,9 @@ export default class Element extends Node {
name === 'seekable' || name === 'seekable' ||
name === 'played' || name === 'played' ||
name === 'volume' || name === 'volume' ||
name === 'playbackRate' name === 'playbackRate' ||
name === 'seeking' ||
name === 'ended'
) { ) {
if (this.name !== 'audio' && this.name !== 'video') { if (this.name !== 'audio' && this.name !== 'video') {
component.error(binding, { component.error(binding, {
@ -600,6 +606,16 @@ export default class Element extends Node {
message: `'${name}' binding can only be used with <audio> or <video>` message: `'${name}' binding can only be used with <audio> or <video>`
}); });
} }
} else if (
name === 'videoHeight' ||
name === 'videoWidth'
) {
if (this.name !== 'video') {
component.error(binding, {
code: `invalid-binding`,
message: `'${name}' binding can only be used with <video>`
});
}
} else if (dimensions.test(name)) { } else if (dimensions.test(name)) {
if (this.name === 'svg' && (name === 'offsetWidth' || name === 'offsetHeight')) { if (this.name === 'svg' && (name === 'offsetWidth' || name === 'offsetHeight')) {
component.error(binding, { component.error(binding, {

@ -1,8 +1,6 @@
import Node from './shared/Node'; import Node from './shared/Node';
import Expression from './shared/Expression'; import Expression from './shared/Expression';
import Component from '../Component'; import Component from '../Component';
import { b, x } from 'code-red';
import Block from '../render_dom/Block';
import { sanitize } from '../../utils/names'; import { sanitize } from '../../utils/names';
import { Identifier } from 'estree'; import { Identifier } from 'estree';
@ -14,6 +12,7 @@ export default class EventHandler extends Node {
handler_name: Identifier; handler_name: Identifier;
uses_context = false; uses_context = false;
can_make_passive = false; can_make_passive = false;
reassigned?: boolean;
constructor(component: Component, parent, template_scope, info) { constructor(component: Component, parent, template_scope, info) {
super(component, parent, template_scope, info); super(component, parent, template_scope, info);
@ -22,7 +21,7 @@ export default class EventHandler extends Node {
this.modifiers = new Set(info.modifiers); this.modifiers = new Set(info.modifiers);
if (info.expression) { if (info.expression) {
this.expression = new Expression(component, this, template_scope, info.expression, true); this.expression = new Expression(component, this, template_scope, info.expression);
this.uses_context = this.expression.uses_context; this.uses_context = this.expression.uses_context;
if (/FunctionExpression/.test(info.expression.type) && info.expression.params.length === 0) { if (/FunctionExpression/.test(info.expression.type) && info.expression.params.length === 0) {
@ -42,34 +41,14 @@ export default class EventHandler extends Node {
if (node && (node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration' || node.type === 'ArrowFunctionExpression') && node.params.length === 0) { if (node && (node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration' || node.type === 'ArrowFunctionExpression') && node.params.length === 0) {
this.can_make_passive = true; this.can_make_passive = true;
} }
this.reassigned = component.var_lookup.get(info.expression.name).reassigned;
} }
} else if (this.expression.dynamic_dependencies().length > 0) {
this.reassigned = true;
} }
} else { } else {
const id = component.get_unique_name(`${sanitize(this.name)}_handler`); this.handler_name = component.get_unique_name(`${sanitize(this.name)}_handler`);
component.add_var({
name: id.name,
internal: true,
referenced: true
});
component.partly_hoisted.push(b`
function ${id}(event) {
@bubble($$self, event);
}
`);
this.handler_name = id;
} }
} }
// TODO move this? it is specific to render-dom
render(block: Block) {
if (this.expression) {
return this.expression.manipulate(block);
}
// this.component.add_reference(this.handler_name);
return x`#ctx.${this.handler_name}`;
}
} }

@ -15,7 +15,7 @@ export default class Transition extends Node {
component.warn_if_undefined(info.name, info, scope); component.warn_if_undefined(info.name, info, scope);
this.name = info.name; this.name = info.name;
component.qualify(info.name); component.add_reference(info.name.split('.')[0]);
this.directive = info.intro && info.outro ? 'transition' : info.intro ? 'in' : 'out'; this.directive = info.intro && info.outro ? 'transition' : info.intro ? 'in' : 'out';
this.is_local = info.modifiers.includes('local'); this.is_local = info.modifiers.includes('local');

@ -9,9 +9,9 @@ import TemplateScope from './TemplateScope';
import get_object from '../../utils/get_object'; import get_object from '../../utils/get_object';
import Block from '../../render_dom/Block'; import Block from '../../render_dom/Block';
import is_dynamic from '../../render_dom/wrappers/shared/is_dynamic'; import is_dynamic from '../../render_dom/wrappers/shared/is_dynamic';
import { x, b, p } from 'code-red'; import { b } from 'code-red';
import { invalidate } from '../../utils/invalidate'; import { invalidate } from '../../render_dom/invalidate';
import { Node, FunctionExpression } from 'estree'; import { Node, FunctionExpression, Identifier } from 'estree';
import { TemplateNode } from '../../../interfaces'; import { TemplateNode } from '../../../interfaces';
type Owner = Wrapper | TemplateNode; type Owner = Wrapper | TemplateNode;
@ -213,7 +213,8 @@ export default class Expression {
component.add_reference(name); // TODO is this redundant/misplaced? component.add_reference(name); // TODO is this redundant/misplaced?
} }
} else if (is_contextual(component, template_scope, name)) { } else if (is_contextual(component, template_scope, name)) {
this.replace(x`#ctx.${node}`); const reference = block.renderer.reference(node);
this.replace(reference);
} }
this.skip(); this.skip();
@ -260,42 +261,38 @@ export default class Expression {
// function can be hoisted inside the component init // function can be hoisted inside the component init
component.partly_hoisted.push(declaration); component.partly_hoisted.push(declaration);
this.replace(x`#ctx.${id}` as any); block.renderer.add_to_context(id.name);
this.replace(block.renderer.reference(id));
component.add_var({
name: id.name,
internal: true,
referenced: true
});
} }
else { else {
// we need a combo block/init recipe // we need a combo block/init recipe
(node as FunctionExpression).params.unshift({ const deps = Array.from(contextual_dependencies);
type: 'ObjectPattern',
properties: Array.from(contextual_dependencies).map(name => p`${name}` as any) (node as FunctionExpression).params = [
}); ...deps.map(name => ({ type: 'Identifier', name } as Identifier)),
...(node as FunctionExpression).params
];
const context_args = deps.map(name => block.renderer.reference(name));
component.partly_hoisted.push(declaration); component.partly_hoisted.push(declaration);
this.replace(id as any); block.renderer.add_to_context(id.name);
const callee = block.renderer.reference(id);
component.add_var({ this.replace(id as any);
name: id.name,
internal: true,
referenced: true
});
if ((node as FunctionExpression).params.length > 0) { if ((node as FunctionExpression).params.length > 0) {
declarations.push(b` declarations.push(b`
function ${id}(...args) { function ${id}(...args) {
return #ctx.${id}(#ctx, ...args); return ${callee}(${context_args}, ...args);
} }
`); `);
} else { } else {
declarations.push(b` declarations.push(b`
function ${id}() { function ${id}() {
return #ctx.${id}(#ctx); return ${callee}(${context_args});
} }
`); `);
} }
@ -329,7 +326,7 @@ export default class Expression {
} }
}); });
this.replace(invalidate(component, scope, node, traced)); this.replace(invalidate(block.renderer, scope, node, traced));
} }
} }
}); });

@ -1,7 +1,7 @@
import Renderer from './Renderer'; import Renderer from './Renderer';
import Wrapper from './wrappers/shared/Wrapper'; import Wrapper from './wrappers/shared/Wrapper';
import { b, x } from 'code-red'; import { b, x } from 'code-red';
import { Node, Identifier } from 'estree'; import { Node, Identifier, ArrayPattern } from 'estree';
import { is_head } from './wrappers/shared/is_head'; import { is_head } from './wrappers/shared/is_head';
export interface BlockOptions { export interface BlockOptions {
@ -34,7 +34,7 @@ export default class Block {
key: Identifier; key: Identifier;
first: Identifier; first: Identifier;
dependencies: Set<string>; dependencies: Set<string> = new Set();
bindings: Map<string, { bindings: Map<string, {
object: Identifier; object: Identifier;
@ -90,8 +90,6 @@ export default class Block {
this.key = options.key; this.key = options.key;
this.first = null; this.first = null;
this.dependencies = new Set();
this.bindings = options.bindings; this.bindings = options.bindings;
this.chunks = { this.chunks = {
@ -203,13 +201,11 @@ export default class Block {
} }
add_variable(id: Identifier, init?: Node) { add_variable(id: Identifier, init?: Node) {
this.variables.forEach(v => { if (this.variables.has(id.name)) {
if (v.id.name === id.name) { throw new Error(
throw new Error( `Variable '${id.name}' already initialised with a different value`
`Variable '${id.name}' already initialised with a different value` );
); }
}
});
this.variables.set(id.name, { id, init }); this.variables.set(id.name, { id, init });
} }
@ -268,7 +264,7 @@ export default class Block {
: this.chunks.hydrate : this.chunks.hydrate
); );
properties.create = x`function create() { properties.create = x`function #create() {
${this.chunks.create} ${this.chunks.create}
${hydrate} ${hydrate}
}`; }`;
@ -278,7 +274,7 @@ export default class Block {
if (this.chunks.claim.length === 0 && this.chunks.hydrate.length === 0) { if (this.chunks.claim.length === 0 && this.chunks.hydrate.length === 0) {
properties.claim = noop; properties.claim = noop;
} else { } else {
properties.claim = x`function claim(#nodes) { properties.claim = x`function #claim(#nodes) {
${this.chunks.claim} ${this.chunks.claim}
${this.renderer.options.hydratable && this.chunks.hydrate.length > 0 && b`this.h();`} ${this.renderer.options.hydratable && this.chunks.hydrate.length > 0 && b`this.h();`}
}`; }`;
@ -286,7 +282,7 @@ export default class Block {
} }
if (this.renderer.options.hydratable && this.chunks.hydrate.length > 0) { if (this.renderer.options.hydratable && this.chunks.hydrate.length > 0) {
properties.hydrate = x`function hydrate() { properties.hydrate = x`function #hydrate() {
${this.chunks.hydrate} ${this.chunks.hydrate}
}`; }`;
} }
@ -294,7 +290,7 @@ export default class Block {
if (this.chunks.mount.length === 0) { if (this.chunks.mount.length === 0) {
properties.mount = noop; properties.mount = noop;
} else { } else {
properties.mount = x`function mount(#target, anchor) { properties.mount = x`function #mount(#target, anchor) {
${this.chunks.mount} ${this.chunks.mount}
}`; }`;
} }
@ -304,7 +300,13 @@ export default class Block {
properties.update = noop; properties.update = noop;
} else { } else {
const ctx = this.maintain_context ? x`#new_ctx` : x`#ctx`; const ctx = this.maintain_context ? x`#new_ctx` : x`#ctx`;
properties.update = x`function update(#changed, ${ctx}) {
let dirty: Identifier | ArrayPattern = { type: 'Identifier', name: '#dirty' };
if (!this.renderer.context_overflow && !this.parent) {
dirty = { type: 'ArrayPattern', elements: [dirty] };
}
properties.update = x`function #update(${ctx}, ${dirty}) {
${this.maintain_context && b`#ctx = ${ctx};`} ${this.maintain_context && b`#ctx = ${ctx};`}
${this.chunks.update} ${this.chunks.update}
}`; }`;
@ -312,15 +314,15 @@ export default class Block {
} }
if (this.has_animation) { if (this.has_animation) {
properties.measure = x`function measure() { properties.measure = x`function #measure() {
${this.chunks.measure} ${this.chunks.measure}
}`; }`;
properties.fix = x`function fix() { properties.fix = x`function #fix() {
${this.chunks.fix} ${this.chunks.fix}
}`; }`;
properties.animate = x`function animate() { properties.animate = x`function #animate() {
${this.chunks.animate} ${this.chunks.animate}
}`; }`;
} }
@ -329,7 +331,7 @@ export default class Block {
if (this.chunks.intro.length === 0) { if (this.chunks.intro.length === 0) {
properties.intro = noop; properties.intro = noop;
} else { } else {
properties.intro = x`function intro(#local) { properties.intro = x`function #intro(#local) {
${this.has_outros && b`if (#current) return;`} ${this.has_outros && b`if (#current) return;`}
${this.chunks.intro} ${this.chunks.intro}
}`; }`;
@ -338,7 +340,7 @@ export default class Block {
if (this.chunks.outro.length === 0) { if (this.chunks.outro.length === 0) {
properties.outro = noop; properties.outro = noop;
} else { } else {
properties.outro = x`function outro(#local) { properties.outro = x`function #outro(#local) {
${this.chunks.outro} ${this.chunks.outro}
}`; }`;
} }
@ -347,7 +349,7 @@ export default class Block {
if (this.chunks.destroy.length === 0) { if (this.chunks.destroy.length === 0) {
properties.destroy = noop; properties.destroy = noop;
} else { } else {
properties.destroy = x`function destroy(detaching) { properties.destroy = x`function #destroy(detaching) {
${this.chunks.destroy} ${this.chunks.destroy}
}`; }`;
} }
@ -376,6 +378,8 @@ export default class Block {
d: ${properties.destroy} d: ${properties.destroy}
}`; }`;
const block = dev && this.get_unique_name('block');
const body = b` const body = b`
${Array.from(this.variables.values()).map(({ id, init }) => { ${Array.from(this.variables.values()).map(({ id, init }) => {
return init return init
@ -387,9 +391,15 @@ export default class Block {
${dev ${dev
? b` ? b`
const block = ${return_value}; const ${block} = ${return_value};
@dispatch_dev("SvelteRegisterBlock", { block, id: ${this.name || 'create_fragment'}.name, type: "${this.type}", source: "${this.comment ? this.comment.replace(/"/g, '\\"') : ''}", ctx: #ctx }); @dispatch_dev("SvelteRegisterBlock", {
return block;` block: ${block},
id: ${this.name || 'create_fragment'}.name,
type: "${this.type}",
source: "${this.comment ? this.comment.replace(/"/g, '\\"') : ''}",
ctx: #ctx
});
return ${block};`
: b` : b`
return ${return_value};` return ${return_value};`
} }
@ -398,21 +408,36 @@ export default class Block {
return body; return body;
} }
has_content() {
return this.renderer.options.dev ||
this.first ||
this.event_listeners.length > 0 ||
this.chunks.intro.length > 0 ||
this.chunks.outro.length > 0 ||
this.chunks.create.length > 0 ||
this.chunks.hydrate.length > 0 ||
this.chunks.claim.length > 0 ||
this.chunks.mount.length > 0 ||
this.chunks.update.length > 0 ||
this.chunks.destroy.length > 0 ||
this.has_animation;
}
render() { render() {
const key = this.key && this.get_unique_name('key'); const key = this.key && this.get_unique_name('key');
const args: any[] = [x`#ctx`]; const args: any[] = [x`#ctx`];
if (key) args.unshift(key); if (key) args.unshift(key);
// TODO include this.comment const fn = b`function ${this.name}(${args}) {
// ${this.comment && `// ${escape(this.comment, { only_escape_at_symbol: true })}`} ${this.get_contents(key)}
}`;
return b` return this.comment
function ${this.name}(${args}) { ? b`
${this.get_contents(key)} // ${this.comment}
} ${fn}`
`; : fn;
} }
render_listeners(chunk: string = '') { render_listeners(chunk: string = '') {

@ -1,14 +1,27 @@
import Block from './Block'; import Block from './Block';
import { CompileOptions } from '../../interfaces'; import { CompileOptions, Var } from '../../interfaces';
import Component from '../Component'; import Component from '../Component';
import FragmentWrapper from './wrappers/Fragment'; import FragmentWrapper from './wrappers/Fragment';
import { x } from 'code-red'; import { x } from 'code-red';
import { Node, Identifier } from 'estree'; import { Node, Identifier, MemberExpression, Literal, Expression, BinaryExpression } from 'estree';
import flatten_reference from '../utils/flatten_reference';
interface ContextMember {
name: string;
index: Literal;
is_contextual: boolean;
is_non_contextual: boolean;
variable: Var;
priority: number;
}
export default class Renderer { export default class Renderer {
component: Component; // TODO Maybe Renderer shouldn't know about Component? component: Component; // TODO Maybe Renderer shouldn't know about Component?
options: CompileOptions; options: CompileOptions;
context: ContextMember[] = [];
context_lookup: Map<string, ContextMember> = new Map();
context_overflow: boolean;
blocks: Array<Block | Node | Node[]> = []; blocks: Array<Block | Node | Node[]> = [];
readonly: Set<string> = new Set(); readonly: Set<string> = new Set();
meta_bindings: Array<Node | Node[]> = []; // initial values for e.g. window.innerWidth, if there's a <svelte:window> meta tag meta_bindings: Array<Node | Node[]> = []; // initial values for e.g. window.innerWidth, if there's a <svelte:window> meta tag
@ -27,6 +40,24 @@ export default class Renderer {
this.file_var = options.dev && this.component.get_unique_name('file'); this.file_var = options.dev && this.component.get_unique_name('file');
component.vars.filter(v => !v.hoistable || (v.export_name && !v.module)).forEach(v => this.add_to_context(v.name));
// ensure store values are included in context
component.vars.filter(v => v.subscribable).forEach(v => this.add_to_context(`$${v.name}`));
if (component.var_lookup.has('$$props')) {
this.add_to_context('$$props');
}
if (component.slots.size > 0) {
this.add_to_context('$$scope');
this.add_to_context('$$slots');
}
if (this.binding_groups.length > 0) {
this.add_to_context('$$binding_groups');
}
// main block // main block
this.block = new Block({ this.block = new Block({
renderer: this, renderer: this,
@ -50,6 +81,8 @@ export default class Renderer {
null null
); );
this.context_overflow = this.context.length > 31;
// TODO messy // TODO messy
this.blocks.forEach(block => { this.blocks.forEach(block => {
if (block instanceof Block) { if (block instanceof Block) {
@ -60,5 +93,195 @@ export default class Renderer {
this.block.assign_variable_names(); this.block.assign_variable_names();
this.fragment.render(this.block, null, x`#nodes` as Identifier); this.fragment.render(this.block, null, x`#nodes` as Identifier);
this.context.forEach(member => {
const { variable } = member;
if (variable) {
member.priority += 2;
if (variable.mutated || variable.reassigned) member.priority += 4;
// these determine whether variable is included in initial context
// array, so must have the highest priority
if (variable.export_name) member.priority += 8;
if (variable.referenced) member.priority += 16;
}
if (!member.is_contextual) {
member.priority += 1;
}
});
this.context.sort((a, b) => (b.priority - a.priority) || ((a.index.value as number) - (b.index.value as number)));
this.context.forEach((member, i) => member.index.value = i);
}
add_to_context(name: string, contextual = false) {
if (!this.context_lookup.has(name)) {
const member: ContextMember = {
name,
index: { type: 'Literal', value: this.context.length }, // index is updated later, but set here to preserve order within groups
is_contextual: false,
is_non_contextual: false, // shadowed vars could be contextual and non-contextual
variable: null,
priority: 0
};
this.context_lookup.set(name, member);
this.context.push(member);
}
const member = this.context_lookup.get(name);
if (contextual) {
member.is_contextual = true;
} else {
member.is_non_contextual = true;
const variable = this.component.var_lookup.get(name);
member.variable = variable;
}
return member;
}
invalidate(name: string, value?) {
const variable = this.component.var_lookup.get(name);
const member = this.context_lookup.get(name);
if (variable && (variable.subscribable && (variable.reassigned || variable.export_name))) {
return x`${`$$subscribe_${name}`}($$invalidate(${member.index}, ${value || name}))`;
}
if (name[0] === '$' && name[1] !== '$') {
return x`${name.slice(1)}.set(${value || name})`;
}
if (
variable &&
!variable.referenced &&
!variable.is_reactive_dependency &&
!variable.export_name &&
!name.startsWith('$$')
) {
return value || name;
}
if (value) {
return x`$$invalidate(${member.index}, ${value})`;
}
// if this is a reactive declaration, invalidate dependencies recursively
const deps = new Set([name]);
deps.forEach(name => {
const reactive_declarations = this.component.reactive_declarations.filter(x =>
x.assignees.has(name)
);
reactive_declarations.forEach(declaration => {
declaration.dependencies.forEach(name => {
deps.add(name);
});
});
});
// TODO ideally globals etc wouldn't be here in the first place
const filtered = Array.from(deps).filter(n => this.context_lookup.has(n));
if (!filtered.length) return null;
return filtered
.map(n => x`$$invalidate(${this.context_lookup.get(n).index}, ${n})`)
.reduce((lhs, rhs) => x`${lhs}, ${rhs}}`);
}
dirty(names, is_reactive_declaration = false): Expression {
const renderer = this;
const dirty = (is_reactive_declaration
? x`$$self.$$.dirty`
: x`#dirty`) as Identifier | MemberExpression;
const get_bitmask = () => names.reduce((bitmask, name) => {
const member = renderer.context_lookup.get(name);
if (!member) return bitmask;
if (member.index.value === -1) {
throw new Error(`unset index`);
}
const value = member.index.value as number;
const i = (value / 31) | 0;
const n = 1 << (value % 31);
if (!bitmask[i]) bitmask[i] = { n: 0, names: [] };
bitmask[i].n |= n;
bitmask[i].names.push(name);
return bitmask;
}, Array((this.context.length / 31) | 0).fill(null));
let operator;
let left;
let right;
return {
get type() {
// we make the type a getter, even though it's always
// a BinaryExpression, because it gives us an opportunity
// to lazily create the node. TODO would be better if
// context was determined before rendering, so that
// this indirection was unnecessary
const bitmask = get_bitmask();
if (renderer.context_overflow) {
const expression = bitmask
.map((b, i) => ({ b, i }))
.filter(({ b }) => b)
.map(({ b, i }) => x`${dirty}[${i}] & /*${b.names.join(', ')}*/ ${b.n}`)
.reduce((lhs, rhs) => x`${lhs} | ${rhs}`);
({ operator, left, right } = expression);
} else {
({ operator, left, right } = x`${dirty} & /*${names.join(', ')}*/ ${bitmask[0] ? bitmask[0].n : 0}` as BinaryExpression); // TODO the `: 0` case should never apply
}
return 'BinaryExpression';
},
get operator() {
return operator;
},
get left() {
return left;
},
get right() {
return right;
}
} as Expression;
}
reference(node: string | Identifier | MemberExpression) {
if (typeof node === 'string') {
node = { type: 'Identifier', name: node };
}
const { name, nodes } = flatten_reference(node);
const member = this.context_lookup.get(name);
// TODO is this correct?
if (this.component.var_lookup.get(name)) {
this.component.add_reference(name);
}
if (member !== undefined) {
const replacement = x`/*${member.name}*/ #ctx[${member.index}]` as MemberExpression;
if (nodes[0].loc) replacement.object.loc = nodes[0].loc;
nodes[0] = replacement;
return nodes.reduce((lhs, rhs) => x`${lhs}.${rhs}`);
}
return node;
} }
} }

@ -3,11 +3,10 @@ import Component from '../Component';
import Renderer from './Renderer'; import Renderer from './Renderer';
import { CompileOptions, Var } from '../../interfaces'; import { CompileOptions, Var } from '../../interfaces';
import { walk } from 'estree-walker'; import { walk } from 'estree-walker';
import add_to_set from '../utils/add_to_set';
import { extract_names } from '../utils/scope'; import { extract_names } from '../utils/scope';
import { invalidate } from '../utils/invalidate'; import { invalidate } from './invalidate';
import Block from './Block'; import Block from './Block';
import { ClassDeclaration, FunctionExpression, Node, Statement, ObjectExpression } from 'estree'; import { ClassDeclaration, FunctionExpression, Node, Statement, ObjectExpression, Expression } from 'estree';
export default function dom( export default function dom(
component: Component, component: Component,
@ -76,20 +75,18 @@ export default function dom(
const props = component.vars.filter(variable => !variable.module && variable.export_name); const props = component.vars.filter(variable => !variable.module && variable.export_name);
const writable_props = props.filter(variable => variable.writable); const writable_props = props.filter(variable => variable.writable);
/* eslint-disable @typescript-eslint/indent,indent */
const set = (uses_props || writable_props.length > 0 || component.slots.size > 0) const set = (uses_props || writable_props.length > 0 || component.slots.size > 0)
? x` ? x`
${$$props} => { ${$$props} => {
${uses_props && component.invalidate('$$props', x`$$props = @assign(@assign({}, $$props), $$new_props)`)} ${uses_props && renderer.invalidate('$$props', x`$$props = @assign(@assign({}, $$props), @exclude_internal_props($$new_props))`)}
${writable_props.map(prop => ${writable_props.map(prop =>
b`if ('${prop.export_name}' in ${$$props}) ${component.invalidate(prop.name, x`${prop.name} = ${$$props}.${prop.export_name}`)};` b`if ('${prop.export_name}' in ${$$props}) ${renderer.invalidate(prop.name, x`${prop.name} = ${$$props}.${prop.export_name}`)};`
)} )}
${component.slots.size > 0 && ${component.slots.size > 0 &&
b`if ('$$scope' in ${$$props}) ${component.invalidate('$$scope', x`$$scope = ${$$props}.$$scope`)};`} b`if ('$$scope' in ${$$props}) ${renderer.invalidate('$$scope', x`$$scope = ${$$props}.$$scope`)};`}
} }
` `
: null; : null;
/* eslint-enable @typescript-eslint/indent,indent */
const accessors = []; const accessors = [];
@ -105,7 +102,7 @@ export default function dom(
kind: 'get', kind: 'get',
key: { type: 'Identifier', name: prop.export_name }, key: { type: 'Identifier', name: prop.export_name },
value: x`function() { value: x`function() {
return ${prop.hoistable ? prop.name : x`this.$$.ctx.${prop.name}`} return ${prop.hoistable ? prop.name : x`this.$$.ctx[${renderer.context_lookup.get(prop.name).index}]`}
}` }`
}); });
} else if (component.compile_options.dev) { } else if (component.compile_options.dev) {
@ -154,14 +151,14 @@ export default function dom(
if (component.compile_options.dev) { if (component.compile_options.dev) {
// checking that expected ones were passed // checking that expected ones were passed
const expected = props.filter(prop => !prop.initialised); const expected = props.filter(prop => prop.writable && !prop.initialised);
if (expected.length) { if (expected.length) {
dev_props_check = b` dev_props_check = b`
const { ctx: #ctx } = this.$$; const { ctx: #ctx } = this.$$;
const props = ${options.customElement ? x`this.attributes` : x`options.props || {}`}; const props = ${options.customElement ? x`this.attributes` : x`options.props || {}`};
${expected.map(prop => b` ${expected.map(prop => b`
if (#ctx.${prop.name} === undefined && !('${prop.export_name}' in props)) { if (${renderer.reference(prop.name)} === undefined && !('${prop.export_name}' in props)) {
@_console.warn("<${component.tag}> was created without expected prop '${prop.export_name}'"); @_console.warn("<${component.tag}> was created without expected prop '${prop.export_name}'");
}`)} }`)}
`; `;
@ -192,9 +189,9 @@ export default function dom(
inject_state = x` inject_state = x`
${$$props} => { ${$$props} => {
${uses_props && component.invalidate('$$props', x`$$props = @assign(@assign({}, $$props), $$new_props)`)} ${uses_props && renderer.invalidate('$$props', x`$$props = @assign(@assign({}, $$props), $$new_props)`)}
${capturable_vars.map(prop => b` ${capturable_vars.map(prop => b`
if ('${prop.name}' in $$props) ${component.invalidate(prop.name, x`${prop.name} = ${$$props}.${prop.name}`)}; if ('${prop.name}' in $$props) ${renderer.invalidate(prop.name, x`${prop.name} = ${$$props}.${prop.name}`)};
`)} `)}
} }
`; `;
@ -230,17 +227,18 @@ export default function dom(
// onto the initial function call // onto the initial function call
const names = new Set(extract_names(assignee)); const names = new Set(extract_names(assignee));
this.replace(invalidate(component, scope, node, names)); this.replace(invalidate(renderer, scope, node, names));
} }
} }
}); });
component.rewrite_props(({ name, reassigned, export_name }) => { component.rewrite_props(({ name, reassigned, export_name }) => {
const value = `$${name}`; const value = `$${name}`;
const i = renderer.context_lookup.get(`$${name}`).index;
const insert = (reassigned || export_name) const insert = (reassigned || export_name)
? b`${`$$subscribe_${name}`}()` ? b`${`$$subscribe_${name}`}()`
: b`@component_subscribe($$self, ${name}, #value => $$invalidate('${value}', ${value} = #value))`; : b`@component_subscribe($$self, ${name}, #value => $$invalidate(${i}, ${value} = #value))`;
if (component.compile_options.dev) { if (component.compile_options.dev) {
return b`@validate_store(${name}, '${name}'); ${insert}`; return b`@validate_store(${name}, '${name}'); ${insert}`;
@ -255,22 +253,21 @@ export default function dom(
args.push(x`$$props`, x`$$invalidate`); args.push(x`$$props`, x`$$invalidate`);
} }
body.push(b` const has_create_fragment = block.has_content();
function create_fragment(#ctx) { if (has_create_fragment) {
${block.get_contents()} body.push(b`
} function create_fragment(#ctx) {
${block.get_contents()}
}
`);
}
body.push(b`
${component.extract_javascript(component.ast.module)} ${component.extract_javascript(component.ast.module)}
${component.fully_hoisted} ${component.fully_hoisted}
`); `);
const filtered_declarations = component.vars
.filter(v => ((v.referenced || v.export_name) && !v.hoistable))
.map(v => p`${v.name}`);
if (uses_props) filtered_declarations.push(p`$$props: $$props = @exclude_internal_props($$props)`);
const filtered_props = props.filter(prop => { const filtered_props = props.filter(prop => {
const variable = component.var_lookup.get(prop.name); const variable = component.var_lookup.get(prop.name);
@ -281,22 +278,25 @@ export default function dom(
const reactive_stores = component.vars.filter(variable => variable.name[0] === '$' && variable.name[1] !== '$'); const reactive_stores = component.vars.filter(variable => variable.name[0] === '$' && variable.name[1] !== '$');
if (component.slots.size > 0) { const instance_javascript = component.extract_javascript(component.ast.instance);
filtered_declarations.push(p`$$slots`, p`$$scope`);
}
if (renderer.binding_groups.length > 0) { let i = renderer.context.length;
filtered_declarations.push(p`$$binding_groups`); while (i--) {
const member = renderer.context[i];
if (member.variable) {
if (member.variable.referenced || member.variable.export_name) break;
} else if (member.is_non_contextual) {
break;
}
} }
const initial_context = renderer.context.slice(0, i + 1);
const instance_javascript = component.extract_javascript(component.ast.instance);
const has_definition = ( const has_definition = (
(instance_javascript && instance_javascript.length > 0) || (instance_javascript && instance_javascript.length > 0) ||
filtered_props.length > 0 || filtered_props.length > 0 ||
uses_props || uses_props ||
component.partly_hoisted.length > 0 || component.partly_hoisted.length > 0 ||
filtered_declarations.length > 0 || initial_context.length > 0 ||
component.reactive_declarations.length > 0 component.reactive_declarations.length > 0
); );
@ -304,11 +304,6 @@ export default function dom(
? component.alias('instance') ? component.alias('instance')
: { type: 'Literal', value: null }; : { type: 'Literal', value: null };
const all_reactive_dependencies = new Set();
component.reactive_declarations.forEach(d => {
add_to_set(all_reactive_dependencies, d.dependencies);
});
const reactive_store_subscriptions = reactive_stores const reactive_store_subscriptions = reactive_stores
.filter(store => { .filter(store => {
const variable = component.var_lookup.get(store.name.slice(1)); const variable = component.var_lookup.get(store.name.slice(1));
@ -316,7 +311,7 @@ export default function dom(
}) })
.map(({ name }) => b` .map(({ name }) => b`
${component.compile_options.dev && b`@validate_store(${name.slice(1)}, '${name.slice(1)}');`} ${component.compile_options.dev && b`@validate_store(${name.slice(1)}, '${name.slice(1)}');`}
@component_subscribe($$self, ${name.slice(1)}, $$value => $$invalidate('${name}', ${name} = $$value)); @component_subscribe($$self, ${name.slice(1)}, $$value => $$invalidate(${renderer.context_lookup.get(name).index}, ${name} = $$value));
`); `);
const resubscribable_reactive_store_unsubscribers = reactive_stores const resubscribable_reactive_store_unsubscribers = reactive_stores
@ -336,12 +331,10 @@ export default function dom(
const writable = dependencies.filter(n => { const writable = dependencies.filter(n => {
const variable = component.var_lookup.get(n); const variable = component.var_lookup.get(n);
return variable && (variable.writable || variable.mutated); return variable && (variable.export_name || variable.mutated || variable.reassigned);
}); });
const condition = !uses_props && writable.length > 0 && (writable const condition = !uses_props && writable.length > 0 && renderer.dirty(writable, true);
.map(n => x`#changed.${n}`)
.reduce((lhs, rhs) => x`${lhs} || ${rhs}`));
let statement = d.node; // TODO remove label (use d.node.body) if it's not referenced let statement = d.node; // TODO remove label (use d.node.body) if it's not referenced
@ -367,7 +360,9 @@ export default function dom(
if (store && (store.reassigned || store.export_name)) { if (store && (store.reassigned || store.export_name)) {
const unsubscribe = `$$unsubscribe_${name}`; const unsubscribe = `$$unsubscribe_${name}`;
const subscribe = `$$subscribe_${name}`; const subscribe = `$$subscribe_${name}`;
return b`let ${$name}, ${unsubscribe} = @noop, ${subscribe} = () => (${unsubscribe}(), ${unsubscribe} = @subscribe(${name}, $$value => $$invalidate('${$name}', ${$name} = $$value)), ${name})`; const i = renderer.context_lookup.get($name).index;
return b`let ${$name}, ${unsubscribe} = @noop, ${subscribe} = () => (${unsubscribe}(), ${unsubscribe} = @subscribe(${name}, $$value => $$invalidate(${i}, ${$name} = $$value)), ${name})`;
} }
return b`let ${$name};`; return b`let ${$name};`;
@ -378,26 +373,17 @@ export default function dom(
unknown_props_check = b` unknown_props_check = b`
const writable_props = [${writable_props.map(prop => x`'${prop.export_name}'`)}]; const writable_props = [${writable_props.map(prop => x`'${prop.export_name}'`)}];
@_Object.keys($$props).forEach(key => { @_Object.keys($$props).forEach(key => {
if (!writable_props.includes(key) && !key.startsWith('$$')) @_console.warn(\`<${component.tag}> was created with unknown prop '\${key}'\`); if (!~writable_props.indexOf(key) && key.slice(0, 2) !== '$$') @_console.warn(\`<${component.tag}> was created with unknown prop '\${key}'\`);
}); });
`; `;
} }
const return_value = { const return_value = {
type: 'ObjectExpression', type: 'ArrayExpression',
properties: filtered_declarations elements: initial_context.map(member => ({
}; type: 'Identifier',
name: member.name
const reactive_dependencies = { }) as Expression)
type: 'ObjectPattern',
properties: Array.from(all_reactive_dependencies).map(name => {
return {
type: 'Property',
kind: 'init',
key: { type: 'Identifier', name },
value: { type: 'Literal', value: 1 }
};
})
}; };
body.push(b` body.push(b`
@ -427,22 +413,32 @@ export default function dom(
${injected.map(name => b`let ${name};`)} ${injected.map(name => b`let ${name};`)}
${reactive_declarations.length > 0 && b` ${reactive_declarations.length > 0 && b`
$$self.$$.update = (#changed = ${reactive_dependencies}) => { $$self.$$.update = () => {
${reactive_declarations} ${reactive_declarations}
}; };
`} `}
${fixed_reactive_declarations} ${fixed_reactive_declarations}
${uses_props && b`$$props = @exclude_internal_props($$props);`}
return ${return_value}; return ${return_value};
} }
`); `);
} }
const prop_names = x`{ const prop_indexes = x`{
${props.map(v => p`${v.export_name}: ${v.export_name === v.name ? 0 : x`"${v.name}"`}}`)} ${props.filter(v => v.export_name && !v.module).map(v => p`${v.export_name}: ${renderer.context_lookup.get(v.name).index}`)}
}` as ObjectExpression; }` as ObjectExpression;
let dirty;
if (renderer.context_overflow) {
dirty = x`[]`;
for (let i = 0; i < renderer.context.length; i += 31) {
dirty.elements.push(x`-1`);
}
}
if (options.customElement) { if (options.customElement) {
const declaration = b` const declaration = b`
class ${name} extends @SvelteElement { class ${name} extends @SvelteElement {
@ -451,7 +447,7 @@ export default function dom(
${css.code && b`this.shadowRoot.innerHTML = \`<style>${css.code.replace(/\\/g, '\\\\')}${options.dev ? `\n/*# sourceMappingURL=${css.map.toUrl()} */` : ''}</style>\`;`} ${css.code && b`this.shadowRoot.innerHTML = \`<style>${css.code.replace(/\\/g, '\\\\')}${options.dev ? `\n/*# sourceMappingURL=${css.map.toUrl()} */` : ''}</style>\`;`}
@init(this, { target: this.shadowRoot }, ${definition}, create_fragment, ${not_equal}, ${prop_names}); @init(this, { target: this.shadowRoot }, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_indexes}, ${dirty});
${dev_props_check} ${dev_props_check}
@ -503,7 +499,7 @@ export default function dom(
constructor(options) { constructor(options) {
super(${options.dev && `options`}); super(${options.dev && `options`});
${should_add_css && b`if (!@_document.getElementById("${component.stylesheet.id}-style")) ${add_css}();`} ${should_add_css && b`if (!@_document.getElementById("${component.stylesheet.id}-style")) ${add_css}();`}
@init(this, options, ${definition}, create_fragment, ${not_equal}, ${prop_names}); @init(this, options, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_indexes}, ${dirty});
${options.dev && b`@dispatch_dev("SvelteRegisterComponent", { component: this, tagName: "${name.name}", options, id: create_fragment.name });`} ${options.dev && b`@dispatch_dev("SvelteRegisterComponent", { component: this, tagName: "${name.name}", options, id: create_fragment.name });`}
${dev_props_check} ${dev_props_check}

@ -1,10 +1,12 @@
import Component from '../Component';
import { nodes_match } from '../../utils/nodes_match'; import { nodes_match } from '../../utils/nodes_match';
import { Scope } from './scope'; import { Scope } from '../utils/scope';
import { x } from 'code-red'; import { x } from 'code-red';
import { Node } from 'estree'; import { Node } from 'estree';
import Renderer from './Renderer';
export function invalidate(renderer: Renderer, scope: Scope, node: Node, names: Set<string>) {
const { component } = renderer;
export function invalidate(component: Component, scope: Scope, node: Node, names: Set<string>) {
const [head, ...tail] = Array.from(names).filter(name => { const [head, ...tail] = Array.from(names).filter(name => {
const owner = scope.find_owner(name); const owner = scope.find_owner(name);
if (owner && owner !== component.instance_scope) return false; if (owner && owner !== component.instance_scope) return false;
@ -17,6 +19,7 @@ export function invalidate(component: Component, scope: Scope, node: Node, names
!variable.module && !variable.module &&
( (
variable.referenced || variable.referenced ||
variable.subscribable ||
variable.is_reactive_dependency || variable.is_reactive_dependency ||
variable.export_name || variable.export_name ||
variable.name[0] === '$' variable.name[0] === '$'
@ -28,12 +31,12 @@ export function invalidate(component: Component, scope: Scope, node: Node, names
component.has_reactive_assignments = true; component.has_reactive_assignments = true;
if (node.type === 'AssignmentExpression' && node.operator === '=' && nodes_match(node.left, node.right) && tail.length === 0) { if (node.type === 'AssignmentExpression' && node.operator === '=' && nodes_match(node.left, node.right) && tail.length === 0) {
return component.invalidate(head); return renderer.invalidate(head);
} else { } else {
const is_store_value = head[0] === '$'; const is_store_value = head[0] === '$';
const variable = component.var_lookup.get(head); const variable = component.var_lookup.get(head);
const extra_args = tail.map(name => component.invalidate(name)); const extra_args = tail.map(name => renderer.invalidate(name));
const pass_value = ( const pass_value = (
extra_args.length > 0 || extra_args.length > 0 ||
@ -48,8 +51,9 @@ export function invalidate(component: Component, scope: Scope, node: Node, names
}); });
} }
const callee = is_store_value ? `@set_store_value` : `$$invalidate`; let invalidate = is_store_value
let invalidate = x`${callee}(${is_store_value ? head.slice(1) : x`"${head}"`}, ${node}, ${extra_args})`; ? x`@set_store_value(${head.slice(1)}, ${node}, ${extra_args})`
: x`$$invalidate(${renderer.context_lookup.get(head).index}, ${node}, ${extra_args})`;
if (variable.subscribable && variable.reassigned) { if (variable.subscribable && variable.reassigned) {
const subscribe = `$$subscribe_${head}`; const subscribe = `$$subscribe_${head}`;

@ -8,7 +8,6 @@ import FragmentWrapper from './Fragment';
import PendingBlock from '../../nodes/PendingBlock'; import PendingBlock from '../../nodes/PendingBlock';
import ThenBlock from '../../nodes/ThenBlock'; import ThenBlock from '../../nodes/ThenBlock';
import CatchBlock from '../../nodes/CatchBlock'; import CatchBlock from '../../nodes/CatchBlock';
import { changed } from './shared/changed';
import { Identifier } from 'estree'; import { Identifier } from 'estree';
class AwaitBlockBranch extends Wrapper { class AwaitBlockBranch extends Wrapper {
@ -119,6 +118,9 @@ export default class AwaitBlockWrapper extends Wrapper {
if (has_outros) { if (has_outros) {
block.add_outro(); block.add_outro();
} }
if (this.node.value) block.renderer.add_to_context(this.node.value, true);
if (this.node.error) block.renderer.add_to_context(this.node.error, true);
} }
render( render(
@ -138,6 +140,9 @@ export default class AwaitBlockWrapper extends Wrapper {
block.maintain_context = true; block.maintain_context = true;
const value_index = this.node.value && block.renderer.context_lookup.get(this.node.value).index;
const error_index = this.node.error && block.renderer.context_lookup.get(this.node.error).index;
const info_props: any = x`{ const info_props: any = x`{
ctx: #ctx, ctx: #ctx,
current: null, current: null,
@ -145,8 +150,8 @@ export default class AwaitBlockWrapper extends Wrapper {
pending: ${this.pending.block.name}, pending: ${this.pending.block.name},
then: ${this.then.block.name}, then: ${this.then.block.name},
catch: ${this.catch.block.name}, catch: ${this.catch.block.name},
value: ${this.then.block.name && x`"${this.node.value}"`}, value: ${value_index},
error: ${this.catch.block.name && x`"${this.node.error}"`}, error: ${error_index},
blocks: ${this.pending.block.has_outro_method && x`[,,,]`} blocks: ${this.pending.block.has_outro_method && x`[,,,]`}
}`; }`;
@ -187,7 +192,7 @@ export default class AwaitBlockWrapper extends Wrapper {
if (dependencies.length > 0) { if (dependencies.length > 0) {
const condition = x` const condition = x`
${changed(dependencies)} && ${block.renderer.dirty(dependencies)} &&
${promise} !== (${promise} = ${snippet}) && ${promise} !== (${promise} = ${snippet}) &&
@handle_promise(${promise}, ${info})`; @handle_promise(${promise}, ${info})`;
@ -198,9 +203,11 @@ export default class AwaitBlockWrapper extends Wrapper {
if (this.pending.block.has_update_method) { if (this.pending.block.has_update_method) {
block.chunks.update.push(b` block.chunks.update.push(b`
if (${condition}) { if (${condition}) {
// nothing
} else { } else {
${info}.block.p(#changed, @assign(@assign({}, #ctx), ${info}.resolved)); const #child_ctx = #ctx.slice();
#child_ctx[${value_index}] = ${info}.resolved;
${info}.block.p(#child_ctx, #dirty);
} }
`); `);
} else { } else {
@ -211,7 +218,11 @@ export default class AwaitBlockWrapper extends Wrapper {
} else { } else {
if (this.pending.block.has_update_method) { if (this.pending.block.has_update_method) {
block.chunks.update.push(b` block.chunks.update.push(b`
${info}.block.p(#changed, @assign(@assign({}, #ctx), ${info}.resolved)); {
const #child_ctx = #ctx.slice();
#child_ctx[${value_index}] = ${info}.resolved;
${info}.block.p(#child_ctx, #dirty);
}
`); `);
} }
} }

@ -3,21 +3,24 @@ import Wrapper from './shared/Wrapper';
import { b } from 'code-red'; import { b } from 'code-red';
import Body from '../../nodes/Body'; import Body from '../../nodes/Body';
import { Identifier } from 'estree'; import { Identifier } from 'estree';
import EventHandler from './Element/EventHandler';
export default class BodyWrapper extends Wrapper { export default class BodyWrapper extends Wrapper {
node: Body; node: Body;
render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) { render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) {
this.node.handlers.forEach(handler => { this.node.handlers
const snippet = handler.render(block); .map(handler => new EventHandler(handler, this))
.forEach(handler => {
const snippet = handler.get_snippet(block);
block.chunks.init.push(b` block.chunks.init.push(b`
@_document.body.addEventListener("${handler.name}", ${snippet}); @_document.body.addEventListener("${handler.node.name}", ${snippet});
`); `);
block.chunks.destroy.push(b` block.chunks.destroy.push(b`
@_document.body.removeEventListener("${handler.name}", ${snippet}); @_document.body.removeEventListener("${handler.node.name}", ${snippet});
`); `);
}); });
} }
} }

@ -5,7 +5,6 @@ import DebugTag from '../../nodes/DebugTag';
import add_to_set from '../../utils/add_to_set'; import add_to_set from '../../utils/add_to_set';
import { b, p } from 'code-red'; import { b, p } from 'code-red';
import { Identifier, DebuggerStatement } from 'estree'; import { Identifier, DebuggerStatement } from 'estree';
import { changed } from './shared/changed';
export default class DebugTagWrapper extends Wrapper { export default class DebugTagWrapper extends Wrapper {
node: DebugTag; node: DebugTag;
@ -60,17 +59,17 @@ export default class DebugTagWrapper extends Wrapper {
const variable = var_lookup.get(e.node.name); const variable = var_lookup.get(e.node.name);
return !(variable && variable.hoistable); return !(variable && variable.hoistable);
}) })
.map(e => p`${e.node.name}`); .map(e => e.node.name);
const logged_identifiers = this.node.expressions.map(e => p`${e.node.name}`); const logged_identifiers = this.node.expressions.map(e => p`${e.node.name}`);
const debug_statements = b` const debug_statements = b`
const { ${contextual_identifiers} } = #ctx; ${contextual_identifiers.map(name => b`const ${name} = ${renderer.reference(name)};`)}
@_console.${log}({ ${logged_identifiers} }); @_console.${log}({ ${logged_identifiers} });
debugger;`; debugger;`;
if (dependencies.size) { if (dependencies.size) {
const condition = changed(Array.from(dependencies)); const condition = renderer.dirty(Array.from(dependencies));
block.chunks.update.push(b` block.chunks.update.push(b`
if (${condition}) { if (${condition}) {

@ -7,7 +7,6 @@ import FragmentWrapper from './Fragment';
import { b, x } from 'code-red'; import { b, x } from 'code-red';
import ElseBlock from '../../nodes/ElseBlock'; import ElseBlock from '../../nodes/ElseBlock';
import { Identifier, Node } from 'estree'; import { Identifier, Node } from 'estree';
import { changed } from './shared/changed';
export class ElseBlockWrapper extends Wrapper { export class ElseBlockWrapper extends Wrapper {
node: ElseBlock; node: ElseBlock;
@ -81,6 +80,10 @@ export default class EachBlockWrapper extends Wrapper {
const { dependencies } = node.expression; const { dependencies } = node.expression;
block.add_dependencies(dependencies); block.add_dependencies(dependencies);
this.node.contexts.forEach(context => {
renderer.add_to_context(context.key.name, true);
});
this.block = block.child({ this.block = block.child({
comment: create_debugging_comment(this.node, this.renderer.component), comment: create_debugging_comment(this.node, this.renderer.component),
name: renderer.component.get_unique_name('create_each_block'), name: renderer.component.get_unique_name('create_each_block'),
@ -119,6 +122,9 @@ export default class EachBlockWrapper extends Wrapper {
const each_block_value = renderer.component.get_unique_name(`${this.var.name}_value`); const each_block_value = renderer.component.get_unique_name(`${this.var.name}_value`);
const iterations = block.get_unique_name(`${this.var.name}_blocks`); const iterations = block.get_unique_name(`${this.var.name}_blocks`);
renderer.add_to_context(each_block_value.name, true);
renderer.add_to_context(this.index_name.name, true);
this.vars = { this.vars = {
create_each_block: this.block.name, create_each_block: this.block.name,
each_block_value, each_block_value,
@ -190,18 +196,19 @@ export default class EachBlockWrapper extends Wrapper {
? !this.next.is_dom_node() : ? !this.next.is_dom_node() :
!parent_node || !this.parent.is_dom_node(); !parent_node || !this.parent.is_dom_node();
this.context_props = this.node.contexts.map(prop => b`child_ctx.${prop.key.name} = ${prop.modifier(x`list[i]`)};`); this.context_props = this.node.contexts.map(prop => b`child_ctx[${renderer.context_lookup.get(prop.key.name).index}] = ${prop.modifier(x`list[i]`)};`);
if (this.node.has_binding) this.context_props.push(b`child_ctx.${this.vars.each_block_value} = list;`); if (this.node.has_binding) this.context_props.push(b`child_ctx[${renderer.context_lookup.get(this.vars.each_block_value.name).index}] = list;`);
if (this.node.has_binding || this.node.index) this.context_props.push(b`child_ctx.${this.index_name} = i;`); if (this.node.has_binding || this.node.index) this.context_props.push(b`child_ctx[${renderer.context_lookup.get(this.index_name.name).index}] = i;`);
const snippet = this.node.expression.manipulate(block); const snippet = this.node.expression.manipulate(block);
block.chunks.init.push(b`let ${this.vars.each_block_value} = ${snippet};`); block.chunks.init.push(b`let ${this.vars.each_block_value} = ${snippet};`);
// TODO which is better — Object.create(array) or array.slice()?
renderer.blocks.push(b` renderer.blocks.push(b`
function ${this.vars.get_each_context}(#ctx, list, i) { function ${this.vars.get_each_context}(#ctx, list, i) {
const child_ctx = @_Object.create(#ctx); const child_ctx = #ctx.slice();
${this.context_props} ${this.context_props}
return child_ctx; return child_ctx;
} }
@ -270,7 +277,7 @@ export default class EachBlockWrapper extends Wrapper {
if (this.else.block.has_update_method) { if (this.else.block.has_update_method) {
block.chunks.update.push(b` block.chunks.update.push(b`
if (!${this.vars.data_length} && ${each_block_else}) { if (!${this.vars.data_length} && ${each_block_else}) {
${each_block_else}.p(#changed, #ctx); ${each_block_else}.p(#ctx, #dirty);
} else if (!${this.vars.data_length}) { } else if (!${this.vars.data_length}) {
${each_block_else} = ${this.else.block.name}(#ctx); ${each_block_else} = ${this.else.block.name}(#ctx);
${each_block_else}.c(); ${each_block_else}.c();
@ -396,7 +403,7 @@ export default class EachBlockWrapper extends Wrapper {
${this.block.has_outros && b`@group_outros();`} ${this.block.has_outros && b`@group_outros();`}
${this.node.has_animation && b`for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].r();`} ${this.node.has_animation && b`for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].r();`}
${iterations} = @update_keyed_each(${iterations}, #changed, ${get_key}, ${dynamic ? 1 : 0}, #ctx, ${this.vars.each_block_value}, ${lookup}, ${update_mount_node}, ${destroy}, ${create_each_block}, ${update_anchor_node}, ${this.vars.get_each_context}); ${iterations} = @update_keyed_each(${iterations}, #dirty, ${get_key}, ${dynamic ? 1 : 0}, #ctx, ${this.vars.each_block_value}, ${lookup}, ${update_mount_node}, ${destroy}, ${create_each_block}, ${update_anchor_node}, ${this.vars.get_each_context});
${this.node.has_animation && b`for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].a();`} ${this.node.has_animation && b`for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].a();`}
${this.block.has_outros && b`@check_outros();`} ${this.block.has_outros && b`@check_outros();`}
`); `);
@ -469,9 +476,8 @@ export default class EachBlockWrapper extends Wrapper {
} }
`); `);
const all_dependencies = new Set(this.block.dependencies); const all_dependencies = new Set(this.block.dependencies); // TODO should be dynamic deps only
const { dependencies } = this.node.expression; this.node.expression.dynamic_dependencies().forEach((dependency: string) => {
dependencies.forEach((dependency: string) => {
all_dependencies.add(dependency); all_dependencies.add(dependency);
}); });
@ -481,7 +487,7 @@ export default class EachBlockWrapper extends Wrapper {
const for_loop_body = this.block.has_update_method const for_loop_body = this.block.has_update_method
? b` ? b`
if (${iterations}[#i]) { if (${iterations}[#i]) {
${iterations}[#i].p(#changed, child_ctx); ${iterations}[#i].p(child_ctx, #dirty);
${has_transitions && b`@transition_in(${this.vars.iterations}[#i], 1);`} ${has_transitions && b`@transition_in(${this.vars.iterations}[#i], 1);`}
} else { } else {
${iterations}[#i] = ${create_each_block}(child_ctx); ${iterations}[#i] = ${create_each_block}(child_ctx);
@ -554,7 +560,7 @@ export default class EachBlockWrapper extends Wrapper {
`; `;
block.chunks.update.push(b` block.chunks.update.push(b`
if (${changed(Array.from(all_dependencies))}) { if (${block.renderer.dirty(Array.from(all_dependencies))}) {
${update} ${update}
} }
`); `);

@ -6,8 +6,6 @@ import { string_literal } from '../../../utils/stringify';
import { b, x } from 'code-red'; import { b, x } from 'code-red';
import Expression from '../../../nodes/shared/Expression'; import Expression from '../../../nodes/shared/Expression';
import Text from '../../../nodes/Text'; import Text from '../../../nodes/Text';
import { changed } from '../shared/changed';
import { Literal } from 'estree';
export default class AttributeWrapper { export default class AttributeWrapper {
node: Attribute; node: Attribute;
@ -72,89 +70,81 @@ export default class AttributeWrapper {
const is_legacy_input_type = element.renderer.component.compile_options.legacy && name === 'type' && this.parent.node.name === 'input'; const is_legacy_input_type = element.renderer.component.compile_options.legacy && name === 'type' && this.parent.node.name === 'input';
const dependencies = this.node.get_dependencies(); const dependencies = this.node.get_dependencies();
if (dependencies.length > 0) { const value = this.get_value(block);
let value;
// TODO some of this code is repeated in Tag.ts — would be good to
// DRY it out if that's possible without introducing crazy indirection
if (this.node.chunks.length === 1) {
// single {tag} — may be a non-string
value = (this.node.chunks[0] as Expression).manipulate(block);
} else {
value = this.node.name === 'class'
? this.get_class_name_text()
: this.render_chunks().reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
// '{foo} {bar}' — treat as string concatenation
if (this.node.chunks[0].type !== 'Text') {
value = x`"" + ${value}`;
}
}
const is_select_value_attribute = const is_src = this.node.name === 'src'; // TODO retire this exception in favour of https://github.com/sveltejs/svelte/issues/3750
name === 'value' && element.node.name === 'select'; const is_select_value_attribute =
name === 'value' && element.node.name === 'select';
const should_cache = is_select_value_attribute; // TODO is this necessary? const should_cache = is_src || this.node.should_cache() || is_select_value_attribute; // TODO is this necessary?
const last = should_cache && block.get_unique_name( const last = should_cache && block.get_unique_name(
`${element.var.name}_${name.replace(/[^a-zA-Z_$]/g, '_')}_value` `${element.var.name}_${name.replace(/[^a-zA-Z_$]/g, '_')}_value`
); );
if (should_cache) block.add_variable(last); if (should_cache) block.add_variable(last);
let updater; let updater;
const init = should_cache ? x`${last} = ${value}` : value; const init = should_cache ? x`${last} = ${value}` : value;
if (is_legacy_input_type) { if (is_legacy_input_type) {
block.chunks.hydrate.push( block.chunks.hydrate.push(
b`@set_input_type(${element.var}, ${init});` b`@set_input_type(${element.var}, ${init});`
); );
updater = b`@set_input_type(${element.var}, ${should_cache ? last : value});`; updater = b`@set_input_type(${element.var}, ${should_cache ? last : value});`;
} else if (is_select_value_attribute) { } else if (is_select_value_attribute) {
// annoying special case // annoying special case
const is_multiple_select = element.node.get_static_attribute_value('multiple'); const is_multiple_select = element.node.get_static_attribute_value('multiple');
const i = block.get_unique_name('i'); const i = block.get_unique_name('i');
const option = block.get_unique_name('option'); const option = block.get_unique_name('option');
const if_statement = is_multiple_select const if_statement = is_multiple_select
? b` ? b`
${option}.selected = ~${last}.indexOf(${option}.__value);` ${option}.selected = ~${last}.indexOf(${option}.__value);`
: b` : b`
if (${option}.__value === ${last}) { if (${option}.__value === ${last}) {
${option}.selected = true; ${option}.selected = true;
${{ type: 'BreakStatement' }}; ${{ type: 'BreakStatement' }};
}`; // TODO the BreakStatement is gross, but it's unsyntactic otherwise... }`; // TODO the BreakStatement is gross, but it's unsyntactic otherwise...
updater = b` updater = b`
for (var ${i} = 0; ${i} < ${element.var}.options.length; ${i} += 1) { for (var ${i} = 0; ${i} < ${element.var}.options.length; ${i} += 1) {
var ${option} = ${element.var}.options[${i}]; var ${option} = ${element.var}.options[${i}];
${if_statement} ${if_statement}
} }
`; `;
block.chunks.mount.push(b` block.chunks.mount.push(b`
${last} = ${value}; ${last} = ${value};
${updater} ${updater}
`); `);
} else if (property_name) { } else if (is_src) {
block.chunks.hydrate.push( block.chunks.hydrate.push(
b`${element.var}.${property_name} = ${init};` b`if (${element.var}.src !== ${init}) ${method}(${element.var}, "${name}", ${last});`
); );
updater = block.renderer.options.dev updater = b`${method}(${element.var}, "${name}", ${should_cache ? last : value});`;
? b`@prop_dev(${element.var}, "${property_name}", ${should_cache ? last : value});` } else if (property_name) {
: b`${element.var}.${property_name} = ${should_cache ? last : value};`; block.chunks.hydrate.push(
} else { b`${element.var}.${property_name} = ${init};`
block.chunks.hydrate.push( );
b`${method}(${element.var}, "${name}", ${init});` updater = block.renderer.options.dev
); ? b`@prop_dev(${element.var}, "${property_name}", ${should_cache ? last : value});`
updater = b`${method}(${element.var}, "${name}", ${should_cache ? last : value});`; : b`${element.var}.${property_name} = ${should_cache ? last : value};`;
} } else {
block.chunks.hydrate.push(
b`${method}(${element.var}, "${name}", ${init});`
);
updater = b`${method}(${element.var}, "${name}", ${should_cache ? last : value});`;
}
let condition = changed(dependencies); if (dependencies.length > 0) {
let condition = block.renderer.dirty(dependencies);
if (should_cache) { if (should_cache) {
condition = x`${condition} && (${last} !== (${last} = ${value}))`; condition = is_src
? x`${condition} && (${element.var}.src !== (${last} = ${value}))`
: x`${condition} && (${last} !== (${last} = ${value}))`;
} }
if (block.has_outros) { if (block.has_outros) {
@ -165,23 +155,11 @@ export default class AttributeWrapper {
if (${condition}) { if (${condition}) {
${updater} ${updater}
}`); }`);
} else { }
const value = this.node.get_value(block);
const statement = (
is_legacy_input_type
? b`@set_input_type(${element.var}, ${value});`
: property_name
? b`${element.var}.${property_name} = ${value};`
: b`${method}(${element.var}, "${name}", ${value.type === 'Literal' && (value as Literal).value === true ? x`""` : value});`
);
block.chunks.hydrate.push(statement);
// special case autofocus. has to be handled in a bit of a weird way // special case autofocus. has to be handled in a bit of a weird way
if (this.node.is_true && name === 'autofocus') { if (this.node.is_true && name === 'autofocus') {
block.autofocus = element.var; block.autofocus = element.var;
}
} }
if (is_indirectly_bound_value) { if (is_indirectly_bound_value) {
@ -199,9 +177,39 @@ export default class AttributeWrapper {
return metadata; return metadata;
} }
get_class_name_text() { get_value(block) {
if (this.node.is_true) {
const metadata = this.get_metadata();
if (metadata && boolean_attribute.has(metadata.property_name.toLowerCase())) {
return x`true`;
}
return x`""`;
}
if (this.node.chunks.length === 0) return x`""`;
// TODO some of this code is repeated in Tag.ts — would be good to
// DRY it out if that's possible without introducing crazy indirection
if (this.node.chunks.length === 1) {
return this.node.chunks[0].type === 'Text'
? string_literal((this.node.chunks[0] as Text).data)
: (this.node.chunks[0] as Expression).manipulate(block);
}
let value = this.node.name === 'class'
? this.get_class_name_text(block)
: this.render_chunks(block).reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
// '{foo} {bar}' — treat as string concatenation
if (this.node.chunks[0].type !== 'Text') {
value = x`"" + ${value}`;
}
return value;
}
get_class_name_text(block) {
const scoped_css = this.node.chunks.some((chunk: Text) => chunk.synthetic); const scoped_css = this.node.chunks.some((chunk: Text) => chunk.synthetic);
const rendered = this.render_chunks(); const rendered = this.render_chunks(block);
if (scoped_css && rendered.length === 2) { if (scoped_css && rendered.length === 2) {
// we have a situation like class={possiblyUndefined} // we have a situation like class={possiblyUndefined}
@ -211,13 +219,13 @@ export default class AttributeWrapper {
return rendered.reduce((lhs, rhs) => x`${lhs} + ${rhs}`); return rendered.reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
} }
render_chunks() { render_chunks(block: Block) {
return this.node.chunks.map((chunk) => { return this.node.chunks.map((chunk) => {
if (chunk.type === 'Text') { if (chunk.type === 'Text') {
return string_literal(chunk.data); return string_literal(chunk.data);
} }
return chunk.manipulate(); return chunk.manipulate(block);
}); });
} }
@ -292,3 +300,32 @@ Object.keys(attribute_lookup).forEach(name => {
const metadata = attribute_lookup[name]; const metadata = attribute_lookup[name];
if (!metadata.property_name) metadata.property_name = name; if (!metadata.property_name) metadata.property_name = name;
}); });
// source: https://html.spec.whatwg.org/multipage/indices.html
const boolean_attribute = new Set([
'allowfullscreen',
'allowpaymentrequest',
'async',
'autofocus',
'autoplay',
'checked',
'controls',
'default',
'defer',
'disabled',
'formnovalidate',
'hidden',
'ismap',
'itemscope',
'loop',
'multiple',
'muted',
'nomodule',
'novalidate',
'open',
'playsinline',
'readonly',
'required',
'reversed',
'selected'
]);

@ -6,7 +6,6 @@ import Block from '../../Block';
import Renderer from '../../Renderer'; import Renderer from '../../Renderer';
import flatten_reference from '../../../utils/flatten_reference'; import flatten_reference from '../../../utils/flatten_reference';
import EachBlock from '../../../nodes/EachBlock'; import EachBlock from '../../../nodes/EachBlock';
import { changed } from '../shared/changed';
import { Node, Identifier } from 'estree'; import { Node, Identifier } from 'estree';
export default class BindingWrapper { export default class BindingWrapper {
@ -86,11 +85,12 @@ export default class BindingWrapper {
const { parent } = this; const { parent } = this;
const update_conditions: any[] = this.needs_lock ? [x`!${lock}`] : []; const update_conditions: any[] = this.needs_lock ? [x`!${lock}`] : [];
const mount_conditions: any[] = [];
const dependency_array = [...this.node.expression.dependencies]; const dependency_array = [...this.node.expression.dependencies];
if (dependency_array.length > 0) { if (dependency_array.length > 0) {
update_conditions.push(changed(dependency_array)); update_conditions.push(block.renderer.dirty(dependency_array));
} }
if (parent.node.name === 'input') { if (parent.node.name === 'input') {
@ -103,6 +103,7 @@ export default class BindingWrapper {
// model to view // model to view
let update_dom = get_dom_updater(parent, this); let update_dom = get_dom_updater(parent, this);
let mount_dom = update_dom;
// special cases // special cases
switch (this.node.name) { switch (this.node.name) {
@ -110,28 +111,38 @@ export default class BindingWrapper {
{ {
const binding_group = get_binding_group(parent.renderer, this.node.expression.node); const binding_group = get_binding_group(parent.renderer, this.node.expression.node);
block.renderer.add_to_context(`$$binding_groups`);
const reference = block.renderer.reference(`$$binding_groups`);
block.chunks.hydrate.push( block.chunks.hydrate.push(
b`#ctx.$$binding_groups[${binding_group}].push(${parent.var});` b`${reference}[${binding_group}].push(${parent.var});`
); );
block.chunks.destroy.push( block.chunks.destroy.push(
b`#ctx.$$binding_groups[${binding_group}].splice(#ctx.$$binding_groups[${binding_group}].indexOf(${parent.var}), 1);` b`${reference}[${binding_group}].splice(${reference}[${binding_group}].indexOf(${parent.var}), 1);`
); );
break; break;
} }
case 'textContent': case 'textContent':
update_conditions.push(x`${this.snippet} !== ${parent.var}.textContent`); update_conditions.push(x`${this.snippet} !== ${parent.var}.textContent`);
mount_conditions.push(x`${this.snippet} !== void 0`);
break; break;
case 'innerHTML': case 'innerHTML':
update_conditions.push(x`${this.snippet} !== ${parent.var}.innerHTML`); update_conditions.push(x`${this.snippet} !== ${parent.var}.innerHTML`);
mount_conditions.push(x`${this.snippet} !== void 0`);
break; break;
case 'currentTime': case 'currentTime':
update_conditions.push(x`!@_isNaN(${this.snippet})`);
mount_dom = null;
break;
case 'playbackRate': case 'playbackRate':
case 'volume': case 'volume':
update_conditions.push(x`!@_isNaN(${this.snippet})`); update_conditions.push(x`!@_isNaN(${this.snippet})`);
mount_conditions.push(x`!@_isNaN(${this.snippet})`);
break; break;
case 'paused': case 'paused':
@ -142,12 +153,14 @@ export default class BindingWrapper {
update_conditions.push(x`${last} !== (${last} = ${this.snippet})`); update_conditions.push(x`${last} !== (${last} = ${this.snippet})`);
update_dom = b`${parent.var}[${last} ? "pause" : "play"]();`; update_dom = b`${parent.var}[${last} ? "pause" : "play"]();`;
mount_dom = null;
break; break;
} }
case 'value': case 'value':
if (parent.node.get_static_attribute_value('type') === 'file') { if (parent.node.get_static_attribute_value('type') === 'file') {
update_dom = null; update_dom = null;
mount_dom = null;
} }
} }
@ -165,13 +178,18 @@ export default class BindingWrapper {
} }
} }
if (this.node.name === 'innerHTML' || this.node.name === 'textContent') { if (mount_dom) {
block.chunks.mount.push(b` if (mount_conditions.length > 0) {
if (${this.snippet} !== void 0) { const condition = mount_conditions.reduce((lhs, rhs) => x`${lhs} && ${rhs}`);
${update_dom}
}`); block.chunks.mount.push(b`
} else if (!/(currentTime|paused)/.test(this.node.name)) { if (${condition}) {
block.chunks.mount.push(update_dom); ${mount_dom}
}
`);
} else {
block.chunks.mount.push(mount_dom);
}
} }
} }
} }

@ -0,0 +1,65 @@
import EventHandler from '../../../nodes/EventHandler';
import Wrapper from '../shared/Wrapper';
import Block from '../../Block';
import { b, x, p } from 'code-red';
const TRUE = x`true`;
const FALSE = x`false`;
export default class EventHandlerWrapper {
node: EventHandler;
parent: Wrapper;
constructor(node: EventHandler, parent: Wrapper) {
this.node = node;
this.parent = parent;
if (!node.expression) {
this.parent.renderer.add_to_context(node.handler_name.name);
this.parent.renderer.component.partly_hoisted.push(b`
function ${node.handler_name.name}(event) {
@bubble($$self, event);
}
`);
}
}
get_snippet(block) {
const snippet = this.node.expression ? this.node.expression.manipulate(block) : block.renderer.reference(this.node.handler_name);
if (this.node.reassigned) {
block.maintain_context = true;
return x`function () { ${snippet}.apply(this, arguments); }`;
}
return snippet;
}
render(block: Block, target: string) {
let snippet = this.get_snippet(block);
if (this.node.modifiers.has('preventDefault')) snippet = x`@prevent_default(${snippet})`;
if (this.node.modifiers.has('stopPropagation')) snippet = x`@stop_propagation(${snippet})`;
if (this.node.modifiers.has('self')) snippet = x`@self(${snippet})`;
const args = [];
const opts = ['passive', 'once', 'capture'].filter(mod => this.node.modifiers.has(mod));
if (opts.length) {
args.push((opts.length === 1 && opts[0] === 'capture')
? TRUE
: x`{ ${opts.map(opt => p`${opt}: true`)} }`);
} else if (block.renderer.options.dev) {
args.push(FALSE);
}
if (block.renderer.options.dev) {
args.push(this.node.modifiers.has('preventDefault') ? TRUE : FALSE);
args.push(this.node.modifiers.has('stopPropagation') ? TRUE : FALSE);
}
block.event_listeners.push(
x`@listen(${target}, "${this.node.name}", ${snippet}, ${args})`
);
}
}

@ -7,7 +7,6 @@ import { string_literal } from '../../../utils/stringify';
import add_to_set from '../../../utils/add_to_set'; import add_to_set from '../../../utils/add_to_set';
import Expression from '../../../nodes/shared/Expression'; import Expression from '../../../nodes/shared/Expression';
import Text from '../../../nodes/Text'; import Text from '../../../nodes/Text';
import { changed } from '../shared/changed';
export interface StyleProp { export interface StyleProp {
key: string; key: string;
@ -46,7 +45,7 @@ export default class StyleAttributeWrapper extends AttributeWrapper {
// } // }
if (prop_dependencies.size) { if (prop_dependencies.size) {
let condition = changed(Array.from(prop_dependencies)); let condition = block.renderer.dirty(Array.from(prop_dependencies));
if (block.has_outros) { if (block.has_outros) {
condition = x`!#current || ${condition}`; condition = x`!#current || ${condition}`;
@ -88,14 +87,12 @@ function optimize_style(value: Array<Text|Expression>) {
const remaining_data = chunk.data.slice(offset); const remaining_data = chunk.data.slice(offset);
if (remaining_data) { if (remaining_data) {
/* eslint-disable @typescript-eslint/no-object-literal-type-assertion */
chunks[0] = { chunks[0] = {
start: chunk.start + offset, start: chunk.start + offset,
end: chunk.end, end: chunk.end,
type: 'Text', type: 'Text',
data: remaining_data data: remaining_data
} as Text; } as Text;
/* eslint-enable @typescript-eslint/no-object-literal-type-assertion */
} else { } else {
chunks.shift(); chunks.shift();
} }

@ -19,11 +19,12 @@ import add_to_set from '../../../utils/add_to_set';
import add_event_handlers from '../shared/add_event_handlers'; import add_event_handlers from '../shared/add_event_handlers';
import add_actions from '../shared/add_actions'; import add_actions from '../shared/add_actions';
import create_debugging_comment from '../shared/create_debugging_comment'; import create_debugging_comment from '../shared/create_debugging_comment';
import { get_context_merger } from '../shared/get_context_merger'; import { get_slot_definition } from '../shared/get_slot_definition';
import bind_this from '../shared/bind_this'; import bind_this from '../shared/bind_this';
import { changed } from '../shared/changed';
import { is_head } from '../shared/is_head'; import { is_head } from '../shared/is_head';
import { Identifier } from 'estree'; import { Identifier } from 'estree';
import EventHandler from './EventHandler';
import { extract_names } from 'periscopic';
const events = [ const events = [
{ {
@ -51,7 +52,7 @@ const events = [
}, },
{ {
event_names: ['resize'], event_names: ['elementresize'],
filter: (_node: Element, name: string) => filter: (_node: Element, name: string) =>
dimensions.test(name) dimensions.test(name)
}, },
@ -61,7 +62,7 @@ const events = [
event_names: ['timeupdate'], event_names: ['timeupdate'],
filter: (node: Element, name: string) => filter: (node: Element, name: string) =>
node.is_media_node() && node.is_media_node() &&
(name === 'currentTime' || name === 'played') (name === 'currentTime' || name === 'played' || name === 'ended')
}, },
{ {
event_names: ['durationchange'], event_names: ['durationchange'],
@ -99,6 +100,24 @@ const events = [
node.is_media_node() && node.is_media_node() &&
name === 'playbackRate' name === 'playbackRate'
}, },
{
event_names: ['seeking', 'seeked'],
filter: (node: Element, name: string) =>
node.is_media_node() &&
(name === 'seeking')
},
{
event_names: ['ended'],
filter: (node: Element, name: string) =>
node.is_media_node() &&
name === 'ended'
},
{
event_names: ['resize'],
filter: (node: Element, name: string) =>
node.is_media_node() &&
(name === 'videoHeight' || name === 'videoWidth')
},
// details event // details event
{ {
@ -113,12 +132,14 @@ export default class ElementWrapper extends Wrapper {
fragment: FragmentWrapper; fragment: FragmentWrapper;
attributes: AttributeWrapper[]; attributes: AttributeWrapper[];
bindings: Binding[]; bindings: Binding[];
event_handlers: EventHandler[];
class_dependencies: string[]; class_dependencies: string[];
slot_block: Block; slot_block: Block;
select_binding_dependencies?: Set<string>; select_binding_dependencies?: Set<string>;
var: any; var: any;
void: boolean;
constructor( constructor(
renderer: Renderer, renderer: Renderer,
@ -134,8 +155,18 @@ export default class ElementWrapper extends Wrapper {
name: node.name.replace(/[^a-zA-Z0-9_$]/g, '_') name: node.name.replace(/[^a-zA-Z0-9_$]/g, '_')
}; };
this.void = is_void(node.name);
this.class_dependencies = []; this.class_dependencies = [];
if (this.node.children.length) {
this.node.lets.forEach(l => {
extract_names(l.value || l.name).forEach(name => {
renderer.add_to_context(name, true);
});
});
}
this.attributes = this.node.attributes.map(attribute => { this.attributes = this.node.attributes.map(attribute => {
if (attribute.name === 'slot') { if (attribute.name === 'slot') {
// TODO make separate subclass for this? // TODO make separate subclass for this?
@ -162,20 +193,17 @@ export default class ElementWrapper extends Wrapper {
type: 'slot' type: 'slot'
}); });
const lets = this.node.lets; const { scope, lets } = this.node;
const seen = new Set(lets.map(l => l.name.name)); const seen = new Set(lets.map(l => l.name.name));
(owner as unknown as InlineComponentWrapper).node.lets.forEach(l => { (owner as unknown as InlineComponentWrapper).node.lets.forEach(l => {
if (!seen.has(l.name.name)) lets.push(l); if (!seen.has(l.name.name)) lets.push(l);
}); });
const fn = get_context_merger(lets); (owner as unknown as InlineComponentWrapper).slots.set(
name,
(owner as unknown as InlineComponentWrapper).slots.set(name, { get_slot_definition(child_block, scope, lets)
block: child_block, );
scope: this.node.scope,
fn
});
this.renderer.blocks.push(child_block); this.renderer.blocks.push(child_block);
} }
@ -194,6 +222,8 @@ export default class ElementWrapper extends Wrapper {
// e.g. <audio bind:paused bind:currentTime> // e.g. <audio bind:paused bind:currentTime>
this.bindings = this.node.bindings.map(binding => new Binding(block, binding, this)); this.bindings = this.node.bindings.map(binding => new Binding(block, binding, this));
this.event_handlers = this.node.handlers.map(event_handler => new EventHandler(event_handler, this));
if (node.intro || node.outro) { if (node.intro || node.outro) {
if (node.intro) block.add_intro(node.intro.is_local); if (node.intro) block.add_intro(node.intro.is_local);
if (node.outro) block.add_outro(node.outro.is_local); if (node.outro) block.add_outro(node.outro.is_local);
@ -254,9 +284,10 @@ export default class ElementWrapper extends Wrapper {
const node = this.var; const node = this.var;
const nodes = parent_nodes && block.get_unique_name(`${this.var.name}_nodes`); // if we're in unclaimable territory, i.e. <head>, parent_nodes is null const nodes = parent_nodes && block.get_unique_name(`${this.var.name}_nodes`); // if we're in unclaimable territory, i.e. <head>, parent_nodes is null
const children = x`@children(${this.node.name === 'template' ? x`${node}.content` : node})`;
block.add_variable(node); block.add_variable(node);
const render_statement = this.get_render_statement(); const render_statement = this.get_render_statement(block);
block.chunks.create.push( block.chunks.create.push(
b`${node} = ${render_statement};` b`${node} = ${render_statement};`
); );
@ -265,8 +296,13 @@ export default class ElementWrapper extends Wrapper {
if (parent_nodes) { if (parent_nodes) {
block.chunks.claim.push(b` block.chunks.claim.push(b`
${node} = ${this.get_claim_statement(parent_nodes)}; ${node} = ${this.get_claim_statement(parent_nodes)};
var ${nodes} = @children(${this.node.name === 'template' ? x`${node}.content` : node});
`); `);
if (!this.void && this.node.children.length > 0) {
block.chunks.claim.push(b`
var ${nodes} = ${children};
`);
}
} else { } else {
block.chunks.claim.push( block.chunks.claim.push(
b`${node} = ${render_statement};` b`${node} = ${render_statement};`
@ -291,7 +327,8 @@ export default class ElementWrapper extends Wrapper {
} }
// insert static children with textContent or innerHTML // insert static children with textContent or innerHTML
if (!this.node.namespace && (this.can_use_innerhtml || this.can_use_textcontent()) && this.fragment.nodes.length > 0) { const can_use_textcontent = this.can_use_textcontent();
if (!this.node.namespace && (this.can_use_innerhtml || can_use_textcontent) && this.fragment.nodes.length > 0) {
if (this.fragment.nodes.length === 1 && this.fragment.nodes[0].node.type === 'Text') { if (this.fragment.nodes.length === 1 && this.fragment.nodes[0].node.type === 'Text') {
block.chunks.create.push( block.chunks.create.push(
// @ts-ignore todo: should it be this.fragment.nodes[0].node.data instead? // @ts-ignore todo: should it be this.fragment.nodes[0].node.data instead?
@ -311,7 +348,8 @@ export default class ElementWrapper extends Wrapper {
quasis: [] quasis: []
}; };
to_html((this.fragment.nodes as unknown as Array<ElementWrapper | TextWrapper>), block, literal, state); const can_use_raw_text = !this.can_use_innerhtml && can_use_textcontent;
to_html((this.fragment.nodes as unknown as Array<ElementWrapper | TextWrapper>), block, literal, state, can_use_raw_text);
literal.quasis.push(state.quasi); literal.quasis.push(state.quasi);
block.chunks.create.push( block.chunks.create.push(
@ -338,18 +376,18 @@ export default class ElementWrapper extends Wrapper {
block.maintain_context = true; block.maintain_context = true;
} }
this.add_attributes(block);
this.add_bindings(block); this.add_bindings(block);
this.add_event_handlers(block); this.add_event_handlers(block);
this.add_attributes(block);
this.add_transitions(block); this.add_transitions(block);
this.add_animation(block); this.add_animation(block);
this.add_actions(block); this.add_actions(block);
this.add_classes(block); this.add_classes(block);
this.add_manual_style_scoping(block); this.add_manual_style_scoping(block);
if (nodes && this.renderer.options.hydratable) { if (nodes && this.renderer.options.hydratable && !this.void) {
block.chunks.claim.push( block.chunks.claim.push(
b`${nodes}.forEach(@detach);` b`${this.node.children.length > 0 ? nodes : children}.forEach(@detach);`
); );
} }
@ -365,7 +403,7 @@ export default class ElementWrapper extends Wrapper {
return this.is_static_content && this.fragment.nodes.every(node => node.node.type === 'Text' || node.node.type === 'MustacheTag'); return this.is_static_content && this.fragment.nodes.every(node => node.node.type === 'Text' || node.node.type === 'MustacheTag');
} }
get_render_statement() { get_render_statement(block: Block) {
const { name, namespace } = this.node; const { name, namespace } = this.node;
if (namespace === 'http://www.w3.org/2000/svg') { if (namespace === 'http://www.w3.org/2000/svg') {
@ -378,7 +416,7 @@ export default class ElementWrapper extends Wrapper {
const is = this.attributes.find(attr => attr.node.name === 'is'); const is = this.attributes.find(attr => attr.node.name === 'is');
if (is) { if (is) {
return x`@element_is("${name}", ${is.render_chunks().reduce((lhs, rhs) => x`${lhs} + ${rhs}`)});`; return x`@element_is("${name}", ${is.render_chunks(block).reduce((lhs, rhs) => x`${lhs} + ${rhs}`)});`;
} }
return x`@element("${name}")`; return x`@element("${name}")`;
@ -422,18 +460,13 @@ export default class ElementWrapper extends Wrapper {
groups.forEach(group => { groups.forEach(group => {
const handler = renderer.component.get_unique_name(`${this.var.name}_${group.events.join('_')}_handler`); const handler = renderer.component.get_unique_name(`${this.var.name}_${group.events.join('_')}_handler`);
renderer.add_to_context(handler.name);
renderer.component.add_var({
name: handler.name,
internal: true,
referenced: true
});
// TODO figure out how to handle locks // TODO figure out how to handle locks
const needs_lock = group.bindings.some(binding => binding.needs_lock); const needs_lock = group.bindings.some(binding => binding.needs_lock);
const dependencies = new Set(); const dependencies: Set<string> = new Set();
const contextual_dependencies = new Set(); const contextual_dependencies: Set<string> = new Set();
group.bindings.forEach(binding => { group.bindings.forEach(binding => {
// TODO this is a mess // TODO this is a mess
@ -455,10 +488,12 @@ export default class ElementWrapper extends Wrapper {
const has_local_function = contextual_dependencies.size > 0 || needs_lock || animation_frame; const has_local_function = contextual_dependencies.size > 0 || needs_lock || animation_frame;
let callee; let callee = renderer.reference(handler);
// TODO dry this out — similar code for event handlers and component bindings // TODO dry this out — similar code for event handlers and component bindings
if (has_local_function) { if (has_local_function) {
const args = Array.from(contextual_dependencies).map(name => renderer.reference(name));
// need to create a block-local function that calls an instance-level function // need to create a block-local function that calls an instance-level function
if (animation_frame) { if (animation_frame) {
block.chunks.init.push(b` block.chunks.init.push(b`
@ -468,48 +503,38 @@ export default class ElementWrapper extends Wrapper {
${animation_frame} = @raf(${handler}); ${animation_frame} = @raf(${handler});
${needs_lock && b`${lock} = true;`} ${needs_lock && b`${lock} = true;`}
} }
#ctx.${handler}.call(${this.var}, ${contextual_dependencies.size > 0 ? '#ctx' : null}); ${callee}.call(${this.var}, ${args});
} }
`); `);
} else { } else {
block.chunks.init.push(b` block.chunks.init.push(b`
function ${handler}() { function ${handler}() {
${needs_lock && b`${lock} = true;`} ${needs_lock && b`${lock} = true;`}
#ctx.${handler}.call(${this.var}, ${contextual_dependencies.size > 0 ? '#ctx' : null}); ${callee}.call(${this.var}, ${args});
} }
`); `);
} }
callee = handler; callee = handler;
} else {
callee = x`#ctx.${handler}`;
} }
const arg = contextual_dependencies.size > 0 && { const params = Array.from(contextual_dependencies).map(name => ({
type: 'ObjectPattern', type: 'Identifier',
properties: Array.from(contextual_dependencies).map(name => { name
const id = { type: 'Identifier', name }; }));
return {
type: 'Property',
kind: 'init',
key: id,
value: id
};
})
};
this.renderer.component.partly_hoisted.push(b` this.renderer.component.partly_hoisted.push(b`
function ${handler}(${arg}) { function ${handler}(${params}) {
${group.bindings.map(b => b.handler.mutation)} ${group.bindings.map(b => b.handler.mutation)}
${Array.from(dependencies) ${Array.from(dependencies)
.filter(dep => dep[0] !== '$') .filter(dep => dep[0] !== '$')
.filter(dep => !contextual_dependencies.has(dep)) .filter(dep => !contextual_dependencies.has(dep))
.map(dep => b`${this.renderer.component.invalidate(dep)};`)} .map(dep => b`${this.renderer.invalidate(dep)};`)}
} }
`); `);
group.events.forEach(name => { group.events.forEach(name => {
if (name === 'resize') { if (name === 'elementresize') {
// special case // special case
const resize_listener = block.get_unique_name(`${this.var.name}_resize_listener`); const resize_listener = block.get_unique_name(`${this.var.name}_resize_listener`);
block.add_variable(resize_listener); block.add_variable(resize_listener);
@ -551,7 +576,7 @@ export default class ElementWrapper extends Wrapper {
); );
} }
if (group.events[0] === 'resize') { if (group.events[0] === 'elementresize') {
block.chunks.hydrate.push( block.chunks.hydrate.push(
b`@add_render_callback(() => ${callee}.call(${this.var}));` b`@add_render_callback(() => ${callee}.call(${this.var}));`
); );
@ -599,7 +624,7 @@ export default class ElementWrapper extends Wrapper {
this.attributes this.attributes
.forEach(attr => { .forEach(attr => {
const condition = attr.node.dependencies.size > 0 const condition = attr.node.dependencies.size > 0
? changed(Array.from(attr.node.dependencies)) ? block.renderer.dirty(Array.from(attr.node.dependencies))
: null; : null;
if (attr.node.is_spread) { if (attr.node.is_spread) {
@ -613,7 +638,7 @@ export default class ElementWrapper extends Wrapper {
const snippet = x`{ ${ const snippet = x`{ ${
(metadata && metadata.property_name) || (metadata && metadata.property_name) ||
fix_attribute_casing(attr.node.name) fix_attribute_casing(attr.node.name)
}: ${attr.node.get_value(block)} }`; }: ${attr.get_value(block)} }`;
initial_props.push(snippet); initial_props.push(snippet);
updates.push(condition ? x`${condition} && ${snippet}` : snippet); updates.push(condition ? x`${condition} && ${snippet}` : snippet);
@ -621,10 +646,10 @@ export default class ElementWrapper extends Wrapper {
}); });
block.chunks.init.push(b` block.chunks.init.push(b`
var ${levels} = [${initial_props}]; let ${levels} = [${initial_props}];
var ${data} = {}; let ${data} = {};
for (var #i = 0; #i < ${levels}.length; #i += 1) { for (let #i = 0; #i < ${levels}.length; #i += 1) {
${data} = @assign(${data}, ${levels}[#i]); ${data} = @assign(${data}, ${levels}[#i]);
} }
`); `);
@ -643,7 +668,7 @@ export default class ElementWrapper extends Wrapper {
} }
add_event_handlers(block: Block) { add_event_handlers(block: Block) {
add_event_handlers(block, this.var, this.node.handlers); add_event_handlers(block, this.var, this.event_handlers);
} }
add_transitions( add_transitions(
@ -652,8 +677,6 @@ export default class ElementWrapper extends Wrapper {
const { intro, outro } = this.node; const { intro, outro } = this.node;
if (!intro && !outro) return; if (!intro && !outro) return;
const { component } = this.renderer;
if (intro === outro) { if (intro === outro) {
// bidirectional transition // bidirectional transition
const name = block.get_unique_name(`${this.var.name}_transition`); const name = block.get_unique_name(`${this.var.name}_transition`);
@ -663,7 +686,7 @@ export default class ElementWrapper extends Wrapper {
block.add_variable(name); block.add_variable(name);
const fn = component.qualify(intro.name); const fn = this.renderer.reference(intro.name);
const intro_block = b` const intro_block = b`
@add_render_callback(() => { @add_render_callback(() => {
@ -707,7 +730,7 @@ export default class ElementWrapper extends Wrapper {
? intro.expression.manipulate(block) ? intro.expression.manipulate(block)
: x`{}`; : x`{}`;
const fn = component.qualify(intro.name); const fn = this.renderer.reference(intro.name);
let intro_block; let intro_block;
@ -749,7 +772,7 @@ export default class ElementWrapper extends Wrapper {
? outro.expression.manipulate(block) ? outro.expression.manipulate(block)
: x`{}`; : x`{}`;
const fn = component.qualify(outro.name); const fn = this.renderer.reference(outro.name);
if (!intro) { if (!intro) {
block.chunks.intro.push(b` block.chunks.intro.push(b`
@ -781,7 +804,6 @@ export default class ElementWrapper extends Wrapper {
add_animation(block: Block) { add_animation(block: Block) {
if (!this.node.animation) return; if (!this.node.animation) return;
const { component } = this.renderer;
const { outro } = this.node; const { outro } = this.node;
const rect = block.get_unique_name('rect'); const rect = block.get_unique_name('rect');
@ -802,7 +824,7 @@ export default class ElementWrapper extends Wrapper {
const params = this.node.animation.expression ? this.node.animation.expression.manipulate(block) : x`{}`; const params = this.node.animation.expression ? this.node.animation.expression.manipulate(block) : x`{}`;
const name = component.qualify(this.node.animation.name); const name = this.renderer.reference(this.node.animation.name);
block.chunks.animate.push(b` block.chunks.animate.push(b`
${stop_animation}(); ${stop_animation}();
@ -811,7 +833,7 @@ export default class ElementWrapper extends Wrapper {
} }
add_actions(block: Block) { add_actions(block: Block) {
add_actions(this.renderer.component, block, this.var, this.node.actions); add_actions(block, this.var, this.node.actions);
} }
add_classes(block: Block) { add_classes(block: Block) {
@ -835,7 +857,7 @@ export default class ElementWrapper extends Wrapper {
block.chunks.update.push(updater); block.chunks.update.push(updater);
} else if ((dependencies && dependencies.size > 0) || this.class_dependencies.length) { } else if ((dependencies && dependencies.size > 0) || this.class_dependencies.length) {
const all_dependencies = this.class_dependencies.concat(...dependencies); const all_dependencies = this.class_dependencies.concat(...dependencies);
const condition = changed(all_dependencies); const condition = block.renderer.dirty(all_dependencies);
block.chunks.update.push(b` block.chunks.update.push(b`
if (${condition}) { if (${condition}) {
@ -854,7 +876,7 @@ export default class ElementWrapper extends Wrapper {
} }
} }
function to_html(wrappers: Array<ElementWrapper | TextWrapper | TagWrapper>, block: Block, literal: any, state: any) { function to_html(wrappers: Array<ElementWrapper | TextWrapper | TagWrapper>, block: Block, literal: any, state: any, can_use_raw_text?: boolean) {
wrappers.forEach(wrapper => { wrappers.forEach(wrapper => {
if (wrapper.node.type === 'Text') { if (wrapper.node.type === 'Text') {
if ((wrapper as TextWrapper).use_space()) state.quasi.value.raw += ' '; if ((wrapper as TextWrapper).use_space()) state.quasi.value.raw += ' ';
@ -863,7 +885,8 @@ function to_html(wrappers: Array<ElementWrapper | TextWrapper | TagWrapper>, blo
const raw = parent && ( const raw = parent && (
parent.name === 'script' || parent.name === 'script' ||
parent.name === 'style' parent.name === 'style' ||
can_use_raw_text
); );
state.quasi.value.raw += (raw ? wrapper.node.data : escape_html(wrapper.node.data)) state.quasi.value.raw += (raw ? wrapper.node.data : escape_html(wrapper.node.data))
@ -894,7 +917,7 @@ function to_html(wrappers: Array<ElementWrapper | TextWrapper | TagWrapper>, blo
attr.node.chunks.forEach(chunk => { attr.node.chunks.forEach(chunk => {
if (chunk.type === 'Text') { if (chunk.type === 'Text') {
state.quasi.value.raw += chunk.data; state.quasi.value.raw += escape_html(chunk.data);
} else { } else {
literal.quasis.push(state.quasi); literal.quasis.push(state.quasi);
literal.expressions.push(chunk.manipulate(block)); literal.expressions.push(chunk.manipulate(block));
@ -911,7 +934,7 @@ function to_html(wrappers: Array<ElementWrapper | TextWrapper | TagWrapper>, blo
state.quasi.value.raw += '>'; state.quasi.value.raw += '>';
if (!is_void(wrapper.node.name)) { if (!(wrapper as ElementWrapper).void) {
to_html((wrapper as ElementWrapper).fragment.nodes as Array<ElementWrapper | TextWrapper>, block, literal, state); to_html((wrapper as ElementWrapper).fragment.nodes as Array<ElementWrapper | TextWrapper>, block, literal, state);
state.quasi.value.raw += `</${wrapper.node.name}>`; state.quasi.value.raw += `</${wrapper.node.name}>`;

@ -10,7 +10,6 @@ import { b, x } from 'code-red';
import { walk } from 'estree-walker'; import { walk } from 'estree-walker';
import { is_head } from './shared/is_head'; import { is_head } from './shared/is_head';
import { Identifier, Node } from 'estree'; import { Identifier, Node } from 'estree';
import { changed } from './shared/changed';
function is_else_if(node: ElseBlock) { function is_else_if(node: ElseBlock) {
return ( return (
@ -264,15 +263,14 @@ export default class IfBlockWrapper extends Wrapper {
? x`${current_block_type}(#ctx)` ? x`${current_block_type}(#ctx)`
: x`${current_block_type} && ${current_block_type}(#ctx)`; : x`${current_block_type} && ${current_block_type}(#ctx)`;
/* eslint-disable @typescript-eslint/indent,indent */
if (this.needs_update) { if (this.needs_update) {
block.chunks.init.push(b` block.chunks.init.push(b`
function ${select_block_type}(#changed, #ctx) { function ${select_block_type}(#ctx, #dirty) {
${this.branches.map(({ dependencies, condition, snippet, block }) => condition ${this.branches.map(({ dependencies, condition, snippet, block }) => condition
? b` ? b`
${snippet && ( ${snippet && (
dependencies.length > 0 dependencies.length > 0
? b`if (${condition} == null || ${changed(dependencies)}) ${condition} = !!${snippet}` ? b`if (${condition} == null || ${block.renderer.dirty(dependencies)}) ${condition} = !!${snippet}`
: b`if (${condition} == null) ${condition} = !!${snippet}` : b`if (${condition} == null) ${condition} = !!${snippet}`
)} )}
if (${condition}) return ${block.name};` if (${condition}) return ${block.name};`
@ -281,17 +279,16 @@ export default class IfBlockWrapper extends Wrapper {
`); `);
} else { } else {
block.chunks.init.push(b` block.chunks.init.push(b`
function ${select_block_type}(#changed, #ctx) { function ${select_block_type}(#ctx, #dirty) {
${this.branches.map(({ condition, snippet, block }) => condition ${this.branches.map(({ condition, snippet, block }) => condition
? b`if (${snippet || condition}) return ${block.name};` ? b`if (${snippet || condition}) return ${block.name};`
: b`return ${block.name};`)} : b`return ${block.name};`)}
} }
`); `);
} }
/* eslint-enable @typescript-eslint/indent,indent */
block.chunks.init.push(b` block.chunks.init.push(b`
let ${current_block_type} = ${select_block_type}(null, #ctx); let ${current_block_type} = ${select_block_type}(#ctx, -1);
let ${name} = ${get_block}; let ${name} = ${get_block};
`); `);
@ -323,21 +320,21 @@ export default class IfBlockWrapper extends Wrapper {
if (dynamic) { if (dynamic) {
block.chunks.update.push(b` block.chunks.update.push(b`
if (${current_block_type} === (${current_block_type} = ${select_block_type}(#changed, #ctx)) && ${name}) { if (${current_block_type} === (${current_block_type} = ${select_block_type}(#ctx, #dirty)) && ${name}) {
${name}.p(#changed, #ctx); ${name}.p(#ctx, #dirty);
} else { } else {
${change_block} ${change_block}
} }
`); `);
} else { } else {
block.chunks.update.push(b` block.chunks.update.push(b`
if (${current_block_type} !== (${current_block_type} = ${select_block_type}(#changed, #ctx))) { if (${current_block_type} !== (${current_block_type} = ${select_block_type}(#ctx, #dirty))) {
${change_block} ${change_block}
} }
`); `);
} }
} else if (dynamic) { } else if (dynamic) {
block.chunks.update.push(b`${name}.p(#changed, #ctx);`); block.chunks.update.push(b`${name}.p(#ctx, #dirty);`);
} }
if (if_exists_condition) { if (if_exists_condition) {
@ -376,7 +373,6 @@ export default class IfBlockWrapper extends Wrapper {
block.add_variable(current_block_type_index); block.add_variable(current_block_type_index);
block.add_variable(name); block.add_variable(name);
/* eslint-disable @typescript-eslint/indent,indent */
block.chunks.init.push(b` block.chunks.init.push(b`
const ${if_block_creators} = [ const ${if_block_creators} = [
${this.branches.map(branch => branch.block.name)} ${this.branches.map(branch => branch.block.name)}
@ -386,13 +382,13 @@ export default class IfBlockWrapper extends Wrapper {
${this.needs_update ${this.needs_update
? b` ? b`
function ${select_block_type}(#changed, #ctx) { function ${select_block_type}(#ctx, #dirty) {
${this.branches.map(({ dependencies, condition, snippet }, i) => condition ${this.branches.map(({ dependencies, condition, snippet }, i) => condition
? b` ? b`
${snippet && ( ${snippet && (
dependencies.length > 0 dependencies.length > 0
? b`if (${condition} == null || ${changed(dependencies)}) ${condition} = !!${snippet}` ? b`if (${block.renderer.dirty(dependencies)}) ${condition} = !!${snippet}`
: b`if (${condition} == null) ${condition} = !!${snippet}` : b`if (${condition} == -1) ${condition} = !!${snippet}`
)} )}
if (${condition}) return ${i};` if (${condition}) return ${i};`
: b`return ${i};`)} : b`return ${i};`)}
@ -400,7 +396,7 @@ export default class IfBlockWrapper extends Wrapper {
} }
` `
: b` : b`
function ${select_block_type}(#changed, #ctx) { function ${select_block_type}(#ctx, #dirty) {
${this.branches.map(({ condition, snippet }, i) => condition ${this.branches.map(({ condition, snippet }, i) => condition
? b`if (${snippet || condition}) return ${i};` ? b`if (${snippet || condition}) return ${i};`
: b`return ${i};`)} : b`return ${i};`)}
@ -408,16 +404,15 @@ export default class IfBlockWrapper extends Wrapper {
} }
`} `}
`); `);
/* eslint-enable @typescript-eslint/indent,indent */
if (has_else) { if (has_else) {
block.chunks.init.push(b` block.chunks.init.push(b`
${current_block_type_index} = ${select_block_type}(null, #ctx); ${current_block_type_index} = ${select_block_type}(#ctx, -1);
${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](#ctx); ${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](#ctx);
`); `);
} else { } else {
block.chunks.init.push(b` block.chunks.init.push(b`
if (~(${current_block_type_index} = ${select_block_type}(null, #ctx))) { if (~(${current_block_type_index} = ${select_block_type}(#ctx, -1))) {
${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](#ctx); ${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](#ctx);
} }
`); `);
@ -474,9 +469,9 @@ export default class IfBlockWrapper extends Wrapper {
if (dynamic) { if (dynamic) {
block.chunks.update.push(b` block.chunks.update.push(b`
let ${previous_block_index} = ${current_block_type_index}; let ${previous_block_index} = ${current_block_type_index};
${current_block_type_index} = ${select_block_type}(#changed, #ctx); ${current_block_type_index} = ${select_block_type}(#ctx, #dirty);
if (${current_block_type_index} === ${previous_block_index}) { if (${current_block_type_index} === ${previous_block_index}) {
${if_current_block_type_index(b`${if_blocks}[${current_block_type_index}].p(#changed, #ctx);`)} ${if_current_block_type_index(b`${if_blocks}[${current_block_type_index}].p(#ctx, #dirty);`)}
} else { } else {
${change_block} ${change_block}
} }
@ -484,14 +479,14 @@ export default class IfBlockWrapper extends Wrapper {
} else { } else {
block.chunks.update.push(b` block.chunks.update.push(b`
let ${previous_block_index} = ${current_block_type_index}; let ${previous_block_index} = ${current_block_type_index};
${current_block_type_index} = ${select_block_type}(#changed, #ctx); ${current_block_type_index} = ${select_block_type}(#ctx, #dirty);
if (${current_block_type_index} !== ${previous_block_index}) { if (${current_block_type_index} !== ${previous_block_index}) {
${change_block} ${change_block}
} }
`); `);
} }
} else if (dynamic) { } else if (dynamic) {
block.chunks.update.push(b`${name}.p(#changed, #ctx);`); block.chunks.update.push(b`${name}.p(#ctx, #dirty);`);
} }
block.chunks.destroy.push( block.chunks.destroy.push(
@ -528,7 +523,7 @@ export default class IfBlockWrapper extends Wrapper {
const enter = dynamic const enter = dynamic
? b` ? b`
if (${name}) { if (${name}) {
${name}.p(#changed, #ctx); ${name}.p(#ctx, #dirty);
${has_transitions && b`@transition_in(${name}, 1);`} ${has_transitions && b`@transition_in(${name}, 1);`}
} else { } else {
${name} = ${branch.block.name}(#ctx); ${name} = ${branch.block.name}(#ctx);
@ -549,7 +544,7 @@ export default class IfBlockWrapper extends Wrapper {
`; `;
if (branch.snippet) { if (branch.snippet) {
block.chunks.update.push(b`if (${changed(branch.dependencies)}) ${branch.condition} = ${branch.snippet}`); block.chunks.update.push(b`if (${block.renderer.dirty(branch.dependencies)}) ${branch.condition} = ${branch.snippet}`);
} }
// no `p()` here — we don't want to update outroing nodes, // no `p()` here — we don't want to update outroing nodes,
@ -578,7 +573,7 @@ export default class IfBlockWrapper extends Wrapper {
} }
} else if (dynamic) { } else if (dynamic) {
block.chunks.update.push(b` block.chunks.update.push(b`
if (${branch.condition}) ${name}.p(#changed, #ctx); if (${branch.condition}) ${name}.p(#ctx, #dirty);
`); `);
} }

@ -9,17 +9,18 @@ import { b, x, p } from 'code-red';
import Attribute from '../../../nodes/Attribute'; import Attribute from '../../../nodes/Attribute';
import get_object from '../../../utils/get_object'; import get_object from '../../../utils/get_object';
import create_debugging_comment from '../shared/create_debugging_comment'; import create_debugging_comment from '../shared/create_debugging_comment';
import { get_context_merger } from '../shared/get_context_merger'; import { get_slot_definition } from '../shared/get_slot_definition';
import EachBlock from '../../../nodes/EachBlock'; import EachBlock from '../../../nodes/EachBlock';
import TemplateScope from '../../../nodes/shared/TemplateScope'; import TemplateScope from '../../../nodes/shared/TemplateScope';
import is_dynamic from '../shared/is_dynamic'; import is_dynamic from '../shared/is_dynamic';
import bind_this from '../shared/bind_this'; import bind_this from '../shared/bind_this';
import { changed } from '../shared/changed';
import { Node, Identifier, ObjectExpression } from 'estree'; import { Node, Identifier, ObjectExpression } from 'estree';
import EventHandler from '../Element/EventHandler';
import { extract_names } from 'periscopic';
export default class InlineComponentWrapper extends Wrapper { export default class InlineComponentWrapper extends Wrapper {
var: Identifier; var: Identifier;
slots: Map<string, { block: Block; scope: TemplateScope; fn?: Node }> = new Map(); slots: Map<string, { block: Block; scope: TemplateScope; get_context?: Node; get_changes?: Node }> = new Map();
node: InlineComponent; node: InlineComponent;
fragment: FragmentWrapper; fragment: FragmentWrapper;
@ -73,6 +74,12 @@ export default class InlineComponentWrapper extends Wrapper {
}; };
if (this.node.children.length) { if (this.node.children.length) {
this.node.lets.forEach(l => {
extract_names(l.value || l.name).forEach(name => {
renderer.add_to_context(name, true);
});
});
const default_slot = block.child({ const default_slot = block.child({
comment: create_debugging_comment(node, renderer.component), comment: create_debugging_comment(node, renderer.component),
name: renderer.component.get_unique_name(`create_default_slot`), name: renderer.component.get_unique_name(`create_default_slot`),
@ -81,13 +88,7 @@ export default class InlineComponentWrapper extends Wrapper {
this.renderer.blocks.push(default_slot); this.renderer.blocks.push(default_slot);
const fn = get_context_merger(this.node.lets); this.slots.set('default', get_slot_definition(default_slot, this.node.scope, this.node.lets));
this.slots.set('default', {
block: default_slot,
scope: this.node.scope,
fn
});
this.fragment = new FragmentWrapper(renderer, default_slot, node.children, this, strip_whitespace, next_sibling); this.fragment = new FragmentWrapper(renderer, default_slot, node.children, this, strip_whitespace, next_sibling);
const dependencies: Set<string> = new Set(); const dependencies: Set<string> = new Set();
@ -129,7 +130,7 @@ export default class InlineComponentWrapper extends Wrapper {
? [ ? [
p`$$slots: { p`$$slots: {
${Array.from(this.slots).map(([name, slot]) => { ${Array.from(this.slots).map(([name, slot]) => {
return p`${name}: [${slot.block.name}, ${slot.fn || null}]`; return p`${name}: [${slot.block.name}, ${slot.get_context || null}, ${slot.get_changes || null}]`;
})} })}
}`, }`,
p`$$scope: { p`$$scope: {
@ -155,6 +156,7 @@ export default class InlineComponentWrapper extends Wrapper {
} }
if (this.fragment) { if (this.fragment) {
this.renderer.add_to_context('$$scope', true);
const default_slot = this.slots.get('default'); const default_slot = this.slots.get('default');
this.fragment.nodes.forEach((child) => { this.fragment.nodes.forEach((child) => {
@ -205,7 +207,7 @@ export default class InlineComponentWrapper extends Wrapper {
const { name, dependencies } = attr; const { name, dependencies } = attr;
const condition = dependencies.size > 0 && (dependencies.size !== all_dependencies.size) const condition = dependencies.size > 0 && (dependencies.size !== all_dependencies.size)
? changed(Array.from(dependencies)) ? renderer.dirty(Array.from(dependencies))
: null; : null;
if (attr.is_spread) { if (attr.is_spread) {
@ -238,7 +240,7 @@ export default class InlineComponentWrapper extends Wrapper {
`); `);
if (all_dependencies.size) { if (all_dependencies.size) {
const condition = changed(Array.from(all_dependencies)); const condition = renderer.dirty(Array.from(all_dependencies));
updates.push(b` updates.push(b`
const ${name_changes} = ${condition} ? @get_spread_update(${levels}, [ const ${name_changes} = ${condition} ? @get_spread_update(${levels}, [
@ -254,7 +256,7 @@ export default class InlineComponentWrapper extends Wrapper {
dynamic_attributes.forEach((attribute: Attribute) => { dynamic_attributes.forEach((attribute: Attribute) => {
const dependencies = attribute.get_dependencies(); const dependencies = attribute.get_dependencies();
if (dependencies.length > 0) { if (dependencies.length > 0) {
const condition = changed(dependencies); const condition = renderer.dirty(dependencies);
updates.push(b` updates.push(b`
if (${condition}) ${name_changes}.${attribute.name} = ${attribute.get_value(block)}; if (${condition}) ${name_changes}.${attribute.name} = ${attribute.get_value(block)};
@ -266,8 +268,8 @@ export default class InlineComponentWrapper extends Wrapper {
if (non_let_dependencies.length > 0) { if (non_let_dependencies.length > 0) {
updates.push(b` updates.push(b`
if (${changed(non_let_dependencies)}) { if (${renderer.dirty(non_let_dependencies)}) {
${name_changes}.$$scope = { changed: #changed, ctx: #ctx }; ${name_changes}.$$scope = { dirty: #dirty, ctx: #ctx };
}`); }`);
} }
@ -279,12 +281,8 @@ export default class InlineComponentWrapper extends Wrapper {
} }
const id = component.get_unique_name(`${this.var.name}_${binding.name}_binding`); const id = component.get_unique_name(`${this.var.name}_${binding.name}_binding`);
renderer.add_to_context(id.name);
component.add_var({ const callee = renderer.reference(id);
name: id.name,
internal: true,
referenced: true
});
const updating = block.get_unique_name(`updating_${binding.name}`); const updating = block.get_unique_name(`updating_${binding.name}`);
block.add_variable(updating); block.add_variable(updating);
@ -298,8 +296,10 @@ export default class InlineComponentWrapper extends Wrapper {
); );
updates.push(b` updates.push(b`
if (!${updating} && ${changed(Array.from(binding.expression.dependencies))}) { if (!${updating} && ${renderer.dirty(Array.from(binding.expression.dependencies))}) {
${updating} = true;
${name_changes}.${binding.name} = ${snippet}; ${name_changes}.${binding.name} = ${snippet};
@add_flush_callback(() => ${updating} = false);
} }
`); `);
@ -318,26 +318,24 @@ export default class InlineComponentWrapper extends Wrapper {
} }
const value = block.get_unique_name('value'); const value = block.get_unique_name('value');
const args: any[] = [value]; const params: any[] = [value];
if (contextual_dependencies.length > 0) { if (contextual_dependencies.length > 0) {
args.push({ const args = [];
type: 'ObjectPattern',
properties: contextual_dependencies.map(name => { contextual_dependencies.forEach(name => {
const id = { type: 'Identifier', name }; params.push({
return { type: 'Identifier',
type: 'Property', name
kind: 'init', });
key: id,
value: id renderer.add_to_context(name, true);
}; args.push(renderer.reference(name));
})
}); });
block.chunks.init.push(b` block.chunks.init.push(b`
function ${id}(${value}) { function ${id}(${value}) {
#ctx.${id}.call(null, ${value}, #ctx); ${callee}.call(null, ${value}, ${args});
${updating} = true;
@add_flush_callback(() => ${updating} = false);
} }
`); `);
@ -345,17 +343,15 @@ export default class InlineComponentWrapper extends Wrapper {
} else { } else {
block.chunks.init.push(b` block.chunks.init.push(b`
function ${id}(${value}) { function ${id}(${value}) {
#ctx.${id}.call(null, ${value}); ${callee}.call(null, ${value});
${updating} = true;
@add_flush_callback(() => ${updating} = false);
} }
`); `);
} }
const body = b` const body = b`
function ${id}(${args}) { function ${id}(${params}) {
${lhs} = ${value}; ${lhs} = ${value};
${component.invalidate(dependencies[0])}; ${renderer.invalidate(dependencies[0])};
} }
`; `;
@ -365,7 +361,8 @@ export default class InlineComponentWrapper extends Wrapper {
}); });
const munged_handlers = this.node.handlers.map(handler => { const munged_handlers = this.node.handlers.map(handler => {
let snippet = handler.render(block); const event_handler = new EventHandler(handler, this);
let snippet = event_handler.get_snippet(block);
if (handler.modifiers.has('once')) snippet = x`@once(${snippet})`; if (handler.modifiers.has('once')) snippet = x`@once(${snippet})`;
return b`${name}.$on("${handler.name}", ${snippet});`; return b`${name}.$on("${handler.name}", ${snippet});`;
@ -396,12 +393,12 @@ export default class InlineComponentWrapper extends Wrapper {
`); `);
block.chunks.create.push( block.chunks.create.push(
b`if (${name}) ${name}.$$.fragment.c();` b`if (${name}) @create_component(${name}.$$.fragment);`
); );
if (parent_nodes && this.renderer.options.hydratable) { if (parent_nodes && this.renderer.options.hydratable) {
block.chunks.claim.push( block.chunks.claim.push(
b`if (${name}) ${name}.$$.fragment.l(${parent_nodes});` b`if (${name}) @claim_component(${name}.$$.fragment, ${parent_nodes});`
); );
} }
@ -437,7 +434,7 @@ export default class InlineComponentWrapper extends Wrapper {
${munged_bindings} ${munged_bindings}
${munged_handlers} ${munged_handlers}
${name}.$$.fragment.c(); @create_component(${name}.$$.fragment);
@transition_in(${name}.$$.fragment, 1); @transition_in(${name}.$$.fragment, 1);
@mount_component(${name}, ${update_mount_node}, ${anchor}); @mount_component(${name}, ${update_mount_node}, ${anchor});
} else { } else {
@ -460,7 +457,7 @@ export default class InlineComponentWrapper extends Wrapper {
} else { } else {
const expression = this.node.name === 'svelte:self' const expression = this.node.name === 'svelte:self'
? component.name ? component.name
: component.qualify(this.node.name); : this.renderer.reference(this.node.name);
block.chunks.init.push(b` block.chunks.init.push(b`
${(this.node.attributes.length > 0 || this.node.bindings.length > 0) && b` ${(this.node.attributes.length > 0 || this.node.bindings.length > 0) && b`
@ -472,11 +469,11 @@ export default class InlineComponentWrapper extends Wrapper {
${munged_handlers} ${munged_handlers}
`); `);
block.chunks.create.push(b`${name}.$$.fragment.c();`); block.chunks.create.push(b`@create_component(${name}.$$.fragment);`);
if (parent_nodes && this.renderer.options.hydratable) { if (parent_nodes && this.renderer.options.hydratable) {
block.chunks.claim.push( block.chunks.claim.push(
b`${name}.$$.fragment.l(${parent_nodes});` b`@claim_component(${name}.$$.fragment, ${parent_nodes});`
); );
} }

@ -10,7 +10,6 @@ import get_slot_data from '../../utils/get_slot_data';
import Expression from '../../nodes/shared/Expression'; import Expression from '../../nodes/shared/Expression';
import is_dynamic from './shared/is_dynamic'; import is_dynamic from './shared/is_dynamic';
import { Identifier, ObjectExpression } from 'estree'; import { Identifier, ObjectExpression } from 'estree';
import { changed } from './shared/changed';
export default class SlotWrapper extends Wrapper { export default class SlotWrapper extends Wrapper {
node: Slot; node: Slot;
@ -60,14 +59,13 @@ export default class SlotWrapper extends Wrapper {
const { slot_name } = this.node; const { slot_name } = this.node;
let get_slot_changes; let get_slot_changes_fn;
let get_slot_context; let get_slot_context_fn;
if (this.node.values.size > 0) { if (this.node.values.size > 0) {
get_slot_changes = renderer.component.get_unique_name(`get_${sanitize(slot_name)}_slot_changes`); get_slot_changes_fn = renderer.component.get_unique_name(`get_${sanitize(slot_name)}_slot_changes`);
get_slot_context = renderer.component.get_unique_name(`get_${sanitize(slot_name)}_slot_context`); get_slot_context_fn = renderer.component.get_unique_name(`get_${sanitize(slot_name)}_slot_context`);
const context = get_slot_data(this.node.values);
const changes = x`{}` as ObjectExpression; const changes = x`{}` as ObjectExpression;
const dependencies = new Set(); const dependencies = new Set();
@ -92,34 +90,25 @@ export default class SlotWrapper extends Wrapper {
}); });
if (dynamic_dependencies.length > 0) { if (dynamic_dependencies.length > 0) {
const expression = dynamic_dependencies changes.properties.push(p`${attribute.name}: ${renderer.dirty(dynamic_dependencies)}`);
.map(name => ({ type: 'Identifier', name } as any))
.reduce((lhs, rhs) => x`${lhs} || ${rhs}`);
changes.properties.push(p`${attribute.name}: ${expression}`);
} }
}); });
const arg = dependencies.size > 0 && {
type: 'ObjectPattern',
properties: Array.from(dependencies).map(name => p`${name}`)
};
renderer.blocks.push(b` renderer.blocks.push(b`
const ${get_slot_changes} = (${arg}) => (${changes}); const ${get_slot_changes_fn} = #dirty => ${changes};
const ${get_slot_context} = (${arg}) => (${context}); const ${get_slot_context_fn} = #ctx => ${get_slot_data(this.node.values, block)};
`); `);
} else { } else {
get_slot_changes = 'null'; get_slot_changes_fn = 'null';
get_slot_context = 'null'; get_slot_context_fn = 'null';
} }
const slot = block.get_unique_name(`${sanitize(slot_name)}_slot`); const slot = block.get_unique_name(`${sanitize(slot_name)}_slot`);
const slot_definition = block.get_unique_name(`${sanitize(slot_name)}_slot_template`); const slot_definition = block.get_unique_name(`${sanitize(slot_name)}_slot_template`);
block.chunks.init.push(b` block.chunks.init.push(b`
const ${slot_definition} = #ctx.$$slots.${slot_name}; const ${slot_definition} = ${renderer.reference('$$slots')}.${slot_name};
const ${slot} = @create_slot(${slot_definition}, #ctx, ${get_slot_context}); const ${slot} = @create_slot(${slot_definition}, #ctx, ${renderer.reference('$$scope')}, ${get_slot_context_fn});
`); `);
// TODO this is a dreadful hack! Should probably make this nicer // TODO this is a dreadful hack! Should probably make this nicer
@ -184,10 +173,10 @@ export default class SlotWrapper extends Wrapper {
}); });
block.chunks.update.push(b` block.chunks.update.push(b`
if (${slot} && ${slot}.p && ${changed(dynamic_dependencies)}) { if (${slot} && ${slot}.p && ${renderer.dirty(dynamic_dependencies)}) {
${slot}.p( ${slot}.p(
@get_slot_changes(${slot_definition}, #ctx, #changed, ${get_slot_changes}), @get_slot_context(${slot_definition}, #ctx, ${renderer.reference('$$scope')}, ${get_slot_context_fn}),
@get_slot_context(${slot_definition}, #ctx, ${get_slot_context}) @get_slot_changes(${slot_definition}, ${renderer.reference('$$scope')}, #dirty, ${get_slot_changes_fn})
); );
} }
`); `);

@ -7,7 +7,6 @@ import { string_literal } from '../../utils/stringify';
import add_to_set from '../../utils/add_to_set'; import add_to_set from '../../utils/add_to_set';
import Text from '../../nodes/Text'; import Text from '../../nodes/Text';
import { Identifier } from 'estree'; import { Identifier } from 'estree';
import { changed } from './shared/changed';
import MustacheTag from '../../nodes/MustacheTag'; import MustacheTag from '../../nodes/MustacheTag';
export default class TitleWrapper extends Wrapper { export default class TitleWrapper extends Wrapper {
@ -76,7 +75,7 @@ export default class TitleWrapper extends Wrapper {
if (all_dependencies.size) { if (all_dependencies.size) {
const dependencies = Array.from(all_dependencies); const dependencies = Array.from(all_dependencies);
let condition = changed(dependencies); let condition = block.renderer.dirty(dependencies);
if (block.has_outros) { if (block.has_outros) {
condition = x`!#current || ${condition}`; condition = x`!#current || ${condition}`;

@ -5,9 +5,9 @@ import { b, x } from 'code-red';
import add_event_handlers from './shared/add_event_handlers'; import add_event_handlers from './shared/add_event_handlers';
import Window from '../../nodes/Window'; import Window from '../../nodes/Window';
import add_actions from './shared/add_actions'; import add_actions from './shared/add_actions';
import { changed } from './shared/changed';
import { Identifier } from 'estree'; import { Identifier } from 'estree';
import { TemplateNode } from '../../../interfaces'; import { TemplateNode } from '../../../interfaces';
import EventHandler from './Element/EventHandler';
const associated_events = { const associated_events = {
innerWidth: 'resize', innerWidth: 'resize',
@ -34,9 +34,11 @@ const readonly = new Set([
export default class WindowWrapper extends Wrapper { export default class WindowWrapper extends Wrapper {
node: Window; node: Window;
handlers: EventHandler[];
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: TemplateNode) { constructor(renderer: Renderer, block: Block, parent: Wrapper, node: TemplateNode) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.handlers = this.node.handlers.map(handler => new EventHandler(handler, this));
} }
render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) { render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) {
@ -46,8 +48,8 @@ export default class WindowWrapper extends Wrapper {
const events = {}; const events = {};
const bindings: Record<string, string> = {}; const bindings: Record<string, string> = {};
add_actions(component, block, '@_window', this.node.actions); add_actions(block, '@_window', this.node.actions);
add_event_handlers(block, '@_window', this.node.handlers); add_event_handlers(block, '@_window', this.handlers);
this.node.bindings.forEach(binding => { this.node.bindings.forEach(binding => {
// in dev mode, throw if read-only values are written to // in dev mode, throw if read-only values are written to
@ -78,6 +80,9 @@ export default class WindowWrapper extends Wrapper {
const id = block.get_unique_name(`onwindow${event}`); const id = block.get_unique_name(`onwindow${event}`);
const props = events[event]; const props = events[event];
renderer.add_to_context(id.name);
const fn = renderer.reference(id.name);
if (event === 'scroll') { if (event === 'scroll') {
// TODO other bidirectional bindings... // TODO other bidirectional bindings...
block.add_variable(scrolling, x`false`); block.add_variable(scrolling, x`false`);
@ -104,7 +109,7 @@ export default class WindowWrapper extends Wrapper {
${scrolling} = true; ${scrolling} = true;
@_clearTimeout(${scrolling_timeout}); @_clearTimeout(${scrolling_timeout});
${scrolling_timeout} = @_setTimeout(${clear_scrolling}, 100); ${scrolling_timeout} = @_setTimeout(${clear_scrolling}, 100);
#ctx.${id}(); ${fn}();
}) })
`); `);
} else { } else {
@ -115,24 +120,18 @@ export default class WindowWrapper extends Wrapper {
}); });
block.event_listeners.push(x` block.event_listeners.push(x`
@listen(@_window, "${event}", #ctx.${id}) @listen(@_window, "${event}", ${fn})
`); `);
} }
component.add_var({
name: id.name,
internal: true,
referenced: true
});
component.partly_hoisted.push(b` component.partly_hoisted.push(b`
function ${id}() { function ${id}() {
${props.map(prop => component.invalidate(prop.name, x`${prop.name} = @_window.${prop.value}`))} ${props.map(prop => renderer.invalidate(prop.name, x`${prop.name} = @_window.${prop.value}`))}
} }
`); `);
block.chunks.init.push(b` block.chunks.init.push(b`
@add_render_callback(#ctx.${id}); @add_render_callback(${fn});
`); `);
component.has_reactive_assignments = true; component.has_reactive_assignments = true;
@ -140,9 +139,10 @@ 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 = changed([bindings.scrollX, bindings.scrollY].filter(Boolean)); const condition = renderer.dirty([bindings.scrollX, bindings.scrollY].filter(Boolean));
const scrollX = bindings.scrollX ? x`#ctx.${bindings.scrollX}` : x`@_window.pageXOffset`;
const scrollY = bindings.scrollY ? x`#ctx.${bindings.scrollY}` : x`@_window.pageYOffset`; const scrollX = bindings.scrollX ? renderer.reference(bindings.scrollX) : x`@_window.pageXOffset`;
const scrollY = bindings.scrollY ? renderer.reference(bindings.scrollY) : x`@_window.pageYOffset`;
block.chunks.update.push(b` block.chunks.update.push(b`
if (${condition} && !${scrolling}) { if (${condition} && !${scrolling}) {
@ -159,25 +159,22 @@ export default class WindowWrapper extends Wrapper {
const id = block.get_unique_name(`onlinestatuschanged`); const id = block.get_unique_name(`onlinestatuschanged`);
const name = bindings.online; const name = bindings.online;
component.add_var({ renderer.add_to_context(id.name);
name: id.name, const reference = renderer.reference(id.name);
internal: true,
referenced: true
});
component.partly_hoisted.push(b` component.partly_hoisted.push(b`
function ${id}() { function ${id}() {
${component.invalidate(name, x`${name} = @_navigator.onLine`)} ${renderer.invalidate(name, x`${name} = @_navigator.onLine`)}
} }
`); `);
block.chunks.init.push(b` block.chunks.init.push(b`
@add_render_callback(#ctx.${id}); @add_render_callback(${reference});
`); `);
block.event_listeners.push( block.event_listeners.push(
x`@listen(@_window, "online", #ctx.${id})`, x`@listen(@_window, "online", ${reference})`,
x`@listen(@_window, "offline", #ctx.${id})` x`@listen(@_window, "offline", ${reference})`
); );
component.has_reactive_assignments = true; component.has_reactive_assignments = true;

@ -5,7 +5,6 @@ import Block from '../../Block';
import MustacheTag from '../../../nodes/MustacheTag'; import MustacheTag from '../../../nodes/MustacheTag';
import RawMustacheTag from '../../../nodes/RawMustacheTag'; import RawMustacheTag from '../../../nodes/RawMustacheTag';
import { Node } from 'estree'; import { Node } from 'estree';
import { changed } from './changed';
export default class Tag extends Wrapper { export default class Tag extends Wrapper {
node: MustacheTag | RawMustacheTag; node: MustacheTag | RawMustacheTag;
@ -40,7 +39,7 @@ export default class Tag extends Wrapper {
if (this.node.should_cache) block.add_variable(value, snippet); // TODO may need to coerce snippet to string if (this.node.should_cache) block.add_variable(value, snippet); // TODO may need to coerce snippet to string
if (dependencies.length > 0) { if (dependencies.length > 0) {
let condition = changed(dependencies); let condition = block.renderer.dirty(dependencies);
if (block.has_outros) { if (block.has_outros) {
condition = x`!#current || ${condition}`; condition = x`!#current || ${condition}`;

@ -1,10 +1,8 @@
import { b, x } from 'code-red'; import { b, x } from 'code-red';
import Block from '../../Block'; import Block from '../../Block';
import Action from '../../../nodes/Action'; import Action from '../../../nodes/Action';
import Component from '../../../Component';
export default function add_actions( export default function add_actions(
component: Component,
block: Block, block: Block,
target: string, target: string,
actions: Action[] actions: Action[]
@ -25,7 +23,7 @@ export default function add_actions(
block.add_variable(id); block.add_variable(id);
const fn = component.qualify(action.name); const fn = block.renderer.reference(action.name);
block.chunks.mount.push( block.chunks.mount.push(
b`${id} = ${fn}.call(null, ${target}, ${snippet}) || {};` b`${id} = ${fn}.call(null, ${target}, ${snippet}) || {};`
@ -34,14 +32,8 @@ export default function add_actions(
if (dependencies && dependencies.length > 0) { if (dependencies && dependencies.length > 0) {
let condition = x`@is_function(${id}.update)`; let condition = x`@is_function(${id}.update)`;
// TODO can this case be handled more elegantly?
if (dependencies.length > 0) { if (dependencies.length > 0) {
let changed = x`#changed.${dependencies[0]}`; condition = x`${condition} && ${block.renderer.dirty(dependencies)}`;
for (let i = 1; i < dependencies.length; i += 1) {
changed = x`${changed} || #changed.${dependencies[i]}`;
}
condition = x`${condition} && ${changed}`;
} }
block.chunks.update.push( block.chunks.update.push(

@ -1,39 +1,10 @@
import Block from '../../Block'; import Block from '../../Block';
import EventHandler from '../../../nodes/EventHandler'; import EventHandler from '../Element/EventHandler';
import { x, p } from 'code-red';
const TRUE = x`true`;
const FALSE = x`false`;
export default function add_event_handlers( export default function add_event_handlers(
block: Block, block: Block,
target: string, target: string,
handlers: EventHandler[] handlers: EventHandler[]
) { ) {
handlers.forEach(handler => { handlers.forEach(handler => handler.render(block, target));
let snippet = handler.render(block);
if (handler.modifiers.has('preventDefault')) snippet = x`@prevent_default(${snippet})`;
if (handler.modifiers.has('stopPropagation')) snippet = x`@stop_propagation(${snippet})`;
if (handler.modifiers.has('self')) snippet = x`@self(${snippet})`;
const args = [];
const opts = ['passive', 'once', 'capture'].filter(mod => handler.modifiers.has(mod));
if (opts.length) {
args.push((opts.length === 1 && opts[0] === 'capture')
? TRUE
: x`{ ${opts.map(opt => p`${opt}: true`)} }`);
} else if (block.renderer.options.dev) {
args.push(FALSE);
}
if (block.renderer.options.dev) {
args.push(handler.modifiers.has('stopPropagation') ? TRUE : FALSE);
args.push(handler.modifiers.has('preventDefault') ? TRUE : FALSE);
}
block.event_listeners.push(
x`@listen(${target}, "${handler.name}", ${snippet}, ${args})`
);
});
} }

@ -8,11 +8,8 @@ import { Identifier } from 'estree';
export default function bind_this(component: Component, block: Block, binding: Binding, variable: Identifier) { export default function bind_this(component: Component, block: Block, binding: Binding, variable: Identifier) {
const fn = component.get_unique_name(`${variable.name}_binding`); const fn = component.get_unique_name(`${variable.name}_binding`);
component.add_var({ block.renderer.add_to_context(fn.name);
name: fn.name, const callee = block.renderer.reference(fn.name);
internal: true,
referenced: true
});
let lhs; let lhs;
let object; let object;
@ -32,11 +29,11 @@ export default function bind_this(component: Component, block: Block, binding: B
body = binding.raw_expression.type === 'Identifier' body = binding.raw_expression.type === 'Identifier'
? b` ? b`
${component.invalidate(object, x`${lhs} = $$value`)}; ${block.renderer.invalidate(object, x`${lhs} = $$value`)};
` `
: b` : b`
${lhs} = $$value; ${lhs} = $$value;
${component.invalidate(object)}; ${block.renderer.invalidate(object)};
`; `;
} }
@ -58,19 +55,19 @@ export default function bind_this(component: Component, block: Block, binding: B
const args = []; const args = [];
for (const id of contextual_dependencies) { for (const id of contextual_dependencies) {
args.push(id); args.push(id);
block.add_variable(id, x`#ctx.${id}`); block.add_variable(id, block.renderer.reference(id.name));
} }
const assign = block.get_unique_name(`assign_${variable.name}`); const assign = block.get_unique_name(`assign_${variable.name}`);
const unassign = block.get_unique_name(`unassign_${variable.name}`); const unassign = block.get_unique_name(`unassign_${variable.name}`);
block.chunks.init.push(b` block.chunks.init.push(b`
const ${assign} = () => #ctx.${fn}(${variable}, ${args}); const ${assign} = () => ${callee}(${variable}, ${args});
const ${unassign} = () => #ctx.${fn}(null, ${args}); const ${unassign} = () => ${callee}(null, ${args});
`); `);
const condition = Array.from(contextual_dependencies) const condition = Array.from(contextual_dependencies)
.map(name => x`${name} !== #ctx.${name}`) .map(name => x`${name} !== ${block.renderer.reference(name.name)}`)
.reduce((lhs, rhs) => x`${lhs} || ${rhs}`); .reduce((lhs, rhs) => x`${lhs} || ${rhs}`);
// we push unassign and unshift assign so that references are // we push unassign and unshift assign so that references are
@ -79,7 +76,7 @@ export default function bind_this(component: Component, block: Block, binding: B
block.chunks.update.push(b` block.chunks.update.push(b`
if (${condition}) { if (${condition}) {
${unassign}(); ${unassign}();
${args.map(a => b`${a} = #ctx.${a}`)}; ${args.map(a => b`${a} = ${block.renderer.reference(a.name)}`)};
${assign}(); ${assign}();
}` }`
); );
@ -96,6 +93,6 @@ export default function bind_this(component: Component, block: Block, binding: B
} }
`); `);
block.chunks.destroy.push(b`#ctx.${fn}(null);`); block.chunks.destroy.push(b`${callee}(null);`);
return b`#ctx.${fn}(${variable});`; return b`${callee}(${variable});`;
} }

@ -1,7 +0,0 @@
import { x } from 'code-red';
export function changed(dependencies: string[]) {
return dependencies
.map(d => x`#changed.${d}`)
.reduce((lhs, rhs) => x`${lhs} || ${rhs}`);
}

@ -1,39 +0,0 @@
import Let from '../../../nodes/Let';
import { x } from 'code-red';
export function get_context_merger(lets: Let[]) {
if (lets.length === 0) return null;
const input = {
type: 'ObjectPattern',
properties: lets.map(l => ({
type: 'Property',
kind: 'init',
key: l.name,
value: l.value || l.name
}))
};
const names = new Set();
lets.forEach(l => {
l.names.forEach(name => {
names.add(name);
});
});
const output = {
type: 'ObjectExpression',
properties: Array.from(names).map(name => {
const id = { type: 'Identifier', name };
return {
type: 'Property',
kind: 'init',
key: id,
value: id
};
})
};
return x`(${input}) => (${output})`;
}

@ -0,0 +1,53 @@
import Let from '../../../nodes/Let';
import { x, p } from 'code-red';
import Block from '../../Block';
import TemplateScope from '../../../nodes/shared/TemplateScope';
export function get_slot_definition(block: Block, scope: TemplateScope, lets: Let[]) {
if (lets.length === 0) return { block, scope };
const input = {
type: 'ObjectPattern',
properties: lets.map(l => ({
type: 'Property',
kind: 'init',
key: l.name,
value: l.value || l.name
}))
};
const names: Set<string> = new Set();
lets.forEach(l => {
l.names.forEach(name => {
names.add(name);
});
});
const context = {
type: 'ObjectExpression',
properties: Array.from(names).map(name => p`${block.renderer.context_lookup.get(name).index}: ${name}`)
};
const changes = Array.from(names)
.map(name => {
const { context_lookup } = block.renderer;
const literal = {
type: 'Literal',
get value() {
const i = context_lookup.get(name).index.value as number;
return 1 << i;
}
};
return x`${name} ? ${literal} : 0`;
})
.reduce((lhs, rhs) => x`${lhs} | ${rhs}`);
return {
block,
scope,
get_context: x`${input} => ${context}`,
get_changes: x`${input} => ${changes}`
};
}

@ -30,7 +30,7 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
const class_expression_list = node.classes.map(class_directive => { const class_expression_list = node.classes.map(class_directive => {
const { expression, name } = class_directive; const { expression, name } = class_directive;
const snippet = expression ? expression.node : x`#ctx.${name}`; const snippet = expression ? expression.node : x`#ctx.${name}`; // TODO is this right?
return x`${snippet} ? "${name}" : ""`; return x`${snippet} ? "${name}" : ""`;
}); });
if (node.needs_manual_style_scoping) { if (node.needs_manual_style_scoping) {

@ -1,7 +1,7 @@
import get_slot_data from '../../utils/get_slot_data';
import Renderer, { RenderOptions } from '../Renderer'; import Renderer, { RenderOptions } from '../Renderer';
import Slot from '../../nodes/Slot'; import Slot from '../../nodes/Slot';
import { x } from 'code-red'; import { x } from 'code-red';
import get_slot_data from '../../utils/get_slot_data';
export default function(node: Slot, renderer: Renderer, options: RenderOptions) { export default function(node: Slot, renderer: Renderer, options: RenderOptions) {
const slot_data = get_slot_data(node.values); const slot_data = get_slot_data(node.values);

@ -1,7 +1,7 @@
export default function get_name_from_filename(filename: string) { export default function get_name_from_filename(filename: string) {
if (!filename) return null; if (!filename) return null;
// eslint-disable-next-line no-useless-escape
const parts = filename.split(/[\/\\]/); const parts = filename.split(/[/\\]/).map(encodeURI);
if (parts.length > 1) { if (parts.length > 1) {
const index_match = parts[parts.length - 1].match(/^index(\.\w+)/); const index_match = parts[parts.length - 1].match(/^index(\.\w+)/);
@ -12,6 +12,7 @@ export default function get_name_from_filename(filename: string) {
} }
const base = parts.pop() const base = parts.pop()
.replace(/%/g, 'u')
.replace(/\.[^.]+$/, "") .replace(/\.[^.]+$/, "")
.replace(/[^a-zA-Z_$0-9]+/g, '_') .replace(/[^a-zA-Z_$0-9]+/g, '_')
.replace(/^_/, '') .replace(/^_/, '')

@ -1,26 +1,26 @@
import Attribute from '../nodes/Attribute'; import Attribute from '../nodes/Attribute';
import { p, x } from 'code-red'; import { p, x } from 'code-red';
import { string_literal } from './stringify'; import { string_literal } from './stringify';
import Block from '../render_dom/Block';
export default function get_slot_data(values: Map<string, Attribute>) { export default function get_slot_data(values: Map<string, Attribute>, block: Block = null) {
return { return {
type: 'ObjectExpression', type: 'ObjectExpression',
properties: Array.from(values.values()) properties: Array.from(values.values())
.filter(attribute => attribute.name !== 'name') .filter(attribute => attribute.name !== 'name')
.map(attribute => { .map(attribute => {
const value = get_value(attribute); const value = get_value(block, attribute);
return p`${attribute.name}: ${value}`; return p`${attribute.name}: ${value}`;
}) })
}; };
} }
// TODO fairly sure this is duplicated at least once function get_value(block: Block, attribute: Attribute) {
function get_value(attribute: Attribute) {
if (attribute.is_true) return x`true`; if (attribute.is_true) return x`true`;
if (attribute.chunks.length === 0) return x`""`; if (attribute.chunks.length === 0) return x`""`;
let value = attribute.chunks let value = attribute.chunks
.map(chunk => chunk.type === 'Text' ? string_literal(chunk.data) : chunk.node) .map(chunk => chunk.type === 'Text' ? string_literal(chunk.data) : (block ? chunk.manipulate(block) : chunk.node))
.reduce((lhs, rhs) => x`${lhs} + ${rhs}`); .reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
if (attribute.chunks.length > 1 && attribute.chunks[0].type !== 'Text') { if (attribute.chunks.length > 1 && attribute.chunks[0].type !== 'Text') {

@ -1,156 +1,8 @@
import { walk } from 'estree-walker'; import { Node } from 'estree';
import is_reference from 'is-reference'; import { analyze, Scope, extract_names, extract_identifiers } from 'periscopic';
import { Node, VariableDeclaration, ClassDeclaration, VariableDeclarator, ObjectPattern, Property, RestElement, ArrayPattern, Identifier } from 'estree';
import get_object from './get_object';
// TODO replace this with periscopic?
export function create_scopes(expression: Node) { export function create_scopes(expression: Node) {
const map = new WeakMap(); return analyze(expression);
const globals: Map<string, Node> = new Map();
let scope = new Scope(null, false);
walk(expression, {
enter(node, parent) {
if (node.type === 'ImportDeclaration') {
node.specifiers.forEach(specifier => {
scope.declarations.set(specifier.local.name, specifier);
});
} else if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') {
if (node.type === 'FunctionDeclaration') {
scope.declarations.set(node.id.name, node);
scope = new Scope(scope, false);
map.set(node, scope);
} else {
scope = new Scope(scope, false);
map.set(node, scope);
if (node.type === 'FunctionExpression' && node.id) {
scope.declarations.set(node.id.name, node);
}
}
node.params.forEach((param) => {
extract_names(param).forEach(name => {
scope.declarations.set(name, node);
});
});
} else if (/For(?:In|Of)?Statement/.test(node.type)) {
scope = new Scope(scope, true);
map.set(node, scope);
} else if (node.type === 'BlockStatement') {
scope = new Scope(scope, true);
map.set(node, scope);
} else if (node.type === 'ClassDeclaration' || node.type === 'VariableDeclaration') {
scope.add_declaration(node);
} else if (node.type === 'CatchClause') {
scope = new Scope(scope, true);
map.set(node, scope);
extract_names(node.param).forEach(name => {
scope.declarations.set(name, node.param);
});
} else if (node.type === 'Identifier' && is_reference(node as Node, parent as Node)) {
if (!scope.has(node.name) && !globals.has(node.name)) {
globals.set(node.name, node);
}
}
},
leave(node: Node) {
if (map.has(node)) {
scope = scope.parent;
}
}
});
scope.declarations.forEach((_node, name) => {
globals.delete(name);
});
return { map, scope, globals };
} }
export class Scope { export { Scope, extract_names, extract_identifiers };
parent: Scope;
block: boolean;
declarations: Map<string, Node> = new Map();
initialised_declarations: Set<string> = new Set();
constructor(parent: Scope, block: boolean) {
this.parent = parent;
this.block = block;
}
add_declaration(node: VariableDeclaration | ClassDeclaration) {
if (node.type === 'VariableDeclaration') {
if (node.kind === 'var' && this.block && this.parent) {
this.parent.add_declaration(node);
} else {
node.declarations.forEach((declarator: VariableDeclarator) => {
extract_names(declarator.id).forEach(name => {
this.declarations.set(name, node);
if (declarator.init) this.initialised_declarations.add(name);
});
});
}
} else {
this.declarations.set(node.id.name, node);
}
}
find_owner(name: string): Scope {
if (this.declarations.has(name)) return this;
return this.parent && this.parent.find_owner(name);
}
has(name: string): boolean {
return (
this.declarations.has(name) || (this.parent && this.parent.has(name))
);
}
}
export function extract_names(param: Node): string[] {
return extract_identifiers(param).map((node: any) => node.name);
}
export function extract_identifiers(param: Node): Identifier[] {
const nodes: Identifier[] = [];
extractors[param.type] && extractors[param.type](nodes, param);
return nodes;
}
const extractors = {
Identifier(nodes: Node[], param: Node) {
nodes.push(param);
},
MemberExpression(nodes: Node[], param: Node) {
nodes.push(get_object(param));
},
ObjectPattern(nodes: Node[], param: ObjectPattern) {
param.properties.forEach((prop: Property | RestElement) => {
if (prop.type === 'RestElement') {
nodes.push(prop.argument);
} else {
extractors[prop.value.type](nodes, prop.value);
}
});
},
ArrayPattern(nodes: Node[], param: ArrayPattern) {
param.elements.forEach((element: Node) => {
if (element) extractors[element.type](nodes, element);
});
},
RestElement(nodes: Node[], param: any) {
extractors[param.argument.type](nodes, param.argument);
},
AssignmentPattern(nodes: Node[], param: any) {
extractors[param.left.type](nodes, param.left);
}
};

@ -12,13 +12,15 @@ export function escape(data: string, { only_escape_at_symbol = false } = {}) {
} }
const escaped = { const escaped = {
'"': '&quot;',
"'": '&#39;',
'&': '&amp;', '&': '&amp;',
'<': '&lt;', '<': '&lt;',
'>': '&gt;', '>': '&gt;',
}; };
export function escape_html(html) { export function escape_html(html) {
return String(html).replace(/[&<>]/g, match => escaped[match]); return String(html).replace(/["'&<>]/g, match => escaped[match]);
} }
export function escape_template(str) { export function escape_template(str) {

@ -121,6 +121,7 @@ export interface CompileOptions {
customElement?: boolean; customElement?: boolean;
tag?: string; tag?: string;
css?: boolean; css?: boolean;
loopGuardTimeout?: number;
preserveComments?: boolean; preserveComments?: boolean;
preserveWhitespace?: boolean; preserveWhitespace?: boolean;

@ -65,11 +65,11 @@ export class Parser {
} }
if (this.html.children.length) { if (this.html.children.length) {
let start = this.html.children[0] && this.html.children[0].start; let start = this.html.children[0].start;
while (/\s/.test(template[start])) start += 1; while (whitespace.test(template[start])) start += 1;
let end = this.html.children[this.html.children.length - 1] && this.html.children[this.html.children.length - 1].end; let end = this.html.children[this.html.children.length - 1].end;
while (/\s/.test(template[end - 1])) end -= 1; while (whitespace.test(template[end - 1])) end -= 1;
this.html.start = start; this.html.start = start;
this.html.end = end; this.html.end = end;

@ -13,7 +13,6 @@ export default function read_expression(parser: Parser): Node {
const end = start + name.length; const end = start + name.length;
if (literals.has(name)) { if (literals.has(name)) {
// eslint-disable-next-line @typescript-eslint/no-object-literal-type-assertion
return { return {
type: 'Literal', type: 'Literal',
start, start,
@ -23,7 +22,6 @@ export default function read_expression(parser: Parser): Node {
} as SimpleLiteral; } as SimpleLiteral;
} }
// eslint-disable-next-line @typescript-eslint/no-object-literal-type-assertion
return { return {
type: 'Identifier', type: 'Identifier',
start, start,

@ -1,5 +1,4 @@
import * as acorn from '../acorn'; import * as acorn from '../acorn';
import repeat from '../../utils/repeat';
import { Parser } from '../index'; import { Parser } from '../index';
import { Script } from '../../interfaces'; import { Script } from '../../interfaces';
import { Node, Program } from 'estree'; import { Node, Program } from 'estree';
@ -38,8 +37,7 @@ export default function read_script(parser: Parser, start: number, attributes: N
message: `<script> must have a closing tag` message: `<script> must have a closing tag`
}); });
const source = const source = ' '.repeat(script_start) + parser.template.slice(script_start, script_end);
repeat(' ', script_start) + parser.template.slice(script_start, script_end);
parser.index = script_end + script_closing_tag.length; parser.index = script_end + script_closing_tag.length;
let ast: Program; let ast: Program;

@ -45,7 +45,7 @@ async function replace_async(str: string, re: RegExp, func: (...any) => Promise<
str.replace(re, (...args) => { str.replace(re, (...args) => {
replacements.push( replacements.push(
func(...args).then( func(...args).then(
res => // eslint-disable-next-line @typescript-eslint/no-object-literal-type-assertion res =>
({ ({
offset: args[args.length - 2], offset: args[args.length - 2],
length: args[0].length, length: args[0].length,
@ -94,8 +94,12 @@ export default async function preprocess(
for (const fn of script) { for (const fn of script) {
source = await replace_async( source = await replace_async(
source, source,
/<script(\s[^]*?)?>([^]*?)<\/script>/gi, /<!--[^]*?-->|<script(\s[^]*?)?>([^]*?)<\/script>/gi,
async (match, attributes = '', content) => { async (match, attributes = '', content) => {
if (!attributes && !content) {
return match;
}
attributes = attributes || '';
const processed = await fn({ const processed = await fn({
content, content,
attributes: parse_attributes(attributes), attributes: parse_attributes(attributes),
@ -110,8 +114,11 @@ export default async function preprocess(
for (const fn of style) { for (const fn of style) {
source = await replace_async( source = await replace_async(
source, source,
/<style(\s[^]*?)?>([^]*?)<\/style>/gi, /<!--[^]*?-->|<style(\s[^]*?)?>([^]*?)<\/style>/gi,
async (match, attributes = '', content) => { async (match, attributes = '', content) => {
if (!attributes && !content) {
return match;
}
const processed: Processed = await fn({ const processed: Processed = await fn({
content, content,
attributes: parse_attributes(attributes), attributes: parse_attributes(attributes),

@ -1,5 +1,3 @@
import repeat from './repeat';
function tabs_to_spaces(str: string) { function tabs_to_spaces(str: string) {
return str.replace(/^\t+/, match => match.split('\t').join(' ')); return str.replace(/^\t+/, match => match.split('\t').join(' '));
} }
@ -20,13 +18,10 @@ export default function get_code_frame(
.slice(frame_start, frame_end) .slice(frame_start, frame_end)
.map((str, i) => { .map((str, i) => {
const isErrorLine = frame_start + i === line; const isErrorLine = frame_start + i === line;
const line_num = String(i + frame_start + 1).padStart(digits, ' ');
let line_num = String(i + frame_start + 1);
while (line_num.length < digits) line_num = ` ${line_num}`;
if (isErrorLine) { if (isErrorLine) {
const indicator = const indicator = ' '.repeat(digits + 2 + tabs_to_spaces(str.slice(0, column)).length) + '^';
repeat(' ', digits + 2 + tabs_to_spaces(str.slice(0, column)).length) + '^';
return `${line_num}: ${tabs_to_spaces(str)}\n${indicator}`; return `${line_num}: ${tabs_to_spaces(str)}\n${indicator}`;
} }

@ -1,5 +0,0 @@
export default function repeat(str: string, i: number) {
let result = '';
while (i--) result += str;
return result;
}

@ -1,19 +1,3 @@
declare module '*.svelte' { declare module '*.svelte' {
type Props = Record<string, any>; export { SvelteComponentDev as default } from 'svelte/internal';
export default class {
constructor(options: {
target: Element;
anchor?: Element;
props?: Props;
hydrate?: boolean;
intro?: boolean;
});
$set(props: Props): void;
$on<T = any>(event: string, callback: (event: CustomEvent<T>) => void): () => void;
$destroy(): void;
[accessor: string]: any;
}
} }

@ -19,9 +19,11 @@ interface FlipParams {
export function flip(node: Element, animation: { from: DOMRect; to: DOMRect }, params: FlipParams): AnimationConfig { export function flip(node: Element, animation: { from: DOMRect; to: DOMRect }, params: FlipParams): AnimationConfig {
const style = getComputedStyle(node); const style = getComputedStyle(node);
const transform = style.transform === 'none' ? '' : style.transform; const transform = style.transform === 'none' ? '' : style.transform;
const scaleX = animation.from.width / node.clientWidth;
const scaleY = animation.from.height / node.clientHeight;
const dx = animation.from.left - animation.to.left; const dx = (animation.from.left - animation.to.left) / scaleX;
const dy = animation.from.top - animation.to.top; const dy = (animation.from.top - animation.to.top) / scaleY;
const d = Math.sqrt(dx * dx + dy * dy); const d = Math.sqrt(dx * dx + dy * dy);

@ -8,5 +8,6 @@ export {
setContext, setContext,
getContext, getContext,
tick, tick,
createEventDispatcher createEventDispatcher,
SvelteComponentDev as SvelteComponent
} from 'svelte/internal'; } from 'svelte/internal';

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

Loading…
Cancel
Save