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: '^_'
}
],
'@typescript-eslint/no-object-literal-type-assertion': [
'error',
{
allowAsParameter: true
}
],
'@typescript-eslint/no-unused-vars': 'off'
'@typescript-eslint/no-object-literal-type-assertion': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/prefer-interface': 'off'
},
globals: {
globalThis: false

1
.gitignore vendored

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

@ -1,6 +1,40 @@
# 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))
@ -25,6 +59,23 @@ Also:
* 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 `<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

@ -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.
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/)
* [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
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.
@ -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'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
@ -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 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
When adding a new breaking change, follow this template in your pull request:

42
package-lock.json generated

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

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

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

@ -6,7 +6,9 @@ authorURL: https://twitter.com/Rich_Harris
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
* svelte-vscode
@ -14,7 +16,7 @@ draft: true
## 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
"*":
@ -45,3 +47,23 @@ To set the filetype for a single file, use a [modeline](https://vim.fandom.com/w
```
<!-- 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
author: 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
* `buffered` (readonly) — an array of `{start, end}` objects
* `seekable` (readonly) — ditto
* `played` (readonly) — ditto
* `seeking` (readonly) — boolean
* `ended` (readonly) — boolean
...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
* `volume` — a value between 0 and 1
Videos additionally have readonly `videoWidth` and `videoHeight` bindings.
```html
<video
src={clip}
bind:duration
bind:buffered
bind:seekable
bind:seeking
bind:played
bind:ended
bind:currentTime
bind:paused
bind:volume
bind:videoWidth
bind:videoHeight
></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
<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:
* [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)
* [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.
@ -53,6 +53,7 @@ The following options can be passed to the compiler. None are required:
| `tag` | string | null
| `accessors` | boolean | `false`
| `css` | boolean | `true`
| `loopGuardTimeout` | number | 0
| `preserveComments` | boolean | `false`
| `preserveWhitespace` | boolean | `false`
| `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.
| `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.
| `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.
| `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.
@ -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.
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
const svelte = require('svelte/compiler');

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

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

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

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

@ -33,7 +33,7 @@
font-family: 'Overpass';
letter-spacing: 0.12em;
color: #676778;
font-weight: 100;
font-weight: 400;
}
.centered span {
@ -71,4 +71,4 @@
toggle me
</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>
let language = "english";
import { longpress } from './longpress.js';
const translations = {
english: {
tooltip: "Switch Languages",
},
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();
},
let pressed = false;
let duration = 2000;
</script>
destroy() {
tooltip.remove();
node.removeEventListener('mouseenter', append);
node.removeEventListener('mouseleave', remove);
}
};
}
<label>
<input type=range bind:value={duration} max={2000} step={100}>
{duration}ms
</label>
function toggleLanguage() {
language = language === 'english' ? 'latin' : 'english'
}
</script>
<button use:longpress={duration}
on:longpress="{() => pressed = true}"
on:mouseenter="{() => pressed = false}"
>press and hold</button>
<button on:click={toggleLanguage} use:tooltip={translations[language].tooltip}>
{language}
</button>
{#if pressed}
<p>congratulations, you pressed and held for {duration}ms</p>
{/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>
let people = [
{
first: 'Hans',
last: 'Emil'
},
{
first: 'Max',
last: 'Mustermann'
},
{
first: 'Roman',
last: 'Tisch'
}
{ first: 'Hans', last: 'Emil' },
{ first: 'Max', last: 'Mustermann' },
{ first: 'Roman', last: 'Tisch' }
];
let prefix = '';
@ -39,7 +30,9 @@
}
function update() {
people[i] = { first, last };
selected.first = first;
selected.last = last;
people = people;
}
function remove() {

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

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

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

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

@ -5,7 +5,7 @@
<style>
p {
color: purple;
font-family: 'Comic Sans MS';
font-family: 'Comic Sans MS', cursive;
font-size: 2em;
}
</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.
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)
* [webpack](https://webpack.js.org/) / [svelte-loader](https://github.com/sveltejs/svelte-loader)
* [Parcel](https://parceljs.org/) / [parcel-plugin-svelte](https://github.com/DeMoorJasper/parcel-plugin-svelte)
* [rollup-plugin-svelte](https://github.com/sveltejs/rollup-plugin-svelte)
* [svelte-loader](https://github.com/sveltejs/svelte-loader)
...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.

@ -11,7 +11,7 @@ let count = 0;
$: 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:

@ -30,3 +30,12 @@ function addNumber() {
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>
button {
font-family: 'Comic Sans MS';
font-family: 'Comic Sans MS', cursive;
font-size: 2em;
padding: 0.5em 1em;
color: royalblue;

@ -1,6 +1,6 @@
<style>
button {
font-family: 'Comic Sans MS';
font-family: 'Comic Sans MS', cursive;
font-size: 2em;
padding: 0.5em 1em;
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`.
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
* `buffered` (readonly) — an array of `{start, end}` objects
* `seekable` (readonly) — ditto
* `played` (readonly) — ditto
* `seeking` (readonly) — boolean
* `ended` (readonly) — boolean
...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'
* `paused` — this one should be self-explanatory
* `volume` — a value between 0 and 1
Videos additionally have readonly `videoWidth` and `videoHeight` bindings.

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

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

@ -28,7 +28,7 @@ Clicking the buttons causes the progress bar to animate to its new value. It's a
</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`:

@ -1,70 +1,20 @@
<script>
let language = "english";
import { longpress } from './longpress.js';
const translations = {
english: {
tooltip: "Switch Languages",
},
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();
},
let pressed = false;
let duration = 2000;
</script>
destroy() {
tooltip.remove();
node.removeEventListener('mouseenter', append);
node.removeEventListener('mouseleave', remove);
}
};
}
<label>
<input type=range bind:value={duration} max={2000} step={100}>
{duration}ms
</label>
function toggleLanguage() {
language = language === 'english' ? 'latin' : 'english'
}
</script>
<button use:longpress
on:longpress="{() => pressed = true}"
on:mouseenter="{() => pressed = false}"
>press and hold</button>
<button on:click={toggleLanguage} use:tooltip={translations[language].tooltip}>
{language}
</button>
{#if pressed}
<p>congratulations, you pressed and held for {duration}ms</p>
{/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>
let language = "english";
import { longpress } from './longpress.js';
const translations = {
english: {
tooltip: "Switch Languages",
},
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();
},
let pressed = false;
let duration = 2000;
</script>
destroy() {
tooltip.remove();
node.removeEventListener('mouseenter', append);
node.removeEventListener('mouseleave', remove);
}
};
}
<label>
<input type=range bind:value={duration} max={2000} step={100}>
{duration}ms
</label>
function toggleLanguage() {
language = language === 'english' ? 'latin' : 'english'
}
</script>
<button use:longpress={duration}
on:longpress="{() => pressed = true}"
on:mouseenter="{() => pressed = false}"
>press and hold</button>
<button on:click={toggleLanguage} use:tooltip={translations[language].tooltip}>
{language}
</button>
{#if pressed}
<p>congratulations, you pressed and held for {duration}ms</p>
{/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
---
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": {
"version": "1.0.0-next.0",
"resolved": "https://registry.npmjs.org/@polka/redirect/-/redirect-1.0.0-next.0.tgz",
"integrity": "sha512-ym6ooqMr09+cV+y52p5kszJ0jYcX+nJfm8POrQb7QYowvpPPuneZ71EclHrQSB7a50lcytgR/xtL6AUFdvyEkg=="
"version": "1.0.0-next.7",
"resolved": "https://registry.npmjs.org/@polka/redirect/-/redirect-1.0.0-next.7.tgz",
"integrity": "sha512-sHh1oVy9VBVhn41fOlrUdlxFkcbKrYdBcqePTSxvf2NqKu7UcMPZse/wDeQZk17A8cqDArKsR4m0MXd+3/Q83g=="
},
"@polka/send": {
"version": "1.0.0-next.6",
"resolved": "https://registry.npmjs.org/@polka/send/-/send-1.0.0-next.6.tgz",
"integrity": "sha512-4ON4Yf/QcP9I6HmFvn8rspRdBQ6NupSkUvAkUKo4gT2SSSWrrHMqDVQJsvDr4BRGRIVZSg+gr6W3M9Xj3V3JSQ=="
"version": "1.0.0-next.7",
"resolved": "https://registry.npmjs.org/@polka/send/-/send-1.0.0-next.7.tgz",
"integrity": "sha512-X/7sxWMfzuHuMXSls3SuOgfwcoNakuaNWciVS/KX3RML8NIPKQYgbhpqYPCujtXK9Z/KvW3/gzr2TUpabIJRMg=="
},
"@polka/url": {
"version": "1.0.0-next.3",
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.3.tgz",
"integrity": "sha512-Uom7l6OeP6vcf85lMImelYu5WKVWjXyhkpi9WsRdRzlJFJFPVhjBtBCktgDUj7dk1N5FURUdegSZ5XOjxf8JZg=="
"version": "1.0.0-next.9",
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.9.tgz",
"integrity": "sha512-VZqSaulg2kVQYMulmuZcvapPwH5/y81YHANiFIKz1GNZoG/F4o1JSeLlrvXJ8tC+RPUjxdrebfT3Qn+bnMi0bA=="
},
"@sindresorhus/slugify": {
"version": "0.9.1",
@ -1291,17 +1291,24 @@
}
},
"@sveltejs/svelte-repl": {
"version": "0.1.9",
"resolved": "https://registry.npmjs.org/@sveltejs/svelte-repl/-/svelte-repl-0.1.9.tgz",
"integrity": "sha512-OXDfHwT5O7UXVYnf4ndTk3dKMITTmWcMty4/lOFte80ui01i47QiVy3GEe9G8FkcU1YBe+c06MMnIgm7j0Ln7Q==",
"version": "0.1.17",
"resolved": "https://registry.npmjs.org/@sveltejs/svelte-repl/-/svelte-repl-0.1.17.tgz",
"integrity": "sha512-rM0DC+pZnqwH6PiuxXUmFRwYZ9XNkexxTNt+prR91Qs7ssxGgf0QkH6kGivSNLbrOtOvcgJbt1nUDybWra5HKA==",
"dev": true,
"requires": {
"codemirror": "^5.48.4",
"estree-walker": "^0.6.1",
"codemirror": "^5.49.2",
"estree-walker": "^0.9.0",
"sourcemap-codec": "^1.4.6",
"svelte-json-tree": "0.0.5",
"yootils": "0.0.16"
},
"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": {
"version": "1.4.6",
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.6.tgz",
@ -1587,9 +1594,9 @@
"dev": true
},
"codemirror": {
"version": "5.48.4",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.48.4.tgz",
"integrity": "sha512-pUhZXDQ6qXSpWdwlgAwHEkd4imA0kf83hINmUEzJpmG80T/XLtDDEzZo8f6PQLuRCcUQhmzqqIo3ZPTRaWByRA==",
"version": "5.49.2",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.49.2.tgz",
"integrity": "sha512-dwJ2HRPHm8w51WB5YTF9J7m6Z5dtkqbU9ntMZ1dqXyFB9IpjoUFDj80ahRVEoVanfIp6pfASJbOlbWdEf8FOzQ==",
"dev": true
},
"color-convert": {
@ -2510,9 +2517,9 @@
}
},
"mime": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.4.3.tgz",
"integrity": "sha512-QgrPRJfE+riq5TPZMcHZOtm8c6K/yYrMbKIoRfapfiGLxS8OTeIfRhUGW5LU7MlRa52KOAGCfUNruqLrIBvWZw=="
"version": "2.4.4",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz",
"integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA=="
},
"mimic-fn": {
"version": "2.1.0",
@ -3119,11 +3126,11 @@
"dev": true
},
"polka": {
"version": "1.0.0-next.6",
"resolved": "https://registry.npmjs.org/polka/-/polka-1.0.0-next.6.tgz",
"integrity": "sha512-e3vZm2cMmPPrgn+0J5DO0rrSTfsCHGyh+YS6jjrqYP8BHJkPq8nCVSDxHkaiEN4f0c2dtR6FB+snDmLE/sRz7A==",
"version": "1.0.0-next.9",
"resolved": "https://registry.npmjs.org/polka/-/polka-1.0.0-next.9.tgz",
"integrity": "sha512-oAWH5O3CIPTzPKNx9KF9NDfy3KRyy9NtUhDEJGmMRCDT6s3CZaGDm7xafcKtm0uK6g0CBiNtoeGWpPFSLUXeaw==",
"requires": {
"@polka/url": "^1.0.0-next.3",
"@polka/url": "^1.0.0-next.9",
"trouter": "^3.1.0"
}
},
@ -3368,12 +3375,6 @@
"requires": {
"@babel/helper-module-imports": "^7.0.0",
"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": {
@ -3411,10 +3412,6 @@
"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=="
},
"resolve": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz",
@ -3458,12 +3455,6 @@
"rollup-pluginutils": "^2.8.1",
"serialize-javascript": "^1.7.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": {
@ -3473,14 +3464,6 @@
"dev": true,
"requires": {
"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": {
@ -3600,19 +3583,13 @@
"dev": true
},
"sirv": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/sirv/-/sirv-0.4.2.tgz",
"integrity": "sha512-dQbZnsMaIiTQPZmbGmktz+c74zt/hyrJEB4tdp2Jj0RNv9J6B/OWR5RyrZEvIn9fyh9Zlg2OlE2XzKz6wMKGAw==",
"version": "1.0.0-next.2",
"resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.0-next.2.tgz",
"integrity": "sha512-hWp0todr4jSb1BFBiANRmqYRXzX02l36/X4tyHPYKqMZ+e1hrDZKUjIIXrAOBRWlAE/G5cGImUciMrUcU8DeOg==",
"requires": {
"@polka/url": "^0.5.0",
"mime": "^2.3.1"
},
"dependencies": {
"@polka/url": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@polka/url/-/url-0.5.0.tgz",
"integrity": "sha512-oZLYFEAzUKyi3SKnXvj32ZCEGH6RDnao7COuCVhDydMS9NrCSVXhM79VaKyP5+Zc33m0QXEd2DN3UkU7OsHcfw=="
}
"@polka/url": "^1.0.0-next.9",
"mime": "^2.3.1",
"totalist": "^1.0.0"
}
},
"source-map": {
@ -3780,6 +3757,12 @@
"integrity": "sha512-9/broj3bjShrsk3FuVDH0Bho2BchPKT8ubAqRcTqwkqrO9npOS3Vi98Yb5mBaa4bYV+ELTuvPvaQZbCJYMFRdg==",
"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": {
"version": "4.4.10",
"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.10.tgz",
@ -3843,6 +3826,11 @@
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
"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": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz",

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

@ -13,6 +13,9 @@ const mode = process.env.NODE_ENV;
const dev = mode === 'development';
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 {
client: {
input: config.client.input(),
@ -28,7 +31,10 @@ export default {
hydratable: true,
emitCss: true
}),
resolve(),
resolve({
browser: true,
dedupe
}),
commonjs(),
json(),
@ -53,6 +59,7 @@ export default {
module: true
})
],
onwarn
},
server: {
@ -67,7 +74,9 @@ export default {
generate: 'ssr',
dev
}),
resolve(),
resolve({
dedupe
}),
commonjs(),
json()
],
@ -78,6 +87,7 @@ export default {
require('module').builtinModules || Object.keys(process.binding('natives'))
)
],
onwarn
},
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="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://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="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>
@ -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://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://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://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://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://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://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>

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 Stats from '../Stats';
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 add_to_set from './utils/add_to_set';
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 {
namespace?: string;
@ -37,13 +37,6 @@ interface ComponentOptions {
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 {
stats: Stats;
warnings: Warning[];
@ -169,13 +162,12 @@ export default class Component {
this.tag = this.name.name;
}
this.walk_module_js_pre_template();
this.walk_module_js();
this.walk_instance_js_pre_template();
this.fragment = new Fragment(this, ast.html);
this.name = this.get_unique_name(name);
this.walk_module_js_post_template();
this.walk_instance_js_post_template();
if (!compile_options.customElement) this.stylesheet.reify();
@ -209,7 +201,6 @@ export default class Component {
});
const subscribable_name = name.slice(1);
this.add_reference(subscribable_name);
const variable = this.var_lookup.get(subscribable_name);
if (variable) variable.subscribable = true;
@ -240,8 +231,7 @@ export default class Component {
const { compile_options, name } = this;
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 };
@ -526,7 +516,7 @@ export default class Component {
});
}
walk_module_js_pre_template() {
walk_module_js() {
const component = this;
const script = this.ast.module;
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() {
@ -674,30 +683,6 @@ export default class Component {
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() {
const script = this.ast.instance;
if (!script) return;
@ -718,11 +703,13 @@ export default class Component {
let scope = instance_scope;
const toRemove = [];
const to_remove = [];
const remove = (parent, prop, index) => {
toRemove.unshift([parent, prop, index]);
to_remove.unshift([parent, prop, index]);
};
const to_insert = new Map();
walk(content, {
enter(node, parent, prop, index) {
if (map.has(node)) {
@ -748,16 +735,41 @@ export default class Component {
}
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) {
if (map.has(node)) {
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 (index !== null) {
parent[prop].splice(index, 1);
@ -837,48 +849,30 @@ export default class Component {
}
}
invalidate(name, value?) {
const variable = this.var_lookup.get(name);
if (variable && (variable.subscribable && (variable.reassigned || variable.export_name))) {
return x`${`$$subscribe_${name}`}($$invalidate('${name}', ${value || name}))`;
}
loop_protect(node, prop, index, timeout) {
if (node.type === 'WhileStatement' ||
node.type === 'ForStatement' ||
node.type === 'DoWhileStatement') {
const guard = this.get_unique_name('guard');
this.add_var({
name: guard.name,
internal: true,
});
if (name[0] === '$' && name[1] !== '$') {
return x`${name.slice(1)}.set(${value || name})`;
}
const before = b`const ${guard} = @loop_guard(${timeout})`;
const inside = b`${guard}();`;
if (
variable &&
!variable.referenced &&
!variable.is_reactive_dependency &&
!variable.export_name &&
!name.startsWith('$$')
) {
return value || name;
// wrap expression statement with BlockStatement
if (node.body.type !== 'BlockStatement') {
node.body = {
type: 'BlockStatement',
body: [node.body],
};
}
if (value) {
return x`$$invalidate('${name}', ${value})`;
node.body.body.push(inside[0]);
return { index, prop, node: before[0] };
}
// 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}}`);
return null;
}
rewrite_props(get_insert: (variable: Var) => Node[]) {
@ -1002,6 +996,10 @@ export default class Component {
if (!d.init) 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 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) {
if (name[0] === '$') {
if (name === '$' || name[1] === '$' && name !== '$$props') {

@ -41,28 +41,11 @@ function edit_source(source, sveltePath) {
: source;
}
function esm(
program: any,
name: Identifier,
_banner: string,
sveltePath: string,
internal_path: string,
helpers: Array<{ name: string; alias: Identifier }>,
function get_internal_globals(
globals: Array<{ name: string; alias: Identifier }>,
imports: ImportDeclaration[],
module_exports: Export[]
helpers: Array<{ name: string; alias: Identifier }>
) {
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 = globals.length > 0 && {
return globals.length > 0 && {
type: 'VariableDeclaration',
kind: 'const',
declarations: [{
@ -82,6 +65,30 @@ function esm(
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
imports.forEach(node => {
@ -98,6 +105,8 @@ function esm(
};
program.body = b`
/* ${banner} */
${import_declaration}
${internal_globals}
${imports}
@ -112,7 +121,7 @@ function esm(
function cjs(
program: any,
name: Identifier,
_banner: string,
banner: string,
sveltePath: string,
internal_path: string,
helpers: Array<{ name: string; alias: Identifier }>,
@ -141,26 +150,7 @@ function cjs(
}]
};
const internal_globals = globals.length > 0 && {
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 internal_globals = get_internal_globals(globals, helpers);
const user_requires = imports.map(node => ({
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 }};`);
program.body = b`
/* ${banner} */
"use strict";
${internal_requires}
${internal_globals}

@ -3,6 +3,7 @@ import Stylesheet from './Stylesheet';
import { gather_possible_values, UNKNOWN } from './gather_possible_values';
import { CssNode } from './interfaces';
import Component from '../Component';
import Element from '../nodes/Element';
enum BlockAppliesToNode {
NotPossible,
@ -34,8 +35,8 @@ export default class Selector {
this.used = this.blocks[0].global;
}
apply(node: CssNode, stack: CssNode[]) {
const to_encapsulate: CssNode[] = [];
apply(node: Element, stack: Element[]) {
const to_encapsulate: any[] = [];
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();
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);
if (!attr) return false;
if (attr.is_true) return operator === null;
if (attr.chunks.length > 1) return true;
if (!expected_value) return true;
if (attr.chunks.length === 1) {
const value = attr.chunks[0];
if (!value) return false;
if (value.type === 'Text') return test_attribute(operator, expected_value, case_insensitive, value.data);
}
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;
for (const value of possible_values) {

@ -24,12 +24,13 @@ const valid_options = [
'customElement',
'tag',
'css',
'loopGuardTimeout',
'preserveComments',
'preserveWhitespace'
];
function validate_options(options: CompileOptions, warnings: Warning[]) {
const { name, filename } = options;
const { name, filename, loopGuardTimeout, dev } = options;
Object.keys(options).forEach(key => {
if (valid_options.indexOf(key) === -1) {
@ -54,6 +55,16 @@ function validate_options(options: CompileOptions, warnings: Warning[]) {
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 = {}) {

@ -14,7 +14,7 @@ export default class Action extends Node {
component.warn_if_undefined(info.name, info, scope);
this.name = info.name;
component.qualify(info.name);
component.add_reference(info.name.split('.')[0]);
this.expression = 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);
this.name = info.name;
component.qualify(info.name);
component.add_reference(info.name.split('.')[0]);
if (parent.animation) {
component.error(this, {

@ -11,7 +11,11 @@ const read_only_media_attributes = new Set([
'duration',
'buffered',
'seekable',
'played'
'played',
'seeking',
'ended',
'videoHeight',
'videoWidth'
]);
export default class Binding extends Node {
@ -48,7 +52,9 @@ export default class Binding extends Node {
} else if (this.is_contextual) {
scope.dependencies_for_name.get(name).forEach(name => {
const variable = component.var_lookup.get(name);
if (variable) {
variable[this.expression.node.type === 'MemberExpression' ? 'mutated' : 'reassigned'] = true;
}
});
} else {
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 => {
switch (node.type) {
case 'Action':
@ -592,7 +596,9 @@ export default class Element extends Node {
name === 'seekable' ||
name === 'played' ||
name === 'volume' ||
name === 'playbackRate'
name === 'playbackRate' ||
name === 'seeking' ||
name === 'ended'
) {
if (this.name !== 'audio' && this.name !== 'video') {
component.error(binding, {
@ -600,6 +606,16 @@ export default class Element extends Node {
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)) {
if (this.name === 'svg' && (name === 'offsetWidth' || name === 'offsetHeight')) {
component.error(binding, {

@ -1,8 +1,6 @@
import Node from './shared/Node';
import Expression from './shared/Expression';
import Component from '../Component';
import { b, x } from 'code-red';
import Block from '../render_dom/Block';
import { sanitize } from '../../utils/names';
import { Identifier } from 'estree';
@ -14,6 +12,7 @@ export default class EventHandler extends Node {
handler_name: Identifier;
uses_context = false;
can_make_passive = false;
reassigned?: boolean;
constructor(component: 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);
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;
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) {
this.can_make_passive = true;
}
}
}
} else {
const id = 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.reassigned = component.var_lookup.get(info.expression.name).reassigned;
}
`);
this.handler_name = id;
} else if (this.expression.dynamic_dependencies().length > 0) {
this.reassigned = true;
}
} else {
this.handler_name = component.get_unique_name(`${sanitize(this.name)}_handler`);
}
// 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);
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.is_local = info.modifiers.includes('local');

@ -9,9 +9,9 @@ import TemplateScope from './TemplateScope';
import get_object from '../../utils/get_object';
import Block from '../../render_dom/Block';
import is_dynamic from '../../render_dom/wrappers/shared/is_dynamic';
import { x, b, p } from 'code-red';
import { invalidate } from '../../utils/invalidate';
import { Node, FunctionExpression } from 'estree';
import { b } from 'code-red';
import { invalidate } from '../../render_dom/invalidate';
import { Node, FunctionExpression, Identifier } from 'estree';
import { TemplateNode } from '../../../interfaces';
type Owner = Wrapper | TemplateNode;
@ -213,7 +213,8 @@ export default class Expression {
component.add_reference(name); // TODO is this redundant/misplaced?
}
} else if (is_contextual(component, template_scope, name)) {
this.replace(x`#ctx.${node}`);
const reference = block.renderer.reference(node);
this.replace(reference);
}
this.skip();
@ -260,42 +261,38 @@ export default class Expression {
// function can be hoisted inside the component init
component.partly_hoisted.push(declaration);
this.replace(x`#ctx.${id}` as any);
component.add_var({
name: id.name,
internal: true,
referenced: true
});
block.renderer.add_to_context(id.name);
this.replace(block.renderer.reference(id));
}
else {
// we need a combo block/init recipe
(node as FunctionExpression).params.unshift({
type: 'ObjectPattern',
properties: Array.from(contextual_dependencies).map(name => p`${name}` as any)
});
const deps = Array.from(contextual_dependencies);
(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);
this.replace(id as any);
block.renderer.add_to_context(id.name);
const callee = block.renderer.reference(id);
component.add_var({
name: id.name,
internal: true,
referenced: true
});
this.replace(id as any);
if ((node as FunctionExpression).params.length > 0) {
declarations.push(b`
function ${id}(...args) {
return #ctx.${id}(#ctx, ...args);
return ${callee}(${context_args}, ...args);
}
`);
} else {
declarations.push(b`
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 Wrapper from './wrappers/shared/Wrapper';
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';
export interface BlockOptions {
@ -34,7 +34,7 @@ export default class Block {
key: Identifier;
first: Identifier;
dependencies: Set<string>;
dependencies: Set<string> = new Set();
bindings: Map<string, {
object: Identifier;
@ -90,8 +90,6 @@ export default class Block {
this.key = options.key;
this.first = null;
this.dependencies = new Set();
this.bindings = options.bindings;
this.chunks = {
@ -203,13 +201,11 @@ export default class Block {
}
add_variable(id: Identifier, init?: Node) {
this.variables.forEach(v => {
if (v.id.name === id.name) {
if (this.variables.has(id.name)) {
throw new Error(
`Variable '${id.name}' already initialised with a different value`
);
}
});
this.variables.set(id.name, { id, init });
}
@ -268,7 +264,7 @@ export default class Block {
: this.chunks.hydrate
);
properties.create = x`function create() {
properties.create = x`function #create() {
${this.chunks.create}
${hydrate}
}`;
@ -278,7 +274,7 @@ export default class Block {
if (this.chunks.claim.length === 0 && this.chunks.hydrate.length === 0) {
properties.claim = noop;
} else {
properties.claim = x`function claim(#nodes) {
properties.claim = x`function #claim(#nodes) {
${this.chunks.claim}
${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) {
properties.hydrate = x`function hydrate() {
properties.hydrate = x`function #hydrate() {
${this.chunks.hydrate}
}`;
}
@ -294,7 +290,7 @@ export default class Block {
if (this.chunks.mount.length === 0) {
properties.mount = noop;
} else {
properties.mount = x`function mount(#target, anchor) {
properties.mount = x`function #mount(#target, anchor) {
${this.chunks.mount}
}`;
}
@ -304,7 +300,13 @@ export default class Block {
properties.update = noop;
} else {
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.chunks.update}
}`;
@ -312,15 +314,15 @@ export default class Block {
}
if (this.has_animation) {
properties.measure = x`function measure() {
properties.measure = x`function #measure() {
${this.chunks.measure}
}`;
properties.fix = x`function fix() {
properties.fix = x`function #fix() {
${this.chunks.fix}
}`;
properties.animate = x`function animate() {
properties.animate = x`function #animate() {
${this.chunks.animate}
}`;
}
@ -329,7 +331,7 @@ export default class Block {
if (this.chunks.intro.length === 0) {
properties.intro = noop;
} else {
properties.intro = x`function intro(#local) {
properties.intro = x`function #intro(#local) {
${this.has_outros && b`if (#current) return;`}
${this.chunks.intro}
}`;
@ -338,7 +340,7 @@ export default class Block {
if (this.chunks.outro.length === 0) {
properties.outro = noop;
} else {
properties.outro = x`function outro(#local) {
properties.outro = x`function #outro(#local) {
${this.chunks.outro}
}`;
}
@ -347,7 +349,7 @@ export default class Block {
if (this.chunks.destroy.length === 0) {
properties.destroy = noop;
} else {
properties.destroy = x`function destroy(detaching) {
properties.destroy = x`function #destroy(detaching) {
${this.chunks.destroy}
}`;
}
@ -376,6 +378,8 @@ export default class Block {
d: ${properties.destroy}
}`;
const block = dev && this.get_unique_name('block');
const body = b`
${Array.from(this.variables.values()).map(({ id, init }) => {
return init
@ -387,9 +391,15 @@ export default class Block {
${dev
? b`
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 });
return block;`
const ${block} = ${return_value};
@dispatch_dev("SvelteRegisterBlock", {
block: ${block},
id: ${this.name || 'create_fragment'}.name,
type: "${this.type}",
source: "${this.comment ? this.comment.replace(/"/g, '\\"') : ''}",
ctx: #ctx
});
return ${block};`
: b`
return ${return_value};`
}
@ -398,21 +408,36 @@ export default class Block {
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() {
const key = this.key && this.get_unique_name('key');
const args: any[] = [x`#ctx`];
if (key) args.unshift(key);
// TODO include this.comment
// ${this.comment && `// ${escape(this.comment, { only_escape_at_symbol: true })}`}
return b`
function ${this.name}(${args}) {
const fn = b`function ${this.name}(${args}) {
${this.get_contents(key)}
}
`;
}`;
return this.comment
? b`
// ${this.comment}
${fn}`
: fn;
}
render_listeners(chunk: string = '') {

@ -1,14 +1,27 @@
import Block from './Block';
import { CompileOptions } from '../../interfaces';
import { CompileOptions, Var } from '../../interfaces';
import Component from '../Component';
import FragmentWrapper from './wrappers/Fragment';
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 {
component: Component; // TODO Maybe Renderer shouldn't know about Component?
options: CompileOptions;
context: ContextMember[] = [];
context_lookup: Map<string, ContextMember> = new Map();
context_overflow: boolean;
blocks: Array<Block | Node | Node[]> = [];
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
@ -27,6 +40,24 @@ export default class Renderer {
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
this.block = new Block({
renderer: this,
@ -50,6 +81,8 @@ export default class Renderer {
null
);
this.context_overflow = this.context.length > 31;
// TODO messy
this.blocks.forEach(block => {
if (block instanceof Block) {
@ -60,5 +93,195 @@ export default class Renderer {
this.block.assign_variable_names();
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 { CompileOptions, Var } from '../../interfaces';
import { walk } from 'estree-walker';
import add_to_set from '../utils/add_to_set';
import { extract_names } from '../utils/scope';
import { invalidate } from '../utils/invalidate';
import { invalidate } from './invalidate';
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(
component: Component,
@ -76,20 +75,18 @@ export default function dom(
const props = component.vars.filter(variable => !variable.module && variable.export_name);
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)
? x`
${$$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 =>
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 &&
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;
/* eslint-enable @typescript-eslint/indent,indent */
const accessors = [];
@ -105,7 +102,7 @@ export default function dom(
kind: 'get',
key: { type: 'Identifier', name: prop.export_name },
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) {
@ -154,14 +151,14 @@ export default function dom(
if (component.compile_options.dev) {
// 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) {
dev_props_check = b`
const { ctx: #ctx } = this.$$;
const props = ${options.customElement ? x`this.attributes` : x`options.props || {}`};
${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}'");
}`)}
`;
@ -192,9 +189,9 @@ export default function dom(
inject_state = x`
${$$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`
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
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 }) => {
const value = `$${name}`;
const i = renderer.context_lookup.get(`$${name}`).index;
const insert = (reassigned || export_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) {
return b`@validate_store(${name}, '${name}'); ${insert}`;
@ -255,22 +253,21 @@ export default function dom(
args.push(x`$$props`, x`$$invalidate`);
}
const has_create_fragment = block.has_content();
if (has_create_fragment) {
body.push(b`
function create_fragment(#ctx) {
${block.get_contents()}
}
`);
}
body.push(b`
${component.extract_javascript(component.ast.module)}
${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 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] !== '$');
if (component.slots.size > 0) {
filtered_declarations.push(p`$$slots`, p`$$scope`);
}
const instance_javascript = component.extract_javascript(component.ast.instance);
if (renderer.binding_groups.length > 0) {
filtered_declarations.push(p`$$binding_groups`);
let i = renderer.context.length;
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 instance_javascript = component.extract_javascript(component.ast.instance);
}
const initial_context = renderer.context.slice(0, i + 1);
const has_definition = (
(instance_javascript && instance_javascript.length > 0) ||
filtered_props.length > 0 ||
uses_props ||
component.partly_hoisted.length > 0 ||
filtered_declarations.length > 0 ||
initial_context.length > 0 ||
component.reactive_declarations.length > 0
);
@ -304,11 +304,6 @@ export default function dom(
? component.alias('instance')
: { 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
.filter(store => {
const variable = component.var_lookup.get(store.name.slice(1));
@ -316,7 +311,7 @@ export default function dom(
})
.map(({ name }) => b`
${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
@ -336,12 +331,10 @@ export default function dom(
const writable = dependencies.filter(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
.map(n => x`#changed.${n}`)
.reduce((lhs, rhs) => x`${lhs} || ${rhs}`));
const condition = !uses_props && writable.length > 0 && renderer.dirty(writable, true);
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)) {
const unsubscribe = `$$unsubscribe_${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};`;
@ -378,26 +373,17 @@ export default function dom(
unknown_props_check = b`
const writable_props = [${writable_props.map(prop => x`'${prop.export_name}'`)}];
@_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 = {
type: 'ObjectExpression',
properties: filtered_declarations
};
const reactive_dependencies = {
type: 'ObjectPattern',
properties: Array.from(all_reactive_dependencies).map(name => {
return {
type: 'Property',
kind: 'init',
key: { type: 'Identifier', name },
value: { type: 'Literal', value: 1 }
};
})
type: 'ArrayExpression',
elements: initial_context.map(member => ({
type: 'Identifier',
name: member.name
}) as Expression)
};
body.push(b`
@ -427,22 +413,32 @@ export default function dom(
${injected.map(name => b`let ${name};`)}
${reactive_declarations.length > 0 && b`
$$self.$$.update = (#changed = ${reactive_dependencies}) => {
$$self.$$.update = () => {
${reactive_declarations}
};
`}
${fixed_reactive_declarations}
${uses_props && b`$$props = @exclude_internal_props($$props);`}
return ${return_value};
}
`);
}
const prop_names = x`{
${props.map(v => p`${v.export_name}: ${v.export_name === v.name ? 0 : x`"${v.name}"`}}`)}
const prop_indexes = x`{
${props.filter(v => v.export_name && !v.module).map(v => p`${v.export_name}: ${renderer.context_lookup.get(v.name).index}`)}
}` 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) {
const declaration = b`
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>\`;`}
@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}
@ -503,7 +499,7 @@ export default function dom(
constructor(options) {
super(${options.dev && `options`});
${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 });`}
${dev_props_check}

@ -1,10 +1,12 @@
import Component from '../Component';
import { nodes_match } from '../../utils/nodes_match';
import { Scope } from './scope';
import { Scope } from '../utils/scope';
import { x } from 'code-red';
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 owner = scope.find_owner(name);
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.referenced ||
variable.subscribable ||
variable.is_reactive_dependency ||
variable.export_name ||
variable.name[0] === '$'
@ -28,12 +31,12 @@ export function invalidate(component: Component, scope: Scope, node: Node, names
component.has_reactive_assignments = true;
if (node.type === 'AssignmentExpression' && node.operator === '=' && nodes_match(node.left, node.right) && tail.length === 0) {
return component.invalidate(head);
return renderer.invalidate(head);
} else {
const is_store_value = head[0] === '$';
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 = (
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 = x`${callee}(${is_store_value ? head.slice(1) : x`"${head}"`}, ${node}, ${extra_args})`;
let invalidate = is_store_value
? 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) {
const subscribe = `$$subscribe_${head}`;

@ -8,7 +8,6 @@ import FragmentWrapper from './Fragment';
import PendingBlock from '../../nodes/PendingBlock';
import ThenBlock from '../../nodes/ThenBlock';
import CatchBlock from '../../nodes/CatchBlock';
import { changed } from './shared/changed';
import { Identifier } from 'estree';
class AwaitBlockBranch extends Wrapper {
@ -119,6 +118,9 @@ export default class AwaitBlockWrapper extends Wrapper {
if (has_outros) {
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(
@ -138,6 +140,9 @@ export default class AwaitBlockWrapper extends Wrapper {
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`{
ctx: #ctx,
current: null,
@ -145,8 +150,8 @@ export default class AwaitBlockWrapper extends Wrapper {
pending: ${this.pending.block.name},
then: ${this.then.block.name},
catch: ${this.catch.block.name},
value: ${this.then.block.name && x`"${this.node.value}"`},
error: ${this.catch.block.name && x`"${this.node.error}"`},
value: ${value_index},
error: ${error_index},
blocks: ${this.pending.block.has_outro_method && x`[,,,]`}
}`;
@ -187,7 +192,7 @@ export default class AwaitBlockWrapper extends Wrapper {
if (dependencies.length > 0) {
const condition = x`
${changed(dependencies)} &&
${block.renderer.dirty(dependencies)} &&
${promise} !== (${promise} = ${snippet}) &&
@handle_promise(${promise}, ${info})`;
@ -198,9 +203,11 @@ export default class AwaitBlockWrapper extends Wrapper {
if (this.pending.block.has_update_method) {
block.chunks.update.push(b`
if (${condition}) {
// nothing
} 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 {
@ -211,7 +218,11 @@ export default class AwaitBlockWrapper extends Wrapper {
} else {
if (this.pending.block.has_update_method) {
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,20 +3,23 @@ import Wrapper from './shared/Wrapper';
import { b } from 'code-red';
import Body from '../../nodes/Body';
import { Identifier } from 'estree';
import EventHandler from './Element/EventHandler';
export default class BodyWrapper extends Wrapper {
node: Body;
render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) {
this.node.handlers.forEach(handler => {
const snippet = handler.render(block);
this.node.handlers
.map(handler => new EventHandler(handler, this))
.forEach(handler => {
const snippet = handler.get_snippet(block);
block.chunks.init.push(b`
@_document.body.addEventListener("${handler.name}", ${snippet});
@_document.body.addEventListener("${handler.node.name}", ${snippet});
`);
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 { b, p } from 'code-red';
import { Identifier, DebuggerStatement } from 'estree';
import { changed } from './shared/changed';
export default class DebugTagWrapper extends Wrapper {
node: DebugTag;
@ -60,17 +59,17 @@ export default class DebugTagWrapper extends Wrapper {
const variable = var_lookup.get(e.node.name);
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 debug_statements = b`
const { ${contextual_identifiers} } = #ctx;
${contextual_identifiers.map(name => b`const ${name} = ${renderer.reference(name)};`)}
@_console.${log}({ ${logged_identifiers} });
debugger;`;
if (dependencies.size) {
const condition = changed(Array.from(dependencies));
const condition = renderer.dirty(Array.from(dependencies));
block.chunks.update.push(b`
if (${condition}) {

@ -7,7 +7,6 @@ import FragmentWrapper from './Fragment';
import { b, x } from 'code-red';
import ElseBlock from '../../nodes/ElseBlock';
import { Identifier, Node } from 'estree';
import { changed } from './shared/changed';
export class ElseBlockWrapper extends Wrapper {
node: ElseBlock;
@ -81,6 +80,10 @@ export default class EachBlockWrapper extends Wrapper {
const { dependencies } = node.expression;
block.add_dependencies(dependencies);
this.node.contexts.forEach(context => {
renderer.add_to_context(context.key.name, true);
});
this.block = block.child({
comment: create_debugging_comment(this.node, this.renderer.component),
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 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 = {
create_each_block: this.block.name,
each_block_value,
@ -190,18 +196,19 @@ export default class EachBlockWrapper extends Wrapper {
? !this.next.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.node.index) this.context_props.push(b`child_ctx.${this.index_name} = i;`);
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[${renderer.context_lookup.get(this.index_name.name).index}] = i;`);
const snippet = this.node.expression.manipulate(block);
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`
function ${this.vars.get_each_context}(#ctx, list, i) {
const child_ctx = @_Object.create(#ctx);
const child_ctx = #ctx.slice();
${this.context_props}
return child_ctx;
}
@ -270,7 +277,7 @@ export default class EachBlockWrapper extends Wrapper {
if (this.else.block.has_update_method) {
block.chunks.update.push(b`
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}) {
${each_block_else} = ${this.else.block.name}(#ctx);
${each_block_else}.c();
@ -396,7 +403,7 @@ export default class EachBlockWrapper extends Wrapper {
${this.block.has_outros && b`@group_outros();`}
${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.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 { dependencies } = this.node.expression;
dependencies.forEach((dependency: string) => {
const all_dependencies = new Set(this.block.dependencies); // TODO should be dynamic deps only
this.node.expression.dynamic_dependencies().forEach((dependency: string) => {
all_dependencies.add(dependency);
});
@ -481,7 +487,7 @@ export default class EachBlockWrapper extends Wrapper {
const for_loop_body = this.block.has_update_method
? b`
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);`}
} else {
${iterations}[#i] = ${create_each_block}(child_ctx);
@ -554,7 +560,7 @@ export default class EachBlockWrapper extends Wrapper {
`;
block.chunks.update.push(b`
if (${changed(Array.from(all_dependencies))}) {
if (${block.renderer.dirty(Array.from(all_dependencies))}) {
${update}
}
`);

@ -6,8 +6,6 @@ import { string_literal } from '../../../utils/stringify';
import { b, x } from 'code-red';
import Expression from '../../../nodes/shared/Expression';
import Text from '../../../nodes/Text';
import { changed } from '../shared/changed';
import { Literal } from 'estree';
export default class AttributeWrapper {
node: Attribute;
@ -72,29 +70,13 @@ export default class AttributeWrapper {
const is_legacy_input_type = element.renderer.component.compile_options.legacy && name === 'type' && this.parent.node.name === 'input';
const dependencies = this.node.get_dependencies();
if (dependencies.length > 0) {
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 value = this.get_value(block);
const is_src = this.node.name === 'src'; // TODO retire this exception in favour of https://github.com/sveltejs/svelte/issues/3750
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(
`${element.var.name}_${name.replace(/[^a-zA-Z_$]/g, '_')}_value`
@ -137,6 +119,11 @@ export default class AttributeWrapper {
${last} = ${value};
${updater}
`);
} else if (is_src) {
block.chunks.hydrate.push(
b`if (${element.var}.src !== ${init}) ${method}(${element.var}, "${name}", ${last});`
);
updater = b`${method}(${element.var}, "${name}", ${should_cache ? last : value});`;
} else if (property_name) {
block.chunks.hydrate.push(
b`${element.var}.${property_name} = ${init};`
@ -151,10 +138,13 @@ export default class AttributeWrapper {
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) {
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) {
@ -165,24 +155,12 @@ export default class AttributeWrapper {
if (${condition}) {
${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
if (this.node.is_true && name === 'autofocus') {
block.autofocus = element.var;
}
}
if (is_indirectly_bound_value) {
const update_value = b`${element.var}.value = ${element.var}.__value;`;
@ -199,9 +177,39 @@ export default class AttributeWrapper {
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 rendered = this.render_chunks();
const rendered = this.render_chunks(block);
if (scoped_css && rendered.length === 2) {
// we have a situation like class={possiblyUndefined}
@ -211,13 +219,13 @@ export default class AttributeWrapper {
return rendered.reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
}
render_chunks() {
render_chunks(block: Block) {
return this.node.chunks.map((chunk) => {
if (chunk.type === 'Text') {
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];
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 flatten_reference from '../../../utils/flatten_reference';
import EachBlock from '../../../nodes/EachBlock';
import { changed } from '../shared/changed';
import { Node, Identifier } from 'estree';
export default class BindingWrapper {
@ -86,11 +85,12 @@ export default class BindingWrapper {
const { parent } = this;
const update_conditions: any[] = this.needs_lock ? [x`!${lock}`] : [];
const mount_conditions: any[] = [];
const dependency_array = [...this.node.expression.dependencies];
if (dependency_array.length > 0) {
update_conditions.push(changed(dependency_array));
update_conditions.push(block.renderer.dirty(dependency_array));
}
if (parent.node.name === 'input') {
@ -103,6 +103,7 @@ export default class BindingWrapper {
// model to view
let update_dom = get_dom_updater(parent, this);
let mount_dom = update_dom;
// special cases
switch (this.node.name) {
@ -110,28 +111,38 @@ export default class BindingWrapper {
{
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(
b`#ctx.$$binding_groups[${binding_group}].push(${parent.var});`
b`${reference}[${binding_group}].push(${parent.var});`
);
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;
}
case 'textContent':
update_conditions.push(x`${this.snippet} !== ${parent.var}.textContent`);
mount_conditions.push(x`${this.snippet} !== void 0`);
break;
case 'innerHTML':
update_conditions.push(x`${this.snippet} !== ${parent.var}.innerHTML`);
mount_conditions.push(x`${this.snippet} !== void 0`);
break;
case 'currentTime':
update_conditions.push(x`!@_isNaN(${this.snippet})`);
mount_dom = null;
break;
case 'playbackRate':
case 'volume':
update_conditions.push(x`!@_isNaN(${this.snippet})`);
mount_conditions.push(x`!@_isNaN(${this.snippet})`);
break;
case 'paused':
@ -142,12 +153,14 @@ export default class BindingWrapper {
update_conditions.push(x`${last} !== (${last} = ${this.snippet})`);
update_dom = b`${parent.var}[${last} ? "pause" : "play"]();`;
mount_dom = null;
break;
}
case 'value':
if (parent.node.get_static_attribute_value('type') === 'file') {
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) {
if (mount_conditions.length > 0) {
const condition = mount_conditions.reduce((lhs, rhs) => x`${lhs} && ${rhs}`);
block.chunks.mount.push(b`
if (${this.snippet} !== void 0) {
${update_dom}
}`);
} else if (!/(currentTime|paused)/.test(this.node.name)) {
block.chunks.mount.push(update_dom);
if (${condition}) {
${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 Expression from '../../../nodes/shared/Expression';
import Text from '../../../nodes/Text';
import { changed } from '../shared/changed';
export interface StyleProp {
key: string;
@ -46,7 +45,7 @@ export default class StyleAttributeWrapper extends AttributeWrapper {
// }
if (prop_dependencies.size) {
let condition = changed(Array.from(prop_dependencies));
let condition = block.renderer.dirty(Array.from(prop_dependencies));
if (block.has_outros) {
condition = x`!#current || ${condition}`;
@ -88,14 +87,12 @@ function optimize_style(value: Array<Text|Expression>) {
const remaining_data = chunk.data.slice(offset);
if (remaining_data) {
/* eslint-disable @typescript-eslint/no-object-literal-type-assertion */
chunks[0] = {
start: chunk.start + offset,
end: chunk.end,
type: 'Text',
data: remaining_data
} as Text;
/* eslint-enable @typescript-eslint/no-object-literal-type-assertion */
} else {
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_actions from '../shared/add_actions';
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 { changed } from '../shared/changed';
import { is_head } from '../shared/is_head';
import { Identifier } from 'estree';
import EventHandler from './EventHandler';
import { extract_names } from 'periscopic';
const events = [
{
@ -51,7 +52,7 @@ const events = [
},
{
event_names: ['resize'],
event_names: ['elementresize'],
filter: (_node: Element, name: string) =>
dimensions.test(name)
},
@ -61,7 +62,7 @@ const events = [
event_names: ['timeupdate'],
filter: (node: Element, name: string) =>
node.is_media_node() &&
(name === 'currentTime' || name === 'played')
(name === 'currentTime' || name === 'played' || name === 'ended')
},
{
event_names: ['durationchange'],
@ -99,6 +100,24 @@ const events = [
node.is_media_node() &&
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
{
@ -113,12 +132,14 @@ export default class ElementWrapper extends Wrapper {
fragment: FragmentWrapper;
attributes: AttributeWrapper[];
bindings: Binding[];
event_handlers: EventHandler[];
class_dependencies: string[];
slot_block: Block;
select_binding_dependencies?: Set<string>;
var: any;
void: boolean;
constructor(
renderer: Renderer,
@ -134,8 +155,18 @@ export default class ElementWrapper extends Wrapper {
name: node.name.replace(/[^a-zA-Z0-9_$]/g, '_')
};
this.void = is_void(node.name);
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 => {
if (attribute.name === 'slot') {
// TODO make separate subclass for this?
@ -162,20 +193,17 @@ export default class ElementWrapper extends Wrapper {
type: 'slot'
});
const lets = this.node.lets;
const { scope, lets } = this.node;
const seen = new Set(lets.map(l => l.name.name));
(owner as unknown as InlineComponentWrapper).node.lets.forEach(l => {
if (!seen.has(l.name.name)) lets.push(l);
});
const fn = get_context_merger(lets);
(owner as unknown as InlineComponentWrapper).slots.set(name, {
block: child_block,
scope: this.node.scope,
fn
});
(owner as unknown as InlineComponentWrapper).slots.set(
name,
get_slot_definition(child_block, scope, lets)
);
this.renderer.blocks.push(child_block);
}
@ -194,6 +222,8 @@ export default class ElementWrapper extends Wrapper {
// e.g. <audio bind:paused bind:currentTime>
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) block.add_intro(node.intro.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 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);
const render_statement = this.get_render_statement();
const render_statement = this.get_render_statement(block);
block.chunks.create.push(
b`${node} = ${render_statement};`
);
@ -265,8 +296,13 @@ export default class ElementWrapper extends Wrapper {
if (parent_nodes) {
block.chunks.claim.push(b`
${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 {
block.chunks.claim.push(
b`${node} = ${render_statement};`
@ -291,7 +327,8 @@ export default class ElementWrapper extends Wrapper {
}
// 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') {
block.chunks.create.push(
// @ts-ignore todo: should it be this.fragment.nodes[0].node.data instead?
@ -311,7 +348,8 @@ export default class ElementWrapper extends Wrapper {
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);
block.chunks.create.push(
@ -338,18 +376,18 @@ export default class ElementWrapper extends Wrapper {
block.maintain_context = true;
}
this.add_attributes(block);
this.add_bindings(block);
this.add_event_handlers(block);
this.add_attributes(block);
this.add_transitions(block);
this.add_animation(block);
this.add_actions(block);
this.add_classes(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(
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');
}
get_render_statement() {
get_render_statement(block: Block) {
const { name, namespace } = this.node;
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');
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}")`;
@ -422,18 +460,13 @@ export default class ElementWrapper extends Wrapper {
groups.forEach(group => {
const handler = renderer.component.get_unique_name(`${this.var.name}_${group.events.join('_')}_handler`);
renderer.component.add_var({
name: handler.name,
internal: true,
referenced: true
});
renderer.add_to_context(handler.name);
// TODO figure out how to handle locks
const needs_lock = group.bindings.some(binding => binding.needs_lock);
const dependencies = new Set();
const contextual_dependencies = new Set();
const dependencies: Set<string> = new Set();
const contextual_dependencies: Set<string> = new Set();
group.bindings.forEach(binding => {
// 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;
let callee;
let callee = renderer.reference(handler);
// TODO dry this out — similar code for event handlers and component bindings
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
if (animation_frame) {
block.chunks.init.push(b`
@ -468,48 +503,38 @@ export default class ElementWrapper extends Wrapper {
${animation_frame} = @raf(${handler});
${needs_lock && b`${lock} = true;`}
}
#ctx.${handler}.call(${this.var}, ${contextual_dependencies.size > 0 ? '#ctx' : null});
${callee}.call(${this.var}, ${args});
}
`);
} else {
block.chunks.init.push(b`
function ${handler}() {
${needs_lock && b`${lock} = true;`}
#ctx.${handler}.call(${this.var}, ${contextual_dependencies.size > 0 ? '#ctx' : null});
${callee}.call(${this.var}, ${args});
}
`);
}
callee = handler;
} else {
callee = x`#ctx.${handler}`;
}
const arg = contextual_dependencies.size > 0 && {
type: 'ObjectPattern',
properties: Array.from(contextual_dependencies).map(name => {
const id = { type: 'Identifier', name };
return {
type: 'Property',
kind: 'init',
key: id,
value: id
};
})
};
}
const params = Array.from(contextual_dependencies).map(name => ({
type: 'Identifier',
name
}));
this.renderer.component.partly_hoisted.push(b`
function ${handler}(${arg}) {
function ${handler}(${params}) {
${group.bindings.map(b => b.handler.mutation)}
${Array.from(dependencies)
.filter(dep => dep[0] !== '$')
.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 => {
if (name === 'resize') {
if (name === 'elementresize') {
// special case
const resize_listener = block.get_unique_name(`${this.var.name}_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(
b`@add_render_callback(() => ${callee}.call(${this.var}));`
);
@ -599,7 +624,7 @@ export default class ElementWrapper extends Wrapper {
this.attributes
.forEach(attr => {
const condition = attr.node.dependencies.size > 0
? changed(Array.from(attr.node.dependencies))
? block.renderer.dirty(Array.from(attr.node.dependencies))
: null;
if (attr.node.is_spread) {
@ -613,7 +638,7 @@ export default class ElementWrapper extends Wrapper {
const snippet = x`{ ${
(metadata && metadata.property_name) ||
fix_attribute_casing(attr.node.name)
}: ${attr.node.get_value(block)} }`;
}: ${attr.get_value(block)} }`;
initial_props.push(snippet);
updates.push(condition ? x`${condition} && ${snippet}` : snippet);
@ -621,10 +646,10 @@ export default class ElementWrapper extends Wrapper {
});
block.chunks.init.push(b`
var ${levels} = [${initial_props}];
let ${levels} = [${initial_props}];
var ${data} = {};
for (var #i = 0; #i < ${levels}.length; #i += 1) {
let ${data} = {};
for (let #i = 0; #i < ${levels}.length; #i += 1) {
${data} = @assign(${data}, ${levels}[#i]);
}
`);
@ -643,7 +668,7 @@ export default class ElementWrapper extends Wrapper {
}
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(
@ -652,8 +677,6 @@ export default class ElementWrapper extends Wrapper {
const { intro, outro } = this.node;
if (!intro && !outro) return;
const { component } = this.renderer;
if (intro === outro) {
// bidirectional 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);
const fn = component.qualify(intro.name);
const fn = this.renderer.reference(intro.name);
const intro_block = b`
@add_render_callback(() => {
@ -707,7 +730,7 @@ export default class ElementWrapper extends Wrapper {
? intro.expression.manipulate(block)
: x`{}`;
const fn = component.qualify(intro.name);
const fn = this.renderer.reference(intro.name);
let intro_block;
@ -749,7 +772,7 @@ export default class ElementWrapper extends Wrapper {
? outro.expression.manipulate(block)
: x`{}`;
const fn = component.qualify(outro.name);
const fn = this.renderer.reference(outro.name);
if (!intro) {
block.chunks.intro.push(b`
@ -781,7 +804,6 @@ export default class ElementWrapper extends Wrapper {
add_animation(block: Block) {
if (!this.node.animation) return;
const { component } = this.renderer;
const { outro } = this.node;
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 name = component.qualify(this.node.animation.name);
const name = this.renderer.reference(this.node.animation.name);
block.chunks.animate.push(b`
${stop_animation}();
@ -811,7 +833,7 @@ export default class ElementWrapper extends Wrapper {
}
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) {
@ -835,7 +857,7 @@ export default class ElementWrapper extends Wrapper {
block.chunks.update.push(updater);
} else if ((dependencies && dependencies.size > 0) || this.class_dependencies.length) {
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`
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 => {
if (wrapper.node.type === 'Text') {
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 && (
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))
@ -894,7 +917,7 @@ function to_html(wrappers: Array<ElementWrapper | TextWrapper | TagWrapper>, blo
attr.node.chunks.forEach(chunk => {
if (chunk.type === 'Text') {
state.quasi.value.raw += chunk.data;
state.quasi.value.raw += escape_html(chunk.data);
} else {
literal.quasis.push(state.quasi);
literal.expressions.push(chunk.manipulate(block));
@ -911,7 +934,7 @@ function to_html(wrappers: Array<ElementWrapper | TextWrapper | TagWrapper>, blo
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);
state.quasi.value.raw += `</${wrapper.node.name}>`;

@ -10,7 +10,6 @@ import { b, x } from 'code-red';
import { walk } from 'estree-walker';
import { is_head } from './shared/is_head';
import { Identifier, Node } from 'estree';
import { changed } from './shared/changed';
function is_else_if(node: ElseBlock) {
return (
@ -264,15 +263,14 @@ export default class IfBlockWrapper extends Wrapper {
? x`${current_block_type}(#ctx)`
: x`${current_block_type} && ${current_block_type}(#ctx)`;
/* eslint-disable @typescript-eslint/indent,indent */
if (this.needs_update) {
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
? b`
${snippet && (
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}`
)}
if (${condition}) return ${block.name};`
@ -281,17 +279,16 @@ export default class IfBlockWrapper extends Wrapper {
`);
} else {
block.chunks.init.push(b`
function ${select_block_type}(#changed, #ctx) {
function ${select_block_type}(#ctx, #dirty) {
${this.branches.map(({ condition, snippet, block }) => condition
? b`if (${snippet || condition}) return ${block.name};`
: b`return ${block.name};`)}
}
`);
}
/* eslint-enable @typescript-eslint/indent,indent */
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};
`);
@ -323,21 +320,21 @@ export default class IfBlockWrapper extends Wrapper {
if (dynamic) {
block.chunks.update.push(b`
if (${current_block_type} === (${current_block_type} = ${select_block_type}(#changed, #ctx)) && ${name}) {
${name}.p(#changed, #ctx);
if (${current_block_type} === (${current_block_type} = ${select_block_type}(#ctx, #dirty)) && ${name}) {
${name}.p(#ctx, #dirty);
} else {
${change_block}
}
`);
} else {
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}
}
`);
}
} 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) {
@ -376,7 +373,6 @@ export default class IfBlockWrapper extends Wrapper {
block.add_variable(current_block_type_index);
block.add_variable(name);
/* eslint-disable @typescript-eslint/indent,indent */
block.chunks.init.push(b`
const ${if_block_creators} = [
${this.branches.map(branch => branch.block.name)}
@ -386,13 +382,13 @@ export default class IfBlockWrapper extends Wrapper {
${this.needs_update
? b`
function ${select_block_type}(#changed, #ctx) {
function ${select_block_type}(#ctx, #dirty) {
${this.branches.map(({ dependencies, condition, snippet }, i) => condition
? b`
${snippet && (
dependencies.length > 0
? b`if (${condition} == null || ${changed(dependencies)}) ${condition} = !!${snippet}`
: b`if (${condition} == null) ${condition} = !!${snippet}`
? b`if (${block.renderer.dirty(dependencies)}) ${condition} = !!${snippet}`
: b`if (${condition} == -1) ${condition} = !!${snippet}`
)}
if (${condition}) return ${i};`
: b`return ${i};`)}
@ -400,7 +396,7 @@ export default class IfBlockWrapper extends Wrapper {
}
`
: b`
function ${select_block_type}(#changed, #ctx) {
function ${select_block_type}(#ctx, #dirty) {
${this.branches.map(({ condition, snippet }, i) => condition
? b`if (${snippet || condition}) return ${i};`
: b`return ${i};`)}
@ -408,16 +404,15 @@ export default class IfBlockWrapper extends Wrapper {
}
`}
`);
/* eslint-enable @typescript-eslint/indent,indent */
if (has_else) {
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);
`);
} else {
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);
}
`);
@ -474,9 +469,9 @@ export default class IfBlockWrapper extends Wrapper {
if (dynamic) {
block.chunks.update.push(b`
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(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 {
${change_block}
}
@ -484,14 +479,14 @@ export default class IfBlockWrapper extends Wrapper {
} else {
block.chunks.update.push(b`
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}) {
${change_block}
}
`);
}
} 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(
@ -528,7 +523,7 @@ export default class IfBlockWrapper extends Wrapper {
const enter = dynamic
? b`
if (${name}) {
${name}.p(#changed, #ctx);
${name}.p(#ctx, #dirty);
${has_transitions && b`@transition_in(${name}, 1);`}
} else {
${name} = ${branch.block.name}(#ctx);
@ -549,7 +544,7 @@ export default class IfBlockWrapper extends Wrapper {
`;
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,
@ -578,7 +573,7 @@ export default class IfBlockWrapper extends Wrapper {
}
} else if (dynamic) {
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 get_object from '../../../utils/get_object';
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 TemplateScope from '../../../nodes/shared/TemplateScope';
import is_dynamic from '../shared/is_dynamic';
import bind_this from '../shared/bind_this';
import { changed } from '../shared/changed';
import { Node, Identifier, ObjectExpression } from 'estree';
import EventHandler from '../Element/EventHandler';
import { extract_names } from 'periscopic';
export default class InlineComponentWrapper extends Wrapper {
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;
fragment: FragmentWrapper;
@ -73,6 +74,12 @@ export default class InlineComponentWrapper extends Wrapper {
};
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({
comment: create_debugging_comment(node, renderer.component),
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);
const fn = get_context_merger(this.node.lets);
this.slots.set('default', {
block: default_slot,
scope: this.node.scope,
fn
});
this.slots.set('default', get_slot_definition(default_slot, this.node.scope, this.node.lets));
this.fragment = new FragmentWrapper(renderer, default_slot, node.children, this, strip_whitespace, next_sibling);
const dependencies: Set<string> = new Set();
@ -129,7 +130,7 @@ export default class InlineComponentWrapper extends Wrapper {
? [
p`$$slots: {
${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: {
@ -155,6 +156,7 @@ export default class InlineComponentWrapper extends Wrapper {
}
if (this.fragment) {
this.renderer.add_to_context('$$scope', true);
const default_slot = this.slots.get('default');
this.fragment.nodes.forEach((child) => {
@ -205,7 +207,7 @@ export default class InlineComponentWrapper extends Wrapper {
const { name, dependencies } = attr;
const condition = dependencies.size > 0 && (dependencies.size !== all_dependencies.size)
? changed(Array.from(dependencies))
? renderer.dirty(Array.from(dependencies))
: null;
if (attr.is_spread) {
@ -238,7 +240,7 @@ export default class InlineComponentWrapper extends Wrapper {
`);
if (all_dependencies.size) {
const condition = changed(Array.from(all_dependencies));
const condition = renderer.dirty(Array.from(all_dependencies));
updates.push(b`
const ${name_changes} = ${condition} ? @get_spread_update(${levels}, [
@ -254,7 +256,7 @@ export default class InlineComponentWrapper extends Wrapper {
dynamic_attributes.forEach((attribute: Attribute) => {
const dependencies = attribute.get_dependencies();
if (dependencies.length > 0) {
const condition = changed(dependencies);
const condition = renderer.dirty(dependencies);
updates.push(b`
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) {
updates.push(b`
if (${changed(non_let_dependencies)}) {
${name_changes}.$$scope = { changed: #changed, ctx: #ctx };
if (${renderer.dirty(non_let_dependencies)}) {
${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`);
component.add_var({
name: id.name,
internal: true,
referenced: true
});
renderer.add_to_context(id.name);
const callee = renderer.reference(id);
const updating = block.get_unique_name(`updating_${binding.name}`);
block.add_variable(updating);
@ -298,8 +296,10 @@ export default class InlineComponentWrapper extends Wrapper {
);
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};
@add_flush_callback(() => ${updating} = false);
}
`);
@ -318,26 +318,24 @@ export default class InlineComponentWrapper extends Wrapper {
}
const value = block.get_unique_name('value');
const args: any[] = [value];
const params: any[] = [value];
if (contextual_dependencies.length > 0) {
args.push({
type: 'ObjectPattern',
properties: contextual_dependencies.map(name => {
const id = { type: 'Identifier', name };
return {
type: 'Property',
kind: 'init',
key: id,
value: id
};
})
const args = [];
contextual_dependencies.forEach(name => {
params.push({
type: 'Identifier',
name
});
renderer.add_to_context(name, true);
args.push(renderer.reference(name));
});
block.chunks.init.push(b`
function ${id}(${value}) {
#ctx.${id}.call(null, ${value}, #ctx);
${updating} = true;
@add_flush_callback(() => ${updating} = false);
${callee}.call(null, ${value}, ${args});
}
`);
@ -345,17 +343,15 @@ export default class InlineComponentWrapper extends Wrapper {
} else {
block.chunks.init.push(b`
function ${id}(${value}) {
#ctx.${id}.call(null, ${value});
${updating} = true;
@add_flush_callback(() => ${updating} = false);
${callee}.call(null, ${value});
}
`);
}
const body = b`
function ${id}(${args}) {
function ${id}(${params}) {
${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 => {
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})`;
return b`${name}.$on("${handler.name}", ${snippet});`;
@ -396,12 +393,12 @@ export default class InlineComponentWrapper extends Wrapper {
`);
block.chunks.create.push(
b`if (${name}) ${name}.$$.fragment.c();`
b`if (${name}) @create_component(${name}.$$.fragment);`
);
if (parent_nodes && this.renderer.options.hydratable) {
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_handlers}
${name}.$$.fragment.c();
@create_component(${name}.$$.fragment);
@transition_in(${name}.$$.fragment, 1);
@mount_component(${name}, ${update_mount_node}, ${anchor});
} else {
@ -460,7 +457,7 @@ export default class InlineComponentWrapper extends Wrapper {
} else {
const expression = this.node.name === 'svelte:self'
? component.name
: component.qualify(this.node.name);
: this.renderer.reference(this.node.name);
block.chunks.init.push(b`
${(this.node.attributes.length > 0 || this.node.bindings.length > 0) && b`
@ -472,11 +469,11 @@ export default class InlineComponentWrapper extends Wrapper {
${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) {
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 is_dynamic from './shared/is_dynamic';
import { Identifier, ObjectExpression } from 'estree';
import { changed } from './shared/changed';
export default class SlotWrapper extends Wrapper {
node: Slot;
@ -60,14 +59,13 @@ export default class SlotWrapper extends Wrapper {
const { slot_name } = this.node;
let get_slot_changes;
let get_slot_context;
let get_slot_changes_fn;
let get_slot_context_fn;
if (this.node.values.size > 0) {
get_slot_changes = 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_changes_fn = renderer.component.get_unique_name(`get_${sanitize(slot_name)}_slot_changes`);
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 dependencies = new Set();
@ -92,34 +90,25 @@ export default class SlotWrapper extends Wrapper {
});
if (dynamic_dependencies.length > 0) {
const expression = dynamic_dependencies
.map(name => ({ type: 'Identifier', name } as any))
.reduce((lhs, rhs) => x`${lhs} || ${rhs}`);
changes.properties.push(p`${attribute.name}: ${expression}`);
changes.properties.push(p`${attribute.name}: ${renderer.dirty(dynamic_dependencies)}`);
}
});
const arg = dependencies.size > 0 && {
type: 'ObjectPattern',
properties: Array.from(dependencies).map(name => p`${name}`)
};
renderer.blocks.push(b`
const ${get_slot_changes} = (${arg}) => (${changes});
const ${get_slot_context} = (${arg}) => (${context});
const ${get_slot_changes_fn} = #dirty => ${changes};
const ${get_slot_context_fn} = #ctx => ${get_slot_data(this.node.values, block)};
`);
} else {
get_slot_changes = 'null';
get_slot_context = 'null';
get_slot_changes_fn = 'null';
get_slot_context_fn = 'null';
}
const slot = block.get_unique_name(`${sanitize(slot_name)}_slot`);
const slot_definition = block.get_unique_name(`${sanitize(slot_name)}_slot_template`);
block.chunks.init.push(b`
const ${slot_definition} = #ctx.$$slots.${slot_name};
const ${slot} = @create_slot(${slot_definition}, #ctx, ${get_slot_context});
const ${slot_definition} = ${renderer.reference('$$slots')}.${slot_name};
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
@ -184,10 +173,10 @@ export default class SlotWrapper extends Wrapper {
});
block.chunks.update.push(b`
if (${slot} && ${slot}.p && ${changed(dynamic_dependencies)}) {
if (${slot} && ${slot}.p && ${renderer.dirty(dynamic_dependencies)}) {
${slot}.p(
@get_slot_changes(${slot_definition}, #ctx, #changed, ${get_slot_changes}),
@get_slot_context(${slot_definition}, #ctx, ${get_slot_context})
@get_slot_context(${slot_definition}, #ctx, ${renderer.reference('$$scope')}, ${get_slot_context_fn}),
@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 Text from '../../nodes/Text';
import { Identifier } from 'estree';
import { changed } from './shared/changed';
import MustacheTag from '../../nodes/MustacheTag';
export default class TitleWrapper extends Wrapper {
@ -76,7 +75,7 @@ export default class TitleWrapper extends Wrapper {
if (all_dependencies.size) {
const dependencies = Array.from(all_dependencies);
let condition = changed(dependencies);
let condition = block.renderer.dirty(dependencies);
if (block.has_outros) {
condition = x`!#current || ${condition}`;

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

@ -5,7 +5,6 @@ import Block from '../../Block';
import MustacheTag from '../../../nodes/MustacheTag';
import RawMustacheTag from '../../../nodes/RawMustacheTag';
import { Node } from 'estree';
import { changed } from './changed';
export default class Tag extends Wrapper {
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 (dependencies.length > 0) {
let condition = changed(dependencies);
let condition = block.renderer.dirty(dependencies);
if (block.has_outros) {
condition = x`!#current || ${condition}`;

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

@ -1,39 +1,10 @@
import Block from '../../Block';
import EventHandler from '../../../nodes/EventHandler';
import { x, p } from 'code-red';
const TRUE = x`true`;
const FALSE = x`false`;
import EventHandler from '../Element/EventHandler';
export default function add_event_handlers(
block: Block,
target: string,
handlers: EventHandler[]
) {
handlers.forEach(handler => {
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})`
);
});
handlers.forEach(handler => handler.render(block, target));
}

@ -8,11 +8,8 @@ import { Identifier } from 'estree';
export default function bind_this(component: Component, block: Block, binding: Binding, variable: Identifier) {
const fn = component.get_unique_name(`${variable.name}_binding`);
component.add_var({
name: fn.name,
internal: true,
referenced: true
});
block.renderer.add_to_context(fn.name);
const callee = block.renderer.reference(fn.name);
let lhs;
let object;
@ -32,11 +29,11 @@ export default function bind_this(component: Component, block: Block, binding: B
body = binding.raw_expression.type === 'Identifier'
? b`
${component.invalidate(object, x`${lhs} = $$value`)};
${block.renderer.invalidate(object, x`${lhs} = $$value`)};
`
: b`
${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 = [];
for (const id of contextual_dependencies) {
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 unassign = block.get_unique_name(`unassign_${variable.name}`);
block.chunks.init.push(b`
const ${assign} = () => #ctx.${fn}(${variable}, ${args});
const ${unassign} = () => #ctx.${fn}(null, ${args});
const ${assign} = () => ${callee}(${variable}, ${args});
const ${unassign} = () => ${callee}(null, ${args});
`);
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}`);
// 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`
if (${condition}) {
${unassign}();
${args.map(a => b`${a} = #ctx.${a}`)};
${args.map(a => b`${a} = ${block.renderer.reference(a.name)}`)};
${assign}();
}`
);
@ -96,6 +93,6 @@ export default function bind_this(component: Component, block: Block, binding: B
}
`);
block.chunks.destroy.push(b`#ctx.${fn}(null);`);
return b`#ctx.${fn}(${variable});`;
block.chunks.destroy.push(b`${callee}(null);`);
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 { 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}" : ""`;
});
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 Slot from '../../nodes/Slot';
import { x } from 'code-red';
import get_slot_data from '../../utils/get_slot_data';
export default function(node: Slot, renderer: Renderer, options: RenderOptions) {
const slot_data = get_slot_data(node.values);

@ -1,7 +1,7 @@
export default function get_name_from_filename(filename: string) {
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) {
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()
.replace(/%/g, 'u')
.replace(/\.[^.]+$/, "")
.replace(/[^a-zA-Z_$0-9]+/g, '_')
.replace(/^_/, '')

@ -1,26 +1,26 @@
import Attribute from '../nodes/Attribute';
import { p, x } from 'code-red';
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 {
type: 'ObjectExpression',
properties: Array.from(values.values())
.filter(attribute => attribute.name !== 'name')
.map(attribute => {
const value = get_value(attribute);
const value = get_value(block, attribute);
return p`${attribute.name}: ${value}`;
})
};
}
// TODO fairly sure this is duplicated at least once
function get_value(attribute: Attribute) {
function get_value(block: Block, attribute: Attribute) {
if (attribute.is_true) return x`true`;
if (attribute.chunks.length === 0) return x`""`;
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}`);
if (attribute.chunks.length > 1 && attribute.chunks[0].type !== 'Text') {

@ -1,156 +1,8 @@
import { walk } from 'estree-walker';
import is_reference from 'is-reference';
import { Node, VariableDeclaration, ClassDeclaration, VariableDeclarator, ObjectPattern, Property, RestElement, ArrayPattern, Identifier } from 'estree';
import get_object from './get_object';
import { Node } from 'estree';
import { analyze, Scope, extract_names, extract_identifiers } from 'periscopic';
// TODO replace this with periscopic?
export function create_scopes(expression: Node) {
const map = new WeakMap();
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;
}
return analyze(expression);
}
});
scope.declarations.forEach((_node, name) => {
globals.delete(name);
});
return { map, scope, globals };
}
export class Scope {
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);
}
};
export { Scope, extract_names, extract_identifiers };

@ -12,13 +12,15 @@ export function escape(data: string, { only_escape_at_symbol = false } = {}) {
}
const escaped = {
'"': '&quot;',
"'": '&#39;',
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
};
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) {

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

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

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

@ -1,5 +1,4 @@
import * as acorn from '../acorn';
import repeat from '../../utils/repeat';
import { Parser } from '../index';
import { Script } from '../../interfaces';
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`
});
const source =
repeat(' ', script_start) + parser.template.slice(script_start, script_end);
const source = ' '.repeat(script_start) + parser.template.slice(script_start, script_end);
parser.index = script_end + script_closing_tag.length;
let ast: Program;

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

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

@ -19,9 +19,11 @@ interface FlipParams {
export function flip(node: Element, animation: { from: DOMRect; to: DOMRect }, params: FlipParams): AnimationConfig {
const style = getComputedStyle(node);
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 dy = animation.from.top - animation.to.top;
const dx = (animation.from.left - animation.to.left) / scaleX;
const dy = (animation.from.top - animation.to.top) / scaleY;
const d = Math.sqrt(dx * dx + dy * dy);

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

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

Loading…
Cancel
Save