Merge branch 'master' into feature/onerror

pull/3587/head
Rich Harris 7 years ago committed by GitHub
commit d920af8e2c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -22,14 +22,7 @@ module.exports = {
'arrow-spacing': 2,
'no-inner-declarations': 0,
'require-atomic-updates': 'off',
'@typescript-eslint/indent': [
'error',
'tab',
{
SwitchCase: 1,
ignoredNodes: ['TemplateLiteral']
}
],
'@typescript-eslint/indent': 'off',
'@typescript-eslint/camelcase': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/array-type': ['error', 'array-simple'],

@ -0,0 +1,25 @@
name: CI
on: [push, pull_request]
jobs:
Tests:
runs-on: ${{ matrix.os }}
strategy:
matrix:
node-version: [8, 10, 12]
os: [ubuntu-latest, windows-latest, macOS-latest]
steps:
- run: git config --global core.autocrlf false
- uses: actions/checkout@v1
- uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm test
env:
CI: true
Lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/setup-node@v1
- run: 'npm i && npm run lint'

@ -1,20 +0,0 @@
language: node_js
node_js:
- "8"
- "10"
- "12"
env:
global:
- BUILD_TIMEOUT=20000
addons:
apt:
packages:
- xvfb
install:
- export DISPLAY=':99.0'
- Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
- npm ci || npm install
after_success: npm run codecov

@ -1,5 +1,62 @@
# Svelte changelog
## 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))
Also:
* Fix `{#each}` context not shadowing outer scope when using `bind:` ([#1565](https://github.com/sveltejs/svelte/issues/1565))
* Fix edge cases in matching selectors against elements ([#1710](https://github.com/sveltejs/svelte/issues/1710))
* Fix several bugs related to interaction of `{...spread}` attributes with other features ([#2721](https://github.com/sveltejs/svelte/issues/2721), [#2916](https://github.com/sveltejs/svelte/issues/2916), [#3421](https://github.com/sveltejs/svelte/issues/3421), [#3681](https://github.com/sveltejs/svelte/issues/3681), [#3764](https://github.com/sveltejs/svelte/issues/3764), [#3790](https://github.com/sveltejs/svelte/issues/3790))
* Allow exiting a reactive block early with `break $` ([#2828](https://github.com/sveltejs/svelte/issues/2828))
* Fix binding to props that have been renamed with `export { ... as ... }` ([#3508](https://github.com/sveltejs/svelte/issues/3508))
* Fix application of style scoping class in cases of ambiguity ([#3544](https://github.com/sveltejs/svelte/issues/3544))
* Check attributes have changed before setting them to avoid image flicker ([#3579](https://github.com/sveltejs/svelte/pull/3579))
* Fix generating malformed code for `{@debug}` tags with no dependencies ([#3588](https://github.com/sveltejs/svelte/issues/3588))
* Fix generated code in specific case involving compound ifs and child components ([#3595](https://github.com/sveltejs/svelte/issues/3595))
* Fix `bind:this` binding to a store ([#3591](https://github.com/sveltejs/svelte/issues/3591))
* Use safer `HTMLElement` check before extending class ([#3608](https://github.com/sveltejs/svelte/issues/3608))
* Add `location` as a known global ([#3619](https://github.com/sveltejs/svelte/pull/3619))
* Support `{#await}` with `{:catch}` but no `{:then}` ([#3623](https://github.com/sveltejs/svelte/issues/3623))
* Clean up dead code emitted for `<slot/>`s ([#3631](https://github.com/sveltejs/svelte/issues/3631))
* Fix tracking of dependencies of compound assignments in reactive statements ([#3634](https://github.com/sveltejs/svelte/issues/3634))
* Flush changes in newly attached block when using `{#await}` ([#3660](https://github.com/sveltejs/svelte/issues/3660))
* 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
* Escape `@` symbols in props, again ([#3545](https://github.com/sveltejs/svelte/issues/3545))
@ -7,6 +64,7 @@
## 3.12.0
* Fire events on `document` in development to facilitate dev tooling ([#3005](https://github.com/sveltejs/svelte/pull/3005))
* Remove old props when the keys in spread props are removed ([#2282](https://github.com/sveltejs/svelte/issues/2282))
## 3.11.0

@ -0,0 +1,138 @@
# Contributing to Svelte
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:
* [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
* [Building Welcoming Communities](https://opensource.guide/building-community/)
## Get involved
There are many ways to contribute to Svelte, and many of them do not involve writing any code. Here's a few ideas to get started:
- Simply start using Svelte. Go through the [Getting Started](https://svelte.dev/blog/the-easiest-way-to-get-started) guide. Does everything work as expected? If not, we're always looking for improvements. Let us know by [opening an issue](#reporting-new-issues).
- Look through the [open issues](https://github.com/sveltejs/svelte/issues). Provide workarounds, ask for clarification, or suggest labels. Help [triage issues](#triaging-issues-and-pull-requests).
- If you find an issue you would like to fix, [open a pull request](#your-first-pull-request).
- Read through our [tutorials](https://svelte.dev/tutorial/basics). If you find anything that is confusing or can be improved, you can make edits by clicking "Edit this chapter" at the bottom left of the tutorial page.
- Take a look at the [features requested](https://github.com/sveltejs/svelte/labels/enhancement) by others in the community and consider opening a pull request if you see something you want to work on.
Contributions are very welcome. If you think you need help planning your contribution, please ping us on Discord at [svelte.dev/chat](https://svelte.dev/chat) and let us know you are looking for a bit of help.
### Triaging issues and pull requests
One great way you can contribute to the project without writing any code is to help triage issues and pull requests as they come in.
- Ask for more information if you believe the issue does not provide all the details required to solve it.
- Suggest [labels](https://github.com/sveltejs/svelte/labels) that can help categorize issues.
- Flag issues that are stale or that should be closed.
- Ask for test plans and review code.
## 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).
If you have questions about using Svelte, contact us on Discord at [svelte.dev/chat](https://svelte.dev/chat), and we will do our best to answer your questions.
If you see anything you'd like to be implemented, create a [feature request issue](https://github.com/sveltejs/svelte/issues/new?template=feature_request.md)
## Reporting new issues
When [opening a new issue](https://github.com/sveltejs/svelte/issues/new/choose), always make sure to fill out the issue template. **This step is very important!** Not doing so may result in your issue not being managed in a timely fashion. Don't take this personally if this happens, and feel free to open a new issue once you've gathered all the information required by the template.
- **One issue, one bug:** Please report a single bug per issue.
- **Provide reproduction steps:** List all the steps necessary to reproduce the issue. The person reading your bug report should be able to follow these steps to reproduce your issue with minimal effort. If possible, use the [REPL](https://svelte.dev/repl) to create your reproduction.
## Installation
1. Ensure you have [npm](https://www.npmjs.com/get-npm) installed.
1. After cloning the repository, run `npm install` in the root of the repository.
1. To start a development server, run `npm run dev`.
## Pull requests
### Your first pull request
So you have decided to contribute code back to upstream by opening a pull request. You've invested a good chunk of time, and we appreciate it. We will do our best to work with you and get the PR looked at.
Working on your first Pull Request? You can learn how from this free video series:
[**How to Contribute to an Open Source Project on GitHub**](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github)
### Proposing a change
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.
### Sending a pull request
Small pull requests are much easier to review and more likely to get merged. Make sure the PR does only one thing, otherwise please split it.
Please make sure the following is done when submitting a pull request:
1. Fork [the repository](https://github.com/sveltejs/svelte) and create your branch from `master`.
1. Describe your **test plan** in your pull request description. Make sure to test your changes.
1. Make sure your code lints (`npm run lint`).
1. Make sure your tests pass (`npm run test`).
All pull requests should be opened against the `master` branch.
#### Test plan
A good test plan has the exact commands you ran and their output, provides screenshots or videos if the pull request changes UI.
- If you've changed APIs, update the documentation.
#### Writing tests
All tests are located in `/test` folder.
Test samples are kept in `/test/xxx/samples` folder.
#### Running tests
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:
```md
### New breaking change here
- **Who does this affect**:
- **How to migrate**:
- **Why make this breaking change**:
- **Severity (number of people affected x effort)**:
```
### What happens next?
The core Svelte team will be monitoring for pull requests. Do help us by making your pull request easy to review by following the guidelines above.
## Style guide
[Eslint](https://eslint.org) will catch most styling issues that may exist in your code. You can check the status of your code styling by simply running `npm run lint`.
### Code conventions
- `snake_case` for internal variable names and methods
- `camelCase` for public variable names and methods.
## License
By contributing to Svelte, you agree that your contributions will be licensed under its [MIT license](https://github.com/sveltejs/svelte/blob/master/LICENSE).

@ -1,31 +0,0 @@
# http://www.appveyor.com/docs/appveyor-yml
version: "{build}"
clone_depth: 10
init:
- git config --global core.autocrlf false
environment:
matrix:
- nodejs_version: 8
- nodejs_version: 10
- nodejs_version: 12
install:
- ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version)
- npm ci || npm install
build: off
test_script:
- node --version && npm --version
- npm test
matrix:
fast_finish: false
# cache:
# - C:\Users\appveyor\AppData\Roaming\npm-cache -> package.json # npm cache
# - node_modules -> package.json # local npm modules

171
package-lock.json generated

@ -1,6 +1,6 @@
{
"name": "svelte",
"version": "3.12.1",
"version": "3.14.1",
"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",
@ -167,9 +177,9 @@
"dev": true
},
"acorn": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.0.0.tgz",
"integrity": "sha512-PaF/MduxijYYt7unVGRuds1vBC9bFxbNf+VWqhOClfdgy7RlVkQqt610ig1/yxTgsDIfW1cWDel5EBbOy3jdtQ==",
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz",
"integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==",
"dev": true
},
"acorn-globals": {
@ -203,25 +213,13 @@
"dev": true
},
"agadoo": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/agadoo/-/agadoo-1.0.1.tgz",
"integrity": "sha512-rEOSqXQ3ABoxmgvxqEvUfS7mcMJBqRhcMbmJq4j+EpCXN0P1cFHYQT66V7rYSu0C+3jialSk58yHTBqzY0m+Sw==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/agadoo/-/agadoo-1.1.0.tgz",
"integrity": "sha512-MiTXQ3As5jARRXa0eZF+ObtzsseQurCXOc1RB19NOGDsimEvoT8DB8iQiC0zAGTfsF/nBhzEle+eV9UpXyWnbw==",
"dev": true,
"requires": {
"rollup": "^0.64.1",
"rollup": "^1",
"rollup-plugin-virtual": "^1.0.1"
},
"dependencies": {
"rollup": {
"version": "0.64.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-0.64.1.tgz",
"integrity": "sha512-+ThdVXrvonJdOTzyybMBipP0uz605Z8AnzWVY3rf+cSGnLO7uNkJBlN+9jXqWOomkvumXfm/esmBpA5d53qm7g==",
"dev": true,
"requires": {
"@types/estree": "0.0.39",
"@types/node": "*"
}
}
}
},
"agent-base": {
@ -501,6 +499,18 @@
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
"dev": true
},
"code-red": {
"version": "0.0.21",
"resolved": "https://registry.npmjs.org/code-red/-/code-red-0.0.21.tgz",
"integrity": "sha512-luUVxyGNwTU/lG+lY+pitXHJzgDvFa0WfxA4Nsd7uk/S366mHY8B86pwAcyRcKY9BE/HvryVNXCR691Q5ry+Ww==",
"dev": true,
"requires": {
"acorn": "^7.1.0",
"is-reference": "^1.1.4",
"periscopic": "^2.0.1",
"sourcemap-codec": "^1.4.6"
}
},
"codecov": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/codecov/-/codecov-3.5.0.tgz",
@ -804,6 +814,13 @@
"resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz",
"integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=",
"dev": true
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"optional": true
}
}
},
@ -1140,14 +1157,6 @@
"acorn": "^7.0.0",
"acorn-jsx": "^5.0.2",
"eslint-visitor-keys": "^1.1.0"
},
"dependencies": {
"acorn": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.0.0.tgz",
"integrity": "sha512-PaF/MduxijYYt7unVGRuds1vBC9bFxbNf+VWqhOClfdgy7RlVkQqt610ig1/yxTgsDIfW1cWDel5EBbOy3jdtQ==",
"dev": true
}
}
},
"esprima": {
@ -1181,9 +1190,9 @@
"dev": true
},
"estree-walker": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz",
"integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==",
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.8.1.tgz",
"integrity": "sha512-H6cJORkqvrNziu0KX2hqOMAlA2CiuAxHeGJXSIoKA/KLv229Dw806J3II6mKTm5xiDX1At1EXCfsOQPB+tMB+g==",
"dev": true
},
"esutils": {
@ -1496,6 +1505,14 @@
"optimist": "^0.6.1",
"source-map": "^0.6.1",
"uglify-js": "^3.1.4"
},
"dependencies": {
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
}
}
},
"har-schema": {
@ -1753,9 +1770,9 @@
"dev": true
},
"is-reference": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.1.3.tgz",
"integrity": "sha512-W1iHHv/oyBb2pPxkBxtaewxa1BC58Pn5J0hogyCdefwUIvb6R+TGbAcIa4qPNYLqLhb3EnOgUf2MQkkF76BcKw==",
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.1.4.tgz",
"integrity": "sha512-uJA/CDPO3Tao3GTrxYn6AwkM4nUPJiGGYu5+cB8qbC7WGFlrKZbiRo7SFKxUAEpFUfiHofWCXBUNhvYJMh+6zw==",
"dev": true,
"requires": {
"@types/estree": "0.0.39"
@ -2705,6 +2722,15 @@
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
"dev": true
},
"periscopic": {
"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"
}
},
"pify": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
@ -3032,26 +3058,20 @@
}
},
"rollup": {
"version": "1.20.3",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-1.20.3.tgz",
"integrity": "sha512-/OMCkY0c6E8tleeVm4vQVDz24CkVgvueK3r8zTYu2AQNpjrcaPwO9hE+pWj5LTFrvvkaxt4MYIp2zha4y0lRvg==",
"version": "1.21.4",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-1.21.4.tgz",
"integrity": "sha512-Pl512XVCmVzgcBz5h/3Li4oTaoDcmpuFZ+kdhS/wLreALz//WuDAMfomD3QEYl84NkDu6Z6wV9twlcREb4qQsw==",
"dev": true,
"requires": {
"@types/estree": "0.0.39",
"@types/node": "^12.7.2",
"@types/node": "^12.7.5",
"acorn": "^7.0.0"
},
"dependencies": {
"@types/node": {
"version": "12.7.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.4.tgz",
"integrity": "sha512-W0+n1Y+gK/8G2P/piTkBBN38Qc5Q1ZSO6B5H3QmPCUewaiXOo2GCAWZ4ElZCcNhjJuBSUSLGFUJnmlCn5+nxOQ==",
"dev": true
},
"acorn": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.0.0.tgz",
"integrity": "sha512-PaF/MduxijYYt7unVGRuds1vBC9bFxbNf+VWqhOClfdgy7RlVkQqt610ig1/yxTgsDIfW1cWDel5EBbOy3jdtQ==",
"version": "12.7.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.5.tgz",
"integrity": "sha512-9fq4jZVhPNW8r+UYKnxF1e2HkDWOWKM5bC2/7c9wPV835I0aOrVbS/Hw/pWPk2uKrNXQqg9Z959Kz+IYDd5p3w==",
"dev": true
}
}
@ -3067,6 +3087,14 @@
"magic-string": "^0.25.2",
"resolve": "^1.11.0",
"rollup-pluginutils": "^2.8.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
}
}
},
"rollup-plugin-json": {
@ -3091,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",
@ -3134,6 +3152,14 @@
"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
}
}
},
"run-async": {
@ -3220,9 +3246,9 @@
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
"dev": true
},
"source-map-support": {
@ -3233,6 +3259,14 @@
"requires": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
},
"dependencies": {
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
}
}
},
"sourcemap-codec": {
@ -3540,6 +3574,15 @@
"requires": {
"commander": "~2.20.0",
"source-map": "~0.6.1"
},
"dependencies": {
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"optional": true
}
}
},
"uri-js": {
@ -3584,14 +3627,6 @@
"@types/istanbul-lib-coverage": "^2.0.1",
"convert-source-map": "^1.6.0",
"source-map": "^0.7.3"
},
"dependencies": {
"source-map": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
"dev": true
}
}
},
"validate-npm-package-license": {

@ -1,6 +1,6 @@
{
"name": "svelte",
"version": "3.12.1",
"version": "3.14.1",
"description": "Cybernetically enhanced web apps",
"module": "index.mjs",
"main": "index",
@ -56,35 +56,37 @@
},
"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",
"@typescript-eslint/parser": "^2.1.0",
"acorn": "^7.0.0",
"agadoo": "^1.0.1",
"acorn": "^7.1.0",
"agadoo": "^1.1.0",
"c8": "^5.0.1",
"code-red": "0.0.21",
"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.6.1",
"is-reference": "^1.1.3",
"estree-walker": "^0.8.1",
"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.20.3",
"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",
"source-map": "^0.6.1",
"source-map": "^0.7.3",
"source-map-support": "^0.5.13",
"tiny-glob": "^0.2.6",
"tslib": "^1.10.0",

@ -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';
@ -20,8 +20,7 @@ const ts_plugin = is_publish
const external = id => id.startsWith('svelte/');
const inlined_estree = fs.readFileSync('./node_modules/estree-walker/index.d.ts', 'utf-8').replace(/declare.*\{((.|[\n\r])+)\}/m, '$1');
fs.writeFileSync(`./compiler.d.ts`, `export { compile, parse, preprocess, VERSION } from './types/compiler/index';\n${inlined_estree}`);
fs.writeFileSync(`./compiler.d.ts`, `export { compile, parse, preprocess, VERSION } from './types/compiler/index';`);
export default [
/* runtime */

@ -2,7 +2,12 @@ NODE_ENV=
PORT=
BASEURL=
DATABASE_URL=
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
MAPBOX_ACCESS_TOKEN=
MAPBOX_ACCESS_TOKEN=
PGHOST=hostname
PGPORT=port
PGUSER=username
PGPASSWORD=password
PGDATABASE=database_name

@ -47,6 +47,12 @@ module.exports = {
},
settings: {
'import/core-modules': ['svelte'],
'svelte3/compiler': require('svelte/compiler')
'svelte3/compiler': (() => {
try {
return require('svelte/compiler');
} catch (e) {
return null;
}
})()
}
};

@ -6,28 +6,64 @@ 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
* associating .svelte files with HTML in VSCode, Sublime, Atom, etc etc etc
* associating .svelte files with HTML in VSCode, Sublime, etc.
## Atom
To treat `*.svelte` files as HTML, open *__Edit → Config...__* and add the following lines to your `core` section:
```cson
"*":
core:
customFileTypes:
"text.html.basic": [
"svelte"
]
```
## Vim/Neovim
To treat all `*.svelte` files as HTML, add the following line to your `init.vim`:
```bash
```
au! BufNewFile,BufRead *.svelte set ft=html
```
To temporarily turn on HTML syntax highlighting for the current buffer, use:
```bash
```
:set ft=html
```
To set the filetype for a single file, use a [modeline](https://vim.fandom.com/wiki/Modeline_magic):
```bash
```
<!-- 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__*.

@ -570,9 +570,10 @@ Media elements (`<audio>` and `<video>`) have their own set of bindings — four
* `seekable` (readonly) — ditto
* `played` (readonly) — ditto
...and three *two-way* bindings:
...and four *two-way* bindings:
* `currentTime` — the current point in the video, in seconds
* `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
@ -1145,7 +1146,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}/>
@ -1380,7 +1381,7 @@ As with `<svelte:window>`, this element allows you to add listeners to events on
---
This element makes it possible to insert elements into `document.head`. During server-side rendering, `head` content exposed separately to the main `html` content.
This element makes it possible to insert elements into `document.head`. During server-side rendering, `head` content is exposed separately to the main `html` content.
```html
<svelte:head>

@ -184,7 +184,7 @@ dispatch: ((name: string, detail?: any) => void) = createEventDispatcher();
---
Creates an event dispatcher that can be used to dispatch [component events](docs#Component_events). Event dispatchers are functions that can take two arguments: `name` and `detail`.
Creates an event dispatcher that can be used to dispatch [component events](docs#on_component_event). Event dispatchers are functions that can take two arguments: `name` and `detail`.
Component events created with `createEventDispatcher` create a [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent). These events do not [bubble](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#Event_bubbling_and_capture) and are not cancellable with `event.preventDefault()`. The `detail` argument corresponds to the [CustomEvent.detail](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/detail) property and can contain any type of data.
@ -433,6 +433,19 @@ Out of the box, Svelte will interpolate between two numbers, two arrays or two o
---
If the initial value is `undefined` or `null`, the first value change will take effect immediately. This is useful when you have tweened values that are based on props, and don't want any motion when the component first renders.
```js
const size = tweened(undefined, {
duration: 300,
easing: cubicOut
});
$: $size = big ? 100 : 10;
```
---
The `interpolate` option allows you to tween between *any* arbitrary values. It must be an `(a, b) => t => value` function, where `a` is the starting value, `b` is the target value, `t` is a number between 0 and 1, and `value` is the result. For example, we can use the [d3-interpolate](https://github.com/d3/d3-interpolate) package to smoothly interpolate between two colours.
```html
@ -493,6 +506,15 @@ Both `set` and `update` can take a second argument — an object with `hard` or
</script>
```
---
If the initial value is `undefined` or `null`, the first value change will take effect immediately, just as with `tweened` values (see above).
```js
const size = spring();
$: $size = big ? 100 : 10;
```
### `svelte/transition`
The `svelte/transition` module exports six functions: `fade`, `fly`, `slide`, `scale`, `draw` and `crossfade`. They are for use with svelte [`transitions`](docs#Transitions).
@ -696,7 +718,7 @@ out:draw={params}
Animates the stroke of an SVG element, like a snake in a tube. `in` transitions begin with the path invisible and draw the path to the screen over time. `out` transitions start in a visible state and gradually erase the path. `draw` only works with elements that have a `getTotalLength` method, like `<path>` and `<polyline>`.
`scale` accepts the following parameters:
`draw` accepts the following parameters:
* `delay` (`number`, default 0) — milliseconds before starting
* `speed` (`number`, default undefined) - the speed of the animation, see below.
@ -945,7 +967,7 @@ app.count += 1;
---
Svelte components can also be compiled to custom elements (aka web components) using the `customElements: true` compiler option. You should specify a tag name for the component using the `<svelte:options>` [element](docs#svelte_options).
Svelte components can also be compiled to custom elements (aka web components) using the `customElement: true` compiler option. You should specify a tag name for the component using the `<svelte:options>` [element](docs#svelte_options).
```html
<svelte:options tag="my-element">
@ -1019,8 +1041,12 @@ Unlike client-side components, server-side components don't have a lifespan afte
A server-side component exposes a `render` method that can be called with optional props. It returns an object with `head`, `html`, and `css` properties, where `head` contains the contents of any `<svelte:head>` elements encountered.
You can import a Svelte component directly into Node using [`svelte/register`](docs#svelte_register).
```js
const App = require('./App.svelte');
require('svelte/register');
const App = require('./App.svelte').default;
const { head, html, css } = App.render({
answer: 42

@ -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.
@ -315,7 +317,7 @@ walk(ast: Node, {
---
The `walk` function provides a way to walk to abstract syntax trees generated by the parser, using the compiler's own built-in instance of [estree-walker](https://github.com/Rich-Harris/estree-walker).
The `walk` function provides a way to walk the abstract syntax trees generated by the parser, using the compiler's own built-in instance of [estree-walker](https://github.com/Rich-Harris/estree-walker).
The walker takes an abstract syntax tree to walk and an object with two optional methods: `enter` and `leave`. For each node, `enter` is called (if present). Then, unless `this.skip()` is called during `enter`, each of the children are traversed, and then `leave` is called on the node.

@ -14,7 +14,7 @@
showControlsTimeout = setTimeout(() => showControls = false, 2500);
showControls = true;
if (e.which !== 1) return; // mouse not down
if (!(e.buttons & 1)) return; // mouse not down
if (!duration) return; // video not loaded yet
const { left, right } = this.getBoundingClientRect();
@ -127,4 +127,4 @@
<span class="time">{format(duration)}</span>
</div>
</div>
</div>
</div>

@ -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,9 +1,56 @@
<script>
import { createEventDispatcher } from 'svelte';
import { createEventDispatcher, onDestroy } from 'svelte';
const dispatch = createEventDispatcher();
const close = () => dispatch('close');
let modal;
const handle_keydown = e => {
if (e.key === 'Escape') {
close();
return;
}
if (e.key === 'Tab') {
// trap focus
const nodes = modal.querySelectorAll('*');
const tabbable = Array.from(nodes).filter(n => n.tabIndex >= 0);
let index = tabbable.indexOf(document.activeElement);
if (index === -1 && e.shiftKey) index = 0;
index += tabbable.length + (e.shiftKey ? -1 : 1);
index %= tabbable.length;
tabbable[index].focus();
e.preventDefault();
}
};
const previously_focused = typeof document !== 'undefined' && document.activeElement;
if (previously_focused) {
onDestroy(() => {
previously_focused.focus();
});
}
</script>
<svelte:window on:keydown={handle_keydown}/>
<div class="modal-background" on:click={close}></div>
<div class="modal" role="dialog" aria-modal="true" bind:this={modal}>
<slot name="header"></slot>
<hr>
<slot></slot>
<hr>
<!-- svelte-ignore a11y-autofocus -->
<button autofocus on:click={close}>close modal</button>
</div>
<style>
.modal-background {
position: fixed;
@ -32,14 +79,3 @@
display: block;
}
</style>
<div class='modal-background' on:click='{() => dispatch("close")}'></div>
<div class='modal'>
<slot name='header'></slot>
<hr>
<slot></slot>
<hr>
<button on:click='{() => dispatch("close")}'>close modal</button>
</div>

@ -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() {

@ -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`.

@ -14,7 +14,7 @@
showControlsTimeout = setTimeout(() => showControls = false, 2500);
showControls = true;
if (e.which !== 1) return; // mouse not down
if (!(e.buttons & 1)) return; // mouse not down
if (!duration) return; // video not loaded yet
const { left, right } = this.getBoundingClientRect();
@ -124,4 +124,4 @@
<span class="time">{format(duration)}</span>
</div>
</div>
</div>
</div>

@ -14,7 +14,7 @@
showControlsTimeout = setTimeout(() => showControls = false, 2500);
showControls = true;
if (e.which !== 1) return; // mouse not down
if (!(e.buttons & 1)) return; // mouse not down
if (!duration) return; // video not loaded yet
const { left, right } = this.getBoundingClientRect();

@ -8,6 +8,7 @@
<style>
input { display: block; }
div { display: inline-block; }
span { word-break: break-all; }
</style>
<input type=range bind:value={size}>

@ -8,6 +8,7 @@
<style>
input { display: block; }
div { display: inline-block; }
span { word-break: break-all; }
</style>
<input type=range bind:value={size}>

@ -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>

@ -88,6 +88,7 @@
background-color: #0074D9;
color: white;
border-radius: 1em 1em 0 1em;
word-break: break-all;
}
</style>

@ -88,6 +88,7 @@
background-color: #0074D9;
color: white;
border-radius: 1em 1em 0 1em;
word-break: break-all;
}
</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`:
@ -37,4 +37,4 @@ The full set of options available to `tweened`:
* `easing` — a `p => t` function
* `interpolate` — a custom `(from, to) => t => value` function for interpolating between arbitrary values. By default, Svelte will interpolate between numbers, dates, and identically-shaped arrays and objects (as long as they only contain numbers and dates or other valid arrays and objects). If you want to interpolate (for example) colour strings or transformation matrices, supply a custom interpolator
You can also pass these options to `progress.set` and `progress.update` as a second argument, in which case they will override the defaults. The `set` and `update` methods both return a promise that resolves when the tween completes.
You can also pass these options to `progress.set` and `progress.update` as a second argument, in which case they will override the defaults. The `set` and `update` methods both return a promise that resolves when the tween completes.

@ -3,7 +3,6 @@
import flash from './flash.js';
export let todo;
export let toggle;
let div;

@ -5,7 +5,6 @@
import flash from './flash.js';
export let todo;
export let toggle;
let div;

@ -1281,9 +1281,9 @@
}
},
"@sveltejs/site-kit": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@sveltejs/site-kit/-/site-kit-1.1.3.tgz",
"integrity": "sha512-cJfz45cqq1nfPnk1V3oYVMcSySI/GnbHyvr+vQAyHhmOpmknO3pYavXDj545YXWJvEXg2sk8Fxcah+Z/56Ka0Q==",
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@sveltejs/site-kit/-/site-kit-1.1.4.tgz",
"integrity": "sha512-PsFUX1C/fhV0ODdCJaEQ8OwzgmaPJVmdefiSYA+i6zttBeV19d/ow+l7SPMXxBkux+vUIl5can4BwValCukCsw==",
"dev": true,
"requires": {
"@sindresorhus/slugify": "^0.9.1",
@ -1291,17 +1291,23 @@
}
},
"@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.13",
"resolved": "https://registry.npmjs.org/@sveltejs/svelte-repl/-/svelte-repl-0.1.13.tgz",
"integrity": "sha512-griMkrRRzAQq22awKaBN5cewGly4xeeo8qpDs8ZhQxmEk5TcX3dMhIGMLuPTSv3Yr0s2c6TDJXcN+ORJn//fpQ==",
"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",
"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 +1593,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": {
@ -3368,12 +3374,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 +3411,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 +3454,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 +3463,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": {

@ -35,8 +35,8 @@
"@babel/preset-env": "^7.6.0",
"@babel/runtime": "^7.6.0",
"@sindresorhus/slugify": "^0.9.1",
"@sveltejs/site-kit": "^1.1.3",
"@sveltejs/svelte-repl": "^0.1.9",
"@sveltejs/site-kit": "^1.1.4",
"@sveltejs/svelte-repl": "^0.1.13",
"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: {

@ -55,11 +55,14 @@
<a target="_blank" rel="noopener" href="https://deck.nl"><img src="organisations/deck.svg" alt="Deck logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://dextra.com.br/pt/"><img src="organisations/dextra.png" alt="Dextra logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://www.entriwise.com/"><img src="organisations/entriwise.png" alt="Entriwise logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://www.entur.org/about-entur/" style="background-color: rgb(25, 25, 84);"><img src="organisations/entur.svg" alt="Entur logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://from-now-on.com"><img src="organisations/from-now-on.png" alt="From-Now-On logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://fusioncharts.com"><img src="organisations/fusioncharts.svg" alt="FusionCharts logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://godaddy.com"><img src="organisations/godaddy.svg" alt="GoDaddy logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://www.grainger.com"><img src="organisations/grainger.svg" alt="Grainger logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="http://healthtree.org/"><img src="organisations/healthtree.png" alt="HealthTree logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://itslearning.com"><img src="organisations/itslearning.svg" alt="itslearning logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://jacoux.com"><img src="organisations/jacoux.png" alt="Jacoux 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>
@ -67,15 +70,18 @@
<a target="_blank" rel="noopener" href="https://www.nzz.ch"><img src="organisations/nzz.svg" alt="Neue Zürcher Zeitung logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://nytimes.com"><img src="organisations/nyt.svg" alt="The New York Times logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://oberonspace.xyz"><img src="organisations/oberonspace.svg" alt="OberonSPACE logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://ofof.nl"><img src="organisations/ofof.png" alt="Ofof logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://openstate.eu"><img src="organisations/open-state-foundation.svg" alt="Open State Foundation logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://panascais.net"><img src="organisations/panascais.svg" alt="Panascais logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://pankod.com"><img src="organisations/pankod.svg" alt="Pankod 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://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.png" alt="Tokopedia logo" srcset="organisations/tokopedia.2x.png 2x, organisations/tokopedia.3x.png 3x" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://m.tokopedia.com"><img src="organisations/tokopedia.svg" alt="Tokopedia logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://webdesq.net"><img src="organisations/webdesq.svg" alt="Webdesq logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://zevvle.com/"><img src="organisations/zevvle.svg" alt="Zevvle logo" loading="lazy"></a>
<a target="_blank" rel="noopener" href="https://github.com/sveltejs/svelte/blob/master/site/src/routes/_components/WhosUsingSvelte.svelte" class="add-yourself"><span>+ your company?</span></a>

@ -1,11 +1,10 @@
import fs from 'fs';
import path from 'path';
import { extract_frontmatter, langs, link_renderer } from '@sveltejs/site-kit/utils/markdown.js';
import { extract_frontmatter, link_renderer } from '@sveltejs/site-kit/utils/markdown.js';
import marked from 'marked';
import { makeSlugProcessor } from '../../utils/slug';
import { highlight } from '../../utils/highlight';
import { SLUG_PRESERVE_UNICODE } from '../../../config';
import PrismJS from 'prismjs';
import 'prismjs/components/prism-bash';
const makeSlug = makeSlugProcessor(SLUG_PRESERVE_UNICODE);
@ -32,16 +31,7 @@ export default function get_posts() {
renderer.link = link_renderer;
renderer.code = (source, lang) => {
const plang = langs[lang];
const highlighted = PrismJS.highlight(
source,
PrismJS.languages[plang],
lang,
);
return `<pre class='language-${plang}'><code>${highlighted}</code></pre>`;
};
renderer.code = highlight;
renderer.heading = (text, level, rawtext) => {
const fragment = makeSlug(rawtext);

@ -1,11 +1,10 @@
import fs from 'fs';
import path from 'path';
import { SLUG_PRESERVE_UNICODE, SLUG_SEPARATOR } from '../../../config';
import { extract_frontmatter, extract_metadata, langs, link_renderer } from '@sveltejs/site-kit/utils/markdown.js';
import { extract_frontmatter, extract_metadata, link_renderer } from '@sveltejs/site-kit/utils/markdown.js';
import { make_session_slug_processor } from '@sveltejs/site-kit/utils/slug';
import { highlight } from '../../utils/highlight';
import marked from 'marked';
import PrismJS from 'prismjs';
import 'prismjs/components/prism-bash';
const blockTypes = [
'blockquote',
@ -73,14 +72,7 @@ export default function() {
if (meta && meta.hidden) return '';
const plang = langs[lang];
const highlighted = PrismJS.highlight(
source,
PrismJS.languages[plang],
lang
);
const html = `<div class='${className}'>${prefix}<pre class='language-${plang}'><code>${highlighted}</code></pre></div>`;
const html = `<div class='${className}'>${prefix}${highlight(source, lang)}</div>`;
if (block_open) {
block_open = false;

@ -140,12 +140,12 @@
if (imports.length > 0) {
const idx = files.findIndex(({ path }) => path === 'package.json');
const pkg = JSON.parse(files[idx].data);
const deps = {};
const { devDependencies } = pkg;
imports.forEach(mod => {
const match = /^(@[^/]+\/)?[^@/]+/.exec(mod);
deps[match[0]] = 'latest';
devDependencies[match[0]] = 'latest';
});
pkg.dependencies = deps;
pkg.devDependencies = devDependencies;
files[idx].data = JSON.stringify(pkg, null, ' ');
}

@ -1,9 +1,9 @@
import * as fs from 'fs';
import * as path from 'path';
import marked from 'marked';
import PrismJS from 'prismjs';
import send from '@polka/send';
import { extract_frontmatter, extract_metadata, langs, link_renderer } from '@sveltejs/site-kit/utils/markdown';
import { extract_frontmatter, extract_metadata, link_renderer } from '@sveltejs/site-kit/utils/markdown';
import { highlight } from '../../../utils/highlight';
const cache = new Map();
@ -57,14 +57,7 @@ function get_tutorial(slug) {
}
}
const plang = langs[lang];
const highlighted = PrismJS.highlight(
source,
PrismJS.languages[plang],
lang
);
return `<div class='${className}'>${prefix}<pre class='language-${plang}'><code>${highlighted}</code></pre></div>`;
return `<div class='${className}'>${prefix}${highlight(source, lang)}</div>`;
};
let html = marked(content, { renderer });

@ -218,17 +218,17 @@
}
.show {
background: rgba(0,0,0,.4);
background: var(--prime);
padding: .3em .7em;
border-radius: var(--border-r);
top: .1em;
position: relative;
font-size: var(--h5);
font-weight: 300;
color: rgba(255,255,255,0.7);
}
.show:hover {
background: rgba(0,0,0,.65);
color: white;
}

@ -14,9 +14,11 @@ const app = polka({
}
});
app.use(
authenticate(),
if (process.env.PGHOST) {
app.use(authenticate());
}
app.use(
sirv('static', {
dev: process.env.NODE_ENV === 'development',
setHeaders(res) {
@ -32,4 +34,4 @@ app.use(
})
);
app.listen(PORT);
app.listen(PORT);

@ -1,8 +1,7 @@
import { Pool } from 'pg';
export const DB = new Pool({
connectionString: process.env.DATABASE_URL
});
// Uses `PG*` ENV vars
export const DB = process.env.PGHOST ? new Pool() : null;
export function query(text, values=[]) {
return DB.query(text, values).then(r => r.rows);

@ -0,0 +1,14 @@
import { langs } from '@sveltejs/site-kit/utils/markdown.js';
import PrismJS from 'prismjs';
import 'prismjs/components/prism-bash';
export function highlight(source, lang) {
const plang = langs[lang] || '';
const highlighted = plang ? PrismJS.highlight(
source,
PrismJS.languages[plang],
lang,
) : source.replace(/[&<>]/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;' })[c]);
return `<pre class='language-${plang}'><code>${highlighted}</code></pre>`;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 5.7 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.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="523" height="158"><rect id="backgroundrect" width="100%" height="100%" x="0" y="0" fill="none" stroke="none"/><style>.st0{fill:#fff}</style><g class="currentLayer"><path class="st0" d="M21.1 18.7v27.6h53.3V65H21.1v26.7h60.1v18.7H0V0h81.2v18.7H21.1z" id="svg_1"/><path d="M195.9 156.4H0v-18.7h195.9v18.7z" id="svg_2" fill="#ff5959"/><path class="st0" d="M193.7 110.2l-72.4-65.5v65.5h-21.1V0h2.3l72.4 66.7V0H196v110.2h-2.3z" id="svg_3"/><path class="st0" d="M299.8 64.9h-31.5v91.5h-21.1V64.9h-31.5V46.2h84.2v18.7z" id="svg_4"/><path class="st0" d="M363.1 158.4c-7.1 0-13.6-1.1-19.4-3.3-5.8-2.2-10.8-5.3-14.9-9.3-4.1-4-7.3-8.9-9.6-14.6-2.3-5.7-3.4-12.1-3.4-19.1V46.3h21.1V112c0 5.5.9 10.1 2.7 13.7 1.8 3.6 4 6.5 6.7 8.6 2.7 2.1 5.5 3.6 8.6 4.4 3.1.8 5.8 1.2 8.2 1.2 2.4 0 5.1-.4 8.2-1.2 3.1-.8 5.9-2.3 8.6-4.4 2.7-2.1 4.9-5 6.7-8.6 1.8-3.6 2.7-8.2 2.7-13.7V46.2h21.1V112c0 7-1.1 13.4-3.3 19.1-2.2 5.7-5.4 10.6-9.5 14.6s-9.1 7.1-14.9 9.3c-5.8 2.3-12.4 3.4-19.6 3.4z" id="svg_5"/><path class="st0" d="M451.2 121.4v35.1h-21.1V46.2h50.1c5.6 0 10.8.9 15.5 2.8 4.7 1.9 8.7 4.5 12 7.8 3.3 3.4 5.9 7.4 7.8 12 1.9 4.7 2.8 9.8 2.8 15.3 0 4.1-.6 8.1-1.7 12-1.1 3.9-2.6 7.4-4.6 10.5-2 3.1-4.4 5.8-7.2 8.1-2.9 2.3-6 3.8-9.4 4.7l27.4 37h-25.1l-25.5-35.1h-21zm0-18.4h24c4.4 0 8-.6 10.9-1.7 2.9-1.1 5.1-2.6 6.8-4.4 1.7-1.8 2.8-3.8 3.4-6.1.6-2.3.9-4.5.9-6.9 0-2.7-.4-5.2-1.3-7.5s-2.2-4.3-3.9-6c-1.8-1.7-4-3.1-6.8-4.1-2.8-1-6.1-1.5-10-1.5h-24V103z" id="svg_6"/></g></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="735px" height="115px" viewBox="0 0 735 115" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 51.1 (57501) - http://www.bohemiancoding.com/sketch -->
<title>logo colour</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-3" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="concept-one-rough-test-copy" transform="translate(-23.000000, -11.000000)" fill="#373C8B">
<g id="logo-colour">
<path d="M185.446,92 L185.446,24.638 L226.131,24.638 L226.131,34.732 L197.394,34.732 L197.394,53.787 L221.805,53.787 L221.805,63.881 L197.394,63.881 L197.394,92 L185.446,92 Z M234.268,73.151 L234.268,41.427 L246.113,41.427 L246.113,71.606 C246.113,79.949 248.482,83.039 253.941,83.039 C258.37,83.039 261.357,80.979 265.168,76.035 L265.168,41.427 L277.013,41.427 L277.013,92 L267.331,92 L266.404,84.584 L266.095,84.584 C261.666,89.837 256.722,93.236 249.821,93.236 C239.006,93.236 234.268,85.923 234.268,73.151 Z M287.004,86.232 L292.566,78.61 C297.098,82.112 301.424,84.275 306.574,84.275 C312.033,84.275 314.608,81.7 314.608,78.198 C314.608,73.975 309.149,72.121 303.793,70.061 C297.098,67.589 289.579,63.778 289.579,55.332 C289.579,46.474 296.686,40.191 308.016,40.191 C315.02,40.191 320.685,43.075 324.805,46.268 L319.346,53.478 C315.844,50.903 312.239,49.152 308.222,49.152 C303.175,49.152 300.806,51.521 300.806,54.714 C300.806,58.628 305.853,60.276 311.312,62.233 C318.213,64.808 325.835,68.104 325.835,77.58 C325.835,86.232 318.934,93.236 306.162,93.236 C299.261,93.236 291.948,90.249 287.004,86.232 Z M342.418,32.569 C338.195,32.569 335.105,29.788 335.105,25.874 C335.105,21.857 338.195,19.076 342.418,19.076 C346.641,19.076 349.731,21.857 349.731,25.874 C349.731,29.788 346.641,32.569 342.418,32.569 Z M336.444,92 L336.444,41.427 L348.289,41.427 L348.289,92 L336.444,92 Z M360.134,66.765 C360.134,49.873 371.567,40.191 384.133,40.191 C396.802,40.191 408.235,49.873 408.235,66.765 C408.235,83.554 396.802,93.236 384.133,93.236 C371.567,93.236 360.134,83.554 360.134,66.765 Z M372.288,66.765 C372.288,76.859 376.717,83.554 384.133,83.554 C391.549,83.554 396.081,76.859 396.081,66.765 C396.081,56.568 391.549,49.873 384.133,49.873 C376.717,49.873 372.288,56.568 372.288,66.765 Z M419.977,92 L419.977,41.427 L429.762,41.427 L430.586,48.225 L430.998,48.225 C435.53,43.796 440.68,40.191 447.581,40.191 C458.396,40.191 463.134,47.504 463.134,60.276 L463.134,92 L451.289,92 L451.289,61.821 C451.289,53.478 448.92,50.388 443.358,50.388 C438.929,50.388 436.045,52.551 431.822,56.671 L431.822,92 L419.977,92 Z M474.979,58.525 C474.979,36.483 488.781,23.402 506.085,23.402 C514.325,23.402 521.02,27.419 525.243,31.951 L518.651,39.367 C515.149,35.968 511.338,33.805 506.188,33.805 C495.167,33.805 487.236,43.075 487.236,58.216 C487.236,73.563 494.549,82.833 505.776,82.833 C511.75,82.833 516.179,80.361 519.99,76.138 L526.582,83.451 C521.123,89.734 514.119,93.236 505.57,93.236 C488.472,93.236 474.979,80.876 474.979,58.525 Z M536.676,92 L536.676,19.282 L548.521,19.282 L548.521,38.028 L548.109,47.813 C552.332,43.796 557.379,40.191 564.28,40.191 C575.095,40.191 579.833,47.504 579.833,60.276 L579.833,92 L567.988,92 L567.988,61.821 C567.988,53.478 565.619,50.388 560.057,50.388 C555.628,50.388 552.744,52.551 548.521,56.671 L548.521,92 L536.676,92 Z M591.987,78.404 C591.987,67.589 601.051,61.924 621.754,59.658 C621.651,54.302 619.385,49.77 612.587,49.77 C607.54,49.77 602.802,52.036 598.373,54.714 L594.047,46.783 C599.609,43.281 606.716,40.191 614.75,40.191 C627.419,40.191 633.599,47.916 633.599,62.027 L633.599,92 L623.917,92 L622.99,86.438 L622.681,86.438 C618.149,90.249 612.999,93.236 607.025,93.236 C598.167,93.236 591.987,87.262 591.987,78.404 Z M603.523,77.477 C603.523,82.009 606.51,83.966 610.733,83.966 C614.853,83.966 618.046,81.906 621.754,78.404 L621.754,67.074 C608.055,68.825 603.523,72.43 603.523,77.477 Z M648.019,92 L648.019,41.427 L657.804,41.427 L658.628,50.388 L659.04,50.388 C662.645,43.796 668.001,40.191 673.357,40.191 C675.932,40.191 677.58,40.5 679.125,41.221 L677.065,51.521 C675.314,51.006 673.975,50.697 671.812,50.697 C667.795,50.697 662.954,53.478 659.864,61.1 L659.864,92 L648.019,92 Z M688.189,74.902 L688.189,50.8 L680.979,50.8 L680.979,41.942 L688.807,41.427 L690.249,27.625 L700.137,27.625 L700.137,41.427 L713.012,41.427 L713.012,50.8 L700.137,50.8 L700.137,74.902 C700.137,80.876 702.403,83.76 707.244,83.76 C708.995,83.76 711.055,83.245 712.497,82.627 L714.557,91.382 C711.776,92.309 708.274,93.236 704.257,93.236 C692.515,93.236 688.189,85.82 688.189,74.902 Z M718.574,86.232 L724.136,78.61 C728.668,82.112 732.994,84.275 738.144,84.275 C743.603,84.275 746.178,81.7 746.178,78.198 C746.178,73.975 740.719,72.121 735.363,70.061 C728.668,67.589 721.149,63.778 721.149,55.332 C721.149,46.474 728.256,40.191 739.586,40.191 C746.59,40.191 752.255,43.075 756.375,46.268 L750.916,53.478 C747.414,50.903 743.809,49.152 739.792,49.152 C734.745,49.152 732.376,51.521 732.376,54.714 C732.376,58.628 737.423,60.276 742.882,62.233 C749.783,64.808 757.405,68.104 757.405,77.58 C757.405,86.232 750.504,93.236 737.732,93.236 C730.831,93.236 723.518,90.249 718.574,86.232 Z" id="FusionCharts"></path>
<rect id="Rectangle-2-Copy-4" x="23.4041667" y="22.8571429" width="23.4041667" height="68.5714286"></rect>
<rect id="Rectangle-2-Copy-5" x="57.0833333" y="56.5714286" width="23.975" height="34.8571429"></rect>
<rect id="Rectangle-2-Copy-6" x="91.9041667" y="11.4285714" width="22.2625" height="80"></rect>
<rect id="Rectangle-2-Copy-7" x="91.9041667" y="101.714286" width="22.2625" height="23.4285714"></rect>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="800px" height="160px" viewBox="0 0 800 160" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 49.3 (51167) - http://www.bohemiancoding.com/sketch -->
<title>Dark</title>
<desc>Created with Sketch.</desc>
<defs>
<polygon id="path-1" points="0.321905678 0.275424046 91.2854215 0.275424046 91.2854215 120.758046 0.321905678 120.758046"></polygon>
<polygon id="path-3" points="0.000265185374 0.245106383 217.001549 0.245106383 217.001549 160 0.000265185374 160"></polygon>
</defs>
<g id="Dark" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Logo">
<path d="M275.066842,101.866245 L298.520492,101.866245 C310.08215,101.866245 318.670431,93.9910625 318.670431,83.3267532 C318.670431,72.8265103 310.08215,64.951328 299.015612,64.951328 L275.066842,64.951328 L275.066842,101.866245 Z M275.066842,120.405359 L275.066842,156 L252.439024,156 L252.439024,46.4122137 L304.466487,46.4122137 C325.111926,46.4122137 341.463415,61.6703793 341.463415,83.3267532 C341.463415,105.147193 325.442259,120.405359 303.970608,120.405359 L275.066842,120.405359 Z" id="Fill-1" fill="#2C3E50"></path>
<path d="M412.210747,116.169033 C412.210747,103.600873 402.12157,93.5132706 389.551448,93.5132706 C376.981707,93.5132706 366.727134,103.600873 366.727134,116.169033 C366.727134,128.737573 376.981707,138.990166 389.551448,138.990166 C402.12157,138.990166 412.210747,128.737573 412.210747,116.169033 M385.581936,73.5038168 C397.325456,73.5038168 409.564405,79.4571558 413.86471,89.0486465 L414.360899,89.0486465 L415.187881,75.6536337 L435.365854,75.6536337 L435.365854,156 L415.187881,156 L414.360899,143.45517 L413.86471,143.45517 C409.398629,153.377402 396.663491,159 385.581936,159 C365.07279,159 343.902439,141.801465 343.902439,116.169033 C343.902439,90.5369813 365.238567,73.5038168 385.581936,73.5038168 Z" id="Fill-3" fill="#2C3E50"></path>
<path d="M442.682927,75.6460779 L462.812052,75.6460779 L464.131994,88.9940123 L464.46198,88.9940123 C469.411765,79.4362321 479.971306,73.5038168 490.860832,73.5038168 C512.804499,73.5038168 526.829268,90.3123268 526.829268,111.899348 L526.829268,156 L504.059879,156 L504.059879,114.206777 C504.059879,103.001104 495.810238,93.7729023 484.756098,93.7729023 C474.196557,93.7729023 465.451937,103.165893 465.451937,114.701145 L465.451937,156 L442.682927,156 L442.682927,75.6460779 Z" id="Fill-5" fill="#2C3E50"></path>
<polygon id="Fill-7" fill="#2C3E50" points="534.146341 38.0839695 557.050183 38.0839695 557.050183 107.0289 557.382123 107.0289 588.086549 75.7649175 616.632984 75.7649175 579.621704 112.788055 617.914429 156 589.206787 156 557.382123 118.547209 557.050183 118.547209 557.050183 156 534.146341 156"></polygon>
<path d="M680.792588,117.169033 C680.792588,104.766624 670.354166,94.5132706 657.92702,94.5132706 C645.499874,94.5132706 635.061071,104.766624 635.061071,117.169033 C635.061071,129.737573 645.499874,139.990166 657.92702,139.990166 C670.354166,139.990166 680.792588,129.737573 680.792588,117.169033 M612.195122,117.169033 C612.195122,93.686418 632.741337,74.5038168 657.92702,74.5038168 C683.112322,74.5038168 703.658537,93.686418 703.658537,117.169033 C703.658537,140.652028 683.112322,160 657.92702,160 C632.741337,160 612.195122,140.652028 612.195122,117.169033" id="Fill-9" fill="#2C3E50"></path>
<g id="Group-13" transform="translate(708.536585, 37.862595)">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="Clip-12"></g>
<path d="M68.5035169,78.1861569 C68.5035169,65.6940195 58.4330533,55.6674355 45.8862463,55.6674355 C33.3398188,55.6674355 23.1042658,65.6940195 23.1042658,78.1861569 C23.1042658,90.6786721 33.3398188,100.869248 45.8862463,100.869248 C58.4330533,100.869248 68.5035169,90.6786721 68.5035169,78.1861569 Z M91.2854974,0.275424046 L91.2854974,118.137405 L71.3027701,118.137405 L70.3195021,105.307245 L69.9893229,105.307245 C65.5319047,115.169458 52.6549185,120.758046 41.5942971,120.758046 C20.6275428,120.758046 0.321905678,103.663542 0.321905678,78.1861569 C0.321905678,52.7091492 20.6275428,35.7790156 41.0986489,35.7790156 C52.6549185,35.7790156 64.0460986,41.8607141 68.1733377,51.2298172 L68.5035169,51.2298172 L68.5035169,0.275424046 L91.2854974,0.275424046 Z" id="Fill-11" fill="#2C3E50" mask="url(#mask-2)"></path>
</g>
<g id="Group-16">
<mask id="mask-4" fill="white">
<use xlink:href="#path-3"></use>
</mask>
<g id="Clip-15"></g>
<path d="M182.173146,72.3045674 C179.882324,72.7406903 176.517879,72.4520851 172.983716,68.9373806 C169.314687,65.3250875 168.638464,63.1758676 168.732794,61.9972388 C168.784695,61.348539 169.590479,61.0701466 170.049629,61.5319905 C175.829533,67.3460804 179.87058,69.6632435 182.307255,70.5770969 C183.159257,70.8959622 183.066821,72.1343546 182.173146,72.3045674 M208.714793,77.9684917 C202.438992,73.3954421 181.685963,52.03374 159.689215,34.322156 C153.250514,28.1619669 145.345717,21.3685863 135.155401,15.1705721 C77.8814213,-19.6647943 18.2991925,17.3969551 18.2991925,17.3969551 C87.7360886,-8.66299764 129.308821,27.0866005 129.308821,27.0866005 C75.6527276,7.74437825 14.4218035,29.2055603 0.000265185374,46.0547329 C6.59466777,42.4590827 32.1161082,28.8768605 77.4082548,30.8399811 C107.795468,32.6922742 125.986806,45.028539 136.685142,57.3368132 C136.950706,57.7177116 137.28143,58.1092009 137.685269,58.5131726 C137.686785,58.5150638 137.688679,58.5169551 137.690194,58.5188463 C137.66216,58.5286809 123.69712,63.5155366 106.920356,56.5572388 C106.920356,56.5572388 117.255009,72.0348747 130.948045,72.8091537 C128.986052,80.5042837 127.100584,89.4154894 125.537885,101.514969 C121.14149,137.54069 172.870822,156.693787 181.428733,159.607073 L182.620173,160.000076 C182.620173,160.000076 163.760189,141.684804 165.568375,98.8630544 C153.16717,92.9298156 152.650438,83.1274515 151.875339,80.0318487 C151.875339,80.0318487 158.334497,90.3505248 164.018177,90.8660804 C169.702236,91.6399811 175.263173,89.971896 177.453226,89.5766241 C180.489977,89.028539 188.304232,90.3505248 188.820965,96.5413522 C189.079331,104.538326 186.754034,117.178326 198.380519,122.853598 C198.380519,122.853598 195.279744,106.602061 203.547466,106.344095 C207.939694,106.85965 211.298077,106.602061 211.815189,103.764426 C212.590288,100.410856 214.140107,96.2837636 215.94905,92.1559149 C217.757235,88.0284444 218.273968,84.9332199 208.714793,77.9684917" id="Fill-14" fill="#2C3E50" mask="url(#mask-4)"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.7 KiB

@ -0,0 +1,106 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
viewBox="0 0 1333.3333 266.66666"
height="266.66666"
width="1333.3333"
xml:space="preserve"
id="svg3713"
version="1.1"
sodipodi:docname="logo.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="3840"
inkscape:window-height="1506"
id="namedview36"
showgrid="false"
inkscape:zoom="2.7532502"
inkscape:cx="599.11011"
inkscape:cy="133.33333"
inkscape:window-x="-11"
inkscape:window-y="-11"
inkscape:window-maximized="1"
inkscape:current-layer="svg3713" /><metadata
id="metadata3719"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
id="defs3717"><clipPath
id="clipPath3729"
clipPathUnits="userSpaceOnUse"><path
id="path3727"
d="M 0,200 H 1000 V 0 H 0 Z" /></clipPath></defs><g
transform="matrix(1.3333333,0,0,-1.3333333,0,266.66667)"
id="g3721"
style="fill:#00a0e3;fill-opacity:1"><g
id="g3723"
style="fill:#00a0e3;fill-opacity:1"><g
clip-path="url(#clipPath3729)"
id="g3725"
style="fill:#00a0e3;fill-opacity:1"><g
transform="translate(46.9448,138.832)"
id="g3731"
style="fill:#00a0e3;fill-opacity:1"><path
id="path3733"
style="fill:#00a0e3;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c 0,-15.33 17.382,-19.257 37.947,-21.872 24.117,-3.178 44.677,-10.657 44.677,-35.707 0,-20.002 -15.513,-38.696 -47.667,-38.696 -19.817,0 -36.265,4.489 -50.475,14.396 -1.869,1.12 -1.869,2.053 -0.75,4.11 l 3.553,5.98 c 1.308,2.058 2.058,2.433 4.3,1.125 11.967,-8.225 27.479,-12.527 41.502,-12.527 17.2,0 35.144,5.61 35.144,25.049 0,15.518 -14.02,20.565 -38.509,23.93 -20.377,2.808 -44.119,8.975 -44.119,33.837 0,14.393 10.844,39.072 47.109,39.072 19.255,0 31.779,-5.61 45.989,-14.21 2.058,-1.12 1.68,-2.245 0.933,-4.11 L 76.081,14.58 C 74.961,12.522 73.836,11.777 71.971,13.272 60.006,21.31 47.667,25.612 34.582,25.612 6.167,25.612 0,9.72 0,0" /></g><g
transform="translate(215.9419,55.2656)"
id="g3735"
style="fill:#00a0e3;fill-opacity:1"><path
id="path3737"
style="fill:#00a0e3;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c 29.537,0 51.967,26.737 51.967,54.777 0,28.227 -22.43,54.584 -51.967,54.772 -29.165,0 -51.969,-26.545 -51.969,-54.772 C -51.969,26.737 -29.165,0 0,0 m 0,122.633 c 36.827,0 66.922,-30.654 66.922,-67.856 0,-25.799 -13.458,-43.182 -29.35,-55.335 l 16.45,-22.616 c 1.312,-1.871 0.937,-2.996 -0.745,-4.115 l -7.293,-5.235 c -1.682,-1.121 -2.799,-0.746 -3.924,0.937 L 25.607,-7.1 C 17.575,-10.652 11.027,-13.084 0,-13.084 c -36.829,0 -66.924,30.664 -66.924,67.861 0,37.202 30.095,67.856 66.924,67.856" /></g><g
transform="translate(389.8018,57.5156)"
id="g3739"
style="fill:#00a0e3;fill-opacity:1"><path
id="path3741"
style="fill:#00a0e3;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c 2.245,0 2.805,-0.75 2.805,-2.994 v -6.918 c 0,-2.245 -0.56,-2.99 -2.805,-2.99 h -76.084 c -2.245,0 -2.992,0.745 -2.992,2.99 v 124.878 c 0,2.24 0.747,2.99 2.992,2.99 h 8.788 c 2.239,0 2.989,-0.75 2.989,-2.99 V 0 Z" /></g><g
transform="translate(555.251,175.4717)"
id="g3743"
style="fill:#00a0e3;fill-opacity:1"><path
id="path3745"
style="fill:#00a0e3;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c 2.428,0 2.803,-0.75 2.803,-2.99 v -7.48 c 0,-2.245 -0.563,-2.99 -2.803,-2.99 h -44.868 v -114.408 c 0,-2.245 -0.563,-2.99 -2.803,-2.99 h -8.975 c -2.43,0 -2.992,0.745 -2.992,2.99 V -13.46 h -44.677 c -2.428,0 -2.99,0.745 -2.99,2.99 v 7.48 c 0,2.24 0.75,2.99 2.99,2.99 z" /></g><g
transform="translate(624.9873,108.5479)"
id="g3747"
style="fill:#00a0e3;fill-opacity:1"><path
id="path3749"
style="fill:#00a0e3;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="M 0,0 C 22.242,0 31.962,10.652 31.962,27.664 31.962,46.358 19.252,54.209 0,54.209 H -31.779 V 0 Z m 17.007,-11.032 35.144,-49.35 c 1.495,-2.432 0,-3.553 -2.24,-3.553 h -8.787 c -2.245,0 -3.74,1.121 -5.052,2.991 L 1.678,-12.715 h -33.457 v -48.229 c 0,-2.428 -0.755,-2.991 -2.995,-2.991 h -8.788 c -2.24,0 -2.99,0.375 -2.99,2.991 V 63.934 c 0,2.24 0.75,2.99 2.99,2.99 H 0.933 c 21.309,0 45.421,-8.976 45.421,-39.26 0,-22.242 -11.959,-34.024 -29.347,-38.696" /></g><g
transform="translate(704.0635,172.4814)"
id="g3751"
style="fill:#00a0e3;fill-opacity:1"><path
id="path3753"
style="fill:#00a0e3;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c 0,2.24 0.75,2.99 2.995,2.99 h 8.97 c 2.245,0 2.807,-0.75 2.807,-2.99 v -124.878 c 0,-2.245 -0.562,-2.99 -2.807,-2.99 h -8.97 c -2.245,0 -2.995,0.745 -2.995,2.99 z" /></g><g
transform="translate(807.8193,57.3232)"
id="g3755"
style="fill:#00a0e3;fill-opacity:1"><path
id="path3757"
style="fill:#00a0e3;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c 22.617,0 31.962,9.162 31.962,24.862 0,12.715 -13.085,23.185 -30.284,23.185 H -37.015 V 0 Z m -0.192,60.382 c 16.08,0 27.482,8.042 27.482,20.189 0,17.2 -12.527,24.863 -31.78,24.863 H -37.015 V 60.382 Z M -48.797,-12.71 c -2.24,0 -2.99,0.563 -2.99,2.99 v 124.878 c 0,2.24 0.75,2.99 2.99,2.99 h 45.244 c 21.31,0 45.797,-8.788 45.797,-36.269 0,-16.45 -13.272,-24.112 -21.309,-27.108 V 54.589 C 28.222,52.345 47.104,44.119 47.104,23.367 47.104,-2.989 25.795,-12.71 1.12,-12.71 Z" /></g><g
transform="translate(967.2793,57.5156)"
id="g3759"
style="fill:#00a0e3;fill-opacity:1"><path
id="path3761"
style="fill:#00a0e3;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 h -71.409 v 59.553 h 68.044 c 2.245,0 2.995,0.75 2.995,2.99 v 6.917 c 0,2.245 -0.75,2.99 -2.995,2.99 H -58.482 -71.409 -86.176 V -9.912 c 0,-2.245 0.744,-2.99 2.989,-2.99 H 0 c 2.245,0 2.808,0.745 2.808,2.99 v 6.918 C 2.808,-0.75 2.245,0 0,0" /></g><g
transform="translate(967.2793,175.4717)"
id="g3763"
style="fill:#00a0e3;fill-opacity:1"><path
id="path3765"
style="fill:#00a0e3;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 h -83.187 c -2.245,0 -2.989,-0.75 -2.989,-2.99 v -9.907 H -71.409 -58.482 0 c 2.058,0 2.808,0.745 2.808,2.99 V -2.99 C 2.808,-0.75 2.058,0 0,0" /></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 111 KiB

File diff suppressed because it is too large Load Diff

@ -1,7 +1,7 @@
import deindent from './utils/deindent';
import list from '../utils/list';
import { ModuleFormat, Node } from '../interfaces';
import { stringify_props } from './utils/stringify_props';
import { ModuleFormat } from '../interfaces';
import { b, x } from 'code-red';
import { Identifier, ImportDeclaration } from 'estree';
const wrappers = { esm, cjs };
@ -11,24 +11,26 @@ interface Export {
}
export default function create_module(
code: string,
program: any,
format: ModuleFormat,
name: string,
name: Identifier,
banner: string,
sveltePath = 'svelte',
helpers: Array<{ name: string; alias: string }>,
globals: Array<{ name: string; alias: string }>,
imports: Node[],
module_exports: Export[],
source: string
): string {
helpers: Array<{ name: string; alias: Identifier }>,
globals: Array<{ name: string; alias: Identifier }>,
imports: ImportDeclaration[],
module_exports: Export[]
) {
const internal_path = `${sveltePath}/internal`;
helpers.sort((a, b) => (a.name < b.name) ? -1 : 1);
globals.sort((a, b) => (a.name < b.name) ? -1 : 1);
if (format === 'esm') {
return esm(code, name, banner, sveltePath, internal_path, helpers, globals, imports, module_exports, source);
return esm(program, name, banner, sveltePath, internal_path, helpers, globals, imports, module_exports);
}
if (format === 'cjs') return cjs(code, name, banner, sveltePath, internal_path, helpers, globals, imports, module_exports);
if (format === 'cjs') return cjs(program, name, banner, sveltePath, internal_path, helpers, globals, imports, module_exports);
throw new Error(`options.format is invalid (must be ${list(Object.keys(wrappers))})`);
}
@ -40,107 +42,164 @@ function edit_source(source, sveltePath) {
}
function esm(
code: string,
name: string,
program: any,
name: Identifier,
banner: string,
sveltePath: string,
internal_path: string,
helpers: Array<{ name: string; alias: string }>,
globals: Array<{ name: string; alias: string }>,
imports: Node[],
module_exports: Export[],
source: string
helpers: Array<{ name: string; alias: Identifier }>,
globals: Array<{ name: string; alias: Identifier }>,
imports: ImportDeclaration[],
module_exports: Export[]
) {
const internal_imports = helpers.length > 0 && (
`import ${stringify_props(helpers.map(h => h.name === h.alias ? h.name : `${h.name} as ${h.alias}`).sort())} from ${JSON.stringify(internal_path)};`
);
const internal_globals = globals.length > 0 && (
`const ${stringify_props(globals.map(g => `${g.name}: ${g.alias}`).sort())} = ${helpers.find(({ name }) => name === 'globals').alias};`
);
const user_imports = imports.length > 0 && (
imports
.map((declaration: Node) => {
const import_source = edit_source(declaration.source.value, sveltePath);
return (
source.slice(declaration.start, declaration.source.start) +
JSON.stringify(import_source) +
source.slice(declaration.source.end, declaration.end)
);
})
.join('\n')
);
return deindent`
${banner}
${internal_imports}
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 && {
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
}]
};
// edit user imports
imports.forEach(node => {
node.source.value = edit_source(node.source.value, sveltePath);
});
const exports = module_exports.length > 0 && {
type: 'ExportNamedDeclaration',
specifiers: module_exports.map(x => ({
type: 'Specifier',
local: { type: 'Identifier', name: x.name },
exported: { type: 'Identifier', name: x.as }
}))
};
program.body = b`
/* ${banner} */
${import_declaration}
${internal_globals}
${user_imports}
${imports}
${code}
${program.body}
export default ${name};
${module_exports.length > 0 && `export { ${module_exports.map(e => e.name === e.as ? e.name : `${e.name} as ${e.as}`).join(', ')} };`}`;
${exports}
`;
}
function cjs(
code: string,
name: string,
program: any,
name: Identifier,
banner: string,
sveltePath: string,
internal_path: string,
helpers: Array<{ name: string; alias: string }>,
globals: Array<{ name: string; alias: string }>,
imports: Node[],
helpers: Array<{ name: string; alias: Identifier }>,
globals: Array<{ name: string; alias: Identifier }>,
imports: ImportDeclaration[],
module_exports: Export[]
) {
const declarations = helpers.map(h => `${h.alias === h.name ? h.name : `${h.name}: ${h.alias}`}`).sort();
const internal_imports = helpers.length > 0 && (
`const ${stringify_props(declarations)} = require(${JSON.stringify(internal_path)});\n`
);
const internal_globals = globals.length > 0 && (
`const ${stringify_props(globals.map(g => `${g.name}: ${g.alias}`).sort())} = ${helpers.find(({ name }) => name === 'globals').alias};`
);
const internal_requires = {
type: 'VariableDeclaration',
kind: 'const',
declarations: [{
type: 'VariableDeclarator',
id: {
type: 'ObjectPattern',
properties: helpers.map(h => ({
type: 'Property',
method: false,
shorthand: false,
computed: false,
key: { type: 'Identifier', name: h.name },
value: h.alias,
kind: 'init'
}))
},
init: x`require("${internal_path}")`
}]
};
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 user_requires = imports.map(node => ({
type: 'VariableDeclaration',
kind: 'const',
declarations: [{
type: 'VariableDeclarator',
id: node.specifiers[0].type === 'ImportNamespaceSpecifier'
? { type: 'Identifier', name: node.specifiers[0].local.name }
: {
type: 'ObjectPattern',
properties: node.specifiers.map(s => ({
type: 'Property',
method: false,
shorthand: false,
computed: false,
key: s.type === 'ImportSpecifier' ? s.imported : { type: 'Identifier', name: 'default' },
value: s.local,
kind: 'init'
}))
},
init: x`require("${edit_source(node.source.value, sveltePath)}")`
}]
}));
const exports = module_exports.map(x => b`exports.${{ type: 'Identifier', name: x.as }} = ${{ type: 'Identifier', name: x.name }};`);
program.body = b`
/* ${banner} */
const requires = imports.map(node => {
let lhs;
if (node.specifiers[0].type === 'ImportNamespaceSpecifier') {
lhs = node.specifiers[0].local.name;
} else {
const properties = node.specifiers.map(s => {
if (s.type === 'ImportDefaultSpecifier') {
return `default: ${s.local.name}`;
}
return s.local.name === s.imported.name
? s.local.name
: `${s.imported.name}: ${s.local.name}`;
});
lhs = `{ ${properties.join(', ')} }`;
}
const source = edit_source(node.source.value, sveltePath);
return `const ${lhs} = require("${source}");`;
});
const exports = [`exports.default = ${name};`].concat(
module_exports.map(x => `exports.${x.as} = ${x.name};`)
);
return deindent`
${banner}
"use strict";
${internal_imports}
${internal_requires}
${internal_globals}
${requires}
${user_requires}
${code}
${program.body}
${exports}`;
exports.default = ${name};
${exports}
`;
}

@ -1,17 +1,24 @@
import MagicString from 'magic-string';
import Stylesheet from './Stylesheet';
import { gather_possible_values, UNKNOWN } from './gather_possible_values';
import { Node } from '../../interfaces';
import { CssNode } from './interfaces';
import Component from '../Component';
import Element from '../nodes/Element';
enum BlockAppliesToNode {
NotPossible,
Possible,
UnknownSelectorType
}
export default class Selector {
node: Node;
node: CssNode;
stylesheet: Stylesheet;
blocks: Block[];
local_blocks: Block[];
used: boolean;
constructor(node: Node, stylesheet: Stylesheet) {
constructor(node: CssNode, stylesheet: Stylesheet) {
this.node = node;
this.stylesheet = stylesheet;
@ -28,13 +35,13 @@ export default class Selector {
this.used = this.blocks[0].global;
}
apply(node: Node, stack: Node[]) {
const to_encapsulate: Node[] = [];
apply(node: Element, stack: Element[]) {
const to_encapsulate: any[] = [];
apply_selector(this.stylesheet, this.local_blocks.slice(), node, stack.slice(), to_encapsulate);
apply_selector(this.local_blocks.slice(), node, stack.slice(), to_encapsulate);
if (to_encapsulate.length > 0) {
to_encapsulate.filter((_, i) => i === 0 || i === to_encapsulate.length - 1).forEach(({ node, block }) => {
to_encapsulate.forEach(({ node, block }) => {
this.stylesheet.nodes_with_css_class.add(node);
block.should_encapsulate = true;
});
@ -126,7 +133,7 @@ export default class Selector {
}
}
function apply_selector(stylesheet: Stylesheet, blocks: Block[], node: Node, stack: Node[], 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;
@ -134,49 +141,30 @@ function apply_selector(stylesheet: Stylesheet, blocks: Block[], node: Node, sta
return blocks.every(block => block.global);
}
let i = block.selectors.length;
while (i--) {
const selector = block.selectors[i];
if (selector.type === 'PseudoClassSelector' && selector.name === 'global') {
// TODO shouldn't see this here... maybe we should enforce that :global(...)
// cannot be sandwiched between non-global selectors?
switch (block_might_apply_to_node(block, node)) {
case BlockAppliesToNode.NotPossible:
return false;
}
if (selector.type === 'PseudoClassSelector' || selector.type === 'PseudoElementSelector') {
continue;
}
if (selector.type === 'ClassSelector') {
if (!attribute_matches(node, 'class', selector.name, '~=', false) && !class_matches(node, selector.name)) return false;
}
else if (selector.type === 'IdSelector') {
if (!attribute_matches(node, 'id', selector.name, '=', false)) return false;
}
else if (selector.type === 'AttributeSelector') {
if (!attribute_matches(node, selector.name.name, selector.value && unquote(selector.value), selector.matcher, selector.flags)) return false;
}
else if (selector.type === 'TypeSelector') {
// remove toLowerCase() in v2, when uppercase elements will be forbidden
if (node.name.toLowerCase() !== selector.name.toLowerCase() && selector.name !== '*') return false;
}
else {
case BlockAppliesToNode.UnknownSelectorType:
// bail. TODO figure out what these could be
to_encapsulate.push({ node, block });
return true;
}
}
if (block.combinator) {
if (block.combinator.type === 'WhiteSpace') {
while (stack.length) {
if (apply_selector(stylesheet, blocks.slice(), stack.pop(), stack, to_encapsulate)) {
for (const ancestor_block of blocks) {
if (ancestor_block.global) {
continue;
}
for (const stack_node of stack) {
if (block_might_apply_to_node(ancestor_block, stack_node) !== BlockAppliesToNode.NotPossible) {
to_encapsulate.push({ node: stack_node, block: ancestor_block });
}
}
if (to_encapsulate.length) {
to_encapsulate.push({ node, block });
return true;
}
@ -189,7 +177,7 @@ function apply_selector(stylesheet: Stylesheet, blocks: Block[], node: Node, sta
return false;
} else if (block.combinator.name === '>') {
if (apply_selector(stylesheet, blocks, stack.pop(), stack, to_encapsulate)) {
if (apply_selector(blocks, stack.pop(), stack, to_encapsulate)) {
to_encapsulate.push({ node, block });
return true;
}
@ -206,51 +194,160 @@ function apply_selector(stylesheet: Stylesheet, blocks: Block[], node: Node, sta
return true;
}
const operators = {
'=' : (value: string, flags: string) => new RegExp(`^${value}$`, flags),
'~=': (value: string, flags: string) => new RegExp(`\\b${value}\\b`, flags),
'|=': (value: string, flags: string) => new RegExp(`^${value}(-.+)?$`, flags),
'^=': (value: string, flags: string) => new RegExp(`^${value}`, flags),
'$=': (value: string, flags: string) => new RegExp(`${value}$`, flags),
'*=': (value: string, flags: string) => new RegExp(value, flags)
};
function block_might_apply_to_node(block, node): BlockAppliesToNode {
let i = block.selectors.length;
while (i--) {
const selector = block.selectors[i];
const name = typeof selector.name === 'string' && selector.name.replace(/\\(.)/g, '$1');
if (selector.type === 'PseudoClassSelector' || selector.type === 'PseudoElementSelector') {
continue;
}
if (selector.type === 'PseudoClassSelector' && name === 'global') {
// TODO shouldn't see this here... maybe we should enforce that :global(...)
// cannot be sandwiched between non-global selectors?
return BlockAppliesToNode.NotPossible;
}
if (selector.type === 'ClassSelector') {
if (!attribute_matches(node, 'class', name, '~=', false) && !node.classes.some(c => c.name === name)) return BlockAppliesToNode.NotPossible;
}
else if (selector.type === 'IdSelector') {
if (!attribute_matches(node, 'id', name, '=', false)) return BlockAppliesToNode.NotPossible;
}
else if (selector.type === 'AttributeSelector') {
if (!attribute_matches(node, selector.name.name, selector.value && unquote(selector.value), selector.matcher, selector.flags)) return BlockAppliesToNode.NotPossible;
}
else if (selector.type === 'TypeSelector') {
if (node.name.toLowerCase() !== name.toLowerCase() && name !== '*') return BlockAppliesToNode.NotPossible;
}
else {
return BlockAppliesToNode.UnknownSelectorType;
}
}
return BlockAppliesToNode.Possible;
}
function test_attribute(operator, expected_value, case_insensitive, value) {
if (case_insensitive) {
expected_value = expected_value.toLowerCase();
value = value.toLowerCase();
}
switch (operator) {
case '=': return value === expected_value;
case '~=': return ` ${value} `.includes(` ${expected_value} `);
case '|=': return `${value}-`.startsWith(`${expected_value}-`);
case '^=': return value.startsWith(expected_value);
case '$=': return value.endsWith(expected_value);
case '*=': return value.includes(expected_value);
default: throw new Error(`this shouldn't happen`);
}
}
function attribute_matches(node: Node, name: string, expected_value: string, operator: string, case_insensitive: boolean) {
function attribute_matches(node: CssNode, name: string, expected_value: string, operator: string, case_insensitive: boolean) {
const spread = node.attributes.find(attr => attr.type === 'Spread');
if (spread) return true;
if (node.bindings.some((binding: Node) => binding.name === name)) return true;
if (node.bindings.some((binding: CssNode) => binding.name === name)) return true;
const attr = node.attributes.find((attr: Node) => attr.name === name);
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;
const pattern = operators[operator](expected_value, case_insensitive ? 'i' : '');
const value = attr.chunks[0];
if (!value) return false;
if (value.type === 'Text') return pattern.test(value.data);
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 x of Array.from(possible_values)) { // TypeScript for-of is slightly unlike JS
if (pattern.test(x)) return true;
for (const value of possible_values) {
if (test_attribute(operator, expected_value, case_insensitive, value)) return true;
}
return false;
}
function class_matches(node, name: string) {
return node.classes.some((class_directive) => {
return new RegExp(`\\b${name}\\b`).test(class_directive.name);
});
}
function unquote(value: Node) {
function unquote(value: CssNode) {
if (value.type === 'Identifier') return value.name;
const str = value.value;
if (str[0] === str[str.length - 1] && str[0] === "'" || str[0] === '"') {
@ -261,13 +358,13 @@ function unquote(value: Node) {
class Block {
global: boolean;
combinator: Node;
selectors: Node[]
combinator: CssNode;
selectors: CssNode[]
start: number;
end: number;
should_encapsulate: boolean;
constructor(combinator: Node) {
constructor(combinator: CssNode) {
this.combinator = combinator;
this.global = false;
this.selectors = [];
@ -278,7 +375,7 @@ class Block {
this.should_encapsulate = false;
}
add(selector: Node) {
add(selector: CssNode) {
if (this.selectors.length === 0) {
this.start = selector.start;
this.global = selector.type === 'PseudoClassSelector' && selector.name === 'global';
@ -289,12 +386,12 @@ class Block {
}
}
function group_selectors(selector: Node) {
function group_selectors(selector: CssNode) {
let block: Block = new Block(null);
const blocks = [block];
selector.children.forEach((child: Node) => {
selector.children.forEach((child: CssNode) => {
if (child.type === 'WhiteSpace' || child.type === 'Combinator') {
block = new Block(child);
blocks.push(block);

@ -2,20 +2,21 @@ import MagicString from 'magic-string';
import { walk } from 'estree-walker';
import Selector from './Selector';
import Element from '../nodes/Element';
import { Node, Ast } from '../../interfaces';
import { Ast, TemplateNode } from '../../interfaces';
import Component from '../Component';
import { CssNode } from './interfaces';
function remove_css_prefix(name: string): string {
return name.replace(/^-((webkit)|(moz)|(o)|(ms))-/, '');
}
const is_keyframes_node = (node: Node) =>
const is_keyframes_node = (node: CssNode) =>
remove_css_prefix(node.name) === 'keyframes';
const at_rule_has_declaration = ({ block }: Node): true =>
const at_rule_has_declaration = ({ block }: CssNode): true =>
block &&
block.children &&
block.children.find((node: Node) => node.type === 'Declaration');
block.children.find((node: CssNode) => node.type === 'Declaration');
function minify_declarations(
code: MagicString,
@ -48,14 +49,14 @@ function hash(str: string): string {
class Rule {
selectors: Selector[];
declarations: Declaration[];
node: Node;
node: CssNode;
parent: Atrule;
constructor(node: Node, stylesheet, parent?: Atrule) {
constructor(node: CssNode, stylesheet, parent?: Atrule) {
this.node = node;
this.parent = parent;
this.selectors = node.selector.children.map((node: Node) => new Selector(node, stylesheet));
this.declarations = node.block.children.map((node: Node) => new Declaration(node));
this.selectors = node.selector.children.map((node: CssNode) => new Selector(node, stylesheet));
this.declarations = node.block.children.map((node: CssNode) => new Declaration(node));
}
apply(node: Element, stack: Element[]) {
@ -117,16 +118,16 @@ class Rule {
}
class Declaration {
node: Node;
node: CssNode;
constructor(node: Node) {
constructor(node: CssNode) {
this.node = node;
}
transform(code: MagicString, keyframes: Map<string, string>) {
const property = this.node.property && remove_css_prefix(this.node.property.toLowerCase());
if (property === 'animation' || property === 'animation-name') {
this.node.value.children.forEach((block: Node) => {
this.node.value.children.forEach((block: CssNode) => {
if (block.type === 'Identifier') {
const name = block.name;
if (keyframes.has(name)) {
@ -155,11 +156,11 @@ class Declaration {
}
class Atrule {
node: Node;
node: CssNode;
children: Array<Atrule|Rule>;
declarations: Declaration[];
constructor(node: Node) {
constructor(node: CssNode) {
this.node = node;
this.children = [];
this.declarations = [];
@ -191,7 +192,7 @@ class Atrule {
let c = this.node.start + (expression_char === '(' ? 6 : 7);
if (this.node.expression.start > c) code.remove(c, this.node.expression.start);
this.node.expression.children.forEach((query: Node) => {
this.node.expression.children.forEach((query: CssNode) => {
// TODO minify queries
c = query.end;
});
@ -200,7 +201,7 @@ class Atrule {
} else if (this.node.name === 'supports') {
let c = this.node.start + 9;
if (this.node.expression.start - c > 1) code.overwrite(c, this.node.expression.start, ' ');
this.node.expression.children.forEach((query: Node) => {
this.node.expression.children.forEach((query: CssNode) => {
// TODO minify queries
c = query.end;
});
@ -240,7 +241,7 @@ class Atrule {
transform(code: MagicString, id: string, keyframes: Map<string, string>) {
if (is_keyframes_node(this.node)) {
this.node.expression.children.forEach(({ type, name, start, end }: Node) => {
this.node.expression.children.forEach(({ type, name, start, end }: CssNode) => {
if (type === 'Identifier') {
if (name.startsWith('-global-')) {
code.remove(start, start + 8);
@ -288,7 +289,7 @@ export default class Stylesheet {
children: Array<Rule|Atrule> = [];
keyframes: Map<string, string> = new Map();
nodes_with_css_class: Set<Node> = new Set();
nodes_with_css_class: Set<CssNode> = new Set();
constructor(source: string, ast: Ast, filename: string, dev: boolean) {
this.source = source;
@ -305,8 +306,8 @@ export default class Stylesheet {
let depth = 0;
let current_atrule: Atrule = null;
walk(ast.css, {
enter: (node: Node) => {
walk(ast.css as any, {
enter: (node: any) => {
if (node.type === 'Atrule') {
const atrule = new Atrule(node);
stack.push(atrule);
@ -318,7 +319,7 @@ export default class Stylesheet {
}
if (is_keyframes_node(node)) {
node.expression.children.forEach((expression: Node) => {
node.expression.children.forEach((expression: CssNode) => {
if (expression.type === 'Identifier' && !expression.name.startsWith('-global-')) {
this.keyframes.set(expression.name, `${this.id}-${expression.name}`);
}
@ -346,7 +347,7 @@ export default class Stylesheet {
depth += 1;
},
leave: (node: Node) => {
leave: (node: any) => {
if (node.type === 'Atrule') {
stack.pop();
current_atrule = stack[stack.length - 1];
@ -364,7 +365,7 @@ export default class Stylesheet {
if (!this.has_styles) return;
const stack: Element[] = [];
let parent: Node = node;
let parent: TemplateNode = node;
while (parent = parent.parent) {
if (parent.type === 'Element') stack.unshift(parent as Element);
}
@ -376,7 +377,7 @@ export default class Stylesheet {
}
reify() {
this.nodes_with_css_class.forEach((node: Node) => {
this.nodes_with_css_class.forEach((node: Element) => {
node.add_css_class();
});
}
@ -388,8 +389,8 @@ export default class Stylesheet {
const code = new MagicString(this.source);
walk(this.ast.css, {
enter: (node: Node) => {
walk(this.ast.css as any, {
enter: (node: any) => {
code.addSourcemapLocation(node.start);
code.addSourcemapLocation(node.end);
}

@ -1,4 +1,4 @@
import { Node } from '../../interfaces';
import { Node } from 'estree';
export const UNKNOWN = {};

@ -0,0 +1,6 @@
export interface CssNode {
type: string;
start: number;
end: number;
[prop_name: string]: any;
}

@ -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 = {}) {

@ -1,4 +1,4 @@
import { stringify } from '../utils/stringify';
import { string_literal } from '../utils/stringify';
import add_to_set from '../utils/add_to_set';
import Component from '../Component';
import Node from './shared/Node';
@ -6,9 +6,10 @@ import Element from './Element';
import Text from './Text';
import Expression from './shared/Expression';
import TemplateScope from './shared/TemplateScope';
import { x } from 'code-red';
export default class Attribute extends Node {
type: 'Attribute';
type: 'Attribute' | 'Spread';
start: number;
end: number;
scope: TemplateScope;
@ -19,7 +20,6 @@ export default class Attribute extends Node {
is_spread: boolean;
is_true: boolean;
is_static: boolean;
is_synthetic: boolean;
expression?: Expression;
chunks: Array<Text | Expression>;
dependencies: Set<string>;
@ -32,7 +32,6 @@ export default class Attribute extends Node {
this.name = null;
this.is_spread = true;
this.is_true = false;
this.is_synthetic = false;
this.expression = new Expression(component, this, scope, info.expression);
this.dependencies = this.expression.dependencies;
@ -45,7 +44,6 @@ export default class Attribute extends Node {
this.name = info.name;
this.is_true = info.value === true;
this.is_static = true;
this.is_synthetic = info.synthetic;
this.dependencies = new Set();
@ -78,28 +76,24 @@ export default class Attribute extends Node {
}
get_value(block) {
if (this.is_true) return true;
if (this.chunks.length === 0) return `""`;
if (this.is_true) return x`true`;
if (this.chunks.length === 0) return x`""`;
if (this.chunks.length === 1) {
return this.chunks[0].type === 'Text'
? stringify((this.chunks[0] as Text).data)
// @ts-ignore todo: probably error
: this.chunks[0].render(block);
? string_literal((this.chunks[0] as Text).data)
: (this.chunks[0] as Expression).manipulate(block);
}
let expression = this.chunks
.map(chunk => chunk.type === 'Text' ? string_literal(chunk.data) : chunk.manipulate(block))
.reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
if (this.chunks[0].type !== 'Text') {
expression = x`"" + ${expression}`;
}
return (this.chunks[0].type === 'Text' ? '' : `"" + `) +
this.chunks
.map(chunk => {
if (chunk.type === 'Text') {
return stringify(chunk.data);
} else {
// @ts-ignore todo: probably error
return chunk.get_precedence() <= 13 ? `(${chunk.render()})` : chunk.render();
}
})
.join(' + ');
return expression;
}
get_static_value() {

@ -4,22 +4,24 @@ import Expression from './shared/Expression';
import Component from '../Component';
import TemplateScope from './shared/TemplateScope';
import {dimensions} from "../../utils/patterns";
import { Node as ESTreeNode } from 'estree';
// TODO this should live in a specific binding
const read_only_media_attributes = new Set([
'duration',
'buffered',
'seekable',
'played'
'played',
'seeking',
'ended'
]);
export default class Binding extends Node {
type: 'Binding';
name: string;
expression: Expression;
raw_expression: ESTreeNode; // TODO exists only for bind:this — is there a more elegant solution?
is_contextual: boolean;
obj: string;
prop: string;
is_readonly: boolean;
constructor(component: Component, parent, scope: TemplateScope, info) {
@ -34,9 +36,7 @@ export default class Binding extends Node {
this.name = info.name;
this.expression = new Expression(component, this, scope, info.expression);
let obj;
let prop;
this.raw_expression = JSON.parse(JSON.stringify(info.expression));
const { name } = get_object(this.expression.node);
this.is_contextual = scope.names.has(name);
@ -63,18 +63,6 @@ export default class Binding extends Node {
variable[this.expression.node.type === 'MemberExpression' ? 'mutated' : 'reassigned'] = true;
}
if (this.expression.node.type === 'MemberExpression') {
prop = `[✂${this.expression.node.property.start}-${this.expression.node.property.end}✂]`;
if (!this.expression.node.computed) prop = `'${prop}'`;
obj = `[✂${this.expression.node.object.start}-${this.expression.node.object.end}✂]`;
} else {
obj = 'ctx';
prop = `'${name}'`;
}
this.obj = obj;
this.prop = prop;
const type = parent.get_static_attribute_value('type');
this.is_readonly = (

@ -1,49 +1,55 @@
import Node from './shared/Node';
import ElseBlock from './ElseBlock';
import Expression from './shared/Expression';
import map_children from './shared/map_children';
import TemplateScope from './shared/TemplateScope';
import AbstractBlock from './shared/AbstractBlock';
import { Node as INode } from '../../interfaces';
import { new_tail } from '../utils/tail';
import Element from './Element';
import { x } from 'code-red';
import { Node, Identifier, RestElement } from 'estree';
interface Context {
key: INode;
key: Identifier;
name?: string;
tail: string;
modifier: (node: Node) => Node;
}
function unpack_destructuring(contexts: Context[], node: INode, tail: string) {
function unpack_destructuring(contexts: Context[], node: Node, modifier: (node: Node) => Node) {
if (!node) return;
if (node.type === 'Identifier' || node.type === 'RestIdentifier') {
if (node.type === 'Identifier' || (node as any).type === 'RestIdentifier') { // TODO is this right? not RestElement?
contexts.push({
key: node,
tail
key: node as Identifier,
modifier
});
} else if (node.type === 'ArrayPattern') {
node.elements.forEach((element, i) => {
if (element && element.type === 'RestIdentifier') {
unpack_destructuring(contexts, element, `${tail}.slice(${i})`);
if (element && (element as any).type === 'RestIdentifier') {
unpack_destructuring(contexts, element, node => x`${modifier(node)}.slice(${i})` as Node);
} else {
unpack_destructuring(contexts, element, `${tail}[${i}]`);
unpack_destructuring(contexts, element, node => x`${modifier(node)}[${i}]` as Node);
}
});
} else if (node.type === 'ObjectPattern') {
const used_properties = [];
node.properties.forEach((property) => {
if (property.kind === 'rest') {
node.properties.forEach((property, i) => {
if ((property as any).kind === 'rest') { // TODO is this right?
const replacement: RestElement = {
type: 'RestElement',
argument: property.key as Identifier
};
node.properties[i] = replacement as any;
unpack_destructuring(
contexts,
property.value,
`@object_without_properties(${tail}, ${JSON.stringify(used_properties)})`
node => x`@object_without_properties(${modifier(node)}, [${used_properties}])` as Node
);
} else {
used_properties.push(property.key.name);
used_properties.push(x`"${(property.key as Identifier).name}"`);
unpack_destructuring(contexts, property.value,`${tail}.${property.key.name}`);
unpack_destructuring(contexts, property.value, node => x`${modifier(node)}.${(property.key as Identifier).name}` as Node);
}
});
}
@ -77,7 +83,7 @@ export default class EachBlock extends AbstractBlock {
this.scope = scope.child();
this.contexts = [];
unpack_destructuring(this.contexts, info.context, new_tail());
unpack_destructuring(this.contexts, info.context, node => node);
this.contexts.forEach(context => {
this.scope.add(context.key.name, this.expression.dependencies, this);

@ -105,6 +105,7 @@ export default class Element extends Node {
animation?: Animation = null;
children: INode[];
namespace: string;
needs_manual_style_scoping: boolean;
constructor(component, parent, scope, info: any) {
super(component, parent, scope, info);
@ -150,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':
@ -201,7 +206,7 @@ export default class Element extends Node {
this.scope = scope.child();
this.lets.forEach(l => {
const dependencies = new Set([l.name]);
const dependencies = new Set([l.name.name]);
l.names.forEach(name => {
this.scope.add(name, dependencies, this);
@ -591,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, {
@ -712,6 +719,11 @@ export default class Element extends Node {
}
add_css_class() {
if (this.attributes.some(attr => attr.is_spread)) {
this.needs_manual_style_scoping = true;
return;
}
const { id } = this.component.stylesheet;
const class_attribute = this.attributes.find(a => a.name === 'class');

@ -1,18 +1,18 @@
import Node from './shared/Node';
import Expression from './shared/Expression';
import Component from '../Component';
import deindent from '../utils/deindent';
import Block from '../render_dom/Block';
import { sanitize } from '../../utils/names';
import { Identifier } from 'estree';
export default class EventHandler extends Node {
type: 'EventHandler';
name: string;
modifiers: Set<string>;
expression: Expression;
handler_name: string;
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);
@ -21,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) {
@ -31,40 +31,22 @@ export default class EventHandler extends Node {
} else if (info.expression.type === 'Identifier') {
let node = component.node_for_declaration.get(info.expression.name);
if (node && node.type === 'VariableDeclaration') {
// for `const handleClick = () => {...}`, we want the [arrow] function expression node
const declarator = node.declarations.find(d => d.id.name === info.expression.name);
node = declarator && declarator.init;
}
if (node) {
if (node.type === 'VariableDeclaration') {
// for `const handleClick = () => {...}`, we want the [arrow] function expression node
const declarator = node.declarations.find(d => (d.id as Identifier).name === info.expression.name);
node = declarator && declarator.init;
}
if (node && (node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration' || node.type === 'ArrowFunctionExpression') && node.params.length === 0) {
this.can_make_passive = true;
}
if (node && /Function/.test(node.type) && node.params.length === 0) {
this.can_make_passive = true;
this.reassigned = component.var_lookup.get(info.expression.name).reassigned;
}
}
} else {
const name = component.get_unique_name(`${sanitize(this.name)}_handler`);
component.add_var({
name,
internal: true,
referenced: true
});
component.partly_hoisted.push(deindent`
function ${name}(event) {
@bubble($$self, event);
}
`);
this.handler_name = name;
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.render(block);
// this.component.add_reference(this.handler_name);
return `ctx.${this.handler_name}`;
}
}

@ -90,7 +90,7 @@ export default class InlineComponent extends Node {
this.scope = scope.child();
this.lets.forEach(l => {
const dependencies = new Set([l.name]);
const dependencies = new Set([l.name.name]);
l.names.forEach(name => {
this.scope.add(name, dependencies, this);

@ -1,38 +1,51 @@
import Node from './shared/Node';
import Component from '../Component';
import { walk } from 'estree-walker';
import { Identifier } from 'estree';
const applicable = new Set(['Identifier', 'ObjectExpression', 'ArrayExpression', 'Property']);
export default class Let extends Node {
type: 'Let';
name: string;
value: string;
name: Identifier;
value: Identifier;
names: string[] = [];
constructor(component: Component, parent, scope, info) {
super(component, parent, scope, info);
this.name = info.name;
this.value = info.expression && `[✂${info.expression.start}-${info.expression.end}✂]`;
this.name = { type: 'Identifier', name: info.name };
const { names } = this;
if (info.expression) {
this.value = info.expression;
walk(info.expression, {
enter: node => {
enter(node) {
if (!applicable.has(node.type)) {
component.error(node, {
component.error(node as any, {
code: 'invalid-let',
message: `let directive value must be an identifier or an object/array pattern`
});
}
if (node.type === 'Identifier') {
this.names.push(node.name);
names.push(node.name);
}
// slightly unfortunate hack
if (node.type === 'ArrayExpression') {
(node as any).type = 'ArrayPattern';
}
if (node.type === 'ObjectExpression') {
(node as any).type = 'ObjectPattern';
}
}
});
} else {
this.names.push(this.name);
names.push(this.name.name);
}
}
}

@ -3,73 +3,24 @@ import { walk } from 'estree-walker';
import is_reference from 'is-reference';
import flatten_reference from '../../utils/flatten_reference';
import { create_scopes, Scope, extract_names } from '../../utils/scope';
import { Node } from '../../../interfaces';
import { globals , sanitize } from '../../../utils/names';
import deindent from '../../utils/deindent';
import { sanitize } from '../../../utils/names';
import Wrapper from '../../render_dom/wrappers/shared/Wrapper';
import TemplateScope from './TemplateScope';
import get_object from '../../utils/get_object';
import Block from '../../render_dom/Block';
import { INode } from '../interfaces';
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 { TemplateNode } from '../../../interfaces';
const binary_operators: Record<string, number> = {
'**': 15,
'*': 14,
'/': 14,
'%': 14,
'+': 13,
'-': 13,
'<<': 12,
'>>': 12,
'>>>': 12,
'<': 11,
'<=': 11,
'>': 11,
'>=': 11,
in: 11,
instanceof: 11,
'==': 10,
'!=': 10,
'===': 10,
'!==': 10,
'&': 9,
'^': 8,
'|': 7
};
const logical_operators: Record<string, number> = {
'&&': 6,
'||': 5
};
const precedence: Record<string, (node?: Node) => number> = {
Literal: () => 21,
Identifier: () => 21,
ParenthesizedExpression: () => 20,
MemberExpression: () => 19,
NewExpression: () => 19, // can be 18 (if no args) but makes no practical difference
CallExpression: () => 19,
UpdateExpression: () => 17,
UnaryExpression: () => 16,
BinaryExpression: (node: Node) => binary_operators[node.operator],
LogicalExpression: (node: Node) => logical_operators[node.operator],
ConditionalExpression: () => 4,
AssignmentExpression: () => 3,
YieldExpression: () => 2,
SpreadElement: () => 1,
SequenceExpression: () => 0
};
type Owner = Wrapper | INode;
type Owner = Wrapper | TemplateNode;
export default class Expression {
type: 'Expression' = 'Expression';
component: Component;
owner: Owner;
node: any;
snippet: string;
references: Set<string>;
dependencies: Set<string> = new Set();
contextual_dependencies: Set<string> = new Set();
@ -78,11 +29,10 @@ export default class Expression {
scope: Scope;
scope_map: WeakMap<Node, Scope>;
is_synthetic: boolean;
declarations: string[] = [];
declarations: Array<(Node | Node[])> = [];
uses_context = false;
rendered: string;
manipulated: Node;
// todo: owner type
constructor(component: Component, owner: Owner, template_scope: TemplateScope, info, lazy?: boolean) {
@ -96,8 +46,6 @@ export default class Expression {
this.node = info;
this.template_scope = template_scope;
this.owner = owner;
// @ts-ignore
this.is_synthetic = owner.is_synthetic;
const { dependencies, contextual_dependencies } = this;
@ -127,8 +75,6 @@ export default class Expression {
if (scope.has(name)) return;
if (globals.has(name) && !component.var_lookup.has(name)) return;
if (name[0] === '$' && template_scope.names.has(name.slice(1))) {
component.error(node, {
code: `contextual-store`,
@ -219,49 +165,41 @@ export default class Expression {
});
}
get_precedence() {
return this.node.type in precedence ? precedence[this.node.type](this.node) : 0;
}
// TODO move this into a render-dom wrapper?
render(block?: Block) {
if (this.rendered) return this.rendered;
manipulate(block?: Block) {
// TODO ideally we wouldn't end up calling this method
// multiple times
if (this.manipulated) return this.manipulated;
const {
component,
declarations,
scope_map: map,
template_scope,
owner,
is_synthetic
owner
} = this;
let scope = this.scope;
const { code } = component;
let function_expression;
let dependencies: Set<string>;
let contextual_dependencies: Set<string>;
// rewrite code as appropriate
walk(this.node, {
enter(node: any, parent: any, key: string) {
// don't manipulate shorthand props twice
if (key === 'value' && parent.shorthand) return;
code.addSourcemapLocation(node.start);
code.addSourcemapLocation(node.end);
const node = walk(this.node, {
enter(node: any, parent: any) {
if (node.type === 'Property' && node.shorthand) {
node.value = JSON.parse(JSON.stringify(node.value));
node.shorthand = false;
}
if (map.has(node)) {
scope = map.get(node);
}
if (is_reference(node, parent)) {
const { name, nodes } = flatten_reference(node);
const { name } = flatten_reference(node);
if (scope.has(name)) return;
if (globals.has(name) && !component.var_lookup.has(name)) return;
if (function_expression) {
if (template_scope.names.has(name)) {
@ -274,17 +212,8 @@ export default class Expression {
dependencies.add(name);
component.add_reference(name); // TODO is this redundant/misplaced?
}
} else if (!is_synthetic && is_contextual(component, template_scope, name)) {
code.prependRight(node.start, key === 'key' && parent.shorthand
? `${name}: ctx.`
: 'ctx.');
}
if (node.type === 'MemberExpression') {
nodes.forEach(node => {
code.addSourcemapLocation(node.start);
code.addSourcemapLocation(node.end);
});
} else if (is_contextual(component, template_scope, name)) {
this.replace(x`#ctx.${node}`);
}
this.skip();
@ -307,34 +236,20 @@ export default class Expression {
if (map.has(node)) scope = scope.parent;
if (node === function_expression) {
const name = component.get_unique_name(
const id = component.get_unique_name(
sanitize(get_function_name(node, owner))
);
const args = contextual_dependencies.size > 0
? [`{ ${Array.from(contextual_dependencies).join(', ')} }`]
: [];
let original_params;
if (node.params.length > 0) {
original_params = code.slice(node.params[0].start, node.params[node.params.length - 1].end);
args.push(original_params);
}
const body = code.slice(node.body.start, node.body.end).trim();
const fn = node.type === 'FunctionExpression'
? `${node.async ? 'async ' : ''}function${node.generator ? '*' : ''} ${name}(${args.join(', ')}) ${body}`
: `const ${name} = ${node.async ? 'async ' : ''}(${args.join(', ')}) => ${body};`;
const declaration = b`const ${id} = ${node}`;
if (dependencies.size === 0 && contextual_dependencies.size === 0) {
// we can hoist this out of the component completely
component.fully_hoisted.push(fn);
code.overwrite(node.start, node.end, name);
component.fully_hoisted.push(declaration);
this.replace(id as any);
component.add_var({
name,
name: id.name,
internal: true,
hoistable: true,
referenced: true
@ -343,11 +258,12 @@ export default class Expression {
else if (contextual_dependencies.size === 0) {
// function can be hoisted inside the component init
component.partly_hoisted.push(fn);
code.overwrite(node.start, node.end, `ctx.${name}`);
component.partly_hoisted.push(declaration);
this.replace(x`#ctx.${id}` as any);
component.add_var({
name,
name: id.name,
internal: true,
referenced: true
});
@ -355,29 +271,43 @@ export default class Expression {
else {
// we need a combo block/init recipe
component.partly_hoisted.push(fn);
code.overwrite(node.start, node.end, name);
(node as FunctionExpression).params.unshift({
type: 'ObjectPattern',
properties: Array.from(contextual_dependencies).map(name => p`${name}` as any)
});
component.partly_hoisted.push(declaration);
this.replace(id as any);
component.add_var({
name,
name: id.name,
internal: true,
referenced: true
});
declarations.push(deindent`
function ${name}(${original_params ? '...args' : ''}) {
return ctx.${name}(ctx${original_params ? ', ...args' : ''});
}
`);
}
if (parent && parent.method) {
code.prependRight(node.start, ': ');
if ((node as FunctionExpression).params.length > 0) {
declarations.push(b`
function ${id}(...args) {
return #ctx.${id}(#ctx, ...args);
}
`);
} else {
declarations.push(b`
function ${id}() {
return #ctx.${id}(#ctx);
}
`);
}
}
function_expression = null;
dependencies = null;
contextual_dependencies = null;
if (parent && parent.type === 'Property') {
parent.method = false;
}
}
if (node.type === 'AssignmentExpression' || node.type === 'UpdateExpression') {
@ -399,7 +329,7 @@ export default class Expression {
}
});
invalidate(component, scope, code, node, traced);
this.replace(invalidate(component, scope, node, traced));
}
}
});
@ -407,11 +337,11 @@ export default class Expression {
if (declarations.length > 0) {
block.maintain_context = true;
declarations.forEach(declaration => {
block.builders.init.add_block(declaration);
block.chunks.init.push(declaration);
});
}
return this.rendered = `[✂${this.node.start}-${this.node.end}✂]`;
return (this.manipulated = node);
}
}

@ -14,7 +14,7 @@ import Slot from '../Slot';
import Text from '../Text';
import Title from '../Title';
import Window from '../Window';
import { Node } from '../../../interfaces';
import { TemplateNode } from '../../../interfaces';
export type Children = ReturnType<typeof map_children>;
@ -40,7 +40,7 @@ function get_constructor(type) {
}
}
export default function map_children(component, parent, scope, children: Node[]) {
export default function map_children(component, parent, scope, children: TemplateNode[]) {
let last = null;
let ignores = [];

@ -1,52 +1,66 @@
import CodeBuilder from '../utils/CodeBuilder';
import deindent from '../utils/deindent';
import Renderer from './Renderer';
import Wrapper from './wrappers/shared/Wrapper';
import { stringify, escape } from '../utils/stringify';
import { b, x } from 'code-red';
import { Node, Identifier } from 'estree';
import { is_head } from './wrappers/shared/is_head';
export interface BlockOptions {
parent?: Block;
name: string;
name: Identifier;
type: string;
renderer?: Renderer;
comment?: string;
key?: string;
bindings?: Map<string, { object: string; property: string; snippet: string; store: string; tail: string }>;
key?: Identifier;
bindings?: Map<string, {
object: Identifier;
property: Identifier;
snippet: Node;
store: string;
tail: Node;
modifier: (node: Node) => Node;
}>;
dependencies?: Set<string>;
}
export default class Block {
parent?: Block;
renderer: Renderer;
name: string;
name: Identifier;
type: string;
comment?: string;
wrappers: Wrapper[];
key: string;
first: string;
key: Identifier;
first: Identifier;
dependencies: Set<string>;
bindings: Map<string, { object: string; property: string; snippet: string; store: string; tail: string }>;
builders: {
init: CodeBuilder;
create: CodeBuilder;
claim: CodeBuilder;
hydrate: CodeBuilder;
mount: CodeBuilder;
measure: CodeBuilder;
fix: CodeBuilder;
animate: CodeBuilder;
intro: CodeBuilder;
update: CodeBuilder;
outro: CodeBuilder;
destroy: CodeBuilder;
bindings: Map<string, {
object: Identifier;
property: Identifier;
snippet: Node;
store: string;
tail: Node;
modifier: (node: Node) => Node;
}>;
chunks: {
init: Array<Node | Node[]>;
create: Array<Node | Node[]>;
claim: Array<Node | Node[]>;
hydrate: Array<Node | Node[]>;
mount: Array<Node | Node[]>;
measure: Array<Node | Node[]>;
fix: Array<Node | Node[]>;
animate: Array<Node | Node[]>;
intro: Array<Node | Node[]>;
update: Array<Node | Node[]>;
outro: Array<Node | Node[]>;
destroy: Array<Node | Node[]>;
};
event_listeners: string[] = [];
event_listeners: Node[] = [];
maintain_context: boolean;
has_animation: boolean;
@ -56,9 +70,9 @@ export default class Block {
has_outro_method: boolean;
outros: number;
aliases: Map<string, string>;
variables: Map<string, string>;
get_unique_name: (name: string) => string;
aliases: Map<string, Identifier>;
variables: Map<string, { id: Identifier; init?: Node }> = new Map();
get_unique_name: (name: string) => Identifier;
has_update_method = false;
autofocus: string;
@ -80,19 +94,19 @@ export default class Block {
this.bindings = options.bindings;
this.builders = {
init: new CodeBuilder(),
create: new CodeBuilder(),
claim: new CodeBuilder(),
hydrate: new CodeBuilder(),
mount: new CodeBuilder(),
measure: new CodeBuilder(),
fix: new CodeBuilder(),
animate: new CodeBuilder(),
intro: new CodeBuilder(),
update: new CodeBuilder(),
outro: new CodeBuilder(),
destroy: new CodeBuilder(),
this.chunks = {
init: [],
create: [],
claim: [],
hydrate: [],
mount: [],
measure: [],
fix: [],
animate: [],
intro: [],
update: [],
outro: [],
destroy: [],
};
this.has_animation = false;
@ -101,15 +115,14 @@ export default class Block {
this.outros = 0;
this.get_unique_name = this.renderer.component.get_unique_name_maker();
this.variables = new Map();
this.aliases = new Map().set('ctx', this.get_unique_name('ctx'));
this.aliases = new Map();
if (this.key) this.aliases.set('key', this.get_unique_name('key'));
}
assign_variable_names() {
const seen = new Set();
const dupes = new Set();
const seen: Set<string> = new Set();
const dupes: Set<string> = new Set();
let i = this.wrappers.length;
@ -117,13 +130,12 @@ export default class Block {
const wrapper = this.wrappers[i];
if (!wrapper.var) continue;
if (wrapper.parent && wrapper.parent.can_use_innerhtml) continue;
if (seen.has(wrapper.var)) {
dupes.add(wrapper.var);
if (seen.has(wrapper.var.name)) {
dupes.add(wrapper.var.name);
}
seen.add(wrapper.var);
seen.add(wrapper.var.name);
}
const counts = new Map();
@ -134,13 +146,13 @@ export default class Block {
if (!wrapper.var) continue;
if (dupes.has(wrapper.var)) {
const i = counts.get(wrapper.var) || 0;
counts.set(wrapper.var, i + 1);
wrapper.var = this.get_unique_name(wrapper.var + i);
} else {
wrapper.var = this.get_unique_name(wrapper.var);
let suffix = '';
if (dupes.has(wrapper.var.name)) {
const i = counts.get(wrapper.var.name) || 0;
counts.set(wrapper.var.name, i + 1);
suffix = i;
}
wrapper.var.name = this.get_unique_name(wrapper.var.name + suffix).name;
}
}
@ -153,25 +165,25 @@ export default class Block {
}
add_element(
name: string,
render_statement: string,
claim_statement: string,
parent_node: string,
id: Identifier,
render_statement: Node,
claim_statement: Node,
parent_node: Node,
no_detach?: boolean
) {
this.add_variable(name);
this.builders.create.add_line(`${name} = ${render_statement};`);
this.add_variable(id);
this.chunks.create.push(b`${id} = ${render_statement};`);
if (this.renderer.options.hydratable) {
this.builders.claim.add_line(`${name} = ${claim_statement || render_statement};`);
this.chunks.claim.push(b`${id} = ${claim_statement || render_statement};`);
}
if (parent_node) {
this.builders.mount.add_line(`@append(${parent_node}, ${name});`);
if (parent_node === '@_document.head' && !no_detach) this.builders.destroy.add_line(`@detach(${name});`);
this.chunks.mount.push(b`@append(${parent_node}, ${id});`);
if (is_head(parent_node) && !no_detach) this.chunks.destroy.push(b`@detach(${id});`);
} else {
this.builders.mount.add_line(`@insert(#target, ${name}, anchor);`);
if (!no_detach) this.builders.destroy.add_conditional('detaching', `@detach(${name});`);
this.chunks.mount.push(b`@insert(#target, ${id}, anchor);`);
if (!no_detach) this.chunks.destroy.push(b`if (detaching) @detach(${id});`);
}
}
@ -190,18 +202,14 @@ export default class Block {
this.has_animation = true;
}
add_variable(name: string, init?: string) {
if (name[0] === '#') {
name = this.alias(name.slice(1));
}
if (this.variables.has(name) && this.variables.get(name) !== init) {
add_variable(id: Identifier, init?: Node) {
if (this.variables.has(id.name)) {
throw new Error(
`Variable '${name}' already initialised with a different value`
`Variable '${id.name}' already initialised with a different value`
);
}
this.variables.set(name, init);
this.variables.set(id.name, { id, init });
}
alias(name: string) {
@ -216,215 +224,246 @@ export default class Block {
return new Block(Object.assign({}, this, { key: null }, options, { parent: this }));
}
get_contents(local_key?: string) {
get_contents(key?: any) {
const { dev } = this.renderer.options;
if (this.has_outros) {
this.add_variable('#current');
this.add_variable({ type: 'Identifier', name: '#current' });
if (!this.builders.intro.is_empty()) {
this.builders.intro.add_line(`#current = true;`);
this.builders.mount.add_line(`#current = true;`);
if (this.chunks.intro.length > 0) {
this.chunks.intro.push(b`#current = true;`);
this.chunks.mount.push(b`#current = true;`);
}
if (!this.builders.outro.is_empty()) {
this.builders.outro.add_line(`#current = false;`);
if (this.chunks.outro.length > 0) {
this.chunks.outro.push(b`#current = false;`);
}
}
if (this.autofocus) {
this.builders.mount.add_line(`${this.autofocus}.focus();`);
this.chunks.mount.push(b`${this.autofocus}.focus();`);
}
this.render_listeners();
const properties = new CodeBuilder();
const properties: Record<string, any> = {};
const method_name = (short: string, long: string) => dev ? `${short}: function ${this.get_unique_name(long)}` : short;
const noop = x`@noop`;
if (local_key) {
properties.add_block(`key: ${local_key},`);
}
properties.key = key;
if (this.first) {
properties.add_block(`first: null,`);
this.builders.hydrate.add_line(`this.first = ${this.first};`);
properties.first = x`null`;
this.chunks.hydrate.push(b`this.first = ${this.first};`);
}
if (this.builders.create.is_empty() && this.builders.hydrate.is_empty()) {
properties.add_line(`c: @noop,`);
if (this.chunks.create.length === 0 && this.chunks.hydrate.length === 0) {
properties.create = noop;
} else {
const hydrate = !this.builders.hydrate.is_empty() && (
const hydrate = this.chunks.hydrate.length > 0 && (
this.renderer.options.hydratable
? `this.h()`
: this.builders.hydrate
? b`this.h();`
: this.chunks.hydrate
);
properties.add_block(deindent`
${method_name('c', 'create')}() {
${this.builders.create}
${hydrate}
},
`);
properties.create = x`function #create() {
${this.chunks.create}
${hydrate}
}`;
}
if (this.renderer.options.hydratable || !this.builders.claim.is_empty()) {
if (this.builders.claim.is_empty() && this.builders.hydrate.is_empty()) {
properties.add_line(`l: @noop,`);
if (this.renderer.options.hydratable || this.chunks.claim.length > 0) {
if (this.chunks.claim.length === 0 && this.chunks.hydrate.length === 0) {
properties.claim = noop;
} else {
properties.add_block(deindent`
${method_name('l', 'claim')}(nodes) {
${this.builders.claim}
${this.renderer.options.hydratable && !this.builders.hydrate.is_empty() && `this.h();`}
},
`);
properties.claim = x`function #claim(#nodes) {
${this.chunks.claim}
${this.renderer.options.hydratable && this.chunks.hydrate.length > 0 && b`this.h();`}
}`;
}
}
if (this.renderer.options.hydratable && !this.builders.hydrate.is_empty()) {
properties.add_block(deindent`
${method_name('h', 'hydrate')}() {
${this.builders.hydrate}
},
`);
if (this.renderer.options.hydratable && this.chunks.hydrate.length > 0) {
properties.hydrate = x`function #hydrate() {
${this.chunks.hydrate}
}`;
}
if (this.builders.mount.is_empty()) {
properties.add_line(`m: @noop,`);
if (this.chunks.mount.length === 0) {
properties.mount = noop;
} else {
properties.add_block(deindent`
${method_name('m', 'mount')}(#target, anchor) {
${this.builders.mount}
},
`);
properties.mount = x`function #mount(#target, anchor) {
${this.chunks.mount}
}`;
}
if (this.has_update_method || this.maintain_context) {
if (this.builders.update.is_empty() && !this.maintain_context) {
properties.add_line(`p: @noop,`);
if (this.chunks.update.length === 0 && !this.maintain_context) {
properties.update = noop;
} else {
properties.add_block(deindent`
${method_name('p', 'update')}(changed, ${this.maintain_context ? 'new_ctx' : 'ctx'}) {
${this.maintain_context && `ctx = new_ctx;`}
${this.builders.update}
},
`);
const ctx = this.maintain_context ? x`#new_ctx` : x`#ctx`;
properties.update = x`function #update(#changed, ${ctx}) {
${this.maintain_context && b`#ctx = ${ctx};`}
${this.chunks.update}
}`;
}
}
if (this.has_animation) {
properties.add_block(deindent`
${method_name('r', 'measure')}() {
${this.builders.measure}
},
${method_name('f', 'fix')}() {
${this.builders.fix}
},
${method_name('a', 'animate')}() {
${this.builders.animate}
},
`);
properties.measure = x`function #measure() {
${this.chunks.measure}
}`;
properties.fix = x`function #fix() {
${this.chunks.fix}
}`;
properties.animate = x`function #animate() {
${this.chunks.animate}
}`;
}
if (this.has_intro_method || this.has_outro_method) {
if (this.builders.intro.is_empty()) {
properties.add_line(`i: @noop,`);
if (this.chunks.intro.length === 0) {
properties.intro = noop;
} else {
properties.add_block(deindent`
${method_name('i', 'intro')}(#local) {
${this.has_outros && `if (#current) return;`}
${this.builders.intro}
},
`);
properties.intro = x`function #intro(#local) {
${this.has_outros && b`if (#current) return;`}
${this.chunks.intro}
}`;
}
if (this.builders.outro.is_empty()) {
properties.add_line(`o: @noop,`);
if (this.chunks.outro.length === 0) {
properties.outro = noop;
} else {
properties.add_block(deindent`
${method_name('o', 'outro')}(#local) {
${this.builders.outro}
},
`);
properties.outro = x`function #outro(#local) {
${this.chunks.outro}
}`;
}
}
if (this.builders.destroy.is_empty()) {
properties.add_line(`d: @noop`);
if (this.chunks.destroy.length === 0) {
properties.destroy = noop;
} else {
properties.add_block(deindent`
${method_name('d', 'destroy')}(detaching) {
${this.builders.destroy}
}
`);
properties.destroy = x`function #destroy(detaching) {
${this.chunks.destroy}
}`;
}
/* eslint-disable @typescript-eslint/indent,indent */
return deindent`
${this.variables.size > 0 &&
`var ${Array.from(this.variables.keys())
.map(key => {
const init = this.variables.get(key);
return init !== undefined ? `${key} = ${init}` : key;
})
.join(', ')};`}
if (!this.renderer.component.compile_options.dev) {
// allow shorthand names
for (const name in properties) {
const property = properties[name];
if (property) property.id = null;
}
}
${!this.builders.init.is_empty() && this.builders.init}
const return_value: any = x`{
key: ${properties.key},
first: ${properties.first},
c: ${properties.create},
l: ${properties.claim},
h: ${properties.hydrate},
m: ${properties.mount},
p: ${properties.update},
r: ${properties.measure},
f: ${properties.fix},
a: ${properties.animate},
i: ${properties.intro},
o: ${properties.outro},
d: ${properties.destroy}
}`;
const block = dev && this.get_unique_name('block');
const body = b`
${Array.from(this.variables.values()).map(({ id, init }) => {
return init
? b`let ${id} = ${init}`
: b`let ${id}`;
})}
${this.chunks.init}
${dev
? deindent`
const block = {
${properties}
};
@dispatch_dev("SvelteRegisterBlock", { block, id: ${this.name || 'create_fragment'}.name, type: "${this.type}", source: ${stringify(this.comment || '', { only_escape_at_symbol: true })}, ctx });
return block;`
: deindent`
return {
${properties}
};`
? b`
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};`
}
`.replace(/([^{])(#+)(\w*)/g, (_match: string, pre: string, sigil: string, name: string) => {
return pre + (sigil === '#' ? this.alias(name) : sigil.slice(1) + name);
});
/* eslint-enable @typescript-eslint/indent,indent */
`;
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);
const fn = b`function ${this.name}(${args}) {
${this.get_contents(key)}
}`;
return this.comment
? b`
// ${this.comment}
${fn}`
: fn;
}
render_listeners(chunk: string = '') {
if (this.event_listeners.length > 0) {
this.add_variable(`#dispose${chunk}`);
const dispose: Identifier = {
type: 'Identifier',
name: `#dispose${chunk}`
};
this.add_variable(dispose);
if (this.event_listeners.length === 1) {
this.builders.hydrate.add_line(
`#dispose${chunk} = ${this.event_listeners[0]};`
this.chunks.hydrate.push(
b`${dispose} = ${this.event_listeners[0]};`
);
this.builders.destroy.add_line(
`#dispose${chunk}();`
this.chunks.destroy.push(
b`${dispose}();`
);
} else {
this.builders.hydrate.add_block(deindent`
#dispose${chunk} = [
${this.event_listeners.join(',\n')}
this.chunks.hydrate.push(b`
${dispose} = [
${this.event_listeners}
];
`);
this.builders.destroy.add_line(
`@run_all(#dispose${chunk});`
this.chunks.destroy.push(
b`@run_all(${dispose});`
);
}
}
}
toString() {
const local_key = this.key && this.get_unique_name('key');
return deindent`
${this.comment && `// ${escape(this.comment, { only_escape_at_symbol: true })}`}
function ${this.name}(${this.key ? `${local_key}, ` : ''}ctx) {
${this.get_contents(local_key)}
}
`;
}
}
}

@ -2,21 +2,22 @@ import Block from './Block';
import { CompileOptions } from '../../interfaces';
import Component from '../Component';
import FragmentWrapper from './wrappers/Fragment';
import CodeBuilder from '../utils/CodeBuilder';
import { x } from 'code-red';
import { Node, Identifier } from 'estree';
export default class Renderer {
component: Component; // TODO Maybe Renderer shouldn't know about Component?
options: CompileOptions;
blocks: Array<Block | string> = [];
blocks: Array<Block | Node | Node[]> = [];
readonly: Set<string> = new Set();
meta_bindings: CodeBuilder = new CodeBuilder(); // initial values for e.g. window.innerWidth, if there's a <svelte:window> meta tag
meta_bindings: Array<Node | Node[]> = []; // initial values for e.g. window.innerWidth, if there's a <svelte:window> meta tag
binding_groups: string[] = [];
block: Block;
fragment: FragmentWrapper;
file_var: string;
file_var: Identifier;
locate: (c: number) => { line: number; column: number };
constructor(component: Component, options: CompileOptions) {
@ -49,14 +50,15 @@ export default class Renderer {
null
);
// TODO messy
this.blocks.forEach(block => {
if (typeof block !== 'string') {
if (block instanceof Block) {
block.assign_variable_names();
}
});
this.block.assign_variable_names();
this.fragment.render(this.block, null, 'nodes');
this.fragment.render(this.block, null, x`#nodes` as Identifier);
}
}

@ -1,20 +1,19 @@
import deindent from '../utils/deindent';
import { stringify, escape } from '../utils/stringify';
import CodeBuilder from '../utils/CodeBuilder';
import { b, x, p } from 'code-red';
import Component from '../Component';
import Renderer from './Renderer';
import { CompileOptions } from '../../interfaces';
import { walk } from 'estree-walker';
import { stringify_props } from '../utils/stringify_props';
import add_to_set from '../utils/add_to_set';
import { extract_names } from '../utils/scope';
import { invalidate } from '../utils/invalidate';
import Block from './Block';
import { ClassDeclaration, FunctionExpression, Node, Statement, ObjectExpression } from 'estree';
export default function dom(
component: Component,
options: CompileOptions
) {
const { name, code } = component;
const { name } = component;
const renderer = new Renderer(component, options);
const { block } = renderer;
@ -22,27 +21,34 @@ export default function dom(
block.has_outro_method = true;
// prevent fragment being created twice (#1063)
if (options.customElement) block.builders.create.add_line(`this.c = @noop;`);
if (options.customElement) block.chunks.create.push(b`this.c = @noop;`);
const builder = new CodeBuilder();
const body = [];
if (component.compile_options.dev) {
builder.add_line(`const ${renderer.file_var} = ${component.file && stringify(component.file, { only_escape_at_symbol: true })};`);
if (renderer.file_var) {
const file = component.file ? x`"${component.file}"` : x`undefined`;
body.push(b`const ${renderer.file_var} = ${file};`);
}
const css = component.stylesheet.render(options.filename, !options.customElement);
const styles = component.stylesheet.has_styles && stringify(options.dev ?
`${css.code}\n/*# sourceMappingURL=${css.map.toUrl()} */` :
css.code, { only_escape_at_symbol: true });
const styles = component.stylesheet.has_styles && options.dev
? `${css.code}\n/*# sourceMappingURL=${css.map.toUrl()} */`
: css.code;
const add_css = component.get_unique_name('add_css');
if (styles && component.compile_options.css !== false && !options.customElement) {
builder.add_block(deindent`
const should_add_css = (
!options.customElement &&
!!styles &&
options.css !== false
);
if (should_add_css) {
body.push(b`
function ${add_css}() {
var style = @element("style");
style.id = '${component.stylesheet.id}-style';
style.textContent = ${styles};
style.id = "${component.stylesheet.id}-style";
style.textContent = "${styles}";
@append(@_document.head, style);
}
`);
@ -52,24 +58,19 @@ export default function dom(
// TODO the deconflicted names of blocks are reversed... should set them here
const blocks = renderer.blocks.slice().reverse();
blocks.forEach(block => {
builder.add_block(block.toString());
});
body.push(...blocks.map(block => {
// TODO this is a horrible mess — renderer.blocks
// contains a mixture of Blocks and Nodes
if ((block as Block).render) return (block as Block).render();
return block;
}));
if (options.dev && !options.hydratable) {
block.builders.claim.add_line(
'throw new @_Error("options.hydrate only works if the component was compiled with the `hydratable: true` option");'
block.chunks.claim.push(
b`throw new @_Error("options.hydrate only works if the component was compiled with the \`hydratable: true\` option");`
);
}
// TODO injecting CSS this way is kinda dirty. Maybe it should be an
// explicit opt-in, or something?
const should_add_css = (
!options.customElement &&
component.stylesheet.has_styles &&
options.css !== false
);
const uses_props = component.var_lookup.has('$$props');
const $$props = uses_props ? `$$new_props` : `$$props`;
const props = component.vars.filter(variable => !variable.module && variable.export_name);
@ -77,62 +78,77 @@ export default function dom(
/* eslint-disable @typescript-eslint/indent,indent */
const set = (uses_props || writable_props.length > 0 || component.slots.size > 0)
? deindent`
? x`
${$$props} => {
${uses_props && component.invalidate('$$props', `$$props = @assign(@assign({}, $$props), $$new_props)`)}
${uses_props && component.invalidate('$$props', x`$$props = @assign(@assign({}, $$props), $$new_props)`)}
${writable_props.map(prop =>
`if ('${prop.export_name}' in ${$$props}) ${component.invalidate(prop.name, `${prop.name} = ${$$props}.${prop.export_name}`)};`
b`if ('${prop.export_name}' in ${$$props}) ${component.invalidate(prop.name, x`${prop.name} = ${$$props}.${prop.export_name}`)};`
)}
${component.slots.size > 0 &&
`if ('$$scope' in ${$$props}) ${component.invalidate('$$scope', `$$scope = ${$$props}.$$scope`)};`}
b`if ('$$scope' in ${$$props}) ${component.invalidate('$$scope', x`$$scope = ${$$props}.$$scope`)};`}
}
`
: null;
/* eslint-enable @typescript-eslint/indent,indent */
const body = [];
const accessors = [];
const not_equal = component.component_options.immutable ? `@not_equal` : `@safe_not_equal`;
const not_equal = component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`;
let dev_props_check; let inject_state; let capture_state;
props.forEach(x => {
const variable = component.var_lookup.get(x.name);
props.forEach(prop => {
const variable = component.var_lookup.get(prop.name);
if (!variable.writable || component.component_options.accessors) {
body.push(deindent`
get ${x.export_name}() {
return ${x.hoistable ? x.name : 'this.$$.ctx.' + x.name};
}
`);
accessors.push({
type: 'MethodDefinition',
kind: 'get',
key: { type: 'Identifier', name: prop.export_name },
value: x`function() {
return ${prop.hoistable ? prop.name : x`this.$$.ctx.${prop.name}`}
}`
});
} else if (component.compile_options.dev) {
body.push(deindent`
get ${x.export_name}() {
accessors.push({
type: 'MethodDefinition',
kind: 'get',
key: { type: 'Identifier', name: prop.export_name },
value: x`function() {
throw new @_Error("<${component.tag}>: Props cannot be read directly from the component instance unless compiling with 'accessors: true' or '<svelte:options accessors/>'");
}
`);
}`
});
}
if (component.component_options.accessors) {
if (variable.writable && !renderer.readonly.has(x.name)) {
body.push(deindent`
set ${x.export_name}(${x.name}) {
this.$set({ ${x.name === x.export_name ? x.name : `${x.export_name}: ${x.name}`} });
if (variable.writable && !renderer.readonly.has(prop.name)) {
accessors.push({
type: 'MethodDefinition',
kind: 'set',
key: { type: 'Identifier', name: prop.export_name },
value: x`function(${prop.name}) {
this.$set({ ${prop.export_name}: ${prop.name} });
@flush();
}
`);
}`
});
} else if (component.compile_options.dev) {
body.push(deindent`
set ${x.export_name}(value) {
throw new @_Error("<${component.tag}>: Cannot set read-only property '${x.export_name}'");
}
`);
accessors.push({
type: 'MethodDefinition',
kind: 'set',
key: { type: 'Identifier', name: prop.export_name },
value: x`function(value) {
throw new @_Error("<${component.tag}>: Cannot set read-only property '${prop.export_name}'");
}`
});
}
} else if (component.compile_options.dev) {
body.push(deindent`
set ${x.export_name}(value) {
accessors.push({
type: 'MethodDefinition',
kind: 'set',
key: { type: 'Identifier', name: prop.export_name },
value: x`function(value) {
throw new @_Error("<${component.tag}>: Props cannot be set directly on the component instance unless compiling with 'accessors: true' or '<svelte:options accessors/>'");
}
`);
}`
});
}
});
@ -141,35 +157,35 @@ export default function dom(
const expected = props.filter(prop => !prop.initialised);
if (expected.length) {
dev_props_check = deindent`
const { ctx } = this.$$;
const props = ${options.customElement ? `this.attributes` : `options.props || {}`};
${expected.map(prop => deindent`
if (ctx.${prop.name} === undefined && !('${prop.export_name}' in props)) {
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)) {
@_console.warn("<${component.tag}> was created without expected prop '${prop.export_name}'");
}`)}
`;
}
capture_state = (uses_props || writable_props.length > 0) ? deindent`
capture_state = (uses_props || writable_props.length > 0) ? x`
() => {
return { ${component.vars.filter(prop => prop.writable).map(prop => prop.name).join(", ")} };
return { ${component.vars.filter(prop => prop.writable).map(prop => p`${prop.name}`)} };
}
` : deindent`
` : x`
() => {
return {};
}
`;
const writable_vars = component.vars.filter(variable => !variable.module && variable.writable);
inject_state = (uses_props || writable_vars.length > 0) ? deindent`
inject_state = (uses_props || writable_vars.length > 0) ? x`
${$$props} => {
${uses_props && component.invalidate('$$props', `$$props = @assign(@assign({}, $$props), $$new_props)`)}
${writable_vars.map(prop => deindent`
if ('${prop.name}' in $$props) ${component.invalidate(prop.name, `${prop.name} = ${$$props}.${prop.name}`)};
${uses_props && component.invalidate('$$props', x`$$props = @assign(@assign({}, $$props), $$new_props)`)}
${writable_vars.map(prop => b`
if ('${prop.name}' in $$props) ${component.invalidate(prop.name, x`${prop.name} = ${$$props}.${prop.name}`)};
`)}
}
` : deindent`
` : x`
${$$props} => {}
`;
}
@ -200,52 +216,51 @@ export default function dom(
// onto the initial function call
const names = new Set(extract_names(assignee));
invalidate(component, scope, code, node, names);
this.replace(invalidate(component, scope, node, names));
}
}
});
component.rewrite_props(({ name, reassigned }) => {
component.rewrite_props(({ name, reassigned, export_name }) => {
const value = `$${name}`;
const callback = `$value => { ${value} = $$value; $$invalidate('${value}', ${value}) }`;
if (reassigned) {
return `$$subscribe_${name}()`;
}
const component_subscribe = component.helper('component_subscribe');
const insert = (reassigned || export_name)
? b`${`$$subscribe_${name}`}()`
: b`@component_subscribe($$self, ${name}, #value => $$invalidate('${value}', ${value} = #value))`;
let insert = `${component_subscribe}($$self, ${name}, $${callback})`;
if (component.compile_options.dev) {
const validate_store = component.helper('validate_store');
insert = `${validate_store}(${name}, '${name}'); ${insert}`;
return b`@validate_store(${name}, '${name}'); ${insert}`;
}
return insert;
});
}
const args = ['$$self'];
const args = [x`$$self`];
if (props.length > 0 || component.has_reactive_assignments || component.slots.size > 0) {
args.push('$$props', '$$invalidate');
args.push(x`$$props`, x`$$invalidate`);
}
builder.add_block(deindent`
function create_fragment(ctx) {
${block.get_contents()}
}
const has_create_fragment = block.has_content();
if (has_create_fragment) {
body.push(b`
function create_fragment(#ctx) {
${block.get_contents()}
}
`);
}
${component.module_javascript}
body.push(b`
${component.extract_javascript(component.ast.module)}
${component.fully_hoisted.length > 0 && component.fully_hoisted.join('\n\n')}
${component.fully_hoisted}
`);
const filtered_declarations = component.vars
.filter(v => ((v.referenced || v.export_name) && !v.hoistable))
.map(v => v.name);
.map(v => p`${v.name}`);
if (uses_props) filtered_declarations.push(`$$props: $$props = ${component.helper('exclude_internal_props')}($$props)`);
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);
@ -258,15 +273,17 @@ 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('$$slots', '$$scope');
filtered_declarations.push(p`$$slots`, p`$$scope`);
}
if (renderer.binding_groups.length > 0) {
filtered_declarations.push(`$$binding_groups`);
filtered_declarations.push(p`$$binding_groups`);
}
const instance_javascript = component.extract_javascript(component.ast.instance);
const has_definition = (
component.javascript ||
(instance_javascript && instance_javascript.length > 0) ||
filtered_props.length > 0 ||
uses_props ||
component.partly_hoisted.length > 0 ||
@ -276,7 +293,7 @@ export default function dom(
const definition = has_definition
? component.alias('instance')
: 'null';
: { type: 'Literal', value: null };
const all_reactive_dependencies = new Set();
component.reactive_declarations.forEach(d => {
@ -288,44 +305,46 @@ export default function dom(
const variable = component.var_lookup.get(store.name.slice(1));
return !variable || variable.hoistable;
})
.map(({ name }) => deindent`
${component.compile_options.dev && `@validate_store(${name.slice(1)}, '${name.slice(1)}');`}
@component_subscribe($$self, ${name.slice(1)}, $$value => { ${name} = $$value; $$invalidate('${name}', ${name}); });
.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));
`);
const resubscribable_reactive_store_unsubscribers = reactive_stores
.filter(store => {
const variable = component.var_lookup.get(store.name.slice(1));
return variable && variable.reassigned;
return variable && (variable.reassigned || variable.export_name);
})
.map(({ name }) => `$$self.$$.on_destroy.push(() => $$unsubscribe_${name.slice(1)}());`);
.map(({ name }) => b`$$self.$$.on_destroy.push(() => ${`$$unsubscribe_${name.slice(1)}`}());`);
if (has_definition) {
const reactive_declarations = [];
const reactive_declarations: (Node | Node[]) = [];
const fixed_reactive_declarations = []; // not really 'reactive' but whatever
component.reactive_declarations
.forEach(d => {
const dependencies = Array.from(d.dependencies);
const uses_props = !!dependencies.find(n => n === '$$props');
const condition = !uses_props && dependencies
.filter(n => {
const variable = component.var_lookup.get(n);
return variable && (variable.writable || variable.mutated);
})
.map(n => `$$dirty.${n}`).join(' || ');
let snippet = `[✂${d.node.body.start}-${d.node.end}✂]`;
if (condition) snippet = `if (${condition}) { ${snippet} }`;
if (condition || uses_props) {
reactive_declarations.push(snippet);
} else {
fixed_reactive_declarations.push(snippet);
}
component.reactive_declarations.forEach(d => {
const dependencies = Array.from(d.dependencies);
const uses_props = !!dependencies.find(n => n === '$$props');
const writable = dependencies.filter(n => {
const variable = component.var_lookup.get(n);
return variable && (variable.writable || variable.mutated);
});
const condition = !uses_props && writable.length > 0 && (writable
.map(n => x`#changed.${n}`)
.reduce((lhs, rhs) => x`${lhs} || ${rhs}`));
let statement = d.node; // TODO remove label (use d.node.body) if it's not referenced
if (condition) statement = b`if (${condition}) { ${statement} }`[0] as Statement;
if (condition || uses_props) {
reactive_declarations.push(statement);
} else {
fixed_reactive_declarations.push(statement);
}
});
const injected = Array.from(component.injected_reactive_declaration_vars).filter(name => {
const variable = component.var_lookup.get(name);
return variable.injected && variable.name[0] !== '$';
@ -336,73 +355,94 @@ export default function dom(
const name = $name.slice(1);
const store = component.var_lookup.get(name);
if (store && store.reassigned) {
return `${$name}, $$unsubscribe_${name} = @noop, $$subscribe_${name} = () => ($$unsubscribe_${name}(), $$unsubscribe_${name} = @subscribe(${name}, $$value => { ${$name} = $$value; $$invalidate('${$name}', ${$name}); }), ${name})`;
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})`;
}
return $name;
return b`let ${$name};`;
});
let unknown_props_check;
if (component.compile_options.dev && !component.var_lookup.has('$$props') && writable_props.length) {
unknown_props_check = deindent`
const writable_props = [${writable_props.map(prop => `'${prop.export_name}'`).join(', ')}];
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}'\`);
});
`;
}
builder.add_block(deindent`
function ${definition}(${args.join(', ')}) {
${reactive_store_declarations.length > 0 && `let ${reactive_store_declarations.join(', ')};`}
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 }
};
})
};
body.push(b`
function ${definition}(${args}) {
${reactive_store_declarations}
${reactive_store_subscriptions}
${resubscribable_reactive_store_unsubscribers}
${component.javascript}
${instance_javascript}
${unknown_props_check}
${component.slots.size && `let { $$slots = {}, $$scope } = $$props;`}
${component.slots.size ? b`let { $$slots = {}, $$scope } = $$props;` : null}
${renderer.binding_groups.length > 0 && `const $$binding_groups = [${renderer.binding_groups.map(_ => `[]`).join(', ')}];`}
${renderer.binding_groups.length > 0 && b`const $$binding_groups = [${renderer.binding_groups.map(_ => x`[]`)}];`}
${component.partly_hoisted.length > 0 && component.partly_hoisted.join('\n\n')}
${component.partly_hoisted}
${set && `$$self.$set = ${set};`}
${set && b`$$self.$set = ${set};`}
${capture_state && `$$self.$capture_state = ${capture_state};`}
${capture_state && x`$$self.$capture_state = ${capture_state};`}
${inject_state && `$$self.$inject_state = ${inject_state};`}
${inject_state && x`$$self.$inject_state = ${inject_state};`}
${injected.length && `let ${injected.join(', ')};`}
${injected.map(name => b`let ${name};`)}
${reactive_declarations.length > 0 && deindent`
$$self.$$.update = ($$dirty = { ${Array.from(all_reactive_dependencies).map(n => `${n}: 1`).join(', ')} }) => {
${reactive_declarations.length > 0 && b`
$$self.$$.update = (#changed = ${reactive_dependencies}) => {
${reactive_declarations}
};
`}
${fixed_reactive_declarations}
return ${stringify_props(filtered_declarations)};
return ${return_value};
}
`);
}
const prop_names = `[${props.map(v => JSON.stringify(v.export_name)).join(', ')}]`;
const prop_names = x`{
${props.map(v => p`${v.export_name}: ${v.export_name === v.name ? 0 : x`"${v.name}"`}}`)}
}` as ObjectExpression;
if (options.customElement) {
builder.add_block(deindent`
const declaration = b`
class ${name} extends @SvelteElement {
constructor(options) {
super();
${css.code && `this.shadowRoot.innerHTML = \`<style>${escape(css.code, { only_escape_at_symbol: true }).replace(/\\/g, '\\\\')}${options.dev ? `\n/*# sourceMappingURL=${css.map.toUrl()} */` : ''}</style>\`;`}
${css.code && b`this.shadowRoot.innerHTML = \`<style>${css.code.replace(/\\/g, '\\\\')}${options.dev ? `\n/*# sourceMappingURL=${css.map.toUrl()} */` : ''}</style>\`;`}
@init(this, { target: this.shadowRoot }, ${definition}, create_fragment, ${not_equal}, ${prop_names});
@init(this, { target: this.shadowRoot }, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_names});
${dev_props_check}
@ -411,46 +451,74 @@ export default function dom(
@insert(options.target, this, options.anchor);
}
${(props.length > 0 || uses_props) && deindent`
${(props.length > 0 || uses_props) && b`
if (options.props) {
this.$set(options.props);
@flush();
}`}
}
}
}
`[0] as ClassDeclaration;
if (props.length > 0) {
declaration.body.body.push({
type: 'MethodDefinition',
kind: 'get',
static: true,
computed: false,
key: { type: 'Identifier', name: 'observedAttributes' },
value: x`function() {
return [${props.map(prop => x`"${prop.export_name}"`)}];
}` as FunctionExpression
});
}
${props.length > 0 && deindent`
static get observedAttributes() {
return ${JSON.stringify(props.map(x => x.export_name))};
}`}
declaration.body.body.push(...accessors);
${body.length > 0 && body.join('\n\n')}
}
`);
body.push(declaration);
if (component.tag != null) {
builder.add_block(deindent`
body.push(b`
@_customElements.define("${component.tag}", ${name});
`);
}
} else {
const superclass = options.dev ? 'SvelteComponentDev' : 'SvelteComponent';
const superclass = {
type: 'Identifier',
name: options.dev ? '@SvelteComponentDev' : '@SvelteComponent'
};
builder.add_block(deindent`
class ${name} extends @${superclass} {
const declaration = b`
class ${name} extends ${superclass} {
constructor(options) {
super(${options.dev && `options`});
${should_add_css && `if (!@_document.getElementById("${component.stylesheet.id}-style")) ${add_css}();`}
@init(this, options, ${definition}, create_fragment, ${not_equal}, ${prop_names});
${options.dev && `@dispatch_dev("SvelteRegisterComponent", { component: this, tagName: "${name}", options, id: create_fragment.name });`}
${should_add_css && b`if (!@_document.getElementById("${component.stylesheet.id}-style")) ${add_css}();`}
@init(this, options, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_names});
${options.dev && b`@dispatch_dev("SvelteRegisterComponent", { component: this, tagName: "${name.name}", options, id: create_fragment.name });`}
${dev_props_check}
}
${body.length > 0 && body.join('\n\n')}
}
`);
`[0] as ClassDeclaration;
declaration.body.body.push(...accessors);
body.push(declaration);
}
return flatten(body, []);
}
function flatten(nodes: any[], target: any[]) {
for (let i = 0; i < nodes.length; i += 1) {
const node = nodes[i];
if (Array.isArray(node)) {
flatten(node, target);
} else {
target.push(node);
}
}
return builder.toString();
return target;
}

@ -3,11 +3,13 @@ import Renderer from '../Renderer';
import Block from '../Block';
import AwaitBlock from '../../nodes/AwaitBlock';
import create_debugging_comment from './shared/create_debugging_comment';
import deindent from '../../utils/deindent';
import { b, x } from 'code-red';
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 {
node: PendingBlock | ThenBlock | CatchBlock;
@ -54,7 +56,7 @@ export default class AwaitBlockWrapper extends Wrapper {
then: AwaitBlockBranch;
catch: AwaitBlockBranch;
var = 'await_block';
var: Identifier = { type: 'Identifier', name: 'await_block' };
constructor(
renderer: Renderer,
@ -67,6 +69,7 @@ export default class AwaitBlockWrapper extends Wrapper {
super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
block.add_dependencies(this.node.expression.dependencies);
@ -120,13 +123,13 @@ export default class AwaitBlockWrapper extends Wrapper {
render(
block: Block,
parent_node: string,
parent_nodes: string
parent_node: Identifier,
parent_nodes: Identifier
) {
const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes);
const update_mount_node = this.get_update_mount_node(anchor);
const snippet = this.node.expression.render(block);
const snippet = this.node.expression.manipulate(block);
const info = block.get_unique_name(`info`);
const promise = block.get_unique_name(`promise`);
@ -135,34 +138,32 @@ export default class AwaitBlockWrapper extends Wrapper {
block.maintain_context = true;
const info_props = [
'ctx',
'current: null',
'token: null',
this.pending.block.name && `pending: ${this.pending.block.name}`,
this.then.block.name && `then: ${this.then.block.name}`,
this.catch.block.name && `catch: ${this.catch.block.name}`,
this.then.block.name && `value: '${this.node.value}'`,
this.catch.block.name && `error: '${this.node.error}'`,
this.pending.block.has_outro_method && `blocks: [,,,]`
].filter(Boolean);
block.builders.init.add_block(deindent`
let ${info} = {
${info_props.join(',\n')}
};
const info_props: any = x`{
ctx: #ctx,
current: null,
token: null,
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}"`},
blocks: ${this.pending.block.has_outro_method && x`[,,,]`}
}`;
block.chunks.init.push(b`
let ${info} = ${info_props};
`);
block.builders.init.add_block(deindent`
block.chunks.init.push(b`
@handle_promise(${promise} = ${snippet}, ${info});
`);
block.builders.create.add_block(deindent`
block.chunks.create.push(b`
${info}.block.c();
`);
if (parent_nodes && this.renderer.options.hydratable) {
block.builders.claim.add_block(deindent`
block.chunks.claim.push(b`
${info}.block.l(${parent_nodes});
`);
}
@ -172,56 +173,51 @@ export default class AwaitBlockWrapper extends Wrapper {
const has_transitions = this.pending.block.has_intro_method || this.pending.block.has_outro_method;
block.builders.mount.add_block(deindent`
block.chunks.mount.push(b`
${info}.block.m(${initial_mount_node}, ${info}.anchor = ${anchor_node});
${info}.mount = () => ${update_mount_node};
${info}.anchor = ${anchor};
`);
if (has_transitions) {
block.builders.intro.add_line(`@transition_in(${info}.block);`);
block.chunks.intro.push(b`@transition_in(${info}.block);`);
}
const conditions = [];
const dependencies = this.node.expression.dynamic_dependencies();
if (dependencies.length > 0) {
conditions.push(
`(${dependencies.map(dep => `'${dep}' in changed`).join(' || ')})`
);
conditions.push(
`${promise} !== (${promise} = ${snippet})`,
`@handle_promise(${promise}, ${info})`
);
const condition = x`
${changed(dependencies)} &&
${promise} !== (${promise} = ${snippet}) &&
@handle_promise(${promise}, ${info})`;
block.builders.update.add_line(
`${info}.ctx = ctx;`
block.chunks.update.push(
b`${info}.ctx = #ctx;`
);
if (this.pending.block.has_update_method) {
block.builders.update.add_block(deindent`
if (${conditions.join(' && ')}) {
block.chunks.update.push(b`
if (${condition}) {
// nothing
} else {
${info}.block.p(changed, @assign(@assign({}, ctx), ${info}.resolved));
${info}.block.p(#changed, @assign(@assign({}, #ctx), ${info}.resolved));
}
`);
} else {
block.builders.update.add_block(deindent`
${conditions.join(' && ')}
block.chunks.update.push(b`
${condition}
`);
}
} else {
if (this.pending.block.has_update_method) {
block.builders.update.add_block(deindent`
${info}.block.p(changed, @assign(@assign({}, ctx), ${info}.resolved));
block.chunks.update.push(b`
${info}.block.p(#changed, @assign(@assign({}, #ctx), ${info}.resolved));
`);
}
}
if (this.pending.block.has_outro_method) {
block.builders.outro.add_block(deindent`
block.chunks.outro.push(b`
for (let #i = 0; #i < 3; #i += 1) {
const block = ${info}.blocks[#i];
@transition_out(block);
@ -229,14 +225,14 @@ export default class AwaitBlockWrapper extends Wrapper {
`);
}
block.builders.destroy.add_block(deindent`
${info}.block.d(${parent_node ? '' : 'detaching'});
block.chunks.destroy.push(b`
${info}.block.d(${parent_node ? null : 'detaching'});
${info}.token = null;
${info} = null;
`);
[this.pending, this.then, this.catch].forEach(branch => {
branch.fragment.render(branch.block, null, 'nodes');
branch.fragment.render(branch.block, null, x`#nodes` as Identifier);
});
}
}

@ -1,22 +1,26 @@
import Block from '../Block';
import Wrapper from './shared/Wrapper';
import deindent from '../../utils/deindent';
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: string, _parent_nodes: string) {
this.node.handlers.forEach(handler => {
const snippet = handler.render(block);
render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) {
this.node.handlers
.map(handler => new EventHandler(handler, this))
.forEach(handler => {
const snippet = handler.get_snippet(block);
block.builders.init.add_block(deindent`
@_document.body.addEventListener("${handler.name}", ${snippet});
`);
block.chunks.init.push(b`
@_document.body.addEventListener("${handler.node.name}", ${snippet});
`);
block.builders.destroy.add_block(deindent`
@_document.body.removeEventListener("${handler.name}", ${snippet});
`);
});
block.chunks.destroy.push(b`
@_document.body.removeEventListener("${handler.node.name}", ${snippet});
`);
});
}
}

@ -3,7 +3,9 @@ import Wrapper from './shared/Wrapper';
import Block from '../Block';
import DebugTag from '../../nodes/DebugTag';
import add_to_set from '../../utils/add_to_set';
import deindent from '../../utils/deindent';
import { b, p } from 'code-red';
import { Identifier, DebuggerStatement } from 'estree';
import { changed } from './shared/changed';
export default class DebugTagWrapper extends Wrapper {
node: DebugTag;
@ -19,61 +21,67 @@ export default class DebugTagWrapper extends Wrapper {
super(renderer, block, parent, node);
}
render(block: Block, _parent_node: string, _parent_nodes: string) {
render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) {
const { renderer } = this;
const { component } = renderer;
if (!renderer.options.dev) return;
const { code, var_lookup } = component;
const { var_lookup } = component;
const start = component.locate(this.node.start + 1);
const end = { line: start.line, column: start.column + 6 };
const loc = { start, end };
const debug: DebuggerStatement = {
type: 'DebuggerStatement',
loc
};
if (this.node.expressions.length === 0) {
// Debug all
code.overwrite(this.node.start + 1, this.node.start + 7, 'debugger', {
storeName: true
});
const statement = `[✂${this.node.start + 1}-${this.node.start + 7}✂];`;
block.builders.create.add_line(statement);
block.builders.update.add_line(statement);
block.chunks.create.push(debug);
block.chunks.update.push(debug);
} else {
const { code } = component;
code.overwrite(this.node.start + 1, this.node.start + 7, 'log', {
storeName: true
});
const log = `[✂${this.node.start + 1}-${this.node.start + 7}✂]`;
const log: Identifier = {
type: 'Identifier',
name: 'log',
loc
};
const dependencies = new Set();
const dependencies: Set<string> = new Set();
this.node.expressions.forEach(expression => {
add_to_set(dependencies, expression.dependencies);
});
const condition = Array.from(dependencies).map(d => `changed.${d}`).join(' || ');
const ctx_identifiers = this.node.expressions
const contextual_identifiers = this.node.expressions
.filter(e => {
const looked_up_var = var_lookup.get(e.node.name);
return !(looked_up_var && looked_up_var.hoistable);
const variable = var_lookup.get(e.node.name);
return !(variable && variable.hoistable);
})
.map(e => e.node.name)
.join(', ');
const logged_identifiers = this.node.expressions.map(e => e.node.name).join(', ');
block.builders.update.add_block(deindent`
if (${condition}) {
const { ${ctx_identifiers} } = ctx;
@_console.${log}({ ${logged_identifiers} });
debugger;
}
`);
block.builders.create.add_block(deindent`
{
const { ${ctx_identifiers} } = ctx;
@_console.${log}({ ${logged_identifiers} });
debugger;
}
`);
.map(e => p`${e.node.name}`);
const logged_identifiers = this.node.expressions.map(e => p`${e.node.name}`);
const debug_statements = b`
const { ${contextual_identifiers} } = #ctx;
@_console.${log}({ ${logged_identifiers} });
debugger;`;
if (dependencies.size) {
const condition = changed(Array.from(dependencies));
block.chunks.update.push(b`
if (${condition}) {
${debug_statements}
}
`);
}
block.chunks.create.push(b`{
${debug_statements}
}`);
}
}
}

@ -4,9 +4,10 @@ import Wrapper from './shared/Wrapper';
import create_debugging_comment from './shared/create_debugging_comment';
import EachBlock from '../../nodes/EachBlock';
import FragmentWrapper from './Fragment';
import deindent from '../../utils/deindent';
import { b, x } from 'code-red';
import ElseBlock from '../../nodes/ElseBlock';
import { attach_head } from '../../utils/tail';
import { Identifier, Node } from 'estree';
import { changed } from './shared/changed';
export class ElseBlockWrapper extends Wrapper {
node: ElseBlock;
@ -51,20 +52,19 @@ export default class EachBlockWrapper extends Wrapper {
fragment: FragmentWrapper;
else?: ElseBlockWrapper;
vars: {
create_each_block: string;
each_block_value: string;
get_each_context: string;
iterations: string;
create_each_block: Identifier;
each_block_value: Identifier;
get_each_context: Identifier;
iterations: Identifier;
fixed_length: number;
data_length: string;
view_length: string;
length: string;
}
context_props: string[];
index_name: string;
context_props: Array<Node | Node[]>;
index_name: Identifier;
var = 'each';
var: Identifier = { type: 'Identifier', name: 'each' };
constructor(
renderer: Renderer,
@ -76,6 +76,7 @@ export default class EachBlockWrapper extends Wrapper {
) {
super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
const { dependencies } = node.expression;
block.add_dependencies(dependencies);
@ -93,7 +94,9 @@ export default class EachBlockWrapper extends Wrapper {
// TODO this seems messy
this.block.has_animation = this.node.has_animation;
this.index_name = this.node.index || renderer.component.get_unique_name(`${this.node.context}_index`);
this.index_name = this.node.index
? { type: 'Identifier', name: this.node.index }
: renderer.component.get_unique_name(`${this.node.context}_index`);
const fixed_length =
node.expression.node.type === 'ArrayExpression' &&
@ -105,26 +108,31 @@ export default class EachBlockWrapper extends Wrapper {
// is easy to find
let c = this.node.start + 2;
while (renderer.component.source[c] !== 'e') c += 1;
renderer.component.code.overwrite(c, c + 4, 'length');
const start = renderer.component.locate(c);
const end = { line: start.line, column: start.column + 4 };
const length = {
type: 'Identifier',
name: 'length',
loc: { start, end }
};
const each_block_value = renderer.component.get_unique_name(`${this.var}_value`);
const iterations = block.get_unique_name(`${this.var}_blocks`);
const each_block_value = renderer.component.get_unique_name(`${this.var.name}_value`);
const iterations = block.get_unique_name(`${this.var.name}_blocks`);
this.vars = {
create_each_block: this.block.name,
each_block_value,
get_each_context: renderer.component.get_unique_name(`get_${this.var}_context`),
get_each_context: renderer.component.get_unique_name(`get_${this.var.name}_context`),
iterations,
length: `[✂${c}-${c+4}✂]`,
// optimisation for array literal
fixed_length,
data_length: fixed_length === null ? `${each_block_value}.[✂${c}-${c+4}✂]` : fixed_length,
view_length: fixed_length === null ? `${iterations}.[✂${c}-${c+4}✂]` : fixed_length
data_length: fixed_length === null ? x`${each_block_value}.${length}` : fixed_length,
view_length: fixed_length === null ? x`${iterations}.length` : fixed_length
};
const store =
node.expression.node.type === 'Identifier' &&
const store =
node.expression.node.type === 'Identifier' &&
node.expression.node.name[0] === '$'
? node.expression.node.name.slice(1)
: null;
@ -133,9 +141,10 @@ export default class EachBlockWrapper extends Wrapper {
this.block.bindings.set(prop.key.name, {
object: this.vars.each_block_value,
property: this.index_name,
snippet: attach_head(`${this.vars.each_block_value}[${this.index_name}]`, prop.tail),
modifier: prop.modifier,
snippet: prop.modifier(x`${this.vars.each_block_value}[${this.index_name}]` as Node),
store,
tail: attach_head(`[${this.index_name}]`, prop.tail)
tail: prop.modifier(x`[${this.index_name}]` as Node)
});
});
@ -171,7 +180,7 @@ export default class EachBlockWrapper extends Wrapper {
}
}
render(block: Block, parent_node: string, parent_nodes: string) {
render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
if (this.fragment.nodes.length === 0) return;
const { renderer } = this;
@ -181,29 +190,29 @@ 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 => `child_ctx.${prop.key.name} = ${attach_head('list[i]', prop.tail)};`);
this.context_props = this.node.contexts.map(prop => b`child_ctx.${prop.key.name} = ${prop.modifier(x`list[i]`)};`);
if (this.node.has_binding) this.context_props.push(`child_ctx.${this.vars.each_block_value} = list;`);
if (this.node.has_binding || this.node.index) this.context_props.push(`child_ctx.${this.index_name} = 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;`);
const snippet = this.node.expression.render(block);
const snippet = this.node.expression.manipulate(block);
block.builders.init.add_line(`let ${this.vars.each_block_value} = ${snippet};`);
block.chunks.init.push(b`let ${this.vars.each_block_value} = ${snippet};`);
renderer.blocks.push(deindent`
function ${this.vars.get_each_context}(ctx, list, i) {
const child_ctx = @_Object.create(ctx);
renderer.blocks.push(b`
function ${this.vars.get_each_context}(#ctx, list, i) {
const child_ctx = @_Object.create(#ctx);
${this.context_props}
return child_ctx;
}
`);
const initial_anchor_node = parent_node ? 'null' : 'anchor';
const initial_mount_node = parent_node || '#target';
const initial_anchor_node: Identifier = { type: 'Identifier', name: parent_node ? 'null' : 'anchor' };
const initial_mount_node: Identifier = parent_node || { type: 'Identifier', name: '#target' };
const update_anchor_node = needs_anchor
? block.get_unique_name(`${this.var}_anchor`)
: (this.next && this.next.var) || 'null';
const update_mount_node = this.get_update_mount_node(update_anchor_node);
? block.get_unique_name(`${this.var.name}_anchor`)
: (this.next && this.next.var) || { type: 'Identifier', name: 'null' };
const update_mount_node: Identifier = this.get_update_mount_node((update_anchor_node as Identifier));
const args = {
block,
@ -223,7 +232,7 @@ export default class EachBlockWrapper extends Wrapper {
}
if (this.block.has_intro_method || this.block.has_outro_method) {
block.builders.intro.add_block(deindent`
block.chunks.intro.push(b`
for (let #i = 0; #i < ${this.vars.data_length}; #i += 1) {
@transition_in(${this.vars.iterations}[#i]);
}
@ -232,38 +241,38 @@ export default class EachBlockWrapper extends Wrapper {
if (needs_anchor) {
block.add_element(
update_anchor_node,
`@empty()`,
parent_nodes && `@empty()`,
update_anchor_node as Identifier,
x`@empty()`,
parent_nodes && x`@empty()`,
parent_node
);
}
if (this.else) {
const each_block_else = component.get_unique_name(`${this.var}_else`);
const each_block_else = component.get_unique_name(`${this.var.name}_else`);
block.builders.init.add_line(`let ${each_block_else} = null;`);
block.chunks.init.push(b`let ${each_block_else} = null;`);
// TODO neaten this up... will end up with an empty line in the block
block.builders.init.add_block(deindent`
block.chunks.init.push(b`
if (!${this.vars.data_length}) {
${each_block_else} = ${this.else.block.name}(ctx);
${each_block_else} = ${this.else.block.name}(#ctx);
${each_block_else}.c();
}
`);
block.builders.mount.add_block(deindent`
block.chunks.mount.push(b`
if (${each_block_else}) {
${each_block_else}.m(${initial_mount_node}, ${initial_anchor_node});
}
`);
if (this.else.block.has_update_method) {
block.builders.update.add_block(deindent`
block.chunks.update.push(b`
if (!${this.vars.data_length} && ${each_block_else}) {
${each_block_else}.p(changed, ctx);
${each_block_else}.p(#changed, #ctx);
} else if (!${this.vars.data_length}) {
${each_block_else} = ${this.else.block.name}(ctx);
${each_block_else} = ${this.else.block.name}(#ctx);
${each_block_else}.c();
${each_block_else}.m(${update_mount_node}, ${update_anchor_node});
} else if (${each_block_else}) {
@ -272,29 +281,29 @@ export default class EachBlockWrapper extends Wrapper {
}
`);
} else {
block.builders.update.add_block(deindent`
block.chunks.update.push(b`
if (${this.vars.data_length}) {
if (${each_block_else}) {
${each_block_else}.d(1);
${each_block_else} = null;
}
} else if (!${each_block_else}) {
${each_block_else} = ${this.else.block.name}(ctx);
${each_block_else} = ${this.else.block.name}(#ctx);
${each_block_else}.c();
${each_block_else}.m(${update_mount_node}, ${update_anchor_node});
}
`);
}
block.builders.destroy.add_block(deindent`
block.chunks.destroy.push(b`
if (${each_block_else}) ${each_block_else}.d(${parent_node ? '' : 'detaching'});
`);
}
this.fragment.render(this.block, null, 'nodes');
this.fragment.render(this.block, null, x`#nodes` as Identifier);
if (this.else) {
this.else.fragment.render(this.else.block, null, 'nodes');
this.else.fragment.render(this.else.block, null, x`#nodes` as Identifier);
}
}
@ -309,26 +318,26 @@ export default class EachBlockWrapper extends Wrapper {
update_mount_node
}: {
block: Block;
parent_node: string;
parent_nodes: string;
snippet: string;
initial_anchor_node: string;
initial_mount_node: string;
update_anchor_node: string;
update_mount_node: string;
parent_node: Identifier;
parent_nodes: Identifier;
snippet: Node;
initial_anchor_node: Identifier;
initial_mount_node: Identifier;
update_anchor_node: Identifier;
update_mount_node: Identifier;
}) {
const {
create_each_block,
length,
iterations,
data_length,
view_length
} = this.vars;
const get_key = block.get_unique_name('get_key');
const lookup = block.get_unique_name(`${this.var}_lookup`);
const lookup = block.get_unique_name(`${this.var.name}_lookup`);
block.add_variable(iterations, '[]');
block.add_variable(lookup, `new @_Map()`);
block.add_variable(iterations, x`[]`);
block.add_variable(lookup, x`new @_Map()`);
if (this.fragment.nodes[0].is_dom_node()) {
this.block.first = this.fragment.nodes[0].var;
@ -336,39 +345,37 @@ export default class EachBlockWrapper extends Wrapper {
this.block.first = this.block.get_unique_name('first');
this.block.add_element(
this.block.first,
`@empty()`,
parent_nodes && `@empty()`,
x`@empty()`,
parent_nodes && x`@empty()`,
null
);
}
block.builders.init.add_block(deindent`
const ${get_key} = ctx => ${
// @ts-ignore todo: probably error
this.node.key.render()};
block.chunks.init.push(b`
const ${get_key} = #ctx => ${this.node.key.manipulate(block)};
for (let #i = 0; #i < ${this.vars.each_block_value}.${length}; #i += 1) {
let child_ctx = ${this.vars.get_each_context}(ctx, ${this.vars.each_block_value}, #i);
for (let #i = 0; #i < ${data_length}; #i += 1) {
let child_ctx = ${this.vars.get_each_context}(#ctx, ${this.vars.each_block_value}, #i);
let key = ${get_key}(child_ctx);
${lookup}.set(key, ${iterations}[#i] = ${create_each_block}(key, child_ctx));
}
`);
block.builders.create.add_block(deindent`
block.chunks.create.push(b`
for (let #i = 0; #i < ${view_length}; #i += 1) {
${iterations}[#i].c();
}
`);
if (parent_nodes && this.renderer.options.hydratable) {
block.builders.claim.add_block(deindent`
block.chunks.claim.push(b`
for (let #i = 0; #i < ${view_length}; #i += 1) {
${iterations}[#i].l(${parent_nodes});
}
`);
}
block.builders.mount.add_block(deindent`
block.chunks.mount.push(b`
for (let #i = 0; #i < ${view_length}; #i += 1) {
${iterations}[#i].m(${initial_mount_node}, ${initial_anchor_node});
}
@ -384,27 +391,27 @@ export default class EachBlockWrapper extends Wrapper {
? `@outro_and_destroy_block`
: `@destroy_block`;
block.builders.update.add_block(deindent`
block.chunks.update.push(b`
const ${this.vars.each_block_value} = ${snippet};
${this.block.has_outros && `@group_outros();`}
${this.node.has_animation && `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});
${this.node.has_animation && `for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].a();`}
${this.block.has_outros && `@check_outros();`}
${this.block.has_outros && b`@group_outros();`}
${this.node.has_animation && b`for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].r();`}
${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});
${this.node.has_animation && b`for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].a();`}
${this.block.has_outros && b`@check_outros();`}
`);
if (this.block.has_outros) {
block.builders.outro.add_block(deindent`
block.chunks.outro.push(b`
for (let #i = 0; #i < ${view_length}; #i += 1) {
@transition_out(${iterations}[#i]);
}
`);
}
block.builders.destroy.add_block(deindent`
block.chunks.destroy.push(b`
for (let #i = 0; #i < ${view_length}; #i += 1) {
${iterations}[#i].d(${parent_node ? '' : 'detaching'});
${iterations}[#i].d(${parent_node ? null : 'detaching'});
}
`);
}
@ -419,45 +426,44 @@ export default class EachBlockWrapper extends Wrapper {
update_mount_node
}: {
block: Block;
parent_nodes: string;
snippet: string;
initial_anchor_node: string;
initial_mount_node: string;
update_anchor_node: string;
update_mount_node: string;
parent_nodes: Identifier;
snippet: Node;
initial_anchor_node: Identifier;
initial_mount_node: Identifier;
update_anchor_node: Identifier;
update_mount_node: Identifier;
}) {
const {
create_each_block,
length,
iterations,
fixed_length,
data_length,
view_length
} = this.vars;
block.builders.init.add_block(deindent`
block.chunks.init.push(b`
let ${iterations} = [];
for (let #i = 0; #i < ${data_length}; #i += 1) {
${iterations}[#i] = ${create_each_block}(${this.vars.get_each_context}(ctx, ${this.vars.each_block_value}, #i));
${iterations}[#i] = ${create_each_block}(${this.vars.get_each_context}(#ctx, ${this.vars.each_block_value}, #i));
}
`);
block.builders.create.add_block(deindent`
block.chunks.create.push(b`
for (let #i = 0; #i < ${view_length}; #i += 1) {
${iterations}[#i].c();
}
`);
if (parent_nodes && this.renderer.options.hydratable) {
block.builders.claim.add_block(deindent`
block.chunks.claim.push(b`
for (let #i = 0; #i < ${view_length}; #i += 1) {
${iterations}[#i].l(${parent_nodes});
}
`);
}
block.builders.mount.add_block(deindent`
block.chunks.mount.push(b`
for (let #i = 0; #i < ${view_length}; #i += 1) {
${iterations}[#i].m(${initial_mount_node}, ${initial_anchor_node});
}
@ -469,27 +475,23 @@ export default class EachBlockWrapper extends Wrapper {
all_dependencies.add(dependency);
});
const condition = Array.from(all_dependencies)
.map(dependency => `changed.${dependency}`)
.join(' || ');
const has_transitions = !!(this.block.has_intro_method || this.block.has_outro_method);
if (all_dependencies.size) {
const has_transitions = !!(this.block.has_intro_method || this.block.has_outro_method);
if (condition !== '') {
const for_loop_body = this.block.has_update_method
? deindent`
? b`
if (${iterations}[#i]) {
${iterations}[#i].p(changed, child_ctx);
${has_transitions && `@transition_in(${this.vars.iterations}[#i], 1);`}
${iterations}[#i].p(#changed, child_ctx);
${has_transitions && b`@transition_in(${this.vars.iterations}[#i], 1);`}
} else {
${iterations}[#i] = ${create_each_block}(child_ctx);
${iterations}[#i].c();
${has_transitions && `@transition_in(${this.vars.iterations}[#i], 1);`}
${has_transitions && b`@transition_in(${this.vars.iterations}[#i], 1);`}
${iterations}[#i].m(${update_mount_node}, ${update_anchor_node});
}
`
: has_transitions
? deindent`
? b`
if (${iterations}[#i]) {
@transition_in(${this.vars.iterations}[#i], 1);
} else {
@ -499,7 +501,7 @@ export default class EachBlockWrapper extends Wrapper {
${iterations}[#i].m(${update_mount_node}, ${update_anchor_node});
}
`
: deindent`
: b`
if (!${iterations}[#i]) {
${iterations}[#i] = ${create_each_block}(child_ctx);
${iterations}[#i].c();
@ -507,43 +509,43 @@ export default class EachBlockWrapper extends Wrapper {
}
`;
const start = this.block.has_update_method ? '0' : `#old_length`;
const start = this.block.has_update_method ? 0 : `#old_length`;
let remove_old_blocks;
if (this.block.has_outros) {
const out = block.get_unique_name('out');
block.builders.init.add_block(deindent`
block.chunks.init.push(b`
const ${out} = i => @transition_out(${iterations}[i], 1, 1, () => {
${iterations}[i] = null;
});
`);
remove_old_blocks = deindent`
remove_old_blocks = b`
@group_outros();
for (#i = ${this.vars.each_block_value}.${length}; #i < ${view_length}; #i += 1) {
for (#i = ${data_length}; #i < ${view_length}; #i += 1) {
${out}(#i);
}
@check_outros();
`;
} else {
remove_old_blocks = deindent`
for (${this.block.has_update_method ? `` : `#i = ${this.vars.each_block_value}.${length}`}; #i < ${this.block.has_update_method ? view_length : '#old_length'}; #i += 1) {
remove_old_blocks = b`
for (${this.block.has_update_method ? null : x`#i = ${data_length}`}; #i < ${this.block.has_update_method ? view_length : '#old_length'}; #i += 1) {
${iterations}[#i].d(1);
}
${!fixed_length && `${view_length} = ${this.vars.each_block_value}.${length};`}
${!fixed_length && b`${view_length} = ${data_length};`}
`;
}
// We declare `i` as block scoped here, as the `remove_old_blocks` code
// may rely on continuing where this iteration stopped.
const update = deindent`
${!this.block.has_update_method && `const #old_length = ${this.vars.each_block_value}.length;`}
const update = b`
${!this.block.has_update_method && b`const #old_length = ${this.vars.each_block_value}.length;`}
${this.vars.each_block_value} = ${snippet};
let #i;
for (#i = ${start}; #i < ${this.vars.each_block_value}.${length}; #i += 1) {
const child_ctx = ${this.vars.get_each_context}(ctx, ${this.vars.each_block_value}, #i);
for (#i = ${start}; #i < ${data_length}; #i += 1) {
const child_ctx = ${this.vars.get_each_context}(#ctx, ${this.vars.each_block_value}, #i);
${for_loop_body}
}
@ -551,15 +553,15 @@ export default class EachBlockWrapper extends Wrapper {
${remove_old_blocks}
`;
block.builders.update.add_block(deindent`
if (${condition}) {
block.chunks.update.push(b`
if (${changed(Array.from(all_dependencies))}) {
${update}
}
`);
}
if (this.block.has_outros) {
block.builders.outro.add_block(deindent`
block.chunks.outro.push(b`
${iterations} = ${iterations}.filter(@_Boolean);
for (let #i = 0; #i < ${view_length}; #i += 1) {
@transition_out(${iterations}[#i]);
@ -567,6 +569,6 @@ export default class EachBlockWrapper extends Wrapper {
`);
}
block.builders.destroy.add_block(`@destroy_each(${iterations}, detaching);`);
block.chunks.destroy.push(b`@destroy_each(${iterations}, detaching);`);
}
}

@ -2,10 +2,11 @@ import Attribute from '../../../nodes/Attribute';
import Block from '../../Block';
import fix_attribute_casing from './fix_attribute_casing';
import ElementWrapper from './index';
import { stringify } from '../../../utils/stringify';
import deindent from '../../../utils/deindent';
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';
export default class AttributeWrapper {
node: Attribute;
@ -17,6 +18,7 @@ export default class AttributeWrapper {
if (node.dependencies.size > 0) {
parent.cannot_use_innerhtml();
parent.not_static_content();
block.add_dependencies(node.dependencies);
@ -42,9 +44,7 @@ export default class AttributeWrapper {
const element = this.parent;
const name = fix_attribute_casing(this.node.name);
let metadata = element.node.namespace ? null : attribute_lookup[name];
if (metadata && metadata.applies_to && !~metadata.applies_to.indexOf(element.node.name))
metadata = null;
const metadata = this.get_metadata();
const is_indirectly_bound_value =
name === 'value' &&
@ -71,125 +71,141 @@ 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).render(block);
} else {
// '{foo} {bar}' — treat as string concatenation
const prefix = this.node.chunks[0].type === 'Text' ? '' : `"" + `;
const text = this.node.name === 'class'
? this.get_class_name_text()
: this.render_chunks().join(' + ');
value = `${prefix}${text}`;
}
const is_select_value_attribute =
name === 'value' && element.node.name === 'select';
const should_cache = (this.node.should_cache() || is_select_value_attribute);
const value = this.get_value(block);
const last = should_cache && block.get_unique_name(
`${element.var}_${name.replace(/[^a-zA-Z_$]/g, '_')}_value`
);
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';
if (should_cache) block.add_variable(last);
let updater;
const init = should_cache ? `${last} = ${value}` : value;
if (is_legacy_input_type) {
block.builders.hydrate.add_line(
`@set_input_type(${element.var}, ${init});`
);
updater = `@set_input_type(${element.var}, ${should_cache ? last : value});`;
} else if (is_select_value_attribute) {
// annoying special case
const is_multiple_select = element.node.get_static_attribute_value('multiple');
const i = block.get_unique_name('i');
const option = block.get_unique_name('option');
const if_statement = is_multiple_select
? deindent`
${option}.selected = ~${last}.indexOf(${option}.__value);`
: deindent`
if (${option}.__value === ${last}) {
${option}.selected = true;
break;
}`;
updater = deindent`
for (var ${i} = 0; ${i} < ${element.var}.options.length; ${i} += 1) {
var ${option} = ${element.var}.options[${i}];
${if_statement}
}
`;
block.builders.mount.add_block(deindent`
${last} = ${value};
${updater}
`);
} else if (property_name) {
block.builders.hydrate.add_line(
`${element.var}.${property_name} = ${init};`
);
updater = block.renderer.options.dev
? `@prop_dev(${element.var}, "${property_name}", ${should_cache ? last : value});`
: `${element.var}.${property_name} = ${should_cache ? last : value};`;
} else {
block.builders.hydrate.add_line(
`${method}(${element.var}, "${name}", ${init});`
);
updater = `${method}(${element.var}, "${name}", ${should_cache ? last : value});`;
}
const should_cache = is_src || this.node.should_cache() || is_select_value_attribute; // TODO is this necessary?
const changed_check = (
(block.has_outros ? `!#current || ` : '') +
dependencies.map(dependency => `changed.${dependency}`).join(' || ')
);
const last = should_cache && block.get_unique_name(
`${element.var.name}_${name.replace(/[^a-zA-Z_$]/g, '_')}_value`
);
const update_cached_value = `${last} !== (${last} = ${value})`;
if (should_cache) block.add_variable(last);
const condition = should_cache
? (dependencies.length ? `(${changed_check}) && ${update_cached_value}` : update_cached_value)
: changed_check;
let updater;
const init = should_cache ? x`${last} = ${value}` : value;
block.builders.update.add_conditional(
condition,
updater
if (is_legacy_input_type) {
block.chunks.hydrate.push(
b`@set_input_type(${element.var}, ${init});`
);
updater = b`@set_input_type(${element.var}, ${should_cache ? last : value});`;
} else if (is_select_value_attribute) {
// annoying special case
const is_multiple_select = element.node.get_static_attribute_value('multiple');
const i = block.get_unique_name('i');
const option = block.get_unique_name('option');
const if_statement = is_multiple_select
? b`
${option}.selected = ~${last}.indexOf(${option}.__value);`
: b`
if (${option}.__value === ${last}) {
${option}.selected = true;
${{ type: 'BreakStatement' }};
}`; // TODO the BreakStatement is gross, but it's unsyntactic otherwise...
updater = b`
for (var ${i} = 0; ${i} < ${element.var}.options.length; ${i} += 1) {
var ${option} = ${element.var}.options[${i}];
${if_statement}
}
`;
block.chunks.mount.push(b`
${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};`
);
updater = block.renderer.options.dev
? b`@prop_dev(${element.var}, "${property_name}", ${should_cache ? last : value});`
: b`${element.var}.${property_name} = ${should_cache ? last : value};`;
} else {
const value = this.node.get_value(block);
const statement = (
is_legacy_input_type
? `@set_input_type(${element.var}, ${value});`
: property_name
? `${element.var}.${property_name} = ${value};`
: `${method}(${element.var}, "${name}", ${value === true ? '""' : value});`
block.chunks.hydrate.push(
b`${method}(${element.var}, "${name}", ${init});`
);
updater = b`${method}(${element.var}, "${name}", ${should_cache ? last : value});`;
}
block.builders.hydrate.add_line(statement);
if (dependencies.length > 0) {
let condition = changed(dependencies);
if (should_cache) {
condition = is_src
? x`${condition} && (${element.var}.src !== (${last} = ${value}))`
: x`${condition} && (${last} !== (${last} = ${value}))`;
}
// 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 (block.has_outros) {
condition = x`!#current || ${condition}`;
}
block.chunks.update.push(b`
if (${condition}) {
${updater}
}`);
}
// 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 = `${element.var}.value = ${element.var}.__value;`;
const update_value = b`${element.var}.value = ${element.var}.__value;`;
block.chunks.hydrate.push(update_value);
if (this.node.get_dependencies().length > 0) block.chunks.update.push(update_value);
}
}
get_metadata() {
if (this.parent.node.namespace) return null;
const metadata = attribute_lookup[fix_attribute_casing(this.node.name)];
if (metadata && metadata.applies_to && !metadata.applies_to.includes(this.parent.node.name)) return null;
return metadata;
}
block.builders.hydrate.add_line(update_value);
if (this.node.get_dependencies().length > 0) block.builders.update.add_line(update_value);
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()
: 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}`;
}
return value;
}
get_class_name_text() {
@ -198,22 +214,19 @@ export default class AttributeWrapper {
if (scoped_css && rendered.length === 2) {
// we have a situation like class={possiblyUndefined}
rendered[0] = `@null_to_empty(${rendered[0]})`;
rendered[0] = x`@null_to_empty(${rendered[0]})`;
}
return rendered.join(' + ');
return rendered.reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
}
render_chunks() {
return this.node.chunks.map((chunk) => {
if (chunk.type === 'Text') {
return stringify(chunk.data);
return string_literal(chunk.data);
}
const rendered = chunk.render();
return chunk.get_precedence() <= 13
? `(${rendered})`
: rendered;
return chunk.manipulate();
});
}
@ -226,7 +239,7 @@ export default class AttributeWrapper {
return `="${value.map(chunk => {
return chunk.type === 'Text'
? chunk.data.replace(/"/g, '\\"')
: `\${${chunk.render()}}`;
: `\${${chunk.manipulate()}}`;
}).join('')}"`;
}
}
@ -288,3 +301,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'
]);

@ -1,18 +1,13 @@
import { b, x } from 'code-red';
import Binding from '../../../nodes/Binding';
import ElementWrapper from '../Element';
import get_object from '../../../utils/get_object';
import Block from '../../Block';
import Node from '../../../nodes/shared/Node';
import Renderer from '../../Renderer';
import flatten_reference from '../../../utils/flatten_reference';
import EachBlock from '../../../nodes/EachBlock';
import { Node as INode } from '../../../../interfaces';
function get_tail(node: INode) {
const end = node.end;
while (node.type === 'MemberExpression') node = node.object;
return { start: node.end, end };
}
import { changed } from '../shared/changed';
import { Node, Identifier } from 'estree';
export default class BindingWrapper {
node: Binding;
@ -21,11 +16,11 @@ export default class BindingWrapper {
object: string;
handler: {
uses_context: boolean;
mutation: string;
mutation: (Node | Node[]);
contextual_dependencies: Set<string>;
snippet?: string;
snippet?: Node;
};
snippet: string;
snippet: Node;
is_readonly: boolean;
needs_lock: boolean;
@ -56,14 +51,10 @@ export default class BindingWrapper {
this.object = get_object(this.node.expression.node).name;
// TODO unfortunate code is necessary because we need to use `ctx`
// inside the fragment, but not inside the <script>
const contextless_snippet = this.parent.renderer.component.source.slice(this.node.expression.node.start, this.node.expression.node.end);
// view to model
this.handler = get_event_handler(this, parent.renderer, block, this.object, contextless_snippet);
this.handler = get_event_handler(this, parent.renderer, block, this.object, this.node.raw_expression);
this.snippet = this.node.expression.render(block);
this.snippet = this.node.expression.manipulate(block);
this.is_readonly = this.node.is_readonly;
@ -89,33 +80,31 @@ export default class BindingWrapper {
return this.node.is_readonly_media_attribute();
}
render(block: Block, lock: string) {
render(block: Block, lock: Identifier) {
if (this.is_readonly) return;
const { parent } = this;
const update_conditions: string[] = this.needs_lock ? [`!${lock}`] : [];
const update_conditions: any[] = this.needs_lock ? [x`!${lock}`] : [];
const mount_conditions: any[] = [];
const dependency_array = [...this.node.expression.dependencies];
if (dependency_array.length === 1) {
update_conditions.push(`changed.${dependency_array[0]}`);
} else if (dependency_array.length > 1) {
update_conditions.push(
`(${dependency_array.map(prop => `changed.${prop}`).join(' || ')})`
);
if (dependency_array.length > 0) {
update_conditions.push(changed(dependency_array));
}
if (parent.node.name === 'input') {
const type = parent.node.get_static_attribute_value('type');
if (type === null || type === "" || type === "text" || type === "email" || type === "password") {
update_conditions.push(`(${parent.var}.${this.node.name} !== ${this.snippet})`);
update_conditions.push(x`(${parent.var}.${this.node.name} !== ${this.snippet})`);
}
}
// model to view
let update_dom = get_dom_updater(parent, this);
let mount_dom = update_dom;
// special cases
switch (this.node.name) {
@ -123,57 +112,82 @@ export default class BindingWrapper {
{
const binding_group = get_binding_group(parent.renderer, this.node.expression.node);
block.builders.hydrate.add_line(
`ctx.$$binding_groups[${binding_group}].push(${parent.var});`
block.chunks.hydrate.push(
b`#ctx.$$binding_groups[${binding_group}].push(${parent.var});`
);
block.builders.destroy.add_line(
`ctx.$$binding_groups[${binding_group}].splice(ctx.$$binding_groups[${binding_group}].indexOf(${parent.var}), 1);`
block.chunks.destroy.push(
b`#ctx.$$binding_groups[${binding_group}].splice(#ctx.$$binding_groups[${binding_group}].indexOf(${parent.var}), 1);`
);
break;
}
case 'textContent':
update_conditions.push(`${this.snippet} !== ${parent.var}.textContent`);
update_conditions.push(x`${this.snippet} !== ${parent.var}.textContent`);
mount_conditions.push(x`${this.snippet} !== void 0`);
break;
case 'innerHTML':
update_conditions.push(`${this.snippet} !== ${parent.var}.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(`!@_isNaN(${this.snippet})`);
update_conditions.push(x`!@_isNaN(${this.snippet})`);
mount_conditions.push(x`!@_isNaN(${this.snippet})`);
break;
case 'paused':
{
// this is necessary to prevent audio restarting by itself
const last = block.get_unique_name(`${parent.var}_is_paused`);
block.add_variable(last, 'true');
const last = block.get_unique_name(`${parent.var.name}_is_paused`);
block.add_variable(last, x`true`);
update_conditions.push(`${last} !== (${last} = ${this.snippet})`);
update_dom = `${parent.var}[${last} ? "pause" : "play"]();`;
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;
}
}
if (update_dom) {
block.builders.update.add_line(
update_conditions.length ? `if (${update_conditions.join(' && ')}) ${update_dom}` : update_dom
);
if (update_conditions.length > 0) {
const condition = update_conditions.reduce((lhs, rhs) => x`${lhs} && ${rhs}`);
block.chunks.update.push(b`
if (${condition}) {
${update_dom}
}
`);
} else {
block.chunks.update.push(update_dom);
}
}
if (this.node.name === 'innerHTML' || this.node.name === 'textContent') {
block.builders.mount.add_block(`if (${this.snippet} !== void 0) ${update_dom}`);
} else if (!/(currentTime|paused)/.test(this.node.name)) {
block.builders.mount.add_block(update_dom);
if (mount_dom) {
if (mount_conditions.length > 0) {
const condition = mount_conditions.reduce((lhs, rhs) => x`${lhs} && ${rhs}`);
block.chunks.mount.push(b`
if (${condition}) {
${mount_dom}
}
`);
} else {
block.chunks.mount.push(mount_dom);
}
}
}
}
@ -194,25 +208,25 @@ function get_dom_updater(
if (node.name === 'select') {
return node.get_static_attribute_value('multiple') === true ?
`@select_options(${element.var}, ${binding.snippet})` :
`@select_option(${element.var}, ${binding.snippet})`;
b`@select_options(${element.var}, ${binding.snippet})` :
b`@select_option(${element.var}, ${binding.snippet})`;
}
if (binding.node.name === 'group') {
const type = node.get_static_attribute_value('type');
const condition = type === 'checkbox'
? `~${binding.snippet}.indexOf(${element.var}.__value)`
: `${element.var}.__value === ${binding.snippet}`;
? x`~${binding.snippet}.indexOf(${element.var}.__value)`
: x`${element.var}.__value === ${binding.snippet}`;
return `${element.var}.checked = ${condition};`;
return b`${element.var}.checked = ${condition};`;
}
if (binding.node.name === 'value') {
return `@set_input_value(${element.var}, ${binding.snippet});`;
return b`@set_input_value(${element.var}, ${binding.snippet});`;
}
return `${element.var}.${binding.node.name} = ${binding.snippet};`;
return b`${element.var}.${binding.node.name} = ${binding.snippet};`;
}
function get_binding_group(renderer: Renderer, value: Node) {
@ -230,68 +244,54 @@ function get_binding_group(renderer: Renderer, value: Node) {
return index;
}
function mutate_store(store, value, tail) {
return tail
? `${store}.update($$value => ($$value${tail} = ${value}, $$value));`
: `${store}.set(${value});`;
}
function get_event_handler(
binding: BindingWrapper,
renderer: Renderer,
block: Block,
name: string,
snippet: string
lhs: Node
): {
uses_context: boolean;
mutation: string;
contextual_dependencies: Set<string>;
snippet?: string;
} {
uses_context: boolean;
mutation: (Node | Node[]);
contextual_dependencies: Set<string>;
lhs?: Node;
} {
const value = get_value_from_dom(renderer, binding.parent, binding);
let store = binding.object[0] === '$' ? binding.object.slice(1) : null;
const contextual_dependencies = new Set(binding.node.expression.contextual_dependencies);
let tail = '';
if (binding.node.expression.node.type === 'MemberExpression') {
const { start, end } = get_tail(binding.node.expression.node);
tail = renderer.component.source.slice(start, end);
}
const context = block.bindings.get(name);
let set_store;
if (binding.node.is_contextual) {
const binding = block.bindings.get(name);
const { object, property, snippet } = binding;
if (context) {
const { object, property, modifier, store } = context;
if (binding.store) {
store = binding.store;
tail = `${binding.tail}${tail}`;
if (lhs.type === 'Identifier') {
lhs = modifier(x`${object}[${property}]`);
contextual_dependencies.add(object.name);
contextual_dependencies.add(property.name);
}
return {
uses_context: true,
mutation: store
? mutate_store(store, value, tail)
: `${snippet}${tail} = ${value};`,
contextual_dependencies: new Set([object, property])
};
if (store) {
set_store = b`${store}.set(${`$${store}`});`;
}
} else {
const object = get_object(lhs);
if (object.name[0] === '$') {
const store = object.name.slice(1);
set_store = b`${store}.set(${object.name});`;
}
}
const mutation = store
? mutate_store(store, value, tail)
: `${snippet} = ${value};`;
if (binding.node.expression.node.type === 'MemberExpression') {
return {
uses_context: binding.node.expression.uses_context,
mutation,
contextual_dependencies: binding.node.expression.contextual_dependencies,
snippet
};
}
const mutation = b`
${lhs} = ${value};
${set_store}
`;
return {
uses_context: false,
uses_context: binding.node.is_contextual || binding.node.expression.uses_context, // TODO this is messy
mutation,
contextual_dependencies: new Set()
contextual_dependencies
};
}
@ -304,14 +304,14 @@ function get_value_from_dom(
const { name } = binding.node;
if (name === 'this') {
return `$$node`;
return x`$$node`;
}
// <select bind:value='selected>
if (node.name === 'select') {
return node.get_static_attribute_value('multiple') === true ?
`@select_multiple_value(this)` :
`@select_value(this)`;
x`@select_multiple_value(this)` :
x`@select_value(this)`;
}
const type = node.get_static_attribute_value('type');
@ -320,21 +320,21 @@ function get_value_from_dom(
if (name === 'group') {
const binding_group = get_binding_group(renderer, binding.node.expression.node);
if (type === 'checkbox') {
return `@get_binding_group_value($$binding_groups[${binding_group}])`;
return x`@get_binding_group_value($$binding_groups[${binding_group}])`;
}
return `this.__value`;
return x`this.__value`;
}
// <input type='range|number' bind:value>
if (type === 'range' || type === 'number') {
return `@to_number(this.${name})`;
return x`@to_number(this.${name})`;
}
if ((name === 'buffered' || name === 'seekable' || name === 'played')) {
return `@time_ranges_to_array(this.${name})`;
return x`@time_ranges_to_array(this.${name})`;
}
// everything else
return `this.${name}`;
return x`this.${name}`;
}

@ -0,0 +1,69 @@
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.component.add_var({
name: node.handler_name.name,
internal: true,
referenced: true,
});
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) : x`#ctx.${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('stopPropagation') ? TRUE : FALSE);
args.push(this.node.modifiers.has('preventDefault') ? TRUE : FALSE);
}
block.event_listeners.push(
x`@listen(${target}, "${this.node.name}", ${snippet}, ${args})`
);
}
}

@ -1,11 +1,13 @@
import { b, x } from 'code-red';
import Attribute from '../../../nodes/Attribute';
import Block from '../../Block';
import AttributeWrapper from './Attribute';
import ElementWrapper from '../Element';
import { stringify } from '../../../utils/stringify';
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;
@ -25,41 +27,44 @@ export default class StyleAttributeWrapper extends AttributeWrapper {
let value;
if (is_dynamic(prop.value)) {
const prop_dependencies = new Set();
value =
((prop.value.length === 1 || prop.value[0].type === 'Text') ? '' : `"" + `) +
prop.value
.map((chunk) => {
if (chunk.type === 'Text') {
return stringify(chunk.data);
} else {
const snippet = chunk.render();
add_to_set(prop_dependencies, chunk.dynamic_dependencies());
return chunk.get_precedence() <= 13 ? `(${snippet})` : snippet;
}
})
.join(' + ');
const prop_dependencies: Set<string> = new Set();
value = prop.value
.map(chunk => {
if (chunk.type === 'Text') {
return string_literal(chunk.data);
} else {
add_to_set(prop_dependencies, chunk.dynamic_dependencies());
return chunk.manipulate(block);
}
})
.reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
// TODO is this necessary? style.setProperty always treats value as string, no?
// if (prop.value.length === 1 || prop.value[0].type !== 'Text') {
// value = x`"" + ${value}`;
// }
if (prop_dependencies.size) {
const dependencies = Array.from(prop_dependencies);
const condition = (
(block.has_outros ? `!#current || ` : '') +
dependencies.map(dependency => `changed.${dependency}`).join(' || ')
);
block.builders.update.add_conditional(
condition,
`@set_style(${this.parent.var}, "${prop.key}", ${value}${prop.important ? ', 1' : ''});`
);
let condition = changed(Array.from(prop_dependencies));
if (block.has_outros) {
condition = x`!#current || ${condition}`;
}
const update = b`
if (${condition}) {
@set_style(${this.parent.var}, "${prop.key}", ${value}, ${prop.important ? 1 : null});
}`;
block.chunks.update.push(update);
}
} else {
value = stringify((prop.value[0] as Text).data);
value = string_literal((prop.value[0] as Text).data);
}
block.builders.hydrate.add_line(
`@set_style(${this.parent.var}, "${prop.key}", ${value}${prop.important ? ', 1' : ''});`
block.chunks.hydrate.push(
b`@set_style(${this.parent.var}, "${prop.key}", ${value}, ${prop.important ? 1 : null});`
);
});
}

@ -2,12 +2,13 @@ import Renderer from '../../Renderer';
import Element from '../../../nodes/Element';
import Wrapper from '../shared/Wrapper';
import Block from '../../Block';
import { is_void, quote_prop_if_necessary, quote_name_if_necessary, sanitize } from '../../../../utils/names';
import { is_void, sanitize } from '../../../../utils/names';
import FragmentWrapper from '../Fragment';
import { stringify, escape_html, escape } from '../../../utils/stringify';
import { escape_html, string_literal } from '../../../utils/stringify';
import TextWrapper from '../Text';
import TagWrapper from '../shared/Tag';
import fix_attribute_casing from './fix_attribute_casing';
import deindent from '../../../utils/deindent';
import { b, x, p } from 'code-red';
import { namespaces } from '../../../../utils/namespaces';
import AttributeWrapper from './Attribute';
import StyleAttributeWrapper from './StyleAttribute';
@ -20,6 +21,10 @@ 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 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';
const events = [
{
@ -57,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'],
@ -95,6 +100,18 @@ 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'
},
// details event
{
@ -109,12 +126,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: string;
var: any;
void: boolean;
constructor(
renderer: Renderer,
@ -125,7 +144,12 @@ export default class ElementWrapper extends Wrapper {
next_sibling: Wrapper
) {
super(renderer, block, parent, node);
this.var = node.name.replace(/[^a-zA-Z0-9_$]/g, '_');
this.var = {
type: 'Identifier',
name: node.name.replace(/[^a-zA-Z0-9_$]/g, '_')
};
this.void = is_void(node.name);
this.class_dependencies = [];
@ -148,7 +172,7 @@ export default class ElementWrapper extends Wrapper {
if (owner && owner.node.type === 'InlineComponent') {
const name = attribute.get_static_value() as string;
if (!(owner as InlineComponentWrapper).slots.has(name)) {
if (!(owner as unknown as InlineComponentWrapper).slots.has(name)) {
const child_block = block.child({
comment: create_debugging_comment(node, this.renderer.component),
name: this.renderer.component.get_unique_name(`create_${sanitize(name)}_slot`),
@ -156,15 +180,15 @@ export default class ElementWrapper extends Wrapper {
});
const lets = this.node.lets;
const seen = new Set(lets.map(l => l.name));
const seen = new Set(lets.map(l => l.name.name));
(owner as InlineComponentWrapper).node.lets.forEach(l => {
if (!seen.has(l.name)) lets.push(l);
(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 InlineComponentWrapper).slots.set(name, {
(owner as unknown as InlineComponentWrapper).slots.set(name, {
block: child_block,
scope: this.node.scope,
fn
@ -172,7 +196,7 @@ export default class ElementWrapper extends Wrapper {
this.renderer.blocks.push(child_block);
}
this.slot_block = (owner as InlineComponentWrapper).slots.get(name).block;
this.slot_block = (owner as unknown as InlineComponentWrapper).slots.get(name).block;
block = this.slot_block;
}
}
@ -187,6 +211,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);
@ -210,17 +236,17 @@ export default class ElementWrapper extends Wrapper {
});
if (this.parent) {
if (node.actions.length > 0) this.parent.cannot_use_innerhtml();
if (node.animation) this.parent.cannot_use_innerhtml();
if (node.bindings.length > 0) this.parent.cannot_use_innerhtml();
if (node.classes.length > 0) this.parent.cannot_use_innerhtml();
if (node.intro || node.outro) this.parent.cannot_use_innerhtml();
if (node.handlers.length > 0) this.parent.cannot_use_innerhtml();
if (this.node.name === 'option') this.parent.cannot_use_innerhtml();
if (renderer.options.dev) {
if (node.actions.length > 0 ||
node.animation ||
node.bindings.length > 0 ||
node.classes.length > 0 ||
node.intro || node.outro ||
node.handlers.length > 0 ||
this.node.name === 'option' ||
renderer.options.dev
) {
this.parent.cannot_use_innerhtml(); // need to use add_location
this.parent.not_static_content();
}
}
@ -236,7 +262,7 @@ export default class ElementWrapper extends Wrapper {
}
}
render(block: Block, parent_node: string, parent_nodes: string) {
render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
const { renderer } = this;
if (this.node.name === 'noscript') return;
@ -246,66 +272,84 @@ export default class ElementWrapper extends Wrapper {
}
const node = this.var;
const nodes = parent_nodes && block.get_unique_name(`${this.var}_nodes`); // if we're in unclaimable territory, i.e. <head>, parent_nodes is null
const nodes = parent_nodes && block.get_unique_name(`${this.var.name}_nodes`); // if we're in unclaimable territory, i.e. <head>, parent_nodes is null
const children = x`@children(${this.node.name === 'template' ? x`${node}.content` : node})`;
block.add_variable(node);
const render_statement = this.get_render_statement();
block.builders.create.add_line(
`${node} = ${render_statement};`
block.chunks.create.push(
b`${node} = ${render_statement};`
);
if (renderer.options.hydratable) {
if (parent_nodes) {
block.builders.claim.add_block(deindent`
block.chunks.claim.push(b`
${node} = ${this.get_claim_statement(parent_nodes)};
var ${nodes} = @children(${this.node.name === 'template' ? `${node}.content` : node});
`);
if (!this.void && this.node.children.length > 0) {
block.chunks.claim.push(b`
var ${nodes} = ${children};
`);
}
} else {
block.builders.claim.add_line(
`${node} = ${render_statement};`
block.chunks.claim.push(
b`${node} = ${render_statement};`
);
}
}
if (parent_node) {
block.builders.mount.add_line(
`@append(${parent_node}, ${node});`
block.chunks.mount.push(
b`@append(${parent_node}, ${node});`
);
if (parent_node === '@_document.head') {
block.builders.destroy.add_line(`@detach(${node});`);
if (is_head(parent_node)) {
block.chunks.destroy.push(b`@detach(${node});`);
}
} else {
block.builders.mount.add_line(`@insert(#target, ${node}, anchor);`);
block.chunks.mount.push(b`@insert(#target, ${node}, anchor);`);
// TODO we eventually need to consider what happens to elements
// that belong to the same outgroup as an outroing element...
block.builders.destroy.add_conditional('detaching', `@detach(${node});`);
block.chunks.destroy.push(b`if (detaching) @detach(${node});`);
}
// insert static children with textContent or innerHTML
if (!this.node.namespace && this.can_use_innerhtml && 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.builders.create.add_line(
block.chunks.create.push(
// @ts-ignore todo: should it be this.fragment.nodes[0].node.data instead?
`${node}.textContent = ${stringify(this.fragment.nodes[0].data)};`
b`${node}.textContent = ${string_literal(this.fragment.nodes[0].data)};`
);
} else {
const inner_html = escape(
this.fragment.nodes
.map(to_html)
.join('')
);
const state = {
quasi: {
type: 'TemplateElement',
value: { raw: '' }
}
};
block.builders.create.add_line(
`${node}.innerHTML = \`${inner_html}\`;`
const literal = {
type: 'TemplateLiteral',
expressions: [],
quasis: []
};
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(
b`${node}.${this.can_use_innerhtml ? 'innerHTML': 'textContent'} = ${literal};`
);
}
} else {
this.fragment.nodes.forEach((child: Wrapper) => {
child.render(
block,
this.node.name === 'template' ? `${node}.content` : node,
this.node.name === 'template' ? x`${node}.content` : node,
nodes
);
});
@ -321,90 +365,64 @@ 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) {
block.builders.claim.add_line(
`${nodes}.forEach(@detach);`
if (nodes && this.renderer.options.hydratable && !this.void) {
block.chunks.claim.push(
b`${this.node.children.length > 0 ? nodes : children}.forEach(@detach);`
);
}
function to_html(wrapper: ElementWrapper | TextWrapper) {
if (wrapper.node.type === 'Text') {
if ((wrapper as TextWrapper).use_space()) return ' ';
const parent = wrapper.node.parent as Element;
const raw = parent && (
parent.name === 'script' ||
parent.name === 'style'
);
return (raw ? wrapper.node.data : escape_html(wrapper.node.data))
.replace(/\\/g, '\\\\')
.replace(/`/g, '\\`')
.replace(/\$/g, '\\$');
}
if (wrapper.node.name === 'noscript') return '';
let open = `<${wrapper.node.name}`;
(wrapper as ElementWrapper).attributes.forEach((attr: AttributeWrapper) => {
open += ` ${fix_attribute_casing(attr.node.name)}${attr.stringify()}`;
});
if (is_void(wrapper.node.name)) return open + '>';
return `${open}>${(wrapper as ElementWrapper).fragment.nodes.map(to_html).join('')}</${wrapper.node.name}>`;
}
if (renderer.options.dev) {
const loc = renderer.locate(this.node.start);
block.builders.hydrate.add_line(
`@add_location(${this.var}, ${renderer.file_var}, ${loc.line}, ${loc.column}, ${this.node.start});`
block.chunks.hydrate.push(
b`@add_location(${this.var}, ${renderer.file_var}, ${loc.line - 1}, ${loc.column}, ${this.node.start});`
);
}
}
can_use_textcontent() {
return this.is_static_content && this.fragment.nodes.every(node => node.node.type === 'Text' || node.node.type === 'MustacheTag');
}
get_render_statement() {
const { name, namespace } = this.node;
if (namespace === 'http://www.w3.org/2000/svg') {
return `@svg_element("${name}")`;
return x`@svg_element("${name}")`;
}
if (namespace) {
return `@_document.createElementNS("${namespace}", "${name}")`;
return x`@_document.createElementNS("${namespace}", "${name}")`;
}
const is = this.attributes.find(attr => attr.node.name === 'is');
if (is) {
return `@element_is("${name}", ${is.render_chunks().join(' + ')});`;
return x`@element_is("${name}", ${is.render_chunks().reduce((lhs, rhs) => x`${lhs} + ${rhs}`)});`;
}
return `@element("${name}")`;
return x`@element("${name}")`;
}
get_claim_statement(nodes: string) {
get_claim_statement(nodes: Identifier) {
const attributes = this.node.attributes
.filter((attr) => attr.type === 'Attribute')
.map((attr) => `${quote_name_if_necessary(attr.name)}: true`)
.join(', ');
.map((attr) => p`${attr.name}: true`);
const name = this.node.namespace
? this.node.name
: this.node.name.toUpperCase();
return `@claim_element(${nodes}, "${name}", ${attributes
? `{ ${attributes} }`
: `{}`}, ${this.node.namespace === namespaces.svg ? true : false})`;
const svg = this.node.namespace === namespaces.svg ? 1 : null;
return x`@claim_element(${nodes}, "${name}", { ${attributes} }, ${svg})`;
}
add_bindings(block: Block) {
@ -415,10 +433,10 @@ export default class ElementWrapper extends Wrapper {
renderer.component.has_reactive_assignments = true;
const lock = this.bindings.some(binding => binding.needs_lock) ?
block.get_unique_name(`${this.var}_updating`) :
block.get_unique_name(`${this.var.name}_updating`) :
null;
if (lock) block.add_variable(lock, 'false');
if (lock) block.add_variable(lock, x`false`);
const groups = events
.map(event => ({
@ -430,10 +448,10 @@ export default class ElementWrapper extends Wrapper {
.filter(group => group.bindings.length);
groups.forEach(group => {
const handler = renderer.component.get_unique_name(`${this.var}_${group.events.join('_')}_handler`);
const handler = renderer.component.get_unique_name(`${this.var.name}_${group.events.join('_')}_handler`);
renderer.component.add_var({
name: handler,
name: handler.name,
internal: true,
referenced: true
});
@ -458,7 +476,7 @@ export default class ElementWrapper extends Wrapper {
// own hands
let animation_frame;
if (group.events[0] === 'timeupdate') {
animation_frame = block.get_unique_name(`${this.var}_animationframe`);
animation_frame = block.get_unique_name(`${this.var.name}_animationframe`);
block.add_variable(animation_frame);
}
@ -470,60 +488,76 @@ export default class ElementWrapper extends Wrapper {
if (has_local_function) {
// need to create a block-local function that calls an instance-level function
if (animation_frame) {
block.builders.init.add_block(deindent`
block.chunks.init.push(b`
function ${handler}() {
@_cancelAnimationFrame(${animation_frame});
if (!${this.var}.paused) {
${animation_frame} = @raf(${handler});
${needs_lock && `${lock} = true;`}
${needs_lock && b`${lock} = true;`}
}
ctx.${handler}.call(${this.var}${contextual_dependencies.size > 0 ? ', ctx' : ''});
#ctx.${handler}.call(${this.var}, ${contextual_dependencies.size > 0 ? '#ctx' : null});
}
`);
} else {
block.builders.init.add_block(deindent`
block.chunks.init.push(b`
function ${handler}() {
${needs_lock && `${lock} = true;`}
ctx.${handler}.call(${this.var}${contextual_dependencies.size > 0 ? ', ctx' : ''});
${needs_lock && b`${lock} = true;`}
#ctx.${handler}.call(${this.var}, ${contextual_dependencies.size > 0 ? '#ctx' : null});
}
`);
}
callee = handler;
} else {
callee = `ctx.${handler}`;
callee = x`#ctx.${handler}`;
}
this.renderer.component.partly_hoisted.push(deindent`
function ${handler}(${contextual_dependencies.size > 0 ? `{ ${Array.from(contextual_dependencies).join(', ')} }` : ``}) {
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
};
})
};
this.renderer.component.partly_hoisted.push(b`
function ${handler}(${arg}) {
${group.bindings.map(b => b.handler.mutation)}
${Array.from(dependencies).filter(dep => dep[0] !== '$').map(dep => `${this.renderer.component.invalidate(dep)};`)}
${Array.from(dependencies)
.filter(dep => dep[0] !== '$')
.filter(dep => !contextual_dependencies.has(dep))
.map(dep => b`${this.renderer.component.invalidate(dep)};`)}
}
`);
group.events.forEach(name => {
if (name === 'resize') {
// special case
const resize_listener = block.get_unique_name(`${this.var}_resize_listener`);
const resize_listener = block.get_unique_name(`${this.var.name}_resize_listener`);
block.add_variable(resize_listener);
block.builders.mount.add_line(
`${resize_listener} = @add_resize_listener(${this.var}, ${callee}.bind(${this.var}));`
block.chunks.mount.push(
b`${resize_listener} = @add_resize_listener(${this.var}, ${callee}.bind(${this.var}));`
);
block.builders.destroy.add_line(
`${resize_listener}.cancel();`
block.chunks.destroy.push(
b`${resize_listener}.cancel();`
);
} else {
block.event_listeners.push(
`@listen(${this.var}, "${name}", ${callee})`
x`@listen(${this.var}, "${name}", ${callee})`
);
}
});
const some_initial_state_is_undefined = group.bindings
.map(binding => `${binding.snippet} === void 0`)
.join(' || ');
.map(binding => x`${binding.snippet} === void 0`)
.reduce((lhs, rhs) => x`${lhs} || ${rhs}`);
const should_initialise = (
this.node.name === 'select' ||
@ -538,28 +572,28 @@ export default class ElementWrapper extends Wrapper {
);
if (should_initialise) {
const callback = has_local_function ? handler : `() => ${callee}.call(${this.var})`;
block.builders.hydrate.add_line(
`if (${some_initial_state_is_undefined}) @add_render_callback(${callback});`
const callback = has_local_function ? handler : x`() => ${callee}.call(${this.var})`;
block.chunks.hydrate.push(
b`if (${some_initial_state_is_undefined}) @add_render_callback(${callback});`
);
}
if (group.events[0] === 'resize') {
block.builders.hydrate.add_line(
`@add_render_callback(() => ${callee}.call(${this.var}));`
block.chunks.hydrate.push(
b`@add_render_callback(() => ${callee}.call(${this.var}));`
);
}
});
if (lock) {
block.builders.update.add_line(`${lock} = false;`);
block.chunks.update.push(b`${lock} = false;`);
}
const this_binding = this.bindings.find(b => b.node.name === 'this');
if (this_binding) {
const binding_callback = bind_this(renderer.component, block, this_binding.node, this.var);
block.builders.mount.add_line(binding_callback);
block.chunks.mount.push(binding_callback);
}
}
@ -572,8 +606,7 @@ export default class ElementWrapper extends Wrapper {
}
});
// @ts-ignore todo:
if (this.node.attributes.find(attr => attr.type === 'Spread')) {
if (this.node.attributes.some(attr => attr.is_spread)) {
this.add_spread_attributes(block);
return;
}
@ -584,59 +617,60 @@ export default class ElementWrapper extends Wrapper {
}
add_spread_attributes(block: Block) {
const levels = block.get_unique_name(`${this.var}_levels`);
const data = block.get_unique_name(`${this.var}_data`);
const levels = block.get_unique_name(`${this.var.name}_levels`);
const data = block.get_unique_name(`${this.var.name}_data`);
const initial_props = [];
const updates = [];
this.node.attributes
.filter(attr => attr.type === 'Attribute' || attr.type === 'Spread')
this.attributes
.forEach(attr => {
const condition = attr.dependencies.size > 0
? `(${[...attr.dependencies].map(d => `changed.${d}`).join(' || ')})`
const condition = attr.node.dependencies.size > 0
? changed(Array.from(attr.node.dependencies))
: null;
if (attr.is_spread) {
const snippet = attr.expression.render(block);
if (attr.node.is_spread) {
const snippet = attr.node.expression.manipulate(block);
initial_props.push(snippet);
updates.push(condition ? `${condition} && ${snippet}` : snippet);
updates.push(condition ? x`${condition} && ${snippet}` : snippet);
} else {
const snippet = `{ ${quote_name_if_necessary(attr.name)}: ${attr.get_value(block)} }`;
const metadata = attr.get_metadata();
const snippet = x`{ ${
(metadata && metadata.property_name) ||
fix_attribute_casing(attr.node.name)
}: ${attr.get_value(block)} }`;
initial_props.push(snippet);
updates.push(condition ? `${condition} && ${snippet}` : snippet);
updates.push(condition ? x`${condition} && ${snippet}` : snippet);
}
});
block.builders.init.add_block(deindent`
var ${levels} = [
${initial_props.join(',\n')}
];
block.chunks.init.push(b`
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]);
}
`);
const fn = this.node.namespace === namespaces.svg ? `set_svg_attributes` : `set_attributes`;
const fn = this.node.namespace === namespaces.svg ? x`@set_svg_attributes` : x`@set_attributes`;
block.builders.hydrate.add_line(
`@${fn}(${this.var}, ${data});`
block.chunks.hydrate.push(
b`${fn}(${this.var}, ${data});`
);
block.builders.update.add_block(deindent`
@${fn}(${this.var}, @get_spread_update(${levels}, [
${updates.join(',\n')}
block.chunks.update.push(b`
${fn}(${this.var}, @get_spread_update(${levels}, [
${updates}
]));
`);
}
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(
@ -649,63 +683,63 @@ export default class ElementWrapper extends Wrapper {
if (intro === outro) {
// bidirectional transition
const name = block.get_unique_name(`${this.var}_transition`);
const name = block.get_unique_name(`${this.var.name}_transition`);
const snippet = intro.expression
? intro.expression.render(block)
: '{}';
? intro.expression.manipulate(block)
: x`{}`;
block.add_variable(name);
const fn = component.qualify(intro.name);
const intro_block = deindent`
const intro_block = b`
@add_render_callback(() => {
if (!${name}) ${name} = @create_bidirectional_transition(${this.var}, ${fn}, ${snippet}, true);
${name}.run(1);
});
`;
const outro_block = deindent`
const outro_block = b`
if (!${name}) ${name} = @create_bidirectional_transition(${this.var}, ${fn}, ${snippet}, false);
${name}.run(0);
`;
if (intro.is_local) {
block.builders.intro.add_block(deindent`
block.chunks.intro.push(b`
if (#local) {
${intro_block}
}
`);
block.builders.outro.add_block(deindent`
block.chunks.outro.push(b`
if (#local) {
${outro_block}
}
`);
} else {
block.builders.intro.add_block(intro_block);
block.builders.outro.add_block(outro_block);
block.chunks.intro.push(intro_block);
block.chunks.outro.push(outro_block);
}
block.builders.destroy.add_conditional('detaching', `if (${name}) ${name}.end();`);
block.chunks.destroy.push(b`if (detaching && ${name}) ${name}.end();`);
}
else {
const intro_name = intro && block.get_unique_name(`${this.var}_intro`);
const outro_name = outro && block.get_unique_name(`${this.var}_outro`);
const intro_name = intro && block.get_unique_name(`${this.var.name}_intro`);
const outro_name = outro && block.get_unique_name(`${this.var.name}_outro`);
if (intro) {
block.add_variable(intro_name);
const snippet = intro.expression
? intro.expression.render(block)
: '{}';
? intro.expression.manipulate(block)
: x`{}`;
const fn = component.qualify(intro.name);
let intro_block;
if (outro) {
intro_block = deindent`
intro_block = b`
@add_render_callback(() => {
if (${outro_name}) ${outro_name}.end(1);
if (!${intro_name}) ${intro_name} = @create_in_transition(${this.var}, ${fn}, ${snippet});
@ -713,9 +747,9 @@ export default class ElementWrapper extends Wrapper {
});
`;
block.builders.outro.add_line(`if (${intro_name}) ${intro_name}.invalidate();`);
block.chunks.outro.push(b`if (${intro_name}) ${intro_name}.invalidate();`);
} else {
intro_block = deindent`
intro_block = b`
if (!${intro_name}) {
@add_render_callback(() => {
${intro_name} = @create_in_transition(${this.var}, ${fn}, ${snippet});
@ -726,47 +760,47 @@ export default class ElementWrapper extends Wrapper {
}
if (intro.is_local) {
intro_block = deindent`
intro_block = b`
if (#local) {
${intro_block}
}
`;
}
block.builders.intro.add_block(intro_block);
block.chunks.intro.push(intro_block);
}
if (outro) {
block.add_variable(outro_name);
const snippet = outro.expression
? outro.expression.render(block)
: '{}';
? outro.expression.manipulate(block)
: x`{}`;
const fn = component.qualify(outro.name);
if (!intro) {
block.builders.intro.add_block(deindent`
block.chunks.intro.push(b`
if (${outro_name}) ${outro_name}.end(1);
`);
}
// TODO hide elements that have outro'd (unless they belong to a still-outroing
// group) prior to their removal from the DOM
let outro_block = deindent`
let outro_block = b`
${outro_name} = @create_out_transition(${this.var}, ${fn}, ${snippet});
`;
if (outro.is_local) {
outro_block = deindent`
outro_block = b`
if (#local) {
${outro_block}
}
`;
}
block.builders.outro.add_block(outro_block);
block.chunks.outro.push(outro_block);
block.builders.destroy.add_conditional('detaching', `if (${outro_name}) ${outro_name}.end();`);
block.chunks.destroy.push(b`if (detaching && ${outro_name}) ${outro_name}.end();`);
}
}
}
@ -781,23 +815,23 @@ export default class ElementWrapper extends Wrapper {
const stop_animation = block.get_unique_name('stop_animation');
block.add_variable(rect);
block.add_variable(stop_animation, '@noop');
block.add_variable(stop_animation, x`@noop`);
block.builders.measure.add_block(deindent`
block.chunks.measure.push(b`
${rect} = ${this.var}.getBoundingClientRect();
`);
block.builders.fix.add_block(deindent`
block.chunks.fix.push(b`
@fix_position(${this.var});
${stop_animation}();
${outro && `@add_transform(${this.var}, ${rect});`}
${outro && b`@add_transform(${this.var}, ${rect});`}
`);
const params = this.node.animation.expression ? this.node.animation.expression.render(block) : '{}';
const params = this.node.animation.expression ? this.node.animation.expression.manipulate(block) : x`{}`;
const name = component.qualify(this.node.animation.name);
block.builders.animate.add_block(deindent`
block.chunks.animate.push(b`
${stop_animation}();
${stop_animation} = @create_animation(${this.var}, ${rect}, ${name}, ${params});
`);
@ -808,31 +842,108 @@ export default class ElementWrapper extends Wrapper {
}
add_classes(block: Block) {
const has_spread = this.node.attributes.some(attr => attr.is_spread);
this.node.classes.forEach(class_directive => {
const { expression, name } = class_directive;
let snippet;
let dependencies;
if (expression) {
snippet = expression.render(block);
snippet = expression.manipulate(block);
dependencies = expression.dependencies;
} else {
snippet = `${quote_prop_if_necessary(name)}`;
snippet = name;
dependencies = new Set([name]);
}
const updater = `@toggle_class(${this.var}, "${name}", ${snippet});`;
const updater = b`@toggle_class(${this.var}, "${name}", ${snippet});`;
block.builders.hydrate.add_line(updater);
block.chunks.hydrate.push(updater);
if ((dependencies && dependencies.size > 0) || this.class_dependencies.length) {
if (has_spread) {
block.chunks.update.push(updater);
} else if ((dependencies && dependencies.size > 0) || this.class_dependencies.length) {
const all_dependencies = this.class_dependencies.concat(...dependencies);
const deps = all_dependencies.map(dependency => `changed${quote_prop_if_necessary(dependency)}`).join(' || ');
const condition = all_dependencies.length > 1 ? `(${deps})` : deps;
const condition = changed(all_dependencies);
block.builders.update.add_conditional(
condition,
updater
);
block.chunks.update.push(b`
if (${condition}) {
${updater}
}`);
}
});
}
add_manual_style_scoping(block) {
if (this.node.needs_manual_style_scoping) {
const updater = b`@toggle_class(${this.var}, "${this.node.component.stylesheet.id}", true);`;
block.chunks.hydrate.push(updater);
block.chunks.update.push(updater);
}
}
}
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 += ' ';
const parent = wrapper.node.parent as Element;
const raw = parent && (
parent.name === 'script' ||
parent.name === 'style' ||
can_use_raw_text
);
state.quasi.value.raw += (raw ? wrapper.node.data : escape_html(wrapper.node.data))
.replace(/\\/g, '\\\\')
.replace(/`/g, '\\`')
.replace(/\$/g, '\\$');
}
else if (wrapper.node.type === 'MustacheTag' || wrapper.node.type === 'RawMustacheTag' ) {
literal.quasis.push(state.quasi);
literal.expressions.push(wrapper.node.expression.manipulate(block));
state.quasi = {
type: 'TemplateElement',
value: { raw: '' }
};
}
else if (wrapper.node.name === 'noscript') {
// do nothing
}
else {
// element
state.quasi.value.raw += `<${wrapper.node.name}`;
(wrapper as ElementWrapper).attributes.forEach((attr: AttributeWrapper) => {
state.quasi.value.raw += ` ${fix_attribute_casing(attr.node.name)}="`;
attr.node.chunks.forEach(chunk => {
if (chunk.type === 'Text') {
state.quasi.value.raw += escape_html(chunk.data);
} else {
literal.quasis.push(state.quasi);
literal.expressions.push(chunk.manipulate(block));
state.quasi = {
type: 'TemplateElement',
value: { raw: '' }
};
}
});
state.quasi.value.raw += `"`;
});
state.quasi.value.raw += '>';
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}>`;
}
}
});
}

@ -17,6 +17,7 @@ import { INode } from '../../nodes/interfaces';
import Renderer from '../Renderer';
import Block from '../Block';
import { trim_start, trim_end } from '../../../utils/trim';
import { Identifier } from 'estree';
const wrappers = {
AwaitBlock,
@ -145,7 +146,7 @@ export default class FragmentWrapper {
}
}
render(block: Block, parent_node: string, parent_nodes: string) {
render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
for (let i = 0; i < this.nodes.length; i += 1) {
this.nodes[i].render(block, parent_node, parent_nodes);
}

@ -3,6 +3,8 @@ import Renderer from '../Renderer';
import Block from '../Block';
import Head from '../../nodes/Head';
import FragmentWrapper from './Fragment';
import { x } from 'code-red';
import { Identifier } from 'estree';
export default class HeadWrapper extends Wrapper {
fragment: FragmentWrapper;
@ -29,7 +31,7 @@ export default class HeadWrapper extends Wrapper {
);
}
render(block: Block, _parent_node: string, _parent_nodes: string) {
this.fragment.render(block, '@_document.head', 'nodes');
render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) {
this.fragment.render(block, x`@_document.head` as unknown as Identifier, x`#nodes` as unknown as Identifier);
}
}

@ -6,8 +6,11 @@ import IfBlock from '../../nodes/IfBlock';
import create_debugging_comment from './shared/create_debugging_comment';
import ElseBlock from '../../nodes/ElseBlock';
import FragmentWrapper from './Fragment';
import deindent from '../../utils/deindent';
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 (
@ -19,8 +22,8 @@ class IfBlockBranch extends Wrapper {
block: Block;
fragment: FragmentWrapper;
dependencies?: string[];
condition?: string;
snippet?: string;
condition?: any;
snippet?: Node;
is_dynamic: boolean;
var = null;
@ -54,9 +57,9 @@ class IfBlockBranch extends Wrapper {
if (should_cache) {
this.condition = block.get_unique_name(`show_if`);
this.snippet = expression.render(block);
this.snippet = (expression.manipulate(block) as Node);
} else {
this.condition = expression.render(block);
this.condition = expression.manipulate(block);
}
}
@ -79,7 +82,7 @@ export default class IfBlockWrapper extends Wrapper {
branches: IfBlockBranch[];
needs_update = false;
var = 'if_block';
var: Identifier = { type: 'Identifier', name: 'if_block' };
constructor(
renderer: Renderer,
@ -92,6 +95,7 @@ export default class IfBlockWrapper extends Wrapper {
super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
this.branches = [];
@ -168,27 +172,27 @@ export default class IfBlockWrapper extends Wrapper {
render(
block: Block,
parent_node: string,
parent_nodes: string
parent_node: Identifier,
parent_nodes: Identifier
) {
const name = this.var;
const needs_anchor = this.next ? !this.next.is_dom_node() : !parent_node || !this.parent.is_dom_node();
const anchor = needs_anchor
? block.get_unique_name(`${name}_anchor`)
? block.get_unique_name(`${this.var.name}_anchor`)
: (this.next && this.next.var) || 'null';
const has_else = !(this.branches[this.branches.length - 1].condition);
const if_name = has_else ? '' : `if (${name}) `;
const if_exists_condition = has_else ? null : name;
const dynamic = this.branches[0].block.has_update_method; // can use [0] as proxy for all, since they necessarily have the same value
const has_intros = this.branches[0].block.has_intro_method;
const has_outros = this.branches[0].block.has_outro_method;
const has_transitions = has_intros || has_outros;
const vars = { name, anchor, if_name, has_else, has_transitions };
const vars = { name, anchor, if_exists_condition, has_else, has_transitions };
const detaching = (parent_node && parent_node !== '@_document.head') ? '' : 'detaching';
const detaching = parent_node && !is_head(parent_node) ? null : 'detaching';
if (this.node.else) {
this.branches.forEach(branch => {
@ -198,7 +202,7 @@ export default class IfBlockWrapper extends Wrapper {
if (has_outros) {
this.render_compound_with_outros(block, parent_node, parent_nodes, dynamic, vars, detaching);
block.builders.outro.add_line(`@transition_out(${name});`);
block.chunks.outro.push(b`@transition_out(${name});`);
} else {
this.render_compound(block, parent_node, parent_nodes, dynamic, vars, detaching);
}
@ -206,126 +210,155 @@ export default class IfBlockWrapper extends Wrapper {
this.render_simple(block, parent_node, parent_nodes, dynamic, vars, detaching);
if (has_outros) {
block.builders.outro.add_line(`@transition_out(${name});`);
block.chunks.outro.push(b`@transition_out(${name});`);
}
}
block.builders.create.add_line(`${if_name}${name}.c();`);
if (if_exists_condition) {
block.chunks.create.push(b`if (${if_exists_condition}) ${name}.c();`);
} else {
block.chunks.create.push(b`${name}.c();`);
}
if (parent_nodes && this.renderer.options.hydratable) {
block.builders.claim.add_line(
`${if_name}${name}.l(${parent_nodes});`
);
if (if_exists_condition) {
block.chunks.claim.push(
b`if (${if_exists_condition}) ${name}.l(${parent_nodes});`
);
} else {
block.chunks.claim.push(
b`${name}.l(${parent_nodes});`
);
}
}
if (has_intros || has_outros) {
block.builders.intro.add_line(`@transition_in(${name});`);
block.chunks.intro.push(b`@transition_in(${name});`);
}
if (needs_anchor) {
block.add_element(
anchor,
`@empty()`,
parent_nodes && `@empty()`,
anchor as Identifier,
x`@empty()`,
parent_nodes && x`@empty()`,
parent_node
);
}
this.branches.forEach(branch => {
branch.fragment.render(branch.block, null, 'nodes');
branch.fragment.render(branch.block, null, x`#nodes` as unknown as Identifier);
});
}
render_compound(
block: Block,
parent_node: string,
_parent_nodes: string,
parent_node: Identifier,
_parent_nodes: Identifier,
dynamic,
{ name, anchor, has_else, if_name, has_transitions },
{ name, anchor, has_else, if_exists_condition, has_transitions },
detaching
) {
const select_block_type = this.renderer.component.get_unique_name(`select_block_type`);
const current_block_type = block.get_unique_name(`current_block_type`);
const current_block_type_and = has_else ? '' : `${current_block_type} && `;
const get_block = has_else
? x`${current_block_type}(#ctx)`
: x`${current_block_type} && ${current_block_type}(#ctx)`;
/* eslint-disable @typescript-eslint/indent,indent */
if (this.needs_update) {
block.builders.init.add_block(deindent`
function ${select_block_type}(changed, ctx) {
block.chunks.init.push(b`
function ${select_block_type}(#changed, #ctx) {
${this.branches.map(({ dependencies, condition, snippet, block }) => condition
? deindent`
? b`
${snippet && (
dependencies.length > 0
? `if ((${condition} == null) || ${dependencies.map(n => `changed.${n}`).join(' || ')}) ${condition} = !!(${snippet})`
: `if (${condition} == null) ${condition} = !!(${snippet})`
? b`if (${condition} == null || ${changed(dependencies)}) ${condition} = !!${snippet}`
: b`if (${condition} == null) ${condition} = !!${snippet}`
)}
if (${condition}) return ${block.name};`
: `return ${block.name};`)}
: b`return ${block.name};`)}
}
`);
} else {
block.builders.init.add_block(deindent`
function ${select_block_type}(changed, ctx) {
block.chunks.init.push(b`
function ${select_block_type}(#changed, #ctx) {
${this.branches.map(({ condition, snippet, block }) => condition
? `if (${snippet || condition}) return ${block.name};`
: `return ${block.name};`)}
? b`if (${snippet || condition}) return ${block.name};`
: b`return ${block.name};`)}
}
`);
}
/* eslint-enable @typescript-eslint/indent,indent */
block.builders.init.add_block(deindent`
var ${current_block_type} = ${select_block_type}(null, ctx);
var ${name} = ${current_block_type_and}${current_block_type}(ctx);
block.chunks.init.push(b`
let ${current_block_type} = ${select_block_type}(null, #ctx);
let ${name} = ${get_block};
`);
const initial_mount_node = parent_node || '#target';
const anchor_node = parent_node ? 'null' : 'anchor';
block.builders.mount.add_line(
`${if_name}${name}.m(${initial_mount_node}, ${anchor_node});`
);
if (if_exists_condition) {
block.chunks.mount.push(
b`if (${if_exists_condition}) ${name}.m(${initial_mount_node}, ${anchor_node});`
);
} else {
block.chunks.mount.push(
b`${name}.m(${initial_mount_node}, ${anchor_node});`
);
}
if (this.needs_update) {
const update_mount_node = this.get_update_mount_node(anchor);
const change_block = deindent`
${if_name}${name}.d(1);
${name} = ${current_block_type_and}${current_block_type}(ctx);
const change_block = b`
${if_exists_condition ? b`if (${if_exists_condition}) ${name}.d(1)` : b`${name}.d(1)`};
${name} = ${get_block};
if (${name}) {
${name}.c();
${has_transitions && `@transition_in(${name}, 1);`}
${has_transitions && b`@transition_in(${name}, 1);`}
${name}.m(${update_mount_node}, ${anchor});
}
`;
if (dynamic) {
block.builders.update.add_block(deindent`
if (${current_block_type} === (${current_block_type} = ${select_block_type}(changed, ctx)) && ${name}) {
${name}.p(changed, ctx);
block.chunks.update.push(b`
if (${current_block_type} === (${current_block_type} = ${select_block_type}(#changed, #ctx)) && ${name}) {
${name}.p(#changed, #ctx);
} else {
${change_block}
}
`);
} else {
block.builders.update.add_block(deindent`
if (${current_block_type} !== (${current_block_type} = ${select_block_type}(changed, ctx))) {
block.chunks.update.push(b`
if (${current_block_type} !== (${current_block_type} = ${select_block_type}(#changed, #ctx))) {
${change_block}
}
`);
}
} else if (dynamic) {
block.builders.update.add_line(`${name}.p(changed, ctx);`);
block.chunks.update.push(b`${name}.p(#changed, #ctx);`);
}
block.builders.destroy.add_line(`${if_name}${name}.d(${detaching});`);
if (if_exists_condition) {
block.chunks.destroy.push(b`
if (${if_exists_condition}) {
${name}.d(${detaching});
}
`);
} else {
block.chunks.destroy.push(b`
${name}.d(${detaching});
`);
}
}
// if any of the siblings have outros, we need to keep references to the blocks
// (TODO does this only apply to bidi transitions?)
render_compound_with_outros(
block: Block,
parent_node: string,
_parent_nodes: string,
parent_node: Identifier,
_parent_nodes: Identifier,
dynamic,
{ name, anchor, has_else, has_transitions },
detaching
@ -337,51 +370,55 @@ export default class IfBlockWrapper extends Wrapper {
const if_blocks = block.get_unique_name(`if_blocks`);
const if_current_block_type_index = has_else
? ''
: `if (~${current_block_type_index}) `;
? nodes => nodes
: nodes => b`if (~${current_block_type_index}) { ${nodes} }`;
block.add_variable(current_block_type_index);
block.add_variable(name);
/* eslint-disable @typescript-eslint/indent,indent */
block.builders.init.add_block(deindent`
var ${if_block_creators} = [
${this.branches.map(branch => branch.block.name).join(',\n')}
block.chunks.init.push(b`
const ${if_block_creators} = [
${this.branches.map(branch => branch.block.name)}
];
var ${if_blocks} = [];
const ${if_blocks} = [];
${this.needs_update
? deindent`
function ${select_block_type}(changed, ctx) {
? b`
function ${select_block_type}(#changed, #ctx) {
${this.branches.map(({ dependencies, condition, snippet }, i) => condition
? deindent`
${snippet && `if ((${condition} == null) || ${dependencies.map(n => `changed.${n}`).join(' || ')}) ${condition} = !!(${snippet})`}
if (${condition}) return ${String(i)};`
: `return ${i};`)}
${!has_else && `return -1;`}
? b`
${snippet && (
dependencies.length > 0
? b`if (${condition} == null || ${changed(dependencies)}) ${condition} = !!${snippet}`
: b`if (${condition} == null) ${condition} = !!${snippet}`
)}
if (${condition}) return ${i};`
: b`return ${i};`)}
${!has_else && b`return -1;`}
}
`
: deindent`
function ${select_block_type}(changed, ctx) {
: b`
function ${select_block_type}(#changed, #ctx) {
${this.branches.map(({ condition, snippet }, i) => condition
? `if (${snippet || condition}) return ${String(i)};`
: `return ${i};`)}
${!has_else && `return -1;`}
? b`if (${snippet || condition}) return ${i};`
: b`return ${i};`)}
${!has_else && b`return -1;`}
}
`}
`);
/* eslint-enable @typescript-eslint/indent,indent */
if (has_else) {
block.builders.init.add_block(deindent`
${current_block_type_index} = ${select_block_type}(null, ctx);
${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](ctx);
block.chunks.init.push(b`
${current_block_type_index} = ${select_block_type}(null, #ctx);
${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](#ctx);
`);
} else {
block.builders.init.add_block(deindent`
if (~(${current_block_type_index} = ${select_block_type}(null, ctx))) {
${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](ctx);
block.chunks.init.push(b`
if (~(${current_block_type_index} = ${select_block_type}(null, #ctx))) {
${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](#ctx);
}
`);
}
@ -389,14 +426,16 @@ export default class IfBlockWrapper extends Wrapper {
const initial_mount_node = parent_node || '#target';
const anchor_node = parent_node ? 'null' : 'anchor';
block.builders.mount.add_line(
`${if_current_block_type_index}${if_blocks}[${current_block_type_index}].m(${initial_mount_node}, ${anchor_node});`
block.chunks.mount.push(
if_current_block_type_index(
b`${if_blocks}[${current_block_type_index}].m(${initial_mount_node}, ${anchor_node});`
)
);
if (this.needs_update) {
const update_mount_node = this.get_update_mount_node(anchor);
const destroy_old_block = deindent`
const destroy_old_block = b`
@group_outros();
@transition_out(${if_blocks}[${previous_block_index}], 1, 1, () => {
${if_blocks}[${previous_block_index}] = null;
@ -404,23 +443,23 @@ export default class IfBlockWrapper extends Wrapper {
@check_outros();
`;
const create_new_block = deindent`
const create_new_block = b`
${name} = ${if_blocks}[${current_block_type_index}];
if (!${name}) {
${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](ctx);
${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](#ctx);
${name}.c();
}
${has_transitions && `@transition_in(${name}, 1);`}
${has_transitions && b`@transition_in(${name}, 1);`}
${name}.m(${update_mount_node}, ${anchor});
`;
const change_block = has_else
? deindent`
? b`
${destroy_old_block}
${create_new_block}
`
: deindent`
: b`
if (${name}) {
${destroy_old_block}
}
@ -433,88 +472,90 @@ export default class IfBlockWrapper extends Wrapper {
`;
if (dynamic) {
block.builders.update.add_block(deindent`
var ${previous_block_index} = ${current_block_type_index};
${current_block_type_index} = ${select_block_type}(changed, ctx);
block.chunks.update.push(b`
let ${previous_block_index} = ${current_block_type_index};
${current_block_type_index} = ${select_block_type}(#changed, #ctx);
if (${current_block_type_index} === ${previous_block_index}) {
${if_current_block_type_index}${if_blocks}[${current_block_type_index}].p(changed, ctx);
${if_current_block_type_index(b`${if_blocks}[${current_block_type_index}].p(#changed, #ctx);`)}
} else {
${change_block}
}
`);
} else {
block.builders.update.add_block(deindent`
var ${previous_block_index} = ${current_block_type_index};
${current_block_type_index} = ${select_block_type}(changed, ctx);
block.chunks.update.push(b`
let ${previous_block_index} = ${current_block_type_index};
${current_block_type_index} = ${select_block_type}(#changed, #ctx);
if (${current_block_type_index} !== ${previous_block_index}) {
${change_block}
}
`);
}
} else if (dynamic) {
block.builders.update.add_line(`${name}.p(changed, ctx);`);
block.chunks.update.push(b`${name}.p(#changed, #ctx);`);
}
block.builders.destroy.add_line(deindent`
${if_current_block_type_index}${if_blocks}[${current_block_type_index}].d(${detaching});
`);
block.chunks.destroy.push(
if_current_block_type_index(b`${if_blocks}[${current_block_type_index}].d(${detaching});`)
);
}
render_simple(
block: Block,
parent_node: string,
_parent_nodes: string,
parent_node: Identifier,
_parent_nodes: Identifier,
dynamic,
{ name, anchor, if_name, has_transitions },
{ name, anchor, if_exists_condition, has_transitions },
detaching
) {
const branch = this.branches[0];
if (branch.snippet) block.add_variable(branch.condition, branch.snippet);
block.builders.init.add_block(deindent`
var ${name} = (${branch.condition}) && ${branch.block.name}(ctx);
block.chunks.init.push(b`
let ${name} = ${branch.condition} && ${branch.block.name}(#ctx);
`);
const initial_mount_node = parent_node || '#target';
const anchor_node = parent_node ? 'null' : 'anchor';
block.builders.mount.add_line(
`if (${name}) ${name}.m(${initial_mount_node}, ${anchor_node});`
block.chunks.mount.push(
b`if (${name}) ${name}.m(${initial_mount_node}, ${anchor_node});`
);
if (branch.dependencies.length > 0) {
const update_mount_node = this.get_update_mount_node(anchor);
const enter = dynamic
? deindent`
? b`
if (${name}) {
${name}.p(changed, ctx);
${has_transitions && `@transition_in(${name}, 1);`}
${name}.p(#changed, #ctx);
${has_transitions && b`@transition_in(${name}, 1);`}
} else {
${name} = ${branch.block.name}(ctx);
${name} = ${branch.block.name}(#ctx);
${name}.c();
${has_transitions && `@transition_in(${name}, 1);`}
${has_transitions && b`@transition_in(${name}, 1);`}
${name}.m(${update_mount_node}, ${anchor});
}
`
: deindent`
: b`
if (!${name}) {
${name} = ${branch.block.name}(ctx);
${name} = ${branch.block.name}(#ctx);
${name}.c();
${has_transitions && `@transition_in(${name}, 1);`}
${has_transitions && b`@transition_in(${name}, 1);`}
${name}.m(${update_mount_node}, ${anchor});
} ${has_transitions && `else @transition_in(${name}, 1);`}
} else {
${has_transitions && b`@transition_in(${name}, 1);`}
}
`;
if (branch.snippet) {
block.builders.update.add_block(`if (${branch.dependencies.map(n => `changed.${n}`).join(' || ')}) ${branch.condition} = ${branch.snippet}`);
block.chunks.update.push(b`if (${changed(branch.dependencies)}) ${branch.condition} = ${branch.snippet}`);
}
// no `p()` here — we don't want to update outroing nodes,
// as that will typically result in glitching
if (branch.block.has_outro_method) {
block.builders.update.add_block(deindent`
block.chunks.update.push(b`
if (${branch.condition}) {
${enter}
} else if (${name}) {
@ -526,7 +567,7 @@ export default class IfBlockWrapper extends Wrapper {
}
`);
} else {
block.builders.update.add_block(deindent`
block.chunks.update.push(b`
if (${branch.condition}) {
${enter}
} else if (${name}) {
@ -536,11 +577,19 @@ export default class IfBlockWrapper extends Wrapper {
`);
}
} else if (dynamic) {
block.builders.update.add_block(
`if (${branch.condition}) ${name}.p(changed, ctx);`
);
block.chunks.update.push(b`
if (${branch.condition}) ${name}.p(#changed, #ctx);
`);
}
block.builders.destroy.add_line(`${if_name}${name}.d(${detaching});`);
if (if_exists_condition) {
block.chunks.destroy.push(b`
if (${if_exists_condition}) ${name}.d(${detaching});
`);
} else {
block.chunks.destroy.push(b`
${name}.d(${detaching});
`);
}
}
}

@ -3,10 +3,9 @@ import Renderer from '../../Renderer';
import Block from '../../Block';
import InlineComponent from '../../../nodes/InlineComponent';
import FragmentWrapper from '../Fragment';
import { quote_name_if_necessary, quote_prop_if_necessary, sanitize } from '../../../../utils/names';
import { stringify_props } from '../../../utils/stringify_props';
import { sanitize } from '../../../../utils/names';
import add_to_set from '../../../utils/add_to_set';
import deindent from '../../../utils/deindent';
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';
@ -15,10 +14,13 @@ 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';
export default class InlineComponentWrapper extends Wrapper {
var: string;
slots: Map<string, { block: Block; scope: TemplateScope; fn?: string }> = new Map();
var: Identifier;
slots: Map<string, { block: Block; scope: TemplateScope; fn?: Node }> = new Map();
node: InlineComponent;
fragment: FragmentWrapper;
@ -33,6 +35,7 @@ export default class InlineComponentWrapper extends Wrapper {
super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
if (this.node.expression) {
block.add_dependencies(this.node.expression.dependencies);
@ -61,11 +64,14 @@ export default class InlineComponentWrapper extends Wrapper {
}
});
this.var = (
this.node.name === 'svelte:self' ? renderer.component.name :
this.node.name === 'svelte:component' ? 'switch_instance' :
sanitize(this.node.name)
).toLowerCase();
this.var = {
type: 'Identifier',
name: (
this.node.name === 'svelte:self' ? renderer.component.name.name :
this.node.name === 'svelte:component' ? 'switch_instance' :
sanitize(this.node.name)
).toLowerCase()
};
if (this.node.children.length) {
const default_slot = block.child({
@ -102,42 +108,50 @@ export default class InlineComponentWrapper extends Wrapper {
render(
block: Block,
parent_node: string,
parent_nodes: string
parent_node: Identifier,
parent_nodes: Identifier
) {
const { renderer } = this;
const { component } = renderer;
const name = this.var;
const component_opts = [];
const component_opts = x`{}` as ObjectExpression;
const statements: string[] = [];
const updates: string[] = [];
const statements: Array<Node | Node[]> = [];
const updates: Array<Node | Node[]> = [];
let props;
const name_changes = block.get_unique_name(`${name}_changes`);
const name_changes = block.get_unique_name(`${name.name}_changes`);
const uses_spread = !!this.node.attributes.find(a => a.is_spread);
const slot_props = Array.from(this.slots).map(([name, slot]) => `${quote_name_if_necessary(name)}: [${slot.block.name}${slot.fn ? `, ${slot.fn}` : ''}]`);
const initial_props = slot_props.length > 0
? [`$$slots: ${stringify_props(slot_props)}`, `$$scope: { ctx }`]
const initial_props = this.slots.size > 0
? [
p`$$slots: {
${Array.from(this.slots).map(([name, slot]) => {
return p`${name}: [${slot.block.name}, ${slot.fn || null}]`;
})}
}`,
p`$$scope: {
ctx: #ctx
}`
]
: [];
const attribute_object = uses_spread
? stringify_props(initial_props)
: stringify_props(
this.node.attributes.map(attr => `${quote_name_if_necessary(attr.name)}: ${attr.get_value(block)}`).concat(initial_props)
);
? x`{ ${initial_props} }`
: x`{
${this.node.attributes.map(attr => p`${attr.name}: ${attr.get_value(block)}`)},
${initial_props}
}`;
if (this.node.attributes.length || this.node.bindings.length || initial_props.length) {
if (!uses_spread && this.node.bindings.length === 0) {
component_opts.push(`props: ${attribute_object}`);
component_opts.properties.push(p`props: ${attribute_object}`);
} else {
props = block.get_unique_name(`${name}_props`);
component_opts.push(`props: ${props}`);
props = block.get_unique_name(`${name.name}_props`);
component_opts.properties.push(p`props: ${props}`);
}
}
@ -145,7 +159,7 @@ export default class InlineComponentWrapper extends Wrapper {
const default_slot = this.slots.get('default');
this.fragment.nodes.forEach((child) => {
child.render(default_slot.block, null, 'nodes');
child.render(default_slot.block, null, x`#nodes` as unknown as Identifier);
});
}
@ -154,7 +168,7 @@ export default class InlineComponentWrapper extends Wrapper {
// will complain that options.target is missing. This would
// work better if components had separate public and private
// APIs
component_opts.push(`$$inline: true`);
component_opts.properties.push(p`$$inline: true`);
}
const fragment_dependencies = new Set(this.fragment ? ['$$scope'] : []);
@ -172,17 +186,17 @@ export default class InlineComponentWrapper extends Wrapper {
const dynamic_attributes = this.node.attributes.filter(a => a.get_dependencies().length > 0);
if (!uses_spread && (dynamic_attributes.length > 0 || this.node.bindings.length > 0 || non_let_dependencies.length > 0)) {
updates.push(`var ${name_changes} = {};`);
updates.push(b`const ${name_changes} = {};`);
}
if (this.node.attributes.length) {
if (uses_spread) {
const levels = block.get_unique_name(`${this.var}_spread_levels`);
const levels = block.get_unique_name(`${this.var.name}_spread_levels`);
const initial_props = [];
const changes = [];
const all_dependencies = new Set();
const all_dependencies: Set<string> = new Set();
this.node.attributes.forEach(attr => {
add_to_set(all_dependencies, attr.dependencies);
@ -192,53 +206,59 @@ export default class InlineComponentWrapper extends Wrapper {
const { name, dependencies } = attr;
const condition = dependencies.size > 0 && (dependencies.size !== all_dependencies.size)
? `(${Array.from(dependencies).map(d => `changed.${d}`).join(' || ')})`
? changed(Array.from(dependencies))
: null;
if (attr.is_spread) {
const value = attr.expression.render(block);
const value = attr.expression.manipulate(block);
initial_props.push(value);
let value_object = value;
if (attr.expression.node.type !== 'ObjectExpression') {
value_object = `@get_spread_object(${value})`;
value_object = x`@get_spread_object(${value})`;
}
changes.push(condition ? `${condition} && ${value_object}` : value_object);
changes.push(condition ? x`${condition} && ${value_object}` : value_object);
} else {
const obj = `{ ${quote_name_if_necessary(name)}: ${attr.get_value(block)} }`;
const obj = x`{ ${name}: ${attr.get_value(block)} }`;
initial_props.push(obj);
changes.push(condition ? `${condition} && ${obj}` : `${levels}[${i}]`);
changes.push(condition ? x`${condition} && ${obj}` : x`${levels}[${i}]`);
}
});
block.builders.init.add_block(deindent`
var ${levels} = [
${initial_props.join(',\n')}
block.chunks.init.push(b`
const ${levels} = [
${initial_props}
];
`);
statements.push(deindent`
for (var #i = 0; #i < ${levels}.length; #i += 1) {
statements.push(b`
for (let #i = 0; #i < ${levels}.length; #i += 1) {
${props} = @assign(${props}, ${levels}[#i]);
}
`);
const conditions = Array.from(all_dependencies).map(dep => `changed.${dep}`).join(' || ');
updates.push(deindent`
var ${name_changes} = ${conditions ? `(${conditions}) ? @get_spread_update(${levels}, [
${changes.join(',\n')}
]) : {}` : '{}'};
`);
if (all_dependencies.size) {
const condition = changed(Array.from(all_dependencies));
updates.push(b`
const ${name_changes} = ${condition} ? @get_spread_update(${levels}, [
${changes}
]) : {}
`);
} else {
updates.push(b`
const ${name_changes} = {};
`);
}
} else {
dynamic_attributes.forEach((attribute: Attribute) => {
const dependencies = attribute.get_dependencies();
if (dependencies.length > 0) {
const condition = dependencies.map(dependency => `changed.${dependency}`).join(' || ');
const condition = changed(dependencies);
updates.push(deindent`
if (${condition}) ${name_changes}${quote_prop_if_necessary(attribute.name)} = ${attribute.get_value(block)};
updates.push(b`
if (${condition}) ${name_changes}.${attribute.name} = ${attribute.get_value(block)};
`);
}
});
@ -246,7 +266,10 @@ export default class InlineComponentWrapper extends Wrapper {
}
if (non_let_dependencies.length > 0) {
updates.push(`if (${non_let_dependencies.map(n => `changed.${n}`).join(' || ')}) ${name_changes}.$$scope = { changed, ctx };`);
updates.push(b`
if (${changed(non_let_dependencies)}) {
${name_changes}.$$scope = { changed: #changed, ctx: #ctx };
}`);
}
const munged_bindings = this.node.bindings.map(binding => {
@ -256,10 +279,10 @@ export default class InlineComponentWrapper extends Wrapper {
return bind_this(component, block, binding, this.var);
}
const name = component.get_unique_name(`${this.var}_${binding.name}_binding`);
const id = component.get_unique_name(`${this.var.name}_${binding.name}_binding`);
component.add_var({
name,
name: id.name,
internal: true,
referenced: true
});
@ -267,24 +290,26 @@ export default class InlineComponentWrapper extends Wrapper {
const updating = block.get_unique_name(`updating_${binding.name}`);
block.add_variable(updating);
const snippet = binding.expression.render(block);
const snippet = binding.expression.manipulate(block);
statements.push(deindent`
statements.push(b`
if (${snippet} !== void 0) {
${props}${quote_prop_if_necessary(binding.name)} = ${snippet};
${props}.${binding.name} = ${snippet};
}`
);
updates.push(deindent`
if (!${updating} && ${[...binding.expression.dependencies].map((dependency: string) => `changed.${dependency}`).join(' || ')}) {
${name_changes}${quote_prop_if_necessary(binding.name)} = ${snippet};
updates.push(b`
if (!${updating} && ${changed(Array.from(binding.expression.dependencies))}) {
${updating} = true;
${name_changes}.${binding.name} = ${snippet};
@add_flush_callback(() => ${updating} = false);
}
`);
const contextual_dependencies = Array.from(binding.expression.contextual_dependencies);
const dependencies = Array.from(binding.expression.dependencies);
let lhs = component.source.slice(binding.expression.node.start, binding.expression.node.end).trim();
let lhs = binding.raw_expression;
if (binding.is_contextual && binding.expression.node.type === 'Identifier') {
// bind:x={y} — we can't just do `y = x`, we need to
@ -292,35 +317,42 @@ export default class InlineComponentWrapper extends Wrapper {
const { name } = binding.expression.node;
const { object, property, snippet } = block.bindings.get(name);
lhs = snippet;
contextual_dependencies.push(object, property);
contextual_dependencies.push(object.name, property.name);
}
const value = block.get_unique_name('value');
const args = [value];
const args: any[] = [value];
if (contextual_dependencies.length > 0) {
args.push(`{ ${contextual_dependencies.join(', ')} }`);
args.push({
type: 'ObjectPattern',
properties: contextual_dependencies.map(name => {
const id = { type: 'Identifier', name };
return {
type: 'Property',
kind: 'init',
key: id,
value: id
};
})
});
block.builders.init.add_block(deindent`
function ${name}(${value}) {
ctx.${name}.call(null, ${value}, ctx);
${updating} = true;
@add_flush_callback(() => ${updating} = false);
block.chunks.init.push(b`
function ${id}(${value}) {
#ctx.${id}.call(null, ${value}, #ctx);
}
`);
block.maintain_context = true; // TODO put this somewhere more logical
} else {
block.builders.init.add_block(deindent`
function ${name}(${value}) {
ctx.${name}.call(null, ${value});
${updating} = true;
@add_flush_callback(() => ${updating} = false);
block.chunks.init.push(b`
function ${id}(${value}) {
#ctx.${id}.call(null, ${value});
}
`);
}
const body = deindent`
function ${name}(${args.join(', ')}) {
const body = b`
function ${id}(${args}) {
${lhs} = ${value};
${component.invalidate(dependencies[0])};
}
@ -328,51 +360,52 @@ export default class InlineComponentWrapper extends Wrapper {
component.partly_hoisted.push(body);
return `@binding_callbacks.push(() => @bind(${this.var}, '${binding.name}', ${name}));`;
return b`@binding_callbacks.push(() => @bind(${this.var}, '${binding.name}', ${id}));`;
});
const munged_handlers = this.node.handlers.map(handler => {
let snippet = handler.render(block);
if (handler.modifiers.has('once')) snippet = `@once(${snippet})`;
const event_handler = new EventHandler(handler, this);
let snippet = event_handler.get_snippet(block);
if (handler.modifiers.has('once')) snippet = x`@once(${snippet})`;
return `${name}.$on("${handler.name}", ${snippet});`;
return b`${name}.$on("${handler.name}", ${snippet});`;
});
if (this.node.name === 'svelte:component') {
const switch_value = block.get_unique_name('switch_value');
const switch_props = block.get_unique_name('switch_props');
const snippet = this.node.expression.render(block);
const snippet = this.node.expression.manipulate(block);
block.builders.init.add_block(deindent`
block.chunks.init.push(b`
var ${switch_value} = ${snippet};
function ${switch_props}(ctx) {
${(this.node.attributes.length || this.node.bindings.length) && deindent`
${props && `let ${props} = ${attribute_object};`}`}
function ${switch_props}(#ctx) {
${(this.node.attributes.length > 0 || this.node.bindings.length > 0) && b`
${props && b`let ${props} = ${attribute_object};`}`}
${statements}
return ${stringify_props(component_opts)};
return ${component_opts};
}
if (${switch_value}) {
var ${name} = new ${switch_value}(${switch_props}(ctx));
var ${name} = new ${switch_value}(${switch_props}(#ctx));
${munged_bindings}
${munged_handlers}
}
`);
block.builders.create.add_line(
`if (${name}) ${name}.$$.fragment.c();`
block.chunks.create.push(
b`if (${name}) @create_component(${name}.$$.fragment);`
);
if (parent_nodes && this.renderer.options.hydratable) {
block.builders.claim.add_line(
`if (${name}) ${name}.$$.fragment.l(${parent_nodes});`
block.chunks.claim.push(
b`if (${name}) @claim_component(${name}.$$.fragment, ${parent_nodes});`
);
}
block.builders.mount.add_block(deindent`
block.chunks.mount.push(b`
if (${name}) {
@mount_component(${name}, ${parent_node || '#target'}, ${parent_node ? 'null' : 'anchor'});
}
@ -382,12 +415,12 @@ export default class InlineComponentWrapper extends Wrapper {
const update_mount_node = this.get_update_mount_node(anchor);
if (updates.length) {
block.builders.update.add_block(deindent`
block.chunks.update.push(b`
${updates}
`);
}
block.builders.update.add_block(deindent`
block.chunks.update.push(b`
if (${switch_value} !== (${switch_value} = ${snippet})) {
if (${name}) {
@group_outros();
@ -399,81 +432,75 @@ export default class InlineComponentWrapper extends Wrapper {
}
if (${switch_value}) {
${name} = new ${switch_value}(${switch_props}(ctx));
${name} = new ${switch_value}(${switch_props}(#ctx));
${munged_bindings}
${munged_handlers}
${name}.$$.fragment.c();
@create_component(${name}.$$.fragment);
@transition_in(${name}.$$.fragment, 1);
@mount_component(${name}, ${update_mount_node}, ${anchor});
} else {
${name} = null;
}
} else if (${switch_value}) {
${updates.length && b`${name}.$set(${name_changes});`}
}
`);
block.builders.intro.add_block(deindent`
block.chunks.intro.push(b`
if (${name}) @transition_in(${name}.$$.fragment, #local);
`);
if (updates.length) {
block.builders.update.add_block(deindent`
else if (${switch_value}) {
${name}.$set(${name_changes});
}
`);
}
block.builders.outro.add_line(
`if (${name}) @transition_out(${name}.$$.fragment, #local);`
block.chunks.outro.push(
b`if (${name}) @transition_out(${name}.$$.fragment, #local);`
);
block.builders.destroy.add_line(`if (${name}) @destroy_component(${name}${parent_node ? '' : ', detaching'});`);
block.chunks.destroy.push(b`if (${name}) @destroy_component(${name}, ${parent_node ? null : 'detaching'});`);
} else {
const expression = this.node.name === 'svelte:self'
? '__svelte:self__' // TODO conflict-proof this
? component.name
: component.qualify(this.node.name);
block.builders.init.add_block(deindent`
${(this.node.attributes.length || this.node.bindings.length) && deindent`
${props && `let ${props} = ${attribute_object};`}`}
block.chunks.init.push(b`
${(this.node.attributes.length > 0 || this.node.bindings.length > 0) && b`
${props && b`let ${props} = ${attribute_object};`}`}
${statements}
var ${name} = new ${expression}(${stringify_props(component_opts)});
const ${name} = new ${expression}(${component_opts});
${munged_bindings}
${munged_handlers}
`);
block.builders.create.add_line(`${name}.$$.fragment.c();`);
block.chunks.create.push(b`@create_component(${name}.$$.fragment);`);
if (parent_nodes && this.renderer.options.hydratable) {
block.builders.claim.add_line(
`${name}.$$.fragment.l(${parent_nodes});`
block.chunks.claim.push(
b`@claim_component(${name}.$$.fragment, ${parent_nodes});`
);
}
block.builders.mount.add_line(
`@mount_component(${name}, ${parent_node || '#target'}, ${parent_node ? 'null' : 'anchor'});`
block.chunks.mount.push(
b`@mount_component(${name}, ${parent_node || '#target'}, ${parent_node ? 'null' : 'anchor'});`
);
block.builders.intro.add_block(deindent`
block.chunks.intro.push(b`
@transition_in(${name}.$$.fragment, #local);
`);
if (updates.length) {
block.builders.update.add_block(deindent`
block.chunks.update.push(b`
${updates}
${name}.$set(${name_changes});
`);
}
block.builders.destroy.add_block(deindent`
@destroy_component(${name}${parent_node ? '' : ', detaching'});
block.chunks.destroy.push(b`
@destroy_component(${name}, ${parent_node ? null : 'detaching'});
`);
block.builders.outro.add_line(
`@transition_out(${name}.$$.fragment, #local);`
block.chunks.outro.push(
b`@transition_out(${name}.$$.fragment, #local);`
);
}
}

@ -4,25 +4,26 @@ import Tag from './shared/Tag';
import Wrapper from './shared/Wrapper';
import MustacheTag from '../../nodes/MustacheTag';
import RawMustacheTag from '../../nodes/RawMustacheTag';
import { x } from 'code-red';
import { Identifier } from 'estree';
export default class MustacheTagWrapper extends Tag {
var = 't';
var: Identifier = { type: 'Identifier', name: 't' };
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: MustacheTag | RawMustacheTag) {
super(renderer, block, parent, node);
this.cannot_use_innerhtml();
}
render(block: Block, parent_node: string, parent_nodes: string) {
render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
const { init } = this.rename_this_method(
block,
value => `@set_data(${this.var}, ${value});`
value => x`@set_data(${this.var}, ${value});`
);
block.add_element(
this.var,
`@text(${init})`,
parent_nodes && `@claim_text(${parent_nodes}, ${init})`,
x`@text(${init})`,
parent_nodes && x`@claim_text(${parent_nodes}, ${init})`,
parent_node
);
}

@ -1,12 +1,15 @@
import { b, x } from 'code-red';
import Renderer from '../Renderer';
import Block from '../Block';
import Tag from './shared/Tag';
import Wrapper from './shared/Wrapper';
import MustacheTag from '../../nodes/MustacheTag';
import RawMustacheTag from '../../nodes/RawMustacheTag';
import { is_head } from './shared/is_head';
import { Identifier } from 'estree';
export default class RawMustacheTagWrapper extends Tag {
var = 'raw';
var: Identifier = { type: 'Identifier', name: 'raw' };
constructor(
renderer: Renderer,
@ -16,22 +19,23 @@ export default class RawMustacheTagWrapper extends Tag {
) {
super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
}
render(block: Block, parent_node: string, _parent_nodes: string) {
const in_head = parent_node === '@_document.head';
render(block: Block, parent_node: Identifier, _parent_nodes: Identifier) {
const in_head = is_head(parent_node);
const can_use_innerhtml = !in_head && parent_node && !this.prev && !this.next;
if (can_use_innerhtml) {
const insert = content => `${parent_node}.innerHTML = ${content};`;
const insert = content => b`${parent_node}.innerHTML = ${content};`[0];
const { init } = this.rename_this_method(
block,
content => insert(content)
);
block.builders.mount.add_line(insert(init));
block.chunks.mount.push(insert(init));
}
else {
@ -44,20 +48,20 @@ export default class RawMustacheTagWrapper extends Tag {
const { init } = this.rename_this_method(
block,
content => `${html_tag}.p(${content});`
content => x`${html_tag}.p(${content});`
);
const update_anchor = in_head ? 'null' : needs_anchor ? html_anchor : this.next ? this.next.var : 'null';
block.builders.hydrate.add_line(`${html_tag} = new @HtmlTag(${init}, ${update_anchor});`);
block.builders.mount.add_line(`${html_tag}.m(${parent_node || '#target'}${parent_node ? '' : ', anchor'});`);
block.chunks.hydrate.push(b`${html_tag} = new @HtmlTag(${init}, ${update_anchor});`);
block.chunks.mount.push(b`${html_tag}.m(${parent_node || '#target'}, ${parent_node ? null : 'anchor'});`);
if (needs_anchor) {
block.add_element(html_anchor, '@empty()', '@empty()', parent_node);
block.add_element(html_anchor, x`@empty()`, x`@empty()`, parent_node);
}
if (!parent_node || in_head) {
block.builders.destroy.add_conditional('detaching', `${html_tag}.d();`);
block.chunks.destroy.push(b`if (detaching) ${html_tag}.d();`);
}
}
}

@ -3,19 +3,20 @@ import Renderer from '../Renderer';
import Block from '../Block';
import Slot from '../../nodes/Slot';
import FragmentWrapper from './Fragment';
import deindent from '../../utils/deindent';
import { sanitize, quote_prop_if_necessary } from '../../../utils/names';
import { b, p, x } from 'code-red';
import { sanitize } from '../../../utils/names';
import add_to_set from '../../utils/add_to_set';
import get_slot_data from '../../utils/get_slot_data';
import { stringify_props } from '../../utils/stringify_props';
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;
fragment: FragmentWrapper;
var = 'slot';
var: Identifier = { type: 'Identifier', name: 'slot' };
dependencies: Set<string> = new Set(['$$scope']);
constructor(
@ -28,6 +29,7 @@ export default class SlotWrapper extends Wrapper {
) {
super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
this.fragment = new FragmentWrapper(
renderer,
@ -51,8 +53,8 @@ export default class SlotWrapper extends Wrapper {
render(
block: Block,
parent_node: string,
parent_nodes: string
parent_node: Identifier,
parent_nodes: Identifier
) {
const { renderer } = this;
@ -65,8 +67,8 @@ export default class SlotWrapper extends Wrapper {
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`);
const context_props = get_slot_data(this.node.values, false);
const changes_props = [];
const context = get_slot_data(this.node.values);
const changes = x`{}` as ObjectExpression;
const dependencies = new Set();
@ -90,15 +92,22 @@ export default class SlotWrapper extends Wrapper {
});
if (dynamic_dependencies.length > 0) {
changes_props.push(`${attribute.name}: ${dynamic_dependencies.join(' || ')}`);
const expression = dynamic_dependencies
.map(name => ({ type: 'Identifier', name } as any))
.reduce((lhs, rhs) => x`${lhs} || ${rhs}`);
changes.properties.push(p`${attribute.name}: ${expression}`);
}
});
const arg = dependencies.size > 0 ? `{ ${Array.from(dependencies).join(', ')} }` : '';
const arg = dependencies.size > 0 && {
type: 'ObjectPattern',
properties: Array.from(dependencies).map(name => p`${name}`)
};
renderer.blocks.push(deindent`
const ${get_slot_changes} = (${arg}) => (${stringify_props(changes_props)});
const ${get_slot_context} = (${arg}) => (${stringify_props(context_props)});
renderer.blocks.push(b`
const ${get_slot_changes} = (${arg}) => (${changes});
const ${get_slot_context} = (${arg}) => (${context});
`);
} else {
get_slot_changes = 'null';
@ -108,57 +117,63 @@ export default class SlotWrapper extends Wrapper {
const slot = block.get_unique_name(`${sanitize(slot_name)}_slot`);
const slot_definition = block.get_unique_name(`${sanitize(slot_name)}_slot_template`);
block.builders.init.add_block(deindent`
const ${slot_definition} = ctx.$$slots${quote_prop_if_necessary(slot_name)};
const ${slot} = @create_slot(${slot_definition}, ctx, ${get_slot_context});
block.chunks.init.push(b`
const ${slot_definition} = #ctx.$$slots.${slot_name};
const ${slot} = @create_slot(${slot_definition}, #ctx, ${get_slot_context});
`);
const mount_before = block.builders.mount.toString();
// TODO this is a dreadful hack! Should probably make this nicer
const { create, claim, hydrate, mount, update, destroy } = block.chunks;
block.builders.create.push_condition(`!${slot}`);
block.builders.claim.push_condition(`!${slot}`);
block.builders.hydrate.push_condition(`!${slot}`);
block.builders.mount.push_condition(`!${slot}`);
block.builders.update.push_condition(`!${slot}`);
block.builders.destroy.push_condition(`!${slot}`);
block.chunks.create = [];
block.chunks.claim = [];
block.chunks.hydrate = [];
block.chunks.mount = [];
block.chunks.update = [];
block.chunks.destroy = [];
const listeners = block.event_listeners;
block.event_listeners = [];
this.fragment.render(block, parent_node, parent_nodes);
block.render_listeners(`_${slot}`);
block.render_listeners(`_${slot.name}`);
block.event_listeners = listeners;
block.builders.create.pop_condition();
block.builders.claim.pop_condition();
block.builders.hydrate.pop_condition();
block.builders.mount.pop_condition();
block.builders.update.pop_condition();
block.builders.destroy.pop_condition();
block.builders.create.add_line(
`if (${slot}) ${slot}.c();`
);
block.builders.claim.add_line(
`if (${slot}) ${slot}.l(${parent_nodes});`
if (block.chunks.create.length) create.push(b`if (!${slot}) { ${block.chunks.create} }`);
if (block.chunks.claim.length) claim.push(b`if (!${slot}) { ${block.chunks.claim} }`);
if (block.chunks.hydrate.length) hydrate.push(b`if (!${slot}) { ${block.chunks.hydrate} }`);
if (block.chunks.mount.length) mount.push(b`if (!${slot}) { ${block.chunks.mount} }`);
if (block.chunks.update.length) update.push(b`if (!${slot}) { ${block.chunks.update} }`);
if (block.chunks.destroy.length) destroy.push(b`if (!${slot}) { ${block.chunks.destroy} }`);
block.chunks.create = create;
block.chunks.claim = claim;
block.chunks.hydrate = hydrate;
block.chunks.mount = mount;
block.chunks.update = update;
block.chunks.destroy = destroy;
block.chunks.create.push(
b`if (${slot}) ${slot}.c();`
);
const mount_leadin = block.builders.mount.toString() !== mount_before
? `else`
: `if (${slot})`;
if (renderer.options.hydratable) {
block.chunks.claim.push(
b`if (${slot}) ${slot}.l(${parent_nodes});`
);
}
block.builders.mount.add_block(deindent`
${mount_leadin} {
block.chunks.mount.push(b`
if (${slot}) {
${slot}.m(${parent_node || '#target'}, ${parent_node ? 'null' : 'anchor'});
}
`);
block.builders.intro.add_line(
`@transition_in(${slot}, #local);`
block.chunks.intro.push(
b`@transition_in(${slot}, #local);`
);
block.builders.outro.add_line(
`@transition_out(${slot}, #local);`
block.chunks.outro.push(
b`@transition_out(${slot}, #local);`
);
const dynamic_dependencies = Array.from(this.dependencies).filter(name => {
@ -168,20 +183,17 @@ export default class SlotWrapper extends Wrapper {
return is_dynamic(variable);
});
let update_conditions = dynamic_dependencies.map(name => `changed.${name}`).join(' || ');
if (dynamic_dependencies.length > 1) update_conditions = `(${update_conditions})`;
block.builders.update.add_block(deindent`
if (${slot} && ${slot}.p && ${update_conditions}) {
block.chunks.update.push(b`
if (${slot} && ${slot}.p && ${changed(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_changes(${slot_definition}, #ctx, #changed, ${get_slot_changes}),
@get_slot_context(${slot_definition}, #ctx, ${get_slot_context})
);
}
`);
block.builders.destroy.add_line(
`if (${slot}) ${slot}.d(detaching);`
block.chunks.destroy.push(
b`if (${slot}) ${slot}.d(detaching);`
);
}
}

@ -2,7 +2,8 @@ import Renderer from '../Renderer';
import Block from '../Block';
import Text from '../../nodes/Text';
import Wrapper from './shared/Wrapper';
import { stringify } from '../../utils/stringify';
import { x } from 'code-red';
import { Identifier } from 'estree';
// Whitespace inside one of these elements will not result in
// a whitespace node being created in any circumstances. (This
@ -33,7 +34,7 @@ export default class TextWrapper extends Wrapper {
node: Text;
data: string;
skip: boolean;
var: string;
var: Identifier;
constructor(
renderer: Renderer,
@ -46,7 +47,7 @@ export default class TextWrapper extends Wrapper {
this.skip = should_skip(this.node);
this.data = data;
this.var = this.skip ? null : 't';
this.var = (this.skip ? null : x`t`) as unknown as Identifier;
}
use_space() {
@ -64,15 +65,15 @@ export default class TextWrapper extends Wrapper {
return true;
}
render(block: Block, parent_node: string, parent_nodes: string) {
render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
if (this.skip) return;
const use_space = this.use_space();
block.add_element(
this.var,
use_space ? `@space()` : `@text(${stringify(this.data)})`,
parent_nodes && (use_space ? `@claim_space(${parent_nodes})` : `@claim_text(${parent_nodes}, ${stringify(this.data)})`),
parent_node
use_space ? x`@space()` : x`@text("${this.data}")`,
parent_nodes && (use_space ? x`@claim_space(${parent_nodes})` : x`@claim_text(${parent_nodes}, "${this.data}")`),
parent_node as Identifier
);
}
}

@ -1,10 +1,14 @@
import { b, x } from 'code-red';
import Wrapper from './shared/Wrapper';
import Renderer from '../Renderer';
import Block from '../Block';
import Title from '../../nodes/Title';
import { stringify } from '../../utils/stringify';
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 {
node: Title;
@ -20,13 +24,13 @@ export default class TitleWrapper extends Wrapper {
super(renderer, block, parent, node);
}
render(block: Block, _parent_node: string, _parent_nodes: string) {
render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) {
const is_dynamic = !!this.node.children.find(node => node.type !== 'Text');
if (is_dynamic) {
let value;
const all_dependencies = new Set();
const all_dependencies: Set<string> = new Set();
// 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
@ -34,29 +38,25 @@ export default class TitleWrapper extends Wrapper {
// single {tag} — may be a non-string
// @ts-ignore todo: check this
const { expression } = this.node.children[0];
value = expression.render(block);
value = expression.manipulate(block);
add_to_set(all_dependencies, expression.dependencies);
} else {
// '{foo} {bar}' — treat as string concatenation
value =
(this.node.children[0].type === 'Text' ? '' : `"" + `) +
this.node.children
.map((chunk) => {
if (chunk.type === 'Text') {
return stringify(chunk.data);
} else {
// @ts-ignore todo: check this
const snippet = chunk.expression.render(block);
// @ts-ignore todo: check this
chunk.expression.dependencies.forEach(d => {
all_dependencies.add(d);
});
// @ts-ignore todo: check this
return chunk.expression.get_precedence() <= 13 ? `(${snippet})` : snippet;
}
})
.join(' + ');
value = this.node.children
.map(chunk => {
if (chunk.type === 'Text') return string_literal(chunk.data);
(chunk as MustacheTag).expression.dependencies.forEach(d => {
all_dependencies.add(d);
});
return (chunk as MustacheTag).expression.manipulate(block);
})
.reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
if (this.node.children[0].type !== 'Text') {
value = x`"" + ${value}`;
}
}
const last = this.node.should_cache && block.get_unique_name(
@ -65,37 +65,38 @@ export default class TitleWrapper extends Wrapper {
if (this.node.should_cache) block.add_variable(last);
const init = this.node.should_cache ? `${last} = ${value}` : value;
const init = this.node.should_cache ? x`${last} = ${value}` : value;
block.builders.init.add_line(
`@_document.title = ${init};`
block.chunks.init.push(
b`@_document.title = ${init};`
);
const updater = `@_document.title = ${this.node.should_cache ? last : value};`;
const updater = b`@_document.title = ${this.node.should_cache ? last : value};`;
if (all_dependencies.size) {
const dependencies = Array.from(all_dependencies);
const changed_check = (
(block.has_outros ? `!#current || ` : '') +
dependencies.map(dependency => `changed.${dependency}`).join(' || ')
);
const update_cached_value = `${last} !== (${last} = ${value})`;
let condition = changed(dependencies);
if (block.has_outros) {
condition = x`!#current || ${condition}`;
}
const condition = this.node.should_cache ?
(dependencies.length ? `(${changed_check}) && ${update_cached_value}` : update_cached_value) :
changed_check;
if (this.node.should_cache) {
condition = x`${condition} && (${last} !== (${last} = ${value}))`;
}
block.builders.update.add_conditional(
condition,
updater
);
block.chunks.update.push(b`
if (${condition}) {
${updater}
}`);
}
} else {
const value = this.node.children.length > 0
? stringify((this.node.children[0] as Text).data)
: '""';
? string_literal((this.node.children[0] as Text).data)
: x`""`;
block.builders.hydrate.add_line(`@_document.title = ${value};`);
block.chunks.hydrate.push(b`@_document.title = ${value};`);
}
}
}

@ -1,11 +1,14 @@
import Renderer from '../Renderer';
import Block from '../Block';
import Wrapper from './shared/Wrapper';
import deindent from '../../utils/deindent';
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 { INode } from '../../nodes/interfaces';
import { changed } from './shared/changed';
import { Identifier } from 'estree';
import { TemplateNode } from '../../../interfaces';
import EventHandler from './Element/EventHandler';
const associated_events = {
innerWidth: 'resize',
@ -32,12 +35,14 @@ const readonly = new Set([
export default class WindowWrapper extends Wrapper {
node: Window;
handlers: EventHandler[];
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: INode) {
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: string, _parent_nodes: string) {
render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) {
const { renderer } = this;
const { component } = renderer;
@ -45,7 +50,7 @@ export default class WindowWrapper extends Wrapper {
const bindings: Record<string, string> = {};
add_actions(component, block, '@_window', this.node.actions);
add_event_handlers(block, '@_window', this.node.handlers);
add_event_handlers(block, '@_window', this.handlers);
this.node.bindings.forEach(binding => {
// in dev mode, throw if read-only values are written to
@ -73,65 +78,64 @@ export default class WindowWrapper extends Wrapper {
const scrolling_timeout = block.get_unique_name(`scrolling_timeout`);
Object.keys(events).forEach(event => {
const handler_name = block.get_unique_name(`onwindow${event}`);
const id = block.get_unique_name(`onwindow${event}`);
const props = events[event];
if (event === 'scroll') {
// TODO other bidirectional bindings...
block.add_variable(scrolling, 'false');
block.add_variable(clear_scrolling, `() => { ${scrolling} = false }`);
block.add_variable(scrolling, x`false`);
block.add_variable(clear_scrolling, x`() => { ${scrolling} = false }`);
block.add_variable(scrolling_timeout);
const condition = [
bindings.scrollX && `"${bindings.scrollX}" in this._state`,
bindings.scrollY && `"${bindings.scrollY}" in this._state`
].filter(Boolean).join(' || ');
const condition = bindings.scrollX && bindings.scrollY
? x`"${bindings.scrollX}" in this._state || "${bindings.scrollY}" in this._state`
: x`"${bindings.scrollX || bindings.scrollY}" in this._state`;
const x = bindings.scrollX && `this._state.${bindings.scrollX}`;
const y = bindings.scrollY && `this._state.${bindings.scrollY}`;
const scrollX = bindings.scrollX && x`this._state.${bindings.scrollX}`;
const scrollY = bindings.scrollY && x`this._state.${bindings.scrollY}`;
renderer.meta_bindings.add_block(deindent`
renderer.meta_bindings.push(b`
if (${condition}) {
@_scrollTo(${x || '@_window.pageXOffset'}, ${y || '@_window.pageYOffset'});
@_scrollTo(${scrollX || '@_window.pageXOffset'}, ${scrollY || '@_window.pageYOffset'});
}
${x && `${x} = @_window.pageXOffset;`}
${y && `${y} = @_window.pageYOffset;`}
${scrollX && `${scrollX} = @_window.pageXOffset;`}
${scrollY && `${scrollY} = @_window.pageYOffset;`}
`);
block.event_listeners.push(deindent`
block.event_listeners.push(x`
@listen(@_window, "${event}", () => {
${scrolling} = true;
@_clearTimeout(${scrolling_timeout});
${scrolling_timeout} = @_setTimeout(${clear_scrolling}, 100);
ctx.${handler_name}();
#ctx.${id}();
})
`);
} else {
props.forEach(prop => {
renderer.meta_bindings.add_line(
`this._state.${prop.name} = @_window.${prop.value};`
renderer.meta_bindings.push(
b`this._state.${prop.name} = @_window.${prop.value};`
);
});
block.event_listeners.push(deindent`
@listen(@_window, "${event}", ctx.${handler_name})
block.event_listeners.push(x`
@listen(@_window, "${event}", #ctx.${id})
`);
}
component.add_var({
name: handler_name,
name: id.name,
internal: true,
referenced: true
});
component.partly_hoisted.push(deindent`
function ${handler_name}() {
${props.map(prop => `${prop.name} = @_window.${prop.value}; $$invalidate('${prop.name}', ${prop.name});`)}
component.partly_hoisted.push(b`
function ${id}() {
${props.map(prop => component.invalidate(prop.name, x`${prop.name} = @_window.${prop.value}`))}
}
`);
block.builders.init.add_block(deindent`
@add_render_callback(ctx.${handler_name});
block.chunks.init.push(b`
@add_render_callback(#ctx.${id});
`);
component.has_reactive_assignments = true;
@ -139,19 +143,15 @@ 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) {
block.builders.update.add_block(deindent`
if (${
[bindings.scrollX, bindings.scrollY].filter(Boolean).map(
b => `changed.${b}`
).join(' || ')
} && !${scrolling}) {
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`;
block.chunks.update.push(b`
if (${condition} && !${scrolling}) {
${scrolling} = true;
@_clearTimeout(${scrolling_timeout});
@_scrollTo(${
bindings.scrollX ? `ctx.${bindings.scrollX}` : `@_window.pageXOffset`
}, ${
bindings.scrollY ? `ctx.${bindings.scrollY}` : `@_window.pageYOffset`
});
@_scrollTo(${scrollX}, ${scrollY});
${scrolling_timeout} = @_setTimeout(${clear_scrolling}, 100);
}
`);
@ -159,28 +159,28 @@ export default class WindowWrapper extends Wrapper {
// another special case. (I'm starting to think these are all special cases.)
if (bindings.online) {
const handler_name = block.get_unique_name(`onlinestatuschanged`);
const id = block.get_unique_name(`onlinestatuschanged`);
const name = bindings.online;
component.add_var({
name: handler_name,
name: id.name,
internal: true,
referenced: true
});
component.partly_hoisted.push(deindent`
function ${handler_name}() {
${name} = @_navigator.onLine; $$invalidate('${name}', ${name});
component.partly_hoisted.push(b`
function ${id}() {
${component.invalidate(name, x`${name} = @_navigator.onLine`)}
}
`);
block.builders.init.add_block(deindent`
@add_render_callback(ctx.${handler_name});
block.chunks.init.push(b`
@add_render_callback(#ctx.${id});
`);
block.event_listeners.push(
`@listen(@_window, "online", ctx.${handler_name})`,
`@listen(@_window, "offline", ctx.${handler_name})`
x`@listen(@_window, "online", #ctx.${id})`,
x`@listen(@_window, "offline", #ctx.${id})`
);
component.has_reactive_assignments = true;

@ -1,47 +1,58 @@
import { b, x } from 'code-red';
import Wrapper from './Wrapper';
import Renderer from '../../Renderer';
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;
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: MustacheTag | RawMustacheTag) {
super(renderer, block, parent, node);
this.cannot_use_innerhtml();
if (!this.is_dependencies_static()) {
this.not_static_content();
}
block.add_dependencies(node.expression.dependencies);
}
is_dependencies_static() {
return this.node.expression.contextual_dependencies.size === 0 && this.node.expression.dynamic_dependencies().length === 0;
}
rename_this_method(
block: Block,
update: ((value: string) => string)
update: ((value: Node) => (Node | Node[]))
) {
const dependencies = this.node.expression.dynamic_dependencies();
const snippet = this.node.expression.render(block);
let snippet = this.node.expression.manipulate(block);
const value = this.node.should_cache && block.get_unique_name(`${this.var}_value`);
const value = this.node.should_cache && block.get_unique_name(`${this.var.name}_value`);
const content = this.node.should_cache ? value : snippet;
if (this.node.should_cache) block.add_variable(value, `${snippet} + ""`);
snippet = x`${snippet} + ""`;
if (this.node.should_cache) block.add_variable(value, snippet); // TODO may need to coerce snippet to string
if (dependencies.length > 0) {
const changed_check = (
(block.has_outros ? `!#current || ` : '') +
dependencies.map((dependency: string) => `changed.${dependency}`).join(' || ')
);
let condition = changed(dependencies);
if (block.has_outros) {
condition = x`!#current || ${condition}`;
}
const update_cached_value = `${value} !== (${value} = ${snippet} + "")`;
const update_cached_value = x`${value} !== (${value} = ${snippet})`;
const condition = this.node.should_cache
? `(${changed_check}) && ${update_cached_value}`
: changed_check;
if (this.node.should_cache) {
condition = x`${condition} && ${update_cached_value}`;
}
block.builders.update.add_conditional(
condition,
update(content)
);
block.chunks.update.push(b`if (${condition}) ${update(content as Node)}`);
}
return { init: content };

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

Loading…
Cancel
Save