Merge remote-tracking branch 'origin/version-4' into sites

pull/8585/head
Puru Vijay 1 year ago
commit fbd3ccdf80

@ -1,6 +1,6 @@
module.exports = { module.exports = {
root: true, root: true,
extends: '@sveltejs', extends: ['@sveltejs', 'prettier'],
settings: { settings: {
'import/core-modules': [ 'import/core-modules': [
'svelte', 'svelte',
@ -10,5 +10,8 @@ module.exports = {
'estree' 'estree'
], ],
'svelte3/compiler': require('./compiler') 'svelte3/compiler': require('./compiler')
},
rules: {
'@typescript-eslint/no-non-null-assertion': 'off'
} }
}; };

@ -1,3 +1,7 @@
# HEADS UP: BIG RESTRUCTURING UNDERWAY
The Svelte repo is currently in the process of heavy restructuring for Svelte 4. After that, work on Svelte 5 will likely change a lot on the compiler aswell. For that reason, please don't open PRs that are large in scope, touch more than a couple of files etc. In other words, bug fixes are fine, but feature PRs will likely not be merged.
### Before submitting the PR, please make sure you do the following ### Before submitting the PR, please make sure you do the following
- [ ] It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs - [ ] It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs

@ -6,73 +6,32 @@ on:
permissions: permissions:
contents: read # to fetch code (actions/checkout) contents: read # to fetch code (actions/checkout)
jobs: jobs:
Setup:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
cache: npm
- run: npm install
env:
SKIP_PREPARE: true
- run: npm run build
env:
PUBLISH: true
- name: Upload build assets
id: upload-artifact
uses: actions/upload-artifact@v3
with:
name: build-assets
path: |
index.*
compiler.*
ssr.*
action/
animate/
easing/
internal/
motion/
store/
transition/
types/
Tests: Tests:
needs: Setup
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
timeout-minutes: 15 timeout-minutes: 15
strategy: strategy:
matrix: matrix:
node-version: [8, 10, 12, 14, 16, 18] include:
os: [ubuntu-latest, windows-latest, macOS-latest] - node-version: 16
os: ubuntu-latest
- node-version: 16
os: windows-latest
- node-version: 16
os: macOS-latest
- node-version: 18
os: ubuntu-latest
- node-version: 20
os: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: pnpm/action-setup@v2.2.4
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
cache: npm cache: pnpm
- name: Download build assets - run: pnpm install --frozen-lockfile
uses: actions/download-artifact@v3 - run: node node_modules/puppeteer/install.js
id: download-artifact - run: pnpm test:integration
with:
name: build-assets
- name: Get Node version ${{ runner.os }}
run: echo "NODE_VERSION=`node --version`" >> $GITHUB_ENV
if: runner.os != 'Windows'
- name: Get Node version ${{ runner.os }}
run: |
chcp 65001
echo ("NODE_VERSION=$(node --version)") >> $env:GITHUB_ENV
if: runner.os == 'Windows'
- run: npm install --save-dev puppeteer@13
if: ${{ runner.os == 'Linux' && (!startsWith(env.NODE_VERSION, 'v8.') && !startsWith(env.NODE_VERSION, 'v10.')) }}
- run: npm install
env:
SKIP_PREPARE: true
- run: npm run test:integration
env: env:
CI: true CI: true
Lint: Lint:
@ -80,22 +39,34 @@ jobs:
timeout-minutes: 5 timeout-minutes: 5
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: pnpm/action-setup@v2.2.4
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
cache: npm node-version: 16
- run: 'npm i && npm run lint' cache: pnpm
- run: 'pnpm i && pnpm format:check && pnpm lint'
Unit: Unit:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
timeout-minutes: 10 timeout-minutes: 10
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest, windows-latest, macOS-latest] include:
- node-version: 16
os: ubuntu-latest
- node-version: 16
os: windows-latest
- node-version: 16
os: macOS-latest
- node-version: 18
os: ubuntu-latest
- node-version: 20
os: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: pnpm/action-setup@v2.2.4
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
cache: npm node-version: ${{ matrix.node-version }}
- run: npm install cache: pnpm
env: - run: pnpm install
SKIP_PREPARE: true - run: pnpm test:unit
- run: npm run test:unit

@ -1,9 +1,12 @@
const is_unit_test = process.env.UNIT_TEST; const is_unit_test = process.env.UNIT_TEST;
module.exports = { module.exports = {
file: is_unit_test ? [] : ['test/test.ts'], file: is_unit_test ? [] : ['test/test.js'],
require: [ require: [
'sucrase/register' 'sucrase/register'
],
"node-option": [
"experimental-modules"
] ]
}; };

@ -1,11 +1,12 @@
/*.js /*
/*.mjs !/elements
/package.json !/scripts
package-lock.json !/src
elements/* src/compiler/compile/internal_exports.ts
scripts/* !/test
/src !site
/test /test/**/*.svelte
.svelte-kit /test/**/_expected*
types /test/**/_actual*
.vercel /test/**/expected*
/types

@ -1,8 +1,23 @@
{ {
"singleQuote": true,
"printWidth": 100,
"useTabs": true, "useTabs": true,
"tabWidth": 2, "singleQuote": true,
"trailingComma": "none", "trailingComma": "none",
"plugins": ["prettier-plugin-svelte"] "printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"overrides": [
{
"files": ["*.svelte"],
"options": {
"bracketSameLine": false
}
},
{
"files": ["README.md", "packages/*/README.md"],
"options": {
"useTabs": false,
"tabWidth": 2
}
}
],
"pluginSearchDirs": ["."]
} }

@ -1,12 +1,46 @@
# Svelte changelog # Svelte changelog
## Unreleased ## Unreleased (4.0)
* Handle `width`/`height` attributes when spreading ([#6752](https://github.com/sveltejs/svelte/issues/6752)) * **breaking** Minimum supported Node version is now Node 16 ([#8566](https://github.com/sveltejs/svelte/pull/8566))
* Add support for resize observer bindings (`<div bind:contentRect|contentBoxSize|borderBoxSize|devicePixelContentBoxSize>`) ([#8022](https://github.com/sveltejs/svelte/pull/8022)) * **breaking** Minimum supported webpack version is now webpack 5 ([#8515](https://github.com/sveltejs/svelte/pull/8515))
* Update interpolated style directive properly when using spread ([#8438](https://github.com/sveltejs/svelte/issues/8438)) * **breaking** Bundlers must specify the `browser` condition when building a frontend bundle for the browser ([#8516](https://github.com/sveltejs/svelte/pull/8516))
* Remove style directive property when value is `undefined` ([#8462](https://github.com/sveltejs/svelte/issues/8462)) * **breaking** Minimum supported vite-plugin-svelte version is now 2.1.1. SvelteKit users can upgrade to 1.15.9 or newer to ensure a compatible version ([#8516](https://github.com/sveltejs/svelte/pull/8516))
* Ensure version is typed as `string` instead of the literal `__VERSION__` ([#8498](https://github.com/sveltejs/svelte/issues/8498)) * **breaking** Minimum supported TypeScript version is now TypeScript 5 (it will likely work with lower versions, but we make no guarantees about that) ([#8488](https://github.com/sveltejs/svelte/pull/8488))
* **breaking** Stricter types for `createEventDispatcher` (see PR for migration instructions) ([#7224](https://github.com/sveltejs/svelte/pull/7224))
* **breaking** Stricter types for `Action` and `ActionReturn` (see PR for migration instructions) ([#7224](https://github.com/sveltejs/svelte/pull/7224))
* **breaking** Stricter types for `onMount` - now throws a type error when returning a function asynchronously to catch potential mistakes around callback functions (see PR for migration instructions) ([#8136](https://github.com/sveltejs/svelte/pull/8136))
* **breaking** Overhaul and drastically improve creating custom elements with Svelte (see PR for list of changes and migration instructions) ([#8457](https://github.com/sveltejs/svelte/pull/8457))
* **breaking** Deprecate `SvelteComponentTyped`, use `SvelteComponent` instead ([#8512](https://github.com/sveltejs/svelte/pull/8512))
* **breaking** Error on falsy values instead of stores passed to `derived` ([#7947](https://github.com/sveltejs/svelte/pull/7947))
* **breaking** Custom store implementers now need to pass an `update` function additionally to the `set` function ([#6750](https://github.com/sveltejs/svelte/pull/6750))
* Improve hydration speed by adding `data-svelte-h` attribute to detect unchanged HTML elements ([#7426](https://github.com/sveltejs/svelte/pull/7426))
* Add `a11y no-noninteractive-element-interactions` rule ([#8391](https://github.com/sveltejs/svelte/pull/8391))
* Add `a11y-no-static-element-interactions`rule ([#8251](https://github.com/sveltejs/svelte/pull/8251))
* Improve duplicate key error for keyed `each` blocks ([#8411](https://github.com/sveltejs/svelte/pull/8411))
* Bind `null` option and input values consistently ([#8312](https://github.com/sveltejs/svelte/issues/8312))
* Allow `$store` to be used with changing values including nullish values ([#7555](https://github.com/sveltejs/svelte/issues/7555))
* Initialize stylesheet with `/* empty */` to enable setting CSP directive that also works in Safari ([#7800](https://github.com/sveltejs/svelte/pull/7800))
* Treat slots as if they don't exist when using CSS adjacent and general sibling combinators ([#8284](https://github.com/sveltejs/svelte/issues/8284))
* Fix transitions so that they don't require a `style-src 'unsafe-inline'` Content Security Policy (CSP) ([#6662](https://github.com/sveltejs/svelte/issues/6662)).
* Explicitly disallow `var` declarations extending the reactive statement scope ([#6800](https://github.com/sveltejs/svelte/pull/6800))
## 3.59.1
* Handle dynamic values in `a11y-autocomplete-valid` ([#8567](https://github.com/sveltejs/svelte/pull/8567))
## 3.59.0
* Add `ResizeObserver` bindings `contentRect`/`contentBoxSize`/`borderBoxSize`/`devicePixelContentBoxSize` ([#8022](https://github.com/sveltejs/svelte/pull/8022))
* Add `devicePixelRatio` binding for `<svelte:window>` ([#8285](https://github.com/sveltejs/svelte/issues/8285))
* Add `fullscreenElement` and `visibilityState` bindings for `<svelte:document>` ([#8507](https://github.com/sveltejs/svelte/pull/8507))
* Add `a11y-autocomplete-valid` warning ([#8520](https://github.com/sveltejs/svelte/pull/8520))
* Fix handling of `width`/`height` attributes when spreading ([#6752](https://github.com/sveltejs/svelte/issues/6752))
* Fix updating of interpolated `style:` directive when using spread ([#8438](https://github.com/sveltejs/svelte/issues/8438))
* Remove `style:` directive property when value is `undefined` ([#8462](https://github.com/sveltejs/svelte/issues/8462))
* Fix type of `VERSION` compiler export ([#8498](https://github.com/sveltejs/svelte/issues/8498))
* Relax `a11y-no-redundant-roles` warning ([#8536](https://github.com/sveltejs/svelte/pull/8536))
* Handle nested array rest destructuring ([#8552](https://github.com/sveltejs/svelte/issues/8552), [#8554](https://github.com/sveltejs/svelte/issues/8554))
## 3.58.0 ## 3.58.0

@ -62,6 +62,8 @@ When [opening a new issue](https://github.com/sveltejs/svelte/issues/new/choose)
## Pull requests ## Pull requests
> HEADS UP: The Svelte repo is currently in the process of heavy restructuring for Svelte 4. After that, work on Svelte 5 will likely change a lot on the compiler aswell. For that reason, please don't open PRs that are large in scope, touch more than a couple of files etc. In other words, bug fixes are fine, but feature PRs will likely not be merged.
### Proposing a change ### 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.yml). 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.yml).
@ -72,9 +74,9 @@ Small pull requests are much easier to review and more likely to get merged.
### Installation ### Installation
1. Ensure you have [npm](https://www.npmjs.com/get-npm) installed. 1. Ensure you have [pnpm](https://pnpm.io/installation) installed.
1. After cloning the repository, run `npm install` in the root of the repository. 1. After cloning the repository, run `pnpm install` in the root of the repository.
1. To start a development server, run `npm run dev`. 1. To compile in watch mode, run `pnpm dev`.
### Creating a branch ### Creating a branch
@ -94,8 +96,8 @@ Test samples are kept in `/test/xxx/samples` folder.
#### Running tests #### Running tests
1. To run test, run `npm run test`. 1. To run test, run `pnpm test`.
1. To run test for a specific feature, you can use the `-g` (aka `--grep`) option. For example, to only run test involving transitions, run `npm run test -- -g transition`. 1. To run test for a specific feature, you can use the `-g` (aka `--grep`) option. For example, to only run test involving transitions, run `pnpm test -- -g transition`.
##### Running solo test ##### Running solo test
@ -106,11 +108,11 @@ Test samples are kept in `/test/xxx/samples` folder.
##### Updating `.expected` files ##### 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. 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`) 1. To update the content of the `.expected` file, run the test with `--update` flag. (`pnpm test --update`)
### Style guide ### 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`. [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 `pnpm lint`.
#### Code conventions #### Code conventions
@ -122,8 +124,8 @@ Test samples are kept in `/test/xxx/samples` folder.
Please make sure the following is done when submitting a pull request: Please make sure the following is done when submitting a pull request:
1. Describe your **test plan** in your pull request description. Make sure to test your changes. 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 code lints (`pnpm lint`).
1. Make sure your tests pass (`npm run test`). 1. Make sure your tests pass (`pnpm test`).
All pull requests should be opened against the `master` branch. Make sure the PR does only one thing, otherwise please split it. All pull requests should be opened against the `master` branch. Make sure the PR does only one thing, otherwise please split it.

@ -29,7 +29,7 @@ To install and work on Svelte locally:
```bash ```bash
git clone https://github.com/sveltejs/svelte.git git clone https://github.com/sveltejs/svelte.git
cd svelte cd svelte
npm install pnpm install
``` ```
> Do not use Yarn to install the dependencies, as the specific package versions in `package-lock.json` are used to build and test Svelte. > Do not use Yarn to install the dependencies, as the specific package versions in `package-lock.json` are used to build and test Svelte.
@ -37,13 +37,13 @@ npm install
To build the compiler and all the other modules included in the package: To build the compiler and all the other modules included in the package:
```bash ```bash
npm run build pnpm build
``` ```
To watch for changes and continually rebuild the package (this is useful if you're using [npm link](https://docs.npmjs.com/cli/link.html) to test out changes in a project locally): To watch for changes and continually rebuild the package (this is useful if you're using [`pnpm link`](https://pnpm.io/cli/link) to test out changes in a project locally):
```bash ```bash
npm run dev pnpm dev
``` ```
The compiler is written in [TypeScript](https://www.typescriptlang.org/), but don't let that put you off — it's basically just JavaScript with type annotations. You'll pick it up in no time. If you're using an editor other than [Visual Studio Code](https://code.visualstudio.com/), you may need to install a plugin in order to get syntax highlighting and code hints, etc. The compiler is written in [TypeScript](https://www.typescriptlang.org/), but don't let that put you off — it's basically just JavaScript with type annotations. You'll pick it up in no time. If you're using an editor other than [Visual Studio Code](https://code.visualstudio.com/), you may need to install a plugin in order to get syntax highlighting and code hints, etc.
@ -51,13 +51,13 @@ The compiler is written in [TypeScript](https://www.typescriptlang.org/), but do
### Running Tests ### Running Tests
```bash ```bash
npm run test pnpm test
``` ```
To filter tests, use `-g` (aka `--grep`). For example, to only run tests involving transitions: To filter tests, use `-g` (aka `--grep`). For example, to only run tests involving transitions:
```bash ```bash
npm run test -- -g transition pnpm test -- -g transition
``` ```
## svelte.dev ## svelte.dev

127
elements/index.d.ts vendored

@ -39,8 +39,9 @@ type Booleanish = boolean | 'true' | 'false';
// Event Handler Types // Event Handler Types
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
type EventHandler<E extends Event = Event, T extends EventTarget = Element> = type EventHandler<E extends Event = Event, T extends EventTarget = Element> = (
(event: E & { currentTarget: EventTarget & T}) => any; event: E & { currentTarget: EventTarget & T }
) => any;
export type ClipboardEventHandler<T extends EventTarget> = EventHandler<ClipboardEvent, T>; export type ClipboardEventHandler<T extends EventTarget> = EventHandler<ClipboardEvent, T>;
export type CompositionEventHandler<T extends EventTarget> = EventHandler<CompositionEvent, T>; export type CompositionEventHandler<T extends EventTarget> = EventHandler<CompositionEvent, T>;
@ -84,9 +85,9 @@ export interface DOMAttributes<T extends EventTarget> {
'on:beforeinput'?: EventHandler<InputEvent, T> | undefined | null; 'on:beforeinput'?: EventHandler<InputEvent, T> | undefined | null;
'on:input'?: FormEventHandler<T> | undefined | null; 'on:input'?: FormEventHandler<T> | undefined | null;
'on:reset'?: FormEventHandler<T> | undefined | null; 'on:reset'?: FormEventHandler<T> | undefined | null;
'on:submit'?: EventHandler<Event & { readonly submitter: HTMLElement | null; }, T> | undefined | null; // TODO make this SubmitEvent once we require TS>=4.4 'on:submit'?: EventHandler<SubmitEvent, T> | undefined | null;
'on:invalid'?: EventHandler<Event, T> | undefined | null; 'on:invalid'?: EventHandler<Event, T> | undefined | null;
'on:formdata'?: EventHandler<Event & { readonly formData: FormData; }, T> | undefined | null; // TODO make this FormDataEvent once we require TS>=4.4 'on:formdata'?: EventHandler<FormDataEvent, T> | undefined | null;
// Image Events // Image Events
'on:load'?: EventHandler | undefined | null; 'on:load'?: EventHandler | undefined | null;
@ -350,7 +351,19 @@ export interface AriaAttributes {
* Indicates what notifications the user agent will trigger when the accessibility tree within a live region is modified. * Indicates what notifications the user agent will trigger when the accessibility tree within a live region is modified.
* @see aria-atomic. * @see aria-atomic.
*/ */
'aria-relevant'?: 'additions' | 'additions removals' | 'additions text' | 'all' | 'removals' | 'removals additions' | 'removals text' | 'text' | 'text additions' | 'text removals' | undefined | null; 'aria-relevant'?:
| 'additions'
| 'additions removals'
| 'additions text'
| 'all'
| 'removals'
| 'removals additions'
| 'removals text'
| 'text'
| 'text additions'
| 'text removals'
| undefined
| null;
/** Indicates that user input is required on the element before a form may be submitted. */ /** Indicates that user input is required on the element before a form may be submitted. */
'aria-required'?: Booleanish | undefined | null; 'aria-required'?: Booleanish | undefined | null;
/** Defines a human-readable, author-localized description for the role of an element. */ /** Defines a human-readable, author-localized description for the role of an element. */
@ -470,14 +483,23 @@ export type AriaRole =
export interface HTMLAttributes<T extends EventTarget> extends AriaAttributes, DOMAttributes<T> { export interface HTMLAttributes<T extends EventTarget> extends AriaAttributes, DOMAttributes<T> {
// Standard HTML Attributes // Standard HTML Attributes
accesskey?: string | undefined | null; accesskey?: string | undefined | null;
autofocus?: boolean | undefined | null; autofocus?: boolean | undefined | null;
class?: string | undefined | null; class?: string | undefined | null;
contenteditable?: Booleanish | 'inherit' | undefined | null; contenteditable?: Booleanish | 'inherit' | undefined | null;
contextmenu?: string | undefined | null; contextmenu?: string | undefined | null;
dir?: string | undefined | null; dir?: string | undefined | null;
draggable?: Booleanish | undefined | null; draggable?: Booleanish | undefined | null;
enterkeyhint?: 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send' | undefined | null; enterkeyhint?:
| 'enter'
| 'done'
| 'go'
| 'next'
| 'previous'
| 'search'
| 'send'
| undefined
| null;
hidden?: boolean | undefined | null; hidden?: boolean | undefined | null;
id?: string | undefined | null; id?: string | undefined | null;
lang?: string | undefined | null; lang?: string | undefined | null;
@ -526,7 +548,17 @@ export interface HTMLAttributes<T extends EventTarget> extends AriaAttributes, D
* Hints at the type of data that might be entered by the user while editing the element or its contents * Hints at the type of data that might be entered by the user while editing the element or its contents
* @see https://html.spec.whatwg.org/multipage/interaction.html#input-modalities:-the-inputmode-attribute * @see https://html.spec.whatwg.org/multipage/interaction.html#input-modalities:-the-inputmode-attribute
*/ */
inputmode?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search' | undefined | null; inputmode?:
| 'none'
| 'text'
| 'tel'
| 'url'
| 'email'
| 'numeric'
| 'decimal'
| 'search'
| undefined
| null;
/** /**
* Specify that a standard HTML element should behave like a defined custom built-in element * Specify that a standard HTML element should behave like a defined custom built-in element
* @see https://html.spec.whatwg.org/multipage/custom-elements.html#attr-is * @see https://html.spec.whatwg.org/multipage/custom-elements.html#attr-is
@ -547,25 +579,32 @@ export interface HTMLAttributes<T extends EventTarget> extends AriaAttributes, D
'bind:innerText'?: string | undefined | null; 'bind:innerText'?: string | undefined | null;
readonly 'bind:contentRect'?: DOMRectReadOnly | undefined | null; readonly 'bind:contentRect'?: DOMRectReadOnly | undefined | null;
readonly 'bind:contentBoxSize'?: Array<{ blockSize: number; inlineSize: number }> | undefined | null; // TODO make this ResizeObserverSize once we require TS>=4.4 readonly 'bind:contentBoxSize'?: Array<ResizeObserverSize> | undefined | null;
readonly 'bind:borderBoxSize'?: Array<{ blockSize: number; inlineSize: number }> | undefined | null; // TODO make this ResizeObserverSize once we require TS>=4.4 readonly 'bind:borderBoxSize'?: Array<ResizeObserverSize> | undefined | null;
readonly 'bind:devicePixelContentBoxSize'?: Array<{ blockSize: number; inlineSize: number }> | undefined | null; // TODO make this ResizeObserverSize once we require TS>=4.4 readonly 'bind:devicePixelContentBoxSize'?: Array<ResizeObserverSize> | undefined | null;
// SvelteKit // SvelteKit
'data-sveltekit-keepfocus'?: true | '' | 'off' | undefined | null; 'data-sveltekit-keepfocus'?: true | '' | 'off' | undefined | null;
'data-sveltekit-noscroll'?: true | '' | 'off' | undefined | null; 'data-sveltekit-noscroll'?: true | '' | 'off' | undefined | null;
'data-sveltekit-preload-code'?: true | '' | 'eager' | 'viewport' | 'hover' | 'tap' | 'off' | undefined | null; 'data-sveltekit-preload-code'?:
| true
| ''
| 'eager'
| 'viewport'
| 'hover'
| 'tap'
| 'off'
| undefined
| null;
'data-sveltekit-preload-data'?: true | '' | 'hover' | 'tap' | 'off' | undefined | null; 'data-sveltekit-preload-data'?: true | '' | 'hover' | 'tap' | 'off' | undefined | null;
'data-sveltekit-reload'?: true | '' | 'off' | undefined | null; 'data-sveltekit-reload'?: true | '' | 'off' | undefined | null;
'data-sveltekit-replacestate'?: true | '' | 'off' | undefined | null; 'data-sveltekit-replacestate'?: true | '' | 'off' | undefined | null;
// allow any data- attribute
[key: `data-${string}`]: any;
} }
export type HTMLAttributeAnchorTarget = export type HTMLAttributeAnchorTarget = '_self' | '_blank' | '_parent' | '_top' | (string & {});
| '_self'
| '_blank'
| '_parent'
| '_top'
| (string & {});
export interface HTMLAnchorAttributes extends HTMLAttributes<HTMLAnchorElement> { export interface HTMLAnchorAttributes extends HTMLAttributes<HTMLAnchorElement> {
download?: any; download?: any;
@ -841,7 +880,14 @@ export interface HTMLMenuAttributes extends HTMLAttributes<HTMLMenuElement> {
export interface HTMLMediaAttributes<T extends HTMLMediaElement> extends HTMLAttributes<T> { export interface HTMLMediaAttributes<T extends HTMLMediaElement> extends HTMLAttributes<T> {
autoplay?: boolean | undefined | null; autoplay?: boolean | undefined | null;
controls?: boolean | undefined | null; controls?: boolean | undefined | null;
controlslist?: 'nodownload' | 'nofullscreen' | 'noplaybackrate' | 'noremoteplayback' | (string & {}) | undefined | null; controlslist?:
| 'nodownload'
| 'nofullscreen'
| 'noplaybackrate'
| 'noremoteplayback'
| (string & {})
| undefined
| null;
crossorigin?: string | undefined | null; crossorigin?: string | undefined | null;
currenttime?: number | undefined | null; currenttime?: number | undefined | null;
defaultmuted?: boolean | undefined | null; defaultmuted?: boolean | undefined | null;
@ -1152,9 +1198,22 @@ export interface SVGAttributes<T extends EventTarget> extends AriaAttributes, DO
'accent-height'?: number | string | undefined | null; 'accent-height'?: number | string | undefined | null;
accumulate?: 'none' | 'sum' | undefined | null; accumulate?: 'none' | 'sum' | undefined | null;
additive?: 'replace' | 'sum' | undefined | null; additive?: 'replace' | 'sum' | undefined | null;
'alignment-baseline'?: 'auto' | 'baseline' | 'before-edge' | 'text-before-edge' | 'middle' | 'alignment-baseline'?:
'central' | 'after-edge' | 'text-after-edge' | 'ideographic' | 'alphabetic' | 'hanging' | | 'auto'
'mathematical' | 'inherit' | undefined | null; | 'baseline'
| 'before-edge'
| 'text-before-edge'
| 'middle'
| 'central'
| 'after-edge'
| 'text-after-edge'
| 'ideographic'
| 'alphabetic'
| 'hanging'
| 'mathematical'
| 'inherit'
| undefined
| null;
allowReorder?: 'no' | 'yes' | undefined | null; allowReorder?: 'no' | 'yes' | undefined | null;
alphabetic?: number | string | undefined | null; alphabetic?: number | string | undefined | null;
amplitude?: number | string | undefined | null; amplitude?: number | string | undefined | null;
@ -1600,7 +1659,29 @@ export interface SvelteHTMLElements {
'svelte:document': SvelteDocumentAttributes; 'svelte:document': SvelteDocumentAttributes;
'svelte:body': HTMLAttributes<HTMLElement>; 'svelte:body': HTMLAttributes<HTMLElement>;
'svelte:fragment': { slot?: string }; 'svelte:fragment': { slot?: string };
'svelte:options': { [name: string]: any }; 'svelte:options': {
customElement?:
| string
| undefined
| {
tag: string;
shadow?: 'open' | 'none' | undefined;
props?:
| Record<
string,
{
attribute?: string;
reflect?: boolean;
type?: 'String' | 'Boolean' | 'Number' | 'Array' | 'Object';
}
>
| undefined;
};
immutable?: boolean | undefined;
accessors?: boolean | undefined;
namespace?: string | undefined;
[name: string]: any;
};
'svelte:head': { [name: string]: any }; 'svelte:head': { [name: string]: any };
[name: string]: { [name: string]: any }; [name: string]: { [name: string]: any };

@ -1,3 +1,3 @@
{ {
"types": "./index.d.ts" "types": "./index.d.ts"
} }

@ -1,23 +0,0 @@
// This script generates the TypeScript definitions
const { execSync } = require('child_process');
const { readFileSync, writeFileSync } = require('fs');
execSync('tsc -p src/compiler --emitDeclarationOnly && tsc -p src/runtime --emitDeclarationOnly', { stdio: 'inherit' });
// We need to add these types to the .d.ts files here because if we add them before building, the build will fail,
// because the TS->JS transformation doesn't know these exports are types and produces code that fails at runtime.
// We can't use `export type` syntax either because the TS version we're on doesn't have this feature yet.
function modify(path, modifyFn) {
const content = readFileSync(path, 'utf8');
writeFileSync(path, modifyFn(content));
}
modify(
'types/runtime/index.d.ts',
content => content.replace('SvelteComponentTyped', 'SvelteComponentTyped, ComponentType, ComponentConstructorOptions, ComponentProps, ComponentEvents')
);
modify(
'types/compiler/index.d.ts',
content => content + '\nexport { CompileOptions, ModuleFormat, EnableSourcemap, CssHashGetter } from "./interfaces"'
);

9543
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,6 +1,6 @@
{ {
"name": "svelte", "name": "svelte",
"version": "3.58.0", "version": "4.0.0-next.0",
"description": "Cybernetically enhanced web apps", "description": "Cybernetically enhanced web apps",
"module": "index.mjs", "module": "index.mjs",
"main": "index", "main": "index",
@ -28,12 +28,8 @@
"import": "./index.mjs", "import": "./index.mjs",
"require": "./index.js" "require": "./index.js"
}, },
"node": { "import": "./ssr.mjs",
"import": "./ssr.mjs", "require": "./ssr.js"
"require": "./ssr.js"
},
"import": "./index.mjs",
"require": "./index.js"
}, },
"./compiler": { "./compiler": {
"types": "./types/compiler/index.d.ts", "types": "./types/compiler/index.d.ts",
@ -78,28 +74,25 @@
}, },
"./elements": { "./elements": {
"types": "./elements/index.d.ts" "types": "./elements/index.d.ts"
},
"./ssr": {
"types": "./types/runtime/index.d.ts",
"import": "./ssr.mjs",
"require": "./ssr.js"
} }
}, },
"engines": { "engines": {
"node": ">= 8" "node": ">=16"
}, },
"types": "types/runtime/index.d.ts", "types": "types/runtime/index.d.ts",
"scripts": { "scripts": {
"test": "npm run test:unit && npm run test:integration", "format:fix": "prettier . --cache --plugin-search-dir=. --write",
"format:check": "prettier . --cache --plugin-search-dir=. --check",
"test": "npm run test:unit && npm run test:integration && echo \"manually check that there are no type errors in test/types by opening the files in there\"",
"test:integration": "mocha --exit", "test:integration": "mocha --exit",
"test:unit": "mocha --config .mocharc.unit.js --exit", "test:unit": "mocha --config .mocharc.unit.js --exit",
"quicktest": "mocha --exit", "quicktest": "mocha --exit",
"build": "rollup -c && npm run tsd", "build": "rollup -c && npm run tsd",
"prepare": "node scripts/skip_in_ci.js npm run build", "prepare": "npm run build",
"dev": "rollup -cw", "dev": "rollup -cw",
"posttest": "agadoo internal/index.mjs", "posttest": "agadoo internal/index.mjs",
"prepublishOnly": "node check_publish_env.js && npm run lint && npm run build && npm test", "prepublishOnly": "node check_publish_env.js && npm run lint && npm run build && npm test",
"tsd": "node ./generate-type-definitions.js", "tsd": "tsc -p src/compiler --emitDeclarationOnly && tsc -p src/runtime --emitDeclarationOnly",
"lint": "eslint \"{src,test}/**/*.{ts,js}\" --cache" "lint": "eslint \"{src,test}/**/*.{ts,js}\" --cache"
}, },
"repository": { "repository": {
@ -119,46 +112,50 @@
}, },
"homepage": "https://svelte.dev", "homepage": "https://svelte.dev",
"devDependencies": { "devDependencies": {
"@ampproject/remapping": "^0.3.0", "@ampproject/remapping": "^2.2.1",
"@jridgewell/sourcemap-codec": "^1.4.15", "@jridgewell/sourcemap-codec": "^1.4.15",
"@rollup/plugin-commonjs": "^11.0.0", "@rollup/plugin-commonjs": "^24.1.0",
"@rollup/plugin-json": "^6.0.0", "@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-node-resolve": "^11.2.1", "@rollup/plugin-node-resolve": "^15.0.2",
"@rollup/plugin-replace": "^5.0.2", "@rollup/plugin-replace": "^5.0.2",
"@rollup/plugin-sucrase": "^3.1.0", "@rollup/plugin-sucrase": "^5.0.1",
"@rollup/plugin-typescript": "^2.0.1", "@rollup/plugin-typescript": "^11.1.0",
"@rollup/plugin-virtual": "^3.0.1", "@rollup/plugin-virtual": "^3.0.1",
"@sveltejs/eslint-config": "github:sveltejs/eslint-config#v5.8.0", "@sveltejs/eslint-config": "github:sveltejs/eslint-config#v5.8.0",
"@types/aria-query": "^5.0.1", "@types/aria-query": "^5.0.1",
"@types/mocha": "^7.0.0", "@types/estree": "^1.0.0",
"@types/node": "^8.10.53", "@types/mocha": "^10.0.1",
"@typescript-eslint/eslint-plugin": "^5.29.0", "@types/node": "^14.14.31",
"@typescript-eslint/parser": "^5.29.0", "@typescript-eslint/eslint-plugin": "^5.58.0",
"acorn": "^8.8.1", "@typescript-eslint/parser": "^5.58.0",
"acorn": "^8.8.2",
"agadoo": "^3.0.0", "agadoo": "^3.0.0",
"aria-query": "^5.1.3", "aria-query": "^5.1.3",
"axobject-query": "^3.1.1", "axobject-query": "^3.1.1",
"code-red": "^1.0.0", "code-red": "^1.0.0",
"css-tree": "^2.3.1", "css-tree": "^2.3.1",
"eslint": "^8.35.0", "eslint": "^8.35.0",
"eslint-plugin-import": "^2.27.0", "eslint-config-prettier": "^8.8.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-svelte3": "^4.0.0", "eslint-plugin-svelte3": "^4.0.0",
"estree-walker": "^3.0.3", "estree-walker": "^3.0.3",
"is-reference": "^3.0.1", "is-reference": "^3.0.1",
"jsdom": "^15.2.1", "jsdom": "^21.1.1",
"kleur": "^4.1.5", "kleur": "^4.1.5",
"locate-character": "^2.0.5", "locate-character": "^2.0.5",
"magic-string": "^0.30.0", "magic-string": "^0.30.0",
"mocha": "^7.0.0", "mocha": "^10.2.0",
"periscopic": "^3.1.0", "periscopic": "^3.1.0",
"prettier": "^2.8.8",
"prettier-plugin-svelte": "^2.10.0", "prettier-plugin-svelte": "^2.10.0",
"puppeteer": "^2.0.0", "puppeteer": "^19.8.5",
"rollup": "^1.27.14", "rollup": "^3.20.2",
"source-map": "^0.7.4", "source-map": "^0.7.4",
"source-map-support": "^0.5.21", "source-map-support": "^0.5.21",
"tiny-glob": "^0.2.9", "tiny-glob": "^0.2.9",
"tslib": "^2.5.0", "tslib": "^2.5.0",
"typescript": "^3.7.5", "typescript": "^5.0.4",
"util": "^0.12.5" "util": "^0.12.5"
} },
"packageManager": "pnpm@8.4.0"
} }

File diff suppressed because it is too large Load Diff

@ -1,148 +0,0 @@
import fs from 'fs';
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';
import sucrase from '@rollup/plugin-sucrase';
import typescript from '@rollup/plugin-typescript';
import pkg from './package.json';
const is_publish = !!process.env.PUBLISH;
const ts_plugin = is_publish
? typescript({
include: 'src/**',
typescript: require('typescript')
})
: sucrase({
transforms: ['typescript']
});
const external = id => id.startsWith('svelte/');
fs.writeFileSync(`./compiler.d.ts`, `export { compile, parse, preprocess, walk, VERSION } from './types/compiler/index';`);
export default [
/* runtime */
{
input: `src/runtime/index.ts`,
output: [
{
file: `index.mjs`,
format: 'esm',
paths: id => id.startsWith('svelte/') && `${id.replace('svelte', '.')}/index.mjs`
},
{
file: `index.js`,
format: 'cjs',
paths: id => id.startsWith('svelte/') && `${id.replace('svelte', '.')}/index.js`
}
],
external,
plugins: [ts_plugin]
},
{
input: `src/runtime/ssr.ts`,
output: [
{
file: `ssr.mjs`,
format: 'esm',
paths: id => id.startsWith('svelte/') && `${id.replace('svelte', '.')}/index.mjs`
},
{
file: `ssr.js`,
format: 'cjs',
paths: id => id.startsWith('svelte/') && `${id.replace('svelte', '.')}/index.js`
}
],
external,
plugins: [ts_plugin]
},
...fs.readdirSync('src/runtime')
.filter(dir => fs.statSync(`src/runtime/${dir}`).isDirectory())
.map(dir => ({
input: `src/runtime/${dir}/index.ts`,
output: [
{
file: `${dir}/index.mjs`,
format: 'esm',
paths: id => id.startsWith('svelte/') && `${id.replace('svelte', '..')}/index.mjs`
},
{
file: `${dir}/index.js`,
format: 'cjs',
paths: id => id.startsWith('svelte/') && `${id.replace('svelte', '..')}/index.js`
}
],
external,
plugins: [
replace({
__VERSION__: pkg.version
}),
ts_plugin,
{
writeBundle(bundle) {
if (dir === 'internal') {
const mod = bundle['index.mjs'];
if (mod) {
fs.writeFileSync('src/compiler/compile/internal_exports.ts', `// This file is automatically generated\nexport default new Set(${JSON.stringify(mod.exports)});`);
}
}
fs.writeFileSync(`${dir}/package.json`, JSON.stringify({
main: './index',
module: './index.mjs',
types: './index.d.ts'
}, null, ' '));
fs.writeFileSync(`${dir}/index.d.ts`, `export * from '../types/runtime/${dir}/index';`);
}
}
]
})),
/* compiler.js */
{
input: 'src/compiler/index.ts',
plugins: [
replace({
__VERSION__: pkg.version,
'process.env.NODE_DEBUG': false // appears inside the util package
}),
{
resolveId(id) {
// util is a built-in module in Node.js, but we want a self-contained compiler bundle
// that also works in the browser, so we load its polyfill instead
if (id === 'util') {
return require.resolve('./node_modules/util'); // just 'utils' would resolve this to the built-in module
}
}
},
resolve(),
commonjs({
include: ['node_modules/**']
}),
json(),
ts_plugin
],
output: [
{
file: 'compiler.js',
format: is_publish ? 'umd' : 'cjs',
name: 'svelte',
sourcemap: true,
},
{
file: 'compiler.mjs',
format: 'esm',
name: 'svelte',
sourcemap: true,
}
],
external: is_publish
? []
: id => id === 'acorn' || id === 'magic-string' || id.startsWith('css-tree')
}
];

@ -0,0 +1,150 @@
import fs from 'node:fs';
import { createRequire } from 'node:module';
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';
import sucrase from '@rollup/plugin-sucrase';
import typescript from '@rollup/plugin-typescript';
const require = createRequire(import.meta.url);
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
const is_publish = !!process.env.PUBLISH;
const ts_plugin = is_publish
? typescript({
typescript: require('typescript'),
paths: {
'svelte/*': ['./src/runtime/*']
}
})
: sucrase({
transforms: ['typescript']
});
fs.writeFileSync(
`./compiler.d.ts`,
`export { compile, parse, preprocess, walk, VERSION } from './types/compiler/index.js';`
);
const runtime_entrypoints = Object.fromEntries(
fs
.readdirSync('src/runtime', { withFileTypes: true })
.filter((dirent) => dirent.isDirectory())
.map((dirent) => [dirent.name, `src/runtime/${dirent.name}/index.ts`])
);
/**
* @type {import("rollup").RollupOptions[]}
*/
export default [
{
input: {
...runtime_entrypoints,
index: 'src/runtime/index.ts',
ssr: 'src/runtime/ssr.ts'
},
output: ['es', 'cjs'].map(
/** @returns {import('rollup').OutputOptions} */
(format) => {
const ext = format === 'es' ? 'mjs' : 'js';
return {
entryFileNames: (entry) => {
if (entry.isEntry) {
if (entry.name === 'index') return `index.${ext}`;
else if (entry.name === 'ssr') return `ssr.${ext}`;
return `${entry.name}/index.${ext}`;
}
},
chunkFileNames: `internal/[name]-[hash].${ext}`,
format,
minifyInternalExports: false,
dir: '.',
};
}
),
plugins: [
replace({
preventAssignment: true,
values: {
__VERSION__: pkg.version,
},
}),
ts_plugin,
{
writeBundle(options, bundle) {
if (options.format !== 'es') return;
for (const entry of Object.values(bundle)) {
const dir = entry.name;
if (!entry.isEntry || !runtime_entrypoints[dir]) continue;
if (dir === 'internal') {
const mod = bundle[`internal/index.mjs`];
if (mod) {
fs.writeFileSync(
'src/compiler/compile/internal_exports.ts',
`// This file is automatically generated\n` +
`export default new Set(${JSON.stringify(mod.exports)});`
);
}
}
fs.writeFileSync(
`${dir}/index.d.ts`,
`export * from '../types/runtime/${dir}/index.js';`
);
}
}
}
]
},
/* compiler.js */
{
input: 'src/compiler/index.ts',
plugins: [
replace({
preventAssignment: true,
values: {
__VERSION__: pkg.version,
'process.env.NODE_DEBUG': false // appears inside the util package
},
}),
{
resolveId(id) {
// util is a built-in module in Node.js, but we want a self-contained compiler bundle
// that also works in the browser, so we load its polyfill instead
if (id === 'util') {
return require.resolve('./node_modules/util'); // just 'utils' would resolve this to the built-in module
}
},
},
resolve(),
commonjs({
include: ['node_modules/**']
}),
json(),
ts_plugin
],
output: [
{
file: 'compiler.js',
format: is_publish ? 'umd' : 'cjs',
name: 'svelte',
sourcemap: true,
},
{
file: 'compiler.mjs',
format: 'esm',
name: 'svelte',
sourcemap: true,
}
],
external: is_publish
? []
: (id) =>
id === 'acorn' || id === 'magic-string' || id.startsWith('css-tree')
}
];

@ -15,13 +15,14 @@ const GLOBAL_TS_PATH = './src/compiler/utils/globals.ts';
// before this script was introduced but could not be retrieved by this process. // before this script was introduced but could not be retrieved by this process.
const SPECIALS = ['global', 'globalThis', 'InternalError', 'process', 'undefined']; const SPECIALS = ['global', 'globalThis', 'InternalError', 'process', 'undefined'];
const get_url = (name) => `https://raw.githubusercontent.com/microsoft/TypeScript/main/lib/lib.${name}.d.ts`; const get_url = (name) =>
`https://raw.githubusercontent.com/microsoft/TypeScript/main/lib/lib.${name}.d.ts`;
const extract_name = (split) => split.match(/^[a-zA-Z0-9_$]+/)[0]; const extract_name = (split) => split.match(/^[a-zA-Z0-9_$]+/)[0];
const extract_functions_and_references = (name, data) => { const extract_functions_and_references = (name, data) => {
const functions = []; const functions = [];
const references = []; const references = [];
data.split('\n').forEach(line => { data.split('\n').forEach((line) => {
const trimmed = line.trim(); const trimmed = line.trim();
const split = trimmed.replace(/[\s+]/, ' ').split(' '); const split = trimmed.replace(/[\s+]/, ' ').split(' ');
if (split[0] === 'declare' && split[1] !== 'type') { if (split[0] === 'declare' && split[1] !== 'type') {
@ -35,17 +36,20 @@ const extract_functions_and_references = (name, data) => {
return { functions, references }; return { functions, references };
}; };
const do_get = (url) => new Promise((resolve, reject) => { const do_get = (url) =>
http.get(url, (res) => { new Promise((resolve, reject) => {
let body = ''; http
res.setEncoding('utf8'); .get(url, (res) => {
res.on('data', (chunk) => body += chunk); let body = '';
res.on('end', () => resolve(body)); res.setEncoding('utf8');
}).on('error', (e) => { res.on('data', (chunk) => (body += chunk));
console.error(e.message); res.on('end', () => resolve(body));
reject(e); })
.on('error', (e) => {
console.error(e.message);
reject(e);
});
}); });
});
const fetched_names = new Set(); const fetched_names = new Set();
const get_functions = async (name) => { const get_functions = async (name) => {
@ -56,7 +60,7 @@ const get_functions = async (name) => {
const { functions, references } = extract_functions_and_references(name, body); const { functions, references } = extract_functions_and_references(name, body);
res.push(...functions); res.push(...functions);
const chile_functions = await Promise.all(references.map(get_functions)); const chile_functions = await Promise.all(references.map(get_functions));
chile_functions.forEach(i => res.push(...i)); chile_functions.forEach((i) => res.push(...i));
return res; return res;
}; };
@ -76,10 +80,11 @@ ${sorted.map((i) => `\t'${i}'`).join(',\n')}
const get_exists_globals = () => { const get_exists_globals = () => {
const regexp = /^\s*["'](.+)["'],?\s*$/; const regexp = /^\s*["'](.+)["'],?\s*$/;
return fs.readFileSync(GLOBAL_TS_PATH, 'utf8') return fs
.readFileSync(GLOBAL_TS_PATH, 'utf8')
.split('\n') .split('\n')
.filter(line => line.match(regexp)) .filter((line) => line.match(regexp))
.map(line => line.match(regexp)[1]); .map((line) => line.match(regexp)[1]);
}; };
(async () => { (async () => {

@ -1,7 +0,0 @@
if (process.env.SKIP_PREPARE) {
console.log('Skipped "prepare" script');
} else {
const { execSync } = require("child_process");
const command = process.argv.slice(2).join(" ");
execSync(command, { stdio: "inherit" });
}

@ -322,10 +322,10 @@ The `<svelte:options>` element provides a place to specify per-component compile
- `accessors={true}` — adds getters and setters for the component's props - `accessors={true}` — adds getters and setters for the component's props
- `accessors={false}` — the default - `accessors={false}` — the default
- `namespace="..."` — the namespace where this component will be used, most commonly "svg"; use the "foreign" namespace to opt out of case-insensitive attribute names and HTML-specific warnings - `namespace="..."` — the namespace where this component will be used, most commonly "svg"; use the "foreign" namespace to opt out of case-insensitive attribute names and HTML-specific warnings
- `tag="..."` — the name to use when compiling this component as a custom element - `customElement="..."` — the name to use when compiling this component as a custom element
```svelte ```svelte
<svelte:options tag="my-custom-element" /> <svelte:options customElement="my-custom-element" />
``` ```
## `<svelte:fragment>` ## `<svelte:fragment>`

@ -33,7 +33,7 @@ count.set(1); // logs '1'
count.update((n) => n + 1); // logs '2' count.update((n) => n + 1); // logs '2'
``` ```
If a function is passed as the second argument, it will be called when the number of subscribers goes from zero to one (but not from one to two, etc). That function will be passed a `set` function which changes the value of the store. It must return a `stop` function that is called when the subscriber count goes from one to zero. If a function is passed as the second argument, it will be called when the number of subscribers goes from zero to one (but not from one to two, etc). That function will be passed a `set` function which changes the value of the store, and an `update` function which works like the `update` method on the store, taking a callback to calculate the store's new value from its old value. It must return a `stop` function that is called when the subscriber count goes from one to zero.
```js ```js
/// file: store.js /// file: store.js
@ -62,7 +62,8 @@ Note that the value of a `writable` is lost when it is destroyed, for example wh
Creates a store whose value cannot be set from 'outside', the first argument is the store's initial value, and the second argument to `readable` is the same as the second argument to `writable`. Creates a store whose value cannot be set from 'outside', the first argument is the store's initial value, and the second argument to `readable` is the same as the second argument to `writable`.
```js ```js
/// file: store.js // @errors: 7006 2769
// ---cut---
import { readable } from 'svelte/store'; import { readable } from 'svelte/store';
const time = readable(new Date(), (set) => { const time = readable(new Date(), (set) => {
@ -74,6 +75,14 @@ const time = readable(new Date(), (set) => {
return () => clearInterval(interval); return () => clearInterval(interval);
}); });
const ticktock = readable('tick', (set, update) => {
const interval = setInterval(() => {
update((sound) => (sound === 'tick' ? 'tock' : 'tick'));
}, 1000);
return () => clearInterval(interval);
});
``` ```
## `derived` ## `derived`
@ -101,9 +110,9 @@ import { derived } from 'svelte/store';
const doubled = derived(a, ($a) => $a * 2); const doubled = derived(a, ($a) => $a * 2);
``` ```
The callback can set a value asynchronously by accepting a second argument, `set`, and calling it when appropriate. The callback can set a value asynchronously by accepting a second argument, `set`, and an optional third argument, `update`, calling either or both of them when appropriate.
In this case, you can also pass a third argument to `derived` — the initial value of the derived store before `set` is first called. In this case, you can also pass a third argument to `derived` — the initial value of the derived store before `set` or `update` is first called. If no initial value is specified, the store's initial value will be `undefined`.
```js ```js
// @filename: ambient.d.ts // @filename: ambient.d.ts
@ -116,16 +125,20 @@ declare global {
export {}; export {};
// @filename: index.ts // @filename: index.ts
// @errors: 2769 7006
// ---cut--- // ---cut---
import { derived } from 'svelte/store'; import { derived } from 'svelte/store';
const delayed = derived( const delayed = derived(a, ($a, set) => {
a, setTimeout(() => set($a), 1000);
($a, set) => { }, 2000);
setTimeout(() => set($a), 1000);
}, const delayedIncrement = derived(a, ($a, set, update) => {
2000 set($a);
); setTimeout(() => update(x => x + 1), 1000);
// every time $a produces a value, this produces two
// values, $a immediately and then $a + 1 a second later
});
``` ```
If you return a function from the callback, it will be called when a) the callback runs again, or b) the last subscriber unsubscribes. If you return a function from the callback, it will be called when a) the callback runs again, or b) the last subscriber unsubscribes.

@ -15,7 +15,7 @@ Svelte components can also be compiled to custom elements (aka web components) u
<slot /> <slot />
``` ```
Alternatively, use `tag={null}` to indicate that the consumer of the custom element should name it. You can leave out the tag name for any of your inner components which you don't want to expose and use them like regular Svelte components. Consumers of the component can still name it afterwards if needed, using the static `element` property which contains the custom element constructor and which is available when the `customElement` compiler option is `true`.
```js ```js
// @noErrors // @noErrors
@ -49,11 +49,37 @@ console.log(el.name);
el.name = 'everybody'; el.name = 'everybody';
``` ```
When constructing a custom element, you can tailor several aspects by defining `customElement` as an object within `<svelte:options>`. This object comprises a mandatory `tag` property for the custom element's name, an optional `shadow` property that can be set to `"none"` to forgo shadow root creation, and a `props` option, which offers the following settings:
- `attribute: string`: To update a custom element's prop, you have two alternatives: either set the property on the custom element's reference as illustrated above or use an HTML attribute. For the latter, the default attribute name is the lowercase property name. Modify this by assigning `attribute: "<desired name>"`.
- `reflect: boolean`: By default, updated prop values do not reflect back to the DOM. To enable this behavior, set `reflect: true`.
- `type: 'String' | 'Boolean' | 'Number' | 'Array' | 'Object'`: While converting an attribute value to a prop value and reflecting it back, the prop value is assumed to be a `String` by default. This may not always be accurate. For instance, for a number type, define it using `type: "Number"`
```svelte
<svelte:options
customElement={{
tag: "custom-element",
shadow: "none",
props: {
name: { reflect: true, type: "Number", attribute: "element-index" },
},
}}
/>
<script>
export let elementIndex;
</script>
...
```
Custom elements can be a useful way to package components for consumption in a non-Svelte app, as they will work with vanilla HTML and JavaScript as well as [most frameworks](https://custom-elements-everywhere.com/). There are, however, some important differences to be aware of: Custom elements can be a useful way to package components for consumption in a non-Svelte app, as they will work with vanilla HTML and JavaScript as well as [most frameworks](https://custom-elements-everywhere.com/). There are, however, some important differences to be aware of:
- Styles are _encapsulated_, rather than merely _scoped_. This means that any non-component styles (such as you might have in a `global.css` file) will not apply to the custom element, including styles with the `:global(...)` modifier - Styles are *encapsulated*, rather than merely *scoped* (unless you set `shadow: "none"`). This means that any non-component styles (such as you might have in a `global.css` file) will not apply to the custom element, including styles with the `:global(...)` modifier
- Instead of being extracted out as a separate .css file, styles are inlined into the component as a JavaScript string - Instead of being extracted out as a separate .css file, styles are inlined into the component as a JavaScript string
- Custom elements are not generally suitable for server-side rendering, as the shadow DOM is invisible until JavaScript loads - Custom elements are not generally suitable for server-side rendering, as the shadow DOM is invisible until JavaScript loads
- In Svelte, slotted content renders _lazily_. In the DOM, it renders _eagerly_. In other words, it will always be created even if the component's `<slot>` element is inside an `{#if ...}` block. Similarly, including a `<slot>` in an `{#each ...}` block will not cause the slotted content to be rendered multiple times - In Svelte, slotted content renders _lazily_. In the DOM, it renders _eagerly_. In other words, it will always be created even if the component's `<slot>` element is inside an `{#if ...}` block. Similarly, including a `<slot>` in an `{#each ...}` block will not cause the slotted content to be rendered multiple times
- The `let:` directive has no effect - The `let:` directive has no effect, because custom elements do not have a way to pass data to the parent component that fills the slot
- Polyfills are required to support older browsers - Polyfills are required to support older browsers
When a custom element written with Svelte is created or updated, the shadow dom will reflect the value in the next tick, not immediately. This way updates can be batched, and DOM moves which temporarily (but synchronously) detach the element from the DOM don't lead to unmounting the inner component.

@ -242,7 +242,19 @@ Some HTML elements have default ARIA roles. Giving these elements an ARIA role t
<textarea role="listitem" /> <textarea role="listitem" />
``` ```
## `a11y-no-noninteractive-element-to-interactive-role` ### `a11y-no-noninteractive-element-interactions`
A non-interactive element does not support event handlers (mouse and key handlers). Non-interactive elements include `<main>`, `<area>`, `<h1>` (,`<h2>`, etc), `<p>`, `<img>`, `<li>`, `<ul>` and `<ol>`. Non-interactive [WAI-ARIA roles](https://www.w3.org/TR/wai-aria-1.1/#usage_intro) include `article`, `banner`, `complementary`, `img`, `listitem`, `main`, `region` and `tooltip`.
```sv
<!-- `A11y: Non-interactive element <li> should not be assigned mouse or keyboard event listeners.` -->
<li on:click={() => {}} />
<!-- `A11y: Non-interactive element <div> should not be assigned mouse or keyboard event listeners.` -->
<div role="listitem" on:click={() => {}} />
```
### `a11y-no-noninteractive-element-to-interactive-role`
[WAI-ARIA](https://www.w3.org/TR/wai-aria-1.1/#usage_intro) roles should not be used to convert a non-interactive element to an interactive element. Interactive ARIA roles include `button`, `link`, `checkbox`, `menuitem`, `menuitemcheckbox`, `menuitemradio`, `option`, `radio`, `searchbox`, `switch` and `textbox`. [WAI-ARIA](https://www.w3.org/TR/wai-aria-1.1/#usage_intro) roles should not be used to convert a non-interactive element to an interactive element. Interactive ARIA roles include `button`, `link`, `checkbox`, `menuitem`, `menuitemcheckbox`, `menuitemradio`, `option`, `radio`, `searchbox`, `switch` and `textbox`.

@ -20,11 +20,11 @@ Now, when you toggle todos by clicking on them, only the updated component flash
The options that can be set here are: The options that can be set here are:
- `immutable={true}` — you never use mutable data, so the compiler can do simple referential equality checks to determine if values have changed * `immutable={true}` — you never use mutable data, so the compiler can do simple referential equality checks to determine if values have changed
- `immutable={false}` — the default. Svelte will be more conservative about whether or not mutable objects have changed * `immutable={false}` — the default. Svelte will be more conservative about whether or not mutable objects have changed
- `accessors={true}` — adds getters and setters for the component's props * `accessors={true}` — adds getters and setters for the component's props
- `accessors={false}` — the default * `accessors={false}` — the default
- `namespace="..."` — the namespace where this component will be used, most commonly `"svg"` * `namespace="..."` — the namespace where this component will be used, most commonly `"svg"`
- `tag="..."` — the name to use when compiling this component as a custom element * `customElement="..."` — the name to use when compiling this component as a custom element
Consult the [API reference](/docs) for more information on these options. Consult the [API reference](/docs) for more information on these options.

File diff suppressed because it is too large Load Diff

@ -5,14 +5,14 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "node scripts/update.js && vite dev", "dev": "node scripts/update.js && vite dev",
"build": "node scripts/update.js && npm run generate && vite build", "build": "node scripts/update.js && pnpm run generate && vite build",
"generate": "node scripts/type-gen/index.js && node scripts/generate_examples.js", "generate": "node scripts/type-gen/index.js && node scripts/generate_examples.js",
"update": "node scripts/update.js --force=true", "update": "node scripts/update.js --force=true",
"preview": "vite preview", "preview": "vite preview",
"start": "node build", "start": "node build",
"check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json", "check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
"format": "npm run check:format -- --write", "format": "pnpm run check:format -- --write",
"check:format": "prettier --check . --ignore-path .gitignore --plugin-search-dir=.", "check:format": "prettier --check . --ignore-path .gitignore --plugin-search-dir=.",
"test": "uvu -r ts-node/register src/lib/server/markdown" "test": "uvu -r ts-node/register src/lib/server/markdown"
}, },
@ -34,14 +34,14 @@
"degit": "^2.8.4", "degit": "^2.8.4",
"dotenv": "^16.0.3", "dotenv": "^16.0.3",
"jimp": "^0.22.8", "jimp": "^0.22.8",
"marked": "^5.0.1", "marked": "^5.0.2",
"node-fetch": "^3.3.1", "node-fetch": "^3.3.1",
"prettier": "^2.8.8", "prettier": "^2.8.8",
"prettier-plugin-svelte": "^2.10.0", "prettier-plugin-svelte": "^2.10.0",
"rollup": "^3.21.6", "rollup": "^3.21.6",
"rollup-plugin-dts": "^5.3.0", "rollup-plugin-dts": "^5.3.0",
"sass": "^1.62.1", "sass": "^1.62.1",
"satori": "^0.8.0", "satori": "^0.8.1",
"satori-html": "^0.3.2", "satori-html": "^0.3.2",
"shelljs": "^0.8.5", "shelljs": "^0.8.5",
"shiki": "^0.14.2", "shiki": "^0.14.2",

File diff suppressed because it is too large Load Diff

@ -1,9 +1,10 @@
const now = (typeof process !== 'undefined' && process.hrtime) const now =
? () => { typeof process !== 'undefined' && process.hrtime
const t = process.hrtime(); ? () => {
return t[0] * 1e3 + t[1] / 1e6; const t = process.hrtime();
} return t[0] * 1e3 + t[1] / 1e6;
: () => self.performance.now(); }
: () => self.performance.now();
interface Timing { interface Timing {
label: string; label: string;
@ -14,10 +15,13 @@ interface Timing {
function collapse_timings(timings) { function collapse_timings(timings) {
const result = {}; const result = {};
timings.forEach(timing => { timings.forEach((timing) => {
result[timing.label] = Object.assign({ result[timing.label] = Object.assign(
total: timing.end - timing.start {
}, timing.children && collapse_timings(timing.children)); total: timing.end - timing.start
},
timing.children && collapse_timings(timing.children)
);
}); });
return result; return result;
} }
@ -52,7 +56,9 @@ export default class Stats {
stop(label) { stop(label) {
if (label !== this.current_timing.label) { if (label !== this.current_timing.label) {
throw new Error(`Mismatched timing labels (expected ${this.current_timing.label}, got ${label})`); throw new Error(
`Mismatched timing labels (expected ${this.current_timing.label}, got ${label})`
);
} }
this.current_timing.end = now(); this.current_timing.end = now();
@ -62,9 +68,12 @@ export default class Stats {
} }
render() { render() {
const timings = Object.assign({ const timings = Object.assign(
total: now() - this.start_time {
}, collapse_timings(this.timings)); total: now() - this.start_time
},
collapse_timings(this.timings)
);
return { return {
timings timings

@ -1,3 +1,4 @@
import type { DecodedSourceMap, RawSourceMap } from '@ampproject/remapping';
import { walk } from 'estree-walker'; import { walk } from 'estree-walker';
import { getLocator } from 'locate-character'; import { getLocator } from 'locate-character';
import Stats from '../Stats'; import Stats from '../Stats';
@ -5,17 +6,12 @@ import { reserved, is_valid } from '../utils/names';
import globals from '../utils/globals'; import globals from '../utils/globals';
import { namespaces, valid_namespaces } from '../utils/namespaces'; import { namespaces, valid_namespaces } from '../utils/namespaces';
import create_module from './create_module'; import create_module from './create_module';
import { import { create_scopes, extract_names, Scope, extract_identifiers } from './utils/scope';
create_scopes,
extract_names,
Scope,
extract_identifiers
} from './utils/scope';
import Stylesheet from './css/Stylesheet'; import Stylesheet from './css/Stylesheet';
import { test } from '../config'; import { test } from '../config';
import Fragment from './nodes/Fragment'; import Fragment from './nodes/Fragment';
import internal_exports from './internal_exports'; import internal_exports from './internal_exports';
import { Ast, CompileOptions, Var, Warning, CssResult } from '../interfaces'; import { Ast, CompileOptions, Var, Warning, CssResult, Attribute } from '../interfaces';
import error from '../utils/error'; import error from '../utils/error';
import get_code_frame from '../utils/get_code_frame'; import get_code_frame from '../utils/get_code_frame';
import flatten_reference from './utils/flatten_reference'; import flatten_reference from './utils/flatten_reference';
@ -25,26 +21,58 @@ import TemplateScope from './nodes/shared/TemplateScope';
import fuzzymatch from '../utils/fuzzymatch'; import fuzzymatch from '../utils/fuzzymatch';
import get_object from './utils/get_object'; import get_object from './utils/get_object';
import Slot from './nodes/Slot'; import Slot from './nodes/Slot';
import { Node, ImportDeclaration, ExportNamedDeclaration, Identifier, ExpressionStatement, AssignmentExpression, Literal, Property, RestElement, ExportDefaultDeclaration, ExportAllDeclaration, FunctionDeclaration, FunctionExpression } from 'estree'; import {
Node,
ImportDeclaration,
ExportNamedDeclaration,
Identifier,
ExpressionStatement,
AssignmentExpression,
Literal,
Property,
RestElement,
ExportDefaultDeclaration,
ExportAllDeclaration,
FunctionDeclaration,
FunctionExpression,
VariableDeclarator,
ObjectExpression,
Pattern,
Expression
} from 'estree';
import add_to_set from './utils/add_to_set'; import add_to_set from './utils/add_to_set';
import check_graph_for_cycles from './utils/check_graph_for_cycles'; import check_graph_for_cycles from './utils/check_graph_for_cycles';
import { print, b } from 'code-red'; import { print, b } from 'code-red';
import { is_reserved_keyword } from './utils/reserved_keywords'; import { is_reserved_keyword } from './utils/reserved_keywords';
import { apply_preprocessor_sourcemap } from '../utils/mapped_code'; import { apply_preprocessor_sourcemap } from '../utils/mapped_code';
import Element from './nodes/Element'; import Element from './nodes/Element';
import { DecodedSourceMap, RawSourceMap } from '@ampproject/remapping/dist/types/types';
import { clone } from '../utils/clone'; import { clone } from '../utils/clone';
import compiler_warnings from './compiler_warnings'; import compiler_warnings from './compiler_warnings';
import compiler_errors from './compiler_errors'; import compiler_errors from './compiler_errors';
import { extract_ignores_above_position, extract_svelte_ignore_from_comments } from '../utils/extract_svelte_ignore'; import {
extract_ignores_above_position,
extract_svelte_ignore_from_comments
} from '../utils/extract_svelte_ignore';
import check_enable_sourcemap from './utils/check_enable_sourcemap'; import check_enable_sourcemap from './utils/check_enable_sourcemap';
import Tag from './nodes/shared/Tag';
interface ComponentOptions { interface ComponentOptions {
namespace?: string; namespace?: string;
tag?: string;
immutable?: boolean; immutable?: boolean;
accessors?: boolean; accessors?: boolean;
preserveWhitespace?: boolean; preserveWhitespace?: boolean;
customElement?: {
tag: string | null;
shadow?: 'open' | 'none';
props?: Record<
string,
{
attribute?: string;
reflect?: boolean;
type?: 'String' | 'Boolean' | 'Number' | 'Array' | 'Object';
}
>;
};
} }
const regex_leading_directory_separator = /^[/\\]/; const regex_leading_directory_separator = /^[/\\]/;
@ -81,8 +109,8 @@ export default class Component {
hoistable_nodes: Set<Node> = new Set(); hoistable_nodes: Set<Node> = new Set();
node_for_declaration: Map<string, Node> = new Map(); node_for_declaration: Map<string, Node> = new Map();
partly_hoisted: Array<(Node | Node[])> = []; partly_hoisted: Array<Node | Node[]> = [];
fully_hoisted: Array<(Node | Node[])> = []; fully_hoisted: Array<Node | Node[]> = [];
reactive_declarations: Array<{ reactive_declarations: Array<{
assignees: Set<string>; assignees: Set<string>;
dependencies: Set<string>; dependencies: Set<string>;
@ -110,6 +138,8 @@ export default class Component {
slots: Map<string, Slot> = new Map(); slots: Map<string, Slot> = new Map();
slot_outlets: Set<string> = new Set(); slot_outlets: Set<string> = new Set();
tags: Tag[] = [];
constructor( constructor(
ast: Ast, ast: Ast,
source: string, source: string,
@ -139,8 +169,8 @@ export default class Component {
compile_options.filename && compile_options.filename &&
(typeof process !== 'undefined' (typeof process !== 'undefined'
? compile_options.filename ? compile_options.filename
.replace(process.cwd(), '') .replace(process.cwd(), '')
.replace(regex_leading_directory_separator, '') .replace(regex_leading_directory_separator, '')
: compile_options.filename); : compile_options.filename);
this.locate = getLocator(this.source, { offsetLine: 1 }); this.locate = getLocator(this.source, { offsetLine: 1 });
@ -155,44 +185,39 @@ export default class Component {
}); });
this.stylesheet.validate(this); this.stylesheet.validate(this);
this.component_options = process_component_options( this.component_options = process_component_options(this, this.ast.html.children);
this,
this.ast.html.children
);
this.namespace = this.namespace =
namespaces[this.component_options.namespace] || namespaces[this.component_options.namespace] || this.component_options.namespace;
this.component_options.namespace;
if (compile_options.customElement) { if (compile_options.customElement) {
if ( this.tag = this.component_options.customElement?.tag || compile_options.tag || this.name.name;
this.component_options.tag === undefined &&
compile_options.tag === undefined
) {
const svelteOptions = ast.html.children.find(
child => child.name === 'svelte:options'
) || { start: 0, end: 0 };
this.warn(svelteOptions, compiler_warnings.custom_element_no_tag);
}
this.tag = this.component_options.tag || compile_options.tag;
} else { } else {
this.tag = this.name.name; this.tag = this.name.name;
} }
this.walk_module_js(); this.walk_module_js();
this.push_ignores(this.ast.instance ? extract_ignores_above_position(this.ast.instance.start, this.ast.html.children) : []); this.push_ignores(
this.ast.instance
? extract_ignores_above_position(this.ast.instance.start, this.ast.html.children)
: []
);
this.walk_instance_js_pre_template(); this.walk_instance_js_pre_template();
this.pop_ignores(); this.pop_ignores();
this.fragment = new Fragment(this, ast.html); this.fragment = new Fragment(this, ast.html);
this.name = this.get_unique_name(name); this.name = this.get_unique_name(name);
this.push_ignores(this.ast.instance ? extract_ignores_above_position(this.ast.instance.start, this.ast.html.children) : []); this.push_ignores(
this.ast.instance
? extract_ignores_above_position(this.ast.instance.start, this.ast.html.children)
: []
);
this.walk_instance_js_post_template(); this.walk_instance_js_post_template();
this.pop_ignores(); this.pop_ignores();
this.elements.forEach(element => this.stylesheet.apply(element)); this.elements.forEach((element) => this.stylesheet.apply(element));
if (!compile_options.customElement) this.stylesheet.reify(); this.stylesheet.reify();
this.stylesheet.warn_on_unused_selectors(this); this.stylesheet.warn_on_unused_selectors(this);
} }
@ -343,17 +368,15 @@ export default class Component {
referenced_globals, referenced_globals,
this.imports, this.imports,
this.vars this.vars
.filter(variable => variable.module && variable.export_name) .filter((variable) => variable.module && variable.export_name)
.map(variable => ({ .map((variable) => ({
name: variable.name, name: variable.name,
as: variable.export_name as: variable.export_name
})), })),
this.exports_from this.exports_from
); );
css = compile_options.customElement css = compile_options.customElement ? { code: null, map: null } : result.css;
? { code: null, map: null }
: result.css;
const js_sourcemap_enabled = check_enable_sourcemap(compile_options.enableSourcemap, 'js'); const js_sourcemap_enabled = check_enable_sourcemap(compile_options.enableSourcemap, 'js');
@ -367,15 +390,15 @@ export default class Component {
sourceMapSource: sourcemap_source_filename sourceMapSource: sourcemap_source_filename
}); });
js.map.sources = [ js.map.sources = [sourcemap_source_filename];
sourcemap_source_filename
];
js.map.sourcesContent = [ js.map.sourcesContent = [this.source];
this.source
];
js.map = apply_preprocessor_sourcemap(sourcemap_source_filename, js.map, compile_options.sourcemap as (string | RawSourceMap | DecodedSourceMap)); js.map = apply_preprocessor_sourcemap(
sourcemap_source_filename,
js.map,
compile_options.sourcemap as string | RawSourceMap | DecodedSourceMap
);
} }
} }
@ -437,13 +460,14 @@ export default class Component {
get_vars_report(): Var[] { get_vars_report(): Var[] {
const { compile_options, vars } = this; const { compile_options, vars } = this;
const vars_report = compile_options.varsReport === false const vars_report =
? [] compile_options.varsReport === false
: compile_options.varsReport === 'full' ? []
: compile_options.varsReport === 'full'
? vars ? vars
: vars.filter(v => !v.global && !v.internal); : vars.filter((v) => !v.global && !v.internal);
return vars_report.map(v => ({ return vars_report.map((v) => ({
name: v.name, name: v.name,
export_name: v.export_name || null, export_name: v.export_name || null,
injected: v.injected || false, injected: v.injected || false,
@ -507,8 +531,7 @@ export default class Component {
end, end,
pos: pos.start, pos: pos.start,
filename: this.compile_options.filename, filename: this.compile_options.filename,
toString: () => toString: () => `${warning.message} (${start.line}:${start.column})\n${frame}`
`${warning.message} (${start.line}:${start.column})\n${frame}`
}); });
} }
@ -524,7 +547,10 @@ export default class Component {
return result; return result;
} }
private _extract_exports(node: ExportDefaultDeclaration | ExportNamedDeclaration | ExportAllDeclaration, module_script: boolean) { private _extract_exports(
node: ExportDefaultDeclaration | ExportNamedDeclaration | ExportAllDeclaration,
module_script: boolean
) {
if (node.type === 'ExportDefaultDeclaration') { if (node.type === 'ExportDefaultDeclaration') {
return this.error(node as any, compiler_errors.default_export); return this.error(node as any, compiler_errors.default_export);
} }
@ -540,12 +566,25 @@ export default class Component {
} }
if (node.declaration) { if (node.declaration) {
if (node.declaration.type === 'VariableDeclaration') { if (node.declaration.type === 'VariableDeclaration') {
node.declaration.declarations.forEach(declarator => { node.declaration.declarations.forEach((declarator) => {
extract_names(declarator.id).forEach(name => { extract_names(declarator.id).forEach((name) => {
const variable = this.var_lookup.get(name); const variable = this.var_lookup.get(name);
variable.export_name = name; variable.export_name = name;
if (!module_script && variable.writable && !(variable.referenced || variable.referenced_from_script || variable.subscribable)) { if (
this.warn(declarator as any, compiler_warnings.unused_export_let(this.name.name, name)); declarator.init?.type === 'Literal' &&
typeof declarator.init.value === 'boolean'
) {
variable.is_boolean = true;
}
if (
!module_script &&
variable.writable &&
!(variable.referenced || variable.referenced_from_script || variable.subscribable)
) {
this.warn(
declarator as any,
compiler_warnings.unused_export_let(this.name.name, name)
);
} }
}); });
}); });
@ -558,14 +597,21 @@ export default class Component {
return node.declaration; return node.declaration;
} else { } else {
node.specifiers.forEach(specifier => { node.specifiers.forEach((specifier) => {
const variable = this.var_lookup.get(specifier.local.name); const variable = this.var_lookup.get(specifier.local.name);
if (variable) { if (variable) {
variable.export_name = specifier.exported.name; variable.export_name = specifier.exported.name;
if (!module_script && variable.writable && !(variable.referenced || variable.referenced_from_script || variable.subscribable)) { if (
this.warn(specifier as any, compiler_warnings.unused_export_let(this.name.name, specifier.exported.name)); !module_script &&
variable.writable &&
!(variable.referenced || variable.referenced_from_script || variable.subscribable)
) {
this.warn(
specifier as any,
compiler_warnings.unused_export_let(this.name.name, specifier.exported.name)
);
} }
} }
}); });
@ -578,7 +624,7 @@ export default class Component {
extract_javascript(script) { extract_javascript(script) {
if (!script) return null; if (!script) return null;
return script.content.body.filter(node => { return script.content.body.filter((node) => {
if (!node) return false; if (!node) return false;
if (this.hoistable_nodes.has(node)) return false; if (this.hoistable_nodes.has(node)) return false;
if (this.reactive_declaration_nodes.has(node)) return false; if (this.reactive_declaration_nodes.has(node)) return false;
@ -609,7 +655,8 @@ export default class Component {
return this.error(node as any, compiler_errors.illegal_declaration); return this.error(node as any, compiler_errors.illegal_declaration);
} }
const writable = node.type === 'VariableDeclaration' && (node.kind === 'var' || node.kind === 'let'); const writable =
node.type === 'VariableDeclaration' && (node.kind === 'var' || node.kind === 'let');
const imported = node.type.startsWith('Import'); const imported = node.type.startsWith('Import');
this.add_var(node, { this.add_var(node, {
@ -658,7 +705,7 @@ export default class Component {
if (!script) return; if (!script) return;
// inject vars for reactive declarations // inject vars for reactive declarations
script.content.body.forEach(node => { script.content.body.forEach((node) => {
if (node.type !== 'LabeledStatement') return; if (node.type !== 'LabeledStatement') return;
if (node.body.type !== 'ExpressionStatement') return; if (node.body.type !== 'ExpressionStatement') return;
@ -666,16 +713,14 @@ export default class Component {
if (expression.type !== 'AssignmentExpression') return; if (expression.type !== 'AssignmentExpression') return;
if (expression.left.type === 'MemberExpression') return; if (expression.left.type === 'MemberExpression') return;
extract_names(expression.left).forEach(name => { extract_names(expression.left).forEach((name) => {
if (!this.var_lookup.has(name) && name[0] !== '$') { if (!this.var_lookup.has(name) && name[0] !== '$') {
this.injected_reactive_declaration_vars.add(name); this.injected_reactive_declaration_vars.add(name);
} }
}); });
}); });
const { scope: instance_scope, map, globals } = create_scopes( const { scope: instance_scope, map, globals } = create_scopes(script.content);
script.content
);
this.instance_scope = instance_scope; this.instance_scope = instance_scope;
this.instance_scope_map = map; this.instance_scope_map = map;
@ -684,7 +729,8 @@ export default class Component {
return this.error(node as any, compiler_errors.illegal_declaration); return this.error(node as any, compiler_errors.illegal_declaration);
} }
const writable = node.type === 'VariableDeclaration' && (node.kind === 'var' || node.kind === 'let'); const writable =
node.type === 'VariableDeclaration' && (node.kind === 'var' || node.kind === 'let');
const imported = node.type.startsWith('Import'); const imported = node.type.startsWith('Import');
this.add_var(node, { this.add_var(node, {
@ -701,11 +747,11 @@ export default class Component {
// as `$store` will mark `store` variable as referenced and subscribable // as `$store` will mark `store` variable as referenced and subscribable
const global_keys = Array.from(globals.keys()); const global_keys = Array.from(globals.keys());
const sorted_globals = [ const sorted_globals = [
...global_keys.filter(key => key[0] !== '$'), ...global_keys.filter((key) => key[0] !== '$'),
...global_keys.filter(key => key[0] === '$') ...global_keys.filter((key) => key[0] === '$')
]; ];
sorted_globals.forEach(name => { sorted_globals.forEach((name) => {
if (this.var_lookup.has(name)) return; if (this.var_lookup.has(name)) return;
const node = globals.get(name); const node = globals.get(name);
@ -761,6 +807,7 @@ export default class Component {
this.hoist_instance_declarations(); this.hoist_instance_declarations();
this.extract_reactive_declarations(); this.extract_reactive_declarations();
this.check_if_tags_content_dynamic();
} }
post_template_walk() { post_template_walk() {
@ -784,8 +831,8 @@ export default class Component {
walk(content, { walk(content, {
enter(node: Node, parent: Node, prop, index) { enter(node: Node, parent: Node, prop, index) {
if ((node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression')) { if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') {
current_function_stack.push(current_function = node); current_function_stack.push((current_function = node));
} }
if (map.has(node)) { if (map.has(node)) {
@ -799,18 +846,18 @@ export default class Component {
if (node.left.type === 'ArrayPattern') { if (node.left.type === 'ArrayPattern') {
walk(node.left, { walk(node.left, {
enter(node: Node, parent: Node) { enter(node: Node, parent: Node) {
if (node.type === 'Identifier' && if (
node.type === 'Identifier' &&
parent.type !== 'MemberExpression' && parent.type !== 'MemberExpression' &&
(parent.type !== 'AssignmentPattern' || parent.right !== node)) { (parent.type !== 'AssignmentPattern' || parent.right !== node)
names.push(node.name); ) {
names.push(node.name);
} }
} }
}); });
} else { } else {
deep = node.left.type === 'MemberExpression'; deep = node.left.type === 'MemberExpression';
names = deep names = deep ? [get_object(node.left).name] : extract_names(node.left);
? [get_object(node.left).name]
: extract_names(node.left);
} }
} else if (node.type === 'UpdateExpression') { } else if (node.type === 'UpdateExpression') {
deep = node.argument.type === 'MemberExpression'; deep = node.argument.type === 'MemberExpression';
@ -818,7 +865,7 @@ export default class Component {
names.push(name); names.push(name);
} }
if (names.length > 0) { if (names.length > 0) {
names.forEach(name => { names.forEach((name) => {
let current_scope = scope; let current_scope = scope;
let declaration; let declaration;
@ -861,14 +908,22 @@ export default class Component {
}, },
leave(node: Node) { leave(node: Node) {
if ((node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression')) { if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') {
current_function_stack.pop(); current_function_stack.pop();
current_function = current_function_stack[current_function_stack.length - 1]; current_function = current_function_stack[current_function_stack.length - 1];
} }
// do it on leave, to prevent infinite loop // do it on leave, to prevent infinite loop
if (component.compile_options.dev && component.compile_options.loopGuardTimeout > 0 && (!current_function || (!current_function.generator && !current_function.async))) { if (
const to_replace_for_loop_protect = component.loop_protect(node, scope, component.compile_options.loopGuardTimeout); component.compile_options.dev &&
component.compile_options.loopGuardTimeout > 0 &&
(!current_function || (!current_function.generator && !current_function.async))
) {
const to_replace_for_loop_protect = component.loop_protect(
node,
scope,
component.compile_options.loopGuardTimeout
);
if (to_replace_for_loop_protect) { if (to_replace_for_loop_protect) {
this.replace(to_replace_for_loop_protect); this.replace(to_replace_for_loop_protect);
scope_updated = true; scope_updated = true;
@ -920,7 +975,7 @@ export default class Component {
const deep = assignee.type === 'MemberExpression'; const deep = assignee.type === 'MemberExpression';
names.forEach(name => { names.forEach((name) => {
const scope_owner = scope.find_owner(name); const scope_owner = scope.find_owner(name);
if ( if (
scope_owner !== null scope_owner !== null
@ -950,12 +1005,13 @@ export default class Component {
}); });
} }
warn_on_undefined_store_value_references(node: Node, parent: Node, prop: string | number | symbol, scope: Scope) { warn_on_undefined_store_value_references(
if ( node: Node,
node.type === 'LabeledStatement' && parent: Node,
node.label.name === '$' && prop: string | number | symbol,
parent.type !== 'Program' scope: Scope
) { ) {
if (node.type === 'LabeledStatement' && node.label.name === '$' && parent.type !== 'Program') {
this.warn(node as any, compiler_warnings.non_top_level_reactive_declaration); this.warn(node as any, compiler_warnings.non_top_level_reactive_declaration);
} }
@ -968,8 +1024,17 @@ export default class Component {
this.warn_if_undefined(name, object, null); this.warn_if_undefined(name, object, null);
} }
if (name[1] !== '$' && scope.has(name.slice(1)) && scope.find_owner(name.slice(1)) !== this.instance_scope) { if (
if (!((regex_contains_term_function.test(parent.type) && prop === 'params') || (parent.type === 'VariableDeclarator' && prop === 'id'))) { name[1] !== '$' &&
scope.has(name.slice(1)) &&
scope.find_owner(name.slice(1)) !== this.instance_scope
) {
if (
!(
(regex_contains_term_function.test(parent.type) && prop === 'params') ||
(parent.type === 'VariableDeclarator' && prop === 'id')
)
) {
return this.error(node as any, compiler_errors.contextual_store); return this.error(node as any, compiler_errors.contextual_store);
} }
} }
@ -978,9 +1043,11 @@ export default class Component {
} }
loop_protect(node, scope: Scope, timeout: number): Node | null { loop_protect(node, scope: Scope, timeout: number): Node | null {
if (node.type === 'WhileStatement' || if (
node.type === 'WhileStatement' ||
node.type === 'ForStatement' || node.type === 'ForStatement' ||
node.type === 'DoWhileStatement') { node.type === 'DoWhileStatement'
) {
const guard = this.get_unique_name('guard', scope); const guard = this.get_unique_name('guard', scope);
this.used_names.add(guard.name); this.used_names.add(guard.name);
@ -998,10 +1065,7 @@ export default class Component {
return { return {
type: 'BlockStatement', type: 'BlockStatement',
body: [ body: [before[0], node]
before[0],
node
]
}; };
} }
return null; return null;
@ -1034,7 +1098,11 @@ export default class Component {
const inserts = []; const inserts = [];
const props = []; const props = [];
function add_new_props(exported, local, default_value) { function add_new_props(
exported: Identifier,
local: Pattern,
default_value: Expression
) {
props.push({ props.push({
type: 'Property', type: 'Property',
method: false, method: false,
@ -1044,10 +1112,10 @@ export default class Component {
key: exported, key: exported,
value: default_value value: default_value
? { ? {
type: 'AssignmentPattern', type: 'AssignmentPattern',
left: local, left: local,
right: default_value right: default_value
} }
: local : local
}); });
} }
@ -1064,7 +1132,7 @@ export default class Component {
for (let index = 0; index < node.declarations.length; index++) { for (let index = 0; index < node.declarations.length; index++) {
const declarator = node.declarations[index]; const declarator = node.declarations[index];
if (declarator.id.type !== 'Identifier') { if (declarator.id.type !== 'Identifier') {
function get_new_name(local) { function get_new_name(local: Identifier): Identifier {
const variable = component.var_lookup.get(local.name); const variable = component.var_lookup.get(local.name);
if (variable.subscribable) { if (variable.subscribable) {
inserts.push(get_insert(variable)); inserts.push(get_insert(variable));
@ -1072,13 +1140,17 @@ export default class Component {
if (variable.export_name && variable.writable) { if (variable.export_name && variable.writable) {
const alias_name = component.get_unique_name(local.name); const alias_name = component.get_unique_name(local.name);
add_new_props({ type: 'Identifier', name: variable.export_name }, local, alias_name); add_new_props(
{ type: 'Identifier', name: variable.export_name },
local,
alias_name
);
return alias_name; return alias_name;
} }
return local; return local;
} }
function rename_identifiers(param: Node) { function rename_identifiers(param: Pattern) {
switch (param.type) { switch (param.type) {
case 'ObjectPattern': { case 'ObjectPattern': {
const handle_prop = (prop: Property | RestElement) => { const handle_prop = (prop: Property | RestElement) => {
@ -1087,7 +1159,7 @@ export default class Component {
} else if (prop.value.type === 'Identifier') { } else if (prop.value.type === 'Identifier') {
prop.value = get_new_name(prop.value); prop.value = get_new_name(prop.value);
} else { } else {
rename_identifiers(prop.value); rename_identifiers(prop.value as Pattern);
} }
}; };
@ -1095,7 +1167,11 @@ export default class Component {
break; break;
} }
case 'ArrayPattern': { case 'ArrayPattern': {
const handle_element = (element: Node, index: number, array: Node[]) => { const handle_element = (
element: Pattern | null,
index: number,
array: Array<Pattern | null>
) => {
if (element) { if (element) {
if (element.type === 'Identifier') { if (element.type === 'Identifier') {
array[index] = get_new_name(element); array[index] = get_new_name(element);
@ -1110,7 +1186,11 @@ export default class Component {
} }
case 'RestElement': case 'RestElement':
param.argument = get_new_name(param.argument); if (param.argument.type === 'Identifier') {
param.argument = get_new_name(param.argument);
} else {
rename_identifiers(param.argument);
}
break; break;
case 'AssignmentPattern': case 'AssignmentPattern':
@ -1129,7 +1209,11 @@ export default class Component {
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);
const is_props = variable.export_name && variable.writable; const is_props = variable.export_name && variable.writable;
if (is_props) { if (is_props) {
add_new_props({ type: 'Identifier', name: variable.export_name }, declarator.id, declarator.init); add_new_props(
{ type: 'Identifier', name: variable.export_name },
declarator.id,
declarator.init
);
node.declarations.splice(index--, 1); node.declarations.splice(index--, 1);
} }
if (variable.subscribable && (is_props || declarator.init)) { if (variable.subscribable && (is_props || declarator.init)) {
@ -1138,11 +1222,13 @@ export default class Component {
} }
} }
this.replace(b` this.replace(
b`
${node.declarations.length ? node : null} ${node.declarations.length ? node : null}
${ props.length > 0 && b`let { ${props} } = $$props;`} ${props.length > 0 && b`let { ${props} } = $$props;`}
${inserts} ${inserts}
` as any); ` as any
);
return this.skip(); return this.skip();
} }
} }
@ -1162,12 +1248,7 @@ export default class Component {
// reference instance variables other than other // reference instance variables other than other
// hoistable functions. TODO others? // hoistable functions. TODO others?
const { const { hoistable_nodes, var_lookup, injected_reactive_declaration_vars, imports } = this;
hoistable_nodes,
var_lookup,
injected_reactive_declaration_vars,
imports
} = this;
const top_level_function_declarations = new Map(); const top_level_function_declarations = new Map();
@ -1177,7 +1258,7 @@ export default class Component {
const node = body[i]; const node = body[i];
if (node.type === 'VariableDeclaration') { if (node.type === 'VariableDeclaration') {
const all_hoistable = node.declarations.every(d => { const all_hoistable = node.declarations.every((d) => {
if (!d.init) return false; if (!d.init) return false;
if (d.init.type !== 'Literal') return false; if (d.init.type !== 'Literal') return false;
@ -1192,11 +1273,7 @@ export default class Component {
if (v.export_name) return false; if (v.export_name) return false;
if (this.var_lookup.get(name).reassigned) return false; if (this.var_lookup.get(name).reassigned) return false;
if ( if (this.vars.find((variable) => variable.name === name && variable.module)) {
this.vars.find(
variable => variable.name === name && variable.module
)
) {
return false; return false;
} }
@ -1204,7 +1281,7 @@ export default class Component {
}); });
if (all_hoistable) { if (all_hoistable) {
node.declarations.forEach(d => { node.declarations.forEach((d) => {
const variable = this.var_lookup.get((d.id as Identifier).name); const variable = this.var_lookup.get((d.id as Identifier).name);
variable.hoistable = true; variable.hoistable = true;
}); });
@ -1232,7 +1309,7 @@ export default class Component {
const checked = new Set(); const checked = new Set();
const walking = new Set(); const walking = new Set();
const is_hoistable = fn_declaration => { const is_hoistable = (fn_declaration) => {
if (fn_declaration.type === 'ExportNamedDeclaration') { if (fn_declaration.type === 'ExportNamedDeclaration') {
fn_declaration = fn_declaration.declaration; fn_declaration = fn_declaration.declaration;
} }
@ -1254,7 +1331,9 @@ export default class Component {
scope = map.get(node); scope = map.get(node);
} }
if (is_reference(node as NodeWithPropertyDefinition, parent as NodeWithPropertyDefinition)) { if (
is_reference(node as NodeWithPropertyDefinition, parent as NodeWithPropertyDefinition)
) {
const { name } = flatten_reference(node); const { name } = flatten_reference(node);
const owner = scope.find_owner(name); const owner = scope.find_owner(name);
@ -1272,9 +1351,7 @@ export default class Component {
if (variable.hoistable) return; if (variable.hoistable) return;
if (top_level_function_declarations.has(name)) { if (top_level_function_declarations.has(name)) {
const other_declaration = top_level_function_declarations.get( const other_declaration = top_level_function_declarations.get(name);
name
);
if (walking.has(other_declaration)) { if (walking.has(other_declaration)) {
hoistable = false; hoistable = false;
@ -1341,7 +1418,7 @@ export default class Component {
declaration: Node; declaration: Node;
}> = []; }> = [];
this.ast.instance.content.body.forEach(node => { this.ast.instance.content.body.forEach((node) => {
const ignores = extract_svelte_ignore_from_comments(node); const ignores = extract_svelte_ignore_from_comments(node);
if (ignores.length) this.push_ignores(ignores); if (ignores.length) this.push_ignores(ignores);
@ -1354,10 +1431,23 @@ export default class Component {
const module_dependencies = new Set<string>(); const module_dependencies = new Set<string>();
let scope = this.instance_scope; let scope = this.instance_scope;
const { declarations: outset_scope_decalarations } = this.instance_scope;
const map = this.instance_scope_map; const map = this.instance_scope_map;
walk(node.body, { walk(node.body, {
enter(node: Node, parent) { enter(node: Node, parent) {
if (node.type === 'VariableDeclaration' && node.kind === 'var') {
const is_var_in_outset = node.declarations.some((declaration: VariableDeclarator) => {
const names: string[] = extract_names(declaration.id);
return !!names.find((name: string) => {
const var_node = outset_scope_decalarations.get(name);
return var_node === node;
});
});
if (is_var_in_outset) {
return component.error(node as any, compiler_errors.invalid_var_declaration);
}
}
if (map.has(node)) { if (map.has(node)) {
scope = map.get(node); scope = map.get(node);
} }
@ -1365,7 +1455,7 @@ export default class Component {
if (node.type === 'AssignmentExpression') { if (node.type === 'AssignmentExpression') {
const left = get_object(node.left); const left = get_object(node.left);
extract_identifiers(left).forEach(node => { extract_identifiers(left).forEach((node) => {
assignee_nodes.add(node); assignee_nodes.add(node);
assignees.add(node.name); assignees.add(node.name);
}); });
@ -1376,7 +1466,9 @@ export default class Component {
} else if (node.type === 'UpdateExpression') { } else if (node.type === 'UpdateExpression') {
const identifier = get_object(node.argument); const identifier = get_object(node.argument);
assignees.add(identifier.name); assignees.add(identifier.name);
} else if (is_reference(node as NodeWithPropertyDefinition, parent as NodeWithPropertyDefinition)) { } else if (
is_reference(node as NodeWithPropertyDefinition, parent as NodeWithPropertyDefinition)
) {
const identifier = get_object(node); const identifier = get_object(node);
if (!assignee_nodes.has(identifier)) { if (!assignee_nodes.has(identifier)) {
const { name } = identifier; const { name } = identifier;
@ -1391,8 +1483,7 @@ export default class Component {
module_dependencies.add(name); module_dependencies.add(name);
} }
} }
const is_writable_or_mutated = const is_writable_or_mutated = variable && (variable.writable || variable.mutated);
variable && (variable.writable || variable.mutated);
if ( if (
should_add_as_dependency && should_add_as_dependency &&
(!owner || owner === component.instance_scope) && (!owner || owner === component.instance_scope) &&
@ -1414,7 +1505,12 @@ export default class Component {
}); });
if (module_dependencies.size > 0 && dependencies.size === 0) { if (module_dependencies.size > 0 && dependencies.size === 0) {
component.warn(node.body as any, compiler_warnings.module_script_variable_reactive_declaration(Array.from(module_dependencies))); component.warn(
node.body as any,
compiler_warnings.module_script_variable_reactive_declaration(
Array.from(module_dependencies)
)
);
} }
const { expression } = node.body as ExpressionStatement; const { expression } = node.body as ExpressionStatement;
@ -1433,8 +1529,8 @@ export default class Component {
const lookup = new Map(); const lookup = new Map();
unsorted_reactive_declarations.forEach(declaration => { unsorted_reactive_declarations.forEach((declaration) => {
declaration.assignees.forEach(name => { declaration.assignees.forEach((name) => {
if (!lookup.has(name)) { if (!lookup.has(name)) {
lookup.set(name, []); lookup.set(name, []);
} }
@ -1445,16 +1541,18 @@ export default class Component {
}); });
}); });
const cycle = check_graph_for_cycles(unsorted_reactive_declarations.reduce((acc, declaration) => { const cycle = check_graph_for_cycles(
declaration.assignees.forEach(v => { unsorted_reactive_declarations.reduce((acc, declaration) => {
declaration.dependencies.forEach(w => { declaration.assignees.forEach((v) => {
if (!declaration.assignees.has(w)) { declaration.dependencies.forEach((w) => {
acc.push([v, w]); if (!declaration.assignees.has(w)) {
} acc.push([v, w]);
}
});
}); });
}); return acc;
return acc; }, [])
}, [])); );
if (cycle && cycle.length) { if (cycle && cycle.length) {
const declarationList = lookup.get(cycle[0]); const declarationList = lookup.get(cycle[0]);
@ -1462,10 +1560,10 @@ export default class Component {
return this.error(declaration.node, compiler_errors.cyclical_reactive_declaration(cycle)); return this.error(declaration.node, compiler_errors.cyclical_reactive_declaration(cycle));
} }
const add_declaration = declaration => { const add_declaration = (declaration) => {
if (this.reactive_declarations.includes(declaration)) return; if (this.reactive_declarations.includes(declaration)) return;
declaration.dependencies.forEach(name => { declaration.dependencies.forEach((name) => {
if (declaration.assignees.has(name)) return; if (declaration.assignees.has(name)) return;
const earlier_declarations = lookup.get(name); const earlier_declarations = lookup.get(name);
if (earlier_declarations) { if (earlier_declarations) {
@ -1479,9 +1577,15 @@ export default class Component {
unsorted_reactive_declarations.forEach(add_declaration); unsorted_reactive_declarations.forEach(add_declaration);
} }
check_if_tags_content_dynamic() {
this.tags.forEach((tag) => {
tag.check_if_content_dynamic();
});
}
warn_if_undefined(name: string, node, template_scope: TemplateScope) { warn_if_undefined(name: string, node, template_scope: TemplateScope) {
if (name[0] === '$') { if (name[0] === '$') {
if (name === '$' || name[1] === '$' && !is_reserved_keyword(name)) { if (name === '$' || (name[1] === '$' && !is_reserved_keyword(name))) {
return this.error(node, compiler_errors.illegal_global(name)); return this.error(node, compiler_errors.illegal_global(name));
} }
@ -1524,7 +1628,7 @@ function process_component_options(component: Component, nodes) {
namespace: component.compile_options.namespace namespace: component.compile_options.namespace
}; };
const node = nodes.find(node => node.name === 'svelte:options'); const node = nodes.find((node) => node.name === 'svelte:options');
function get_value(attribute, { code, message }) { function get_value(attribute, { code, message }) {
const { value } = attribute; const { value } = attribute;
@ -1546,27 +1650,117 @@ function process_component_options(component: Component, nodes) {
} }
if (node) { if (node) {
node.attributes.forEach(attribute => { node.attributes.forEach((attribute) => {
if (attribute.type === 'Attribute') { if (attribute.type === 'Attribute') {
const { name } = attribute; const { name } = attribute;
function parse_tag(attribute: Attribute, tag: string) {
if (typeof tag !== 'string' && tag !== null) {
return component.error(attribute, compiler_errors.invalid_tag_attribute);
}
if (tag && !regex_valid_tag_name.test(tag)) {
return component.error(attribute, compiler_errors.invalid_tag_property);
}
if (tag && !component.compile_options.customElement) {
component.warn(attribute, compiler_warnings.missing_custom_element_compile_options);
}
component_options.customElement = component_options.customElement || ({} as any);
component_options.customElement.tag = tag;
}
switch (name) { switch (name) {
case 'tag': { case 'tag': {
const tag = get_value(attribute, compiler_errors.invalid_tag_attribute); component.warn(attribute, compiler_warnings.tag_option_deprecated);
parse_tag(attribute, get_value(attribute, compiler_errors.invalid_tag_attribute));
break;
}
case 'customElement': {
component_options.customElement = component_options.customElement || ({} as any);
if (typeof tag !== 'string' && tag !== null) { const { value } = attribute;
return component.error(attribute, compiler_errors.invalid_tag_attribute);
if (value[0].type === 'MustacheTag' && value[0].expression?.value === null) {
component_options.customElement.tag = null;
break;
} else if (value[0].type === 'Text') {
parse_tag(attribute, get_value(attribute, compiler_errors.invalid_tag_attribute));
break;
} else if (value[0].expression.type !== 'ObjectExpression') {
return component.error(attribute, compiler_errors.invalid_customElement_attribute);
} }
if (tag && !regex_valid_tag_name.test(tag)) { const tag = value[0].expression.properties.find((prop: any) => prop.key.name === 'tag');
return component.error(attribute, compiler_errors.invalid_tag_property); if (tag) {
parse_tag(tag, tag.value?.value);
} else {
return component.error(attribute, compiler_errors.invalid_customElement_attribute);
} }
if (tag && !component.compile_options.customElement) { const props = value[0].expression.properties.find(
component.warn(attribute, compiler_warnings.missing_custom_element_compile_options); (prop: any) => prop.key.name === 'props'
);
if (props) {
const error = () =>
component.error(attribute, compiler_errors.invalid_props_attribute);
if (props.value?.type !== 'ObjectExpression') {
return error();
}
component_options.customElement.props = {};
for (const property of (props.value as ObjectExpression).properties) {
if (
property.type !== 'Property' ||
property.computed ||
property.key.type !== 'Identifier' ||
property.value.type !== 'ObjectExpression'
) {
return error();
}
component_options.customElement.props[property.key.name] = {};
for (const prop of property.value.properties) {
if (
prop.type !== 'Property' ||
prop.computed ||
prop.key.type !== 'Identifier' ||
prop.value.type !== 'Literal'
) {
return error();
}
if (
['reflect', 'attribute', 'type'].indexOf(prop.key.name) === -1 ||
(prop.key.name === 'type' &&
['String', 'Number', 'Boolean', 'Array', 'Object'].indexOf(
prop.value.value as string
) === -1) ||
(prop.key.name === 'reflect' && typeof prop.value.value !== 'boolean') ||
(prop.key.name === 'attribute' && typeof prop.value.value !== 'string')
) {
return error();
}
component_options.customElement.props[property.key.name][prop.key.name] =
prop.value.value;
}
}
}
const shadow = value[0].expression.properties.find(
(prop: any) => prop.key.name === 'shadow'
);
if (shadow) {
const shadowdom = shadow.value?.value;
if (shadowdom !== 'open' && shadowdom !== 'none') {
return component.error(shadow, compiler_errors.invalid_shadow_attribute);
}
component_options.customElement.shadow = shadowdom;
} }
component_options.tag = tag;
break; break;
} }
@ -1579,7 +1773,10 @@ function process_component_options(component: Component, nodes) {
if (valid_namespaces.indexOf(ns) === -1) { if (valid_namespaces.indexOf(ns) === -1) {
const match = fuzzymatch(ns, valid_namespaces); const match = fuzzymatch(ns, valid_namespaces);
return component.error(attribute, compiler_errors.invalid_namespace_property(ns, match)); return component.error(
attribute,
compiler_errors.invalid_namespace_property(ns, match)
);
} }
component_options.namespace = ns; component_options.namespace = ns;
@ -1600,7 +1797,10 @@ function process_component_options(component: Component, nodes) {
} }
default: default:
return component.error(attribute, compiler_errors.invalid_options_attribute_unknown); return component.error(
attribute,
compiler_errors.invalid_options_attribute_unknown(name)
);
} }
} else { } else {
return component.error(attribute, compiler_errors.invalid_options_attribute); return component.error(attribute, compiler_errors.invalid_options_attribute);

@ -22,7 +22,9 @@ export default {
}), }),
invalid_binding_no_checkbox: (binding: string, is_radio: boolean) => ({ invalid_binding_no_checkbox: (binding: string, is_radio: boolean) => ({
code: 'invalid-binding', code: 'invalid-binding',
message: `'${binding}' binding can only be used with <input type="checkbox">` + (is_radio ? ' — for <input type="radio">, use \'group\' binding' : '') message:
`'${binding}' binding can only be used with <input type="checkbox">` +
(is_radio ? ' — for <input type="radio">, use \'group\' binding' : '')
}), }),
invalid_binding: (binding: string) => ({ invalid_binding: (binding: string) => ({
code: 'invalid-binding', code: 'invalid-binding',
@ -30,7 +32,9 @@ export default {
}), }),
invalid_binding_window: (parts: string[]) => ({ invalid_binding_window: (parts: string[]) => ({
code: 'invalid-binding', code: 'invalid-binding',
message: `Bindings on <svelte:window> must be to top-level properties, e.g. '${parts[parts.length - 1]}' rather than '${parts.join('.')}'` message: `Bindings on <svelte:window> must be to top-level properties, e.g. '${
parts[parts.length - 1]
}' rather than '${parts.join('.')}'`
}), }),
invalid_binding_let: { invalid_binding_let: {
code: 'invalid-binding', code: 'invalid-binding',
@ -54,23 +58,24 @@ export default {
}), }),
invalid_type: { invalid_type: {
code: 'invalid-type', code: 'invalid-type',
message: '\'type\' attribute cannot be dynamic if input uses two-way binding' message: "'type' attribute cannot be dynamic if input uses two-way binding"
}, },
missing_type: { missing_type: {
code: 'missing-type', code: 'missing-type',
message: '\'type\' attribute must be specified' message: "'type' attribute must be specified"
}, },
dynamic_multiple_attribute: { dynamic_multiple_attribute: {
code: 'dynamic-multiple-attribute', code: 'dynamic-multiple-attribute',
message: '\'multiple\' attribute cannot be dynamic if select uses two-way binding' message: "'multiple' attribute cannot be dynamic if select uses two-way binding"
}, },
missing_contenteditable_attribute: { missing_contenteditable_attribute: {
code: 'missing-contenteditable-attribute', code: 'missing-contenteditable-attribute',
message: '\'contenteditable\' attribute is required for textContent, innerHTML and innerText two-way bindings' message:
"'contenteditable' attribute is required for textContent, innerHTML and innerText two-way bindings"
}, },
dynamic_contenteditable_attribute: { dynamic_contenteditable_attribute: {
code: 'dynamic-contenteditable-attribute', code: 'dynamic-contenteditable-attribute',
message: '\'contenteditable\' attribute cannot be dynamic if element uses two-way binding' message: "'contenteditable' attribute cannot be dynamic if element uses two-way binding"
}, },
invalid_event_modifier_combination: (modifier1: string, modifier2: string) => ({ invalid_event_modifier_combination: (modifier1: string, modifier2: string) => ({
code: 'invalid-event-modifier', code: 'invalid-event-modifier',
@ -90,7 +95,8 @@ export default {
}, },
textarea_duplicate_value: { textarea_duplicate_value: {
code: 'textarea-duplicate-value', code: 'textarea-duplicate-value',
message: 'A <textarea> can have either a value attribute or (equivalently) child content, but not both' message:
'A <textarea> can have either a value attribute or (equivalently) child content, but not both'
}, },
illegal_attribute: (name: string) => ({ illegal_attribute: (name: string) => ({
code: 'illegal-attribute', code: 'illegal-attribute',
@ -106,7 +112,8 @@ export default {
}), }),
invalid_slotted_content: { invalid_slotted_content: {
code: 'invalid-slotted-content', code: 'invalid-slotted-content',
message: 'Element with a slot=\'...\' attribute must be a child of a component or a descendant of a custom element' message:
"Element with a slot='...' attribute must be a child of a component or a descendant of a custom element"
}, },
invalid_attribute_head: { invalid_attribute_head: {
code: 'invalid-attribute', code: 'invalid-attribute',
@ -158,13 +165,14 @@ export default {
}, },
duplicate_transition: (directive: string, parent_directive: string) => { duplicate_transition: (directive: string, parent_directive: string) => {
function describe(_directive: string) { function describe(_directive: string) {
return _directive === 'transition' return _directive === 'transition' ? "a 'transition'" : `an '${_directive}'`;
? "a 'transition'"
: `an '${_directive}'`;
} }
const message = directive === parent_directive const message =
? `An element can only have one '${directive}' directive` directive === parent_directive
: `An element cannot have both ${describe(parent_directive)} directive and ${describe(directive)} directive`; ? `An element can only have one '${directive}' directive`
: `An element cannot have both ${describe(parent_directive)} directive and ${describe(
directive
)} directive`;
return { return {
code: 'duplicate-transition', code: 'duplicate-transition',
message message
@ -172,7 +180,8 @@ export default {
}, },
contextual_store: { contextual_store: {
code: 'contextual-store', code: 'contextual-store',
message: 'Stores must be declared at the top level of the component (this may change in a future version of Svelte)' message:
'Stores must be declared at the top level of the component (this may change in a future version of Svelte)'
}, },
default_export: { default_export: {
code: 'default-export', code: 'default-export',
@ -202,13 +211,30 @@ export default {
code: 'invalid-tag-property', code: 'invalid-tag-property',
message: "tag name must be two or more words joined by the '-' character" message: "tag name must be two or more words joined by the '-' character"
}, },
invalid_customElement_attribute: {
code: 'invalid-customElement-attribute',
message:
"'customElement' must be a string literal defining a valid custom element name or an object of the form " +
"{ tag: string; shadow?: 'open' | 'none'; props?: { [key: string]: { attribute?: string; reflect?: boolean; type: .. } } }"
},
invalid_tag_attribute: { invalid_tag_attribute: {
code: 'invalid-tag-attribute', code: 'invalid-tag-attribute',
message: "'tag' must be a string literal" message: "'tag' must be a string literal"
}, },
invalid_shadow_attribute: {
code: 'invalid-shadow-attribute',
message: "'shadow' must be either 'open' or 'none'"
},
invalid_props_attribute: {
code: 'invalid-props-attribute',
message:
"'props' must be a statically analyzable object literal of the form " +
"'{ [key: string]: { attribute?: string; reflect?: boolean; type?: 'String' | 'Boolean' | 'Number' | 'Array' | 'Object' }'"
},
invalid_namespace_property: (namespace: string, suggestion?: string) => ({ invalid_namespace_property: (namespace: string, suggestion?: string) => ({
code: 'invalid-namespace-property', code: 'invalid-namespace-property',
message: `Invalid namespace '${namespace}'` + (suggestion ? ` (did you mean '${suggestion}'?)` : '') message:
`Invalid namespace '${namespace}'` + (suggestion ? ` (did you mean '${suggestion}'?)` : '')
}), }),
invalid_namespace_attribute: { invalid_namespace_attribute: {
code: 'invalid-namespace-attribute', code: 'invalid-namespace-attribute',
@ -218,13 +244,14 @@ export default {
code: `invalid-${name}-value`, code: `invalid-${name}-value`,
message: `${name} attribute must be true or false` message: `${name} attribute must be true or false`
}), }),
invalid_options_attribute_unknown: { invalid_options_attribute_unknown: (name: string) => ({
code: 'invalid-options-attribute', code: 'invalid-options-attribute',
message: '<svelte:options> unknown attribute' message: `<svelte:options> unknown attribute '${name}'`
}, }),
invalid_options_attribute: { invalid_options_attribute: {
code: 'invalid-options-attribute', code: 'invalid-options-attribute',
message: "<svelte:options> can only have static 'tag', 'namespace', 'accessors', 'immutable' and 'preserveWhitespace' attributes" message:
"<svelte:options> can only have static 'tag', 'namespace', 'accessors', 'immutable' and 'preserveWhitespace' attributes"
}, },
css_invalid_global: { css_invalid_global: {
code: 'css-invalid-global', code: 'css-invalid-global',
@ -236,7 +263,8 @@ export default {
}, },
css_invalid_global_selector_position: { css_invalid_global_selector_position: {
code: 'css-invalid-global-selector-position', code: 'css-invalid-global-selector-position',
message: ':global(...) not at the start of a selector sequence should not contain type or universal selectors' message:
':global(...) not at the start of a selector sequence should not contain type or universal selectors'
}, },
css_invalid_selector: (selector: string) => ({ css_invalid_selector: (selector: string) => ({
code: 'css-invalid-selector', code: 'css-invalid-selector',
@ -248,15 +276,18 @@ export default {
}, },
invalid_animation_immediate: { invalid_animation_immediate: {
code: 'invalid-animation', code: 'invalid-animation',
message: 'An element that uses the animate directive must be the immediate child of a keyed each block' message:
'An element that uses the animate directive must be the immediate child of a keyed each block'
}, },
invalid_animation_key: { invalid_animation_key: {
code: 'invalid-animation', code: 'invalid-animation',
message: 'An element that uses the animate directive must be used inside a keyed each block. Did you forget to add a key to your each block?' message:
'An element that uses the animate directive must be used inside a keyed each block. Did you forget to add a key to your each block?'
}, },
invalid_animation_sole: { invalid_animation_sole: {
code: 'invalid-animation', code: 'invalid-animation',
message: 'An element that uses the animate directive must be the sole child of a keyed each block' message:
'An element that uses the animate directive must be the sole child of a keyed each block'
}, },
invalid_animation_dynamic_element: { invalid_animation_dynamic_element: {
code: 'invalid-animation', code: 'invalid-animation',
@ -264,11 +295,13 @@ export default {
}, },
invalid_directive_value: { invalid_directive_value: {
code: 'invalid-directive-value', code: 'invalid-directive-value',
message: 'Can only bind to an identifier (e.g. `foo`) or a member expression (e.g. `foo.bar` or `foo[baz]`)' message:
'Can only bind to an identifier (e.g. `foo`) or a member expression (e.g. `foo.bar` or `foo[baz]`)'
}, },
invalid_const_placement: { invalid_const_placement: {
code: 'invalid-const-placement', code: 'invalid-const-placement',
message: '{@const} must be the immediate child of {#if}, {:else if}, {:else}, {#each}, {:then}, {:catch}, <svelte:fragment> or <Component>' message:
'{@const} must be the immediate child of {#if}, {:else if}, {:else}, {#each}, {:then}, {:catch}, <svelte:fragment> or <Component>'
}, },
invalid_const_declaration: (name: string) => ({ invalid_const_declaration: (name: string) => ({
code: 'invalid-const-declaration', code: 'invalid-const-declaration',
@ -286,6 +319,10 @@ export default {
code: 'invalid-component-style-directive', code: 'invalid-component-style-directive',
message: 'Style directives cannot be used on components' message: 'Style directives cannot be used on components'
}, },
invalid_var_declaration: {
code: 'invalid_var_declaration',
message: '"var" scope should not extend outside the reactive block'
},
invalid_style_directive_modifier: (valid: string) => ({ invalid_style_directive_modifier: (valid: string) => ({
code: 'invalid-style-directive-modifier', code: 'invalid-style-directive-modifier',
message: `Valid modifiers for style directives are: ${valid}` message: `Valid modifiers for style directives are: ${valid}`

@ -6,9 +6,9 @@ import { ARIAPropertyDefinition } from 'aria-query';
* @internal * @internal
*/ */
export default { export default {
custom_element_no_tag: { tag_option_deprecated: {
code: 'custom-element-no-tag', code: 'tag-option-deprecated',
message: 'No custom element \'tag\' option was specified. To automatically register a custom element, specify a name with a hyphen in it, e.g. <svelte:options tag="my-thing"/>. To hide this warning, use <svelte:options tag={null}/>' message: "'tag' option is deprecated — use 'customElement' instead"
}, },
unused_export_let: (component: string, property: string) => ({ unused_export_let: (component: string, property: string) => ({
code: 'unused-export-let', code: 'unused-export-let',
@ -24,15 +24,22 @@ export default {
}, },
module_script_variable_reactive_declaration: (names: string[]) => ({ module_script_variable_reactive_declaration: (names: string[]) => ({
code: 'module-script-reactive-declaration', code: 'module-script-reactive-declaration',
message: `${names.map(name => `"${name}"`).join(', ')} ${names.length > 1 ? 'are' : 'is'} declared in a module script and will not be reactive` message: `${names.map((name) => `"${name}"`).join(', ')} ${
names.length > 1 ? 'are' : 'is'
} declared in a module script and will not be reactive`
}), }),
missing_declaration: (name: string, has_script: boolean) => ({ missing_declaration: (name: string, has_script: boolean) => ({
code: 'missing-declaration', code: 'missing-declaration',
message: `'${name}' is not defined` + (has_script ? '' : `. Consider adding a <script> block with 'export let ${name}' to declare a prop`) message:
`'${name}' is not defined` +
(has_script
? ''
: `. Consider adding a <script> block with 'export let ${name}' to declare a prop`)
}), }),
missing_custom_element_compile_options: { missing_custom_element_compile_options: {
code: 'missing-custom-element-compile-options', code: 'missing-custom-element-compile-options',
message: "The 'tag' option is used when generating a custom element. Did you forget the 'customElement: true' compile option?" message:
"The 'customElement' option is used when generating a custom element. Did you forget the 'customElement: true' compile option?"
}, },
css_unused_selector: (selector: string) => ({ css_unused_selector: (selector: string) => ({
code: 'css-unused-selector', code: 'css-unused-selector',
@ -52,7 +59,7 @@ export default {
}), }),
avoid_is: { avoid_is: {
code: 'avoid-is', code: 'avoid-is',
message: 'The \'is\' attribute is not supported cross-browser and should be avoided' message: "The 'is' attribute is not supported cross-browser and should be avoided"
}, },
invalid_html_attribute: (name: string, suggestion: string) => ({ invalid_html_attribute: (name: string, suggestion: string) => ({
code: 'invalid-html-attribute', code: 'invalid-html-attribute',
@ -78,10 +85,14 @@ export default {
message = `The value of '${attribute}' must be exactly one of true, false, or mixed`; message = `The value of '${attribute}' must be exactly one of true, false, or mixed`;
break; break;
case 'token': case 'token':
message = `The value of '${attribute}' must be exactly one of ${(schema.values || []).join(', ')}`; message = `The value of '${attribute}' must be exactly one of ${(schema.values || []).join(
', '
)}`;
break; break;
case 'tokenlist': case 'tokenlist':
message = `The value of '${attribute}' must be a space-separated list of one or more of ${(schema.values || []).join(', ')}`; message = `The value of '${attribute}' must be a space-separated list of one or more of ${(
schema.values || []
).join(', ')}`;
break; break;
default: default:
message = `The value of '${attribute}' must be of type ${schema.type}`; message = `The value of '${attribute}' must be of type ${schema.type}`;
@ -93,7 +104,9 @@ export default {
}, },
a11y_unknown_aria_attribute: (attribute: string, suggestion?: string) => ({ a11y_unknown_aria_attribute: (attribute: string, suggestion?: string) => ({
code: 'a11y-unknown-aria-attribute', code: 'a11y-unknown-aria-attribute',
message: `A11y: Unknown aria attribute 'aria-${attribute}'` + (suggestion ? ` (did you mean '${suggestion}'?)` : '') message:
`A11y: Unknown aria attribute 'aria-${attribute}'` +
(suggestion ? ` (did you mean '${suggestion}'?)` : '')
}), }),
a11y_hidden: (name: string) => ({ a11y_hidden: (name: string) => ({
code: 'a11y-hidden', code: 'a11y-hidden',
@ -115,29 +128,52 @@ export default {
code: 'a11y-no-redundant-roles', code: 'a11y-no-redundant-roles',
message: `A11y: Redundant role '${role}'` message: `A11y: Redundant role '${role}'`
}), }),
a11y_no_interactive_element_to_noninteractive_role: (role: string | boolean, element: string) => ({ a11y_no_static_element_interactions: (element: string, handlers: string[]) => ({
code: 'a11y-no-static-element-interactions',
message: `A11y: <${element}> with ${handlers.join(', ')} ${
handlers.length === 1 ? 'handler' : 'handlers'
} must have an ARIA role`
}),
a11y_no_interactive_element_to_noninteractive_role: (
role: string | boolean,
element: string
) => ({
code: 'a11y-no-interactive-element-to-noninteractive-role', code: 'a11y-no-interactive-element-to-noninteractive-role',
message: `A11y: <${element}> cannot have role '${role}'` message: `A11y: <${element}> cannot have role '${role}'`
}), }),
a11y_no_noninteractive_element_to_interactive_role: (role: string | boolean, element: string) => ({ a11y_no_noninteractive_element_interactions: (element: string) => ({
code: 'a11y-no-noninteractive-element-interactions',
message: `A11y: Non-interactive element <${element}> should not be assigned mouse or keyboard event listeners.`
}),
a11y_no_noninteractive_element_to_interactive_role: (
role: string | boolean,
element: string
) => ({
code: 'a11y-no-noninteractive-element-to-interactive-role', code: 'a11y-no-noninteractive-element-to-interactive-role',
message: `A11y: Non-interactive element <${element}> cannot have interactive role '${role}'` message: `A11y: Non-interactive element <${element}> cannot have interactive role '${role}'`
}), }),
a11y_role_has_required_aria_props: (role: string, props: string[]) => ({ a11y_role_has_required_aria_props: (role: string, props: string[]) => ({
code: 'a11y-role-has-required-aria-props', code: 'a11y-role-has-required-aria-props',
message: `A11y: Elements with the ARIA role "${role}" must have the following attributes defined: ${props.map(name => `"${name}"`).join(', ')}` message: `A11y: Elements with the ARIA role "${role}" must have the following attributes defined: ${props
.map((name) => `"${name}"`)
.join(', ')}`
}), }),
a11y_role_supports_aria_props: (attribute: string, role: string, is_implicit: boolean, name: string) => { a11y_role_supports_aria_props: (
let message = `The attribute '${attribute}' is not supported by the role '${role}'.`; attribute: string,
if (is_implicit) { role: string,
message += ` This role is implicit on the element <${name}>.`; is_implicit: boolean,
} name: string
) => {
let message = `The attribute '${attribute}' is not supported by the role '${role}'.`;
if (is_implicit) {
message += ` This role is implicit on the element <${name}>.`;
}
return { return {
code: 'a11y-role-supports-aria-props', code: 'a11y-role-supports-aria-props',
message: `A11y: ${message}` message: `A11y: ${message}`
}; };
}, },
a11y_accesskey: { a11y_accesskey: {
code: 'a11y-accesskey', code: 'a11y-accesskey',
message: 'A11y: Avoid using accesskey' message: 'A11y: Avoid using accesskey'
@ -162,6 +198,12 @@ export default {
code: 'a11y-missing-attribute', code: 'a11y-missing-attribute',
message: `A11y: <${name}> element should have ${article} ${sequence} attribute` message: `A11y: <${name}> element should have ${article} ${sequence} attribute`
}), }),
a11y_autocomplete_valid: (type: null | true | string, value: null | true | string) => ({
code: 'a11y-autocomplete-valid',
message: `A11y: The value '${value}' is not supported by the attribute 'autocomplete' on element <input type="${
type || '...'
}">`
}),
a11y_img_redundant_alt: { a11y_img_redundant_alt: {
code: 'a11y-img-redundant-alt', code: 'a11y-img-redundant-alt',
message: 'A11y: Screenreaders already announce <img> elements as an image.' message: 'A11y: Screenreaders already announce <img> elements as an image.'
@ -196,7 +238,8 @@ export default {
}), }),
a11y_click_events_have_key_events: { a11y_click_events_have_key_events: {
code: 'a11y-click-events-have-key-events', code: 'a11y-click-events-have-key-events',
message: 'A11y: visible, non-interactive elements with an on:click event must be accompanied by an on:keydown, on:keyup, or on:keypress event.' message:
'A11y: visible, non-interactive elements with an on:click event must be accompanied by an on:keydown, on:keyup, or on:keypress event.'
}, },
a11y_missing_content: (name: string) => ({ a11y_missing_content: (name: string) => ({
code: 'a11y-missing-content', code: 'a11y-missing-content',
@ -212,7 +255,7 @@ export default {
}, },
redundant_event_modifier_for_touch: { redundant_event_modifier_for_touch: {
code: 'redundant-event-modifier', code: 'redundant-event-modifier',
message: 'Touch event handlers that don\'t use the \'event\' object are passive by default' message: "Touch event handlers that don't use the 'event' object are passive by default"
}, },
redundant_event_modifier_passive: { redundant_event_modifier_passive: {
code: 'redundant-event-modifier', code: 'redundant-event-modifier',
@ -220,10 +263,11 @@ export default {
}, },
invalid_rest_eachblock_binding: (rest_element_name: string) => ({ invalid_rest_eachblock_binding: (rest_element_name: string) => ({
code: 'invalid-rest-eachblock-binding', code: 'invalid-rest-eachblock-binding',
message: `...${rest_element_name} operator will create a new object and binding propagation with original object will not work` message: `The rest operator (...) will create a new object and binding '${rest_element_name}' with the original object will not work`
}), }),
avoid_mouse_events_on_document: { avoid_mouse_events_on_document: {
code: 'avoid-mouse-events-on-document', code: 'avoid-mouse-events-on-document',
message: 'Mouse enter/leave events on the document are not supported in all browsers and should be avoided' message:
'Mouse enter/leave events on the document are not supported in all browsers and should be avoided'
} }
}; };

@ -24,8 +24,8 @@ export default function create_module(
) { ) {
const internal_path = `${sveltePath}/internal`; const internal_path = `${sveltePath}/internal`;
helpers.sort((a, b) => (a.name < b.name) ? -1 : 1); helpers.sort((a, b) => (a.name < b.name ? -1 : 1));
globals.sort((a, b) => (a.name < b.name) ? -1 : 1); globals.sort((a, b) => (a.name < b.name ? -1 : 1));
const formatter = wrappers[format]; const formatter = wrappers[format];
@ -33,7 +33,18 @@ export default function create_module(
throw new Error(`options.format is invalid (must be ${list(Object.keys(wrappers))})`); throw new Error(`options.format is invalid (must be ${list(Object.keys(wrappers))})`);
} }
return formatter(program, name, banner, sveltePath, internal_path, helpers, globals, imports, module_exports, exports_from); return formatter(
program,
name,
banner,
sveltePath,
internal_path,
helpers,
globals,
imports,
module_exports,
exports_from
);
} }
function edit_source(source, sveltePath) { function edit_source(source, sveltePath) {
@ -46,26 +57,30 @@ function get_internal_globals(
globals: Array<{ name: string; alias: Identifier }>, globals: Array<{ name: string; alias: Identifier }>,
helpers: Array<{ name: string; alias: Identifier }> helpers: Array<{ name: string; alias: Identifier }>
) { ) {
return globals.length > 0 && { return (
type: 'VariableDeclaration', globals.length > 0 && {
kind: 'const', type: 'VariableDeclaration',
declarations: [{ kind: 'const',
type: 'VariableDeclarator', declarations: [
id: { {
type: 'ObjectPattern', type: 'VariableDeclarator',
properties: globals.map(g => ({ id: {
type: 'Property', type: 'ObjectPattern',
method: false, properties: globals.map((g) => ({
shorthand: false, type: 'Property',
computed: false, method: false,
key: { type: 'Identifier', name: g.name }, shorthand: false,
value: g.alias, computed: false,
kind: 'init' key: { type: 'Identifier', name: g.name },
})) value: g.alias,
}, kind: 'init'
init: helpers.find(({ name }) => name === 'globals').alias }))
}] },
}; init: helpers.find(({ name }) => name === 'globals').alias
}
]
}
);
} }
function esm( function esm(
@ -82,7 +97,7 @@ function esm(
) { ) {
const import_declaration = { const import_declaration = {
type: 'ImportDeclaration', type: 'ImportDeclaration',
specifiers: helpers.map(h => ({ specifiers: helpers.map((h) => ({
type: 'ImportSpecifier', type: 'ImportSpecifier',
local: h.alias, local: h.alias,
imported: { type: 'Identifier', name: h.name } imported: { type: 'Identifier', name: h.name }
@ -105,7 +120,7 @@ function esm(
const exports = module_exports.length > 0 && { const exports = module_exports.length > 0 && {
type: 'ExportNamedDeclaration', type: 'ExportNamedDeclaration',
specifiers: module_exports.map(x => ({ specifiers: module_exports.map((x) => ({
type: 'Specifier', type: 'Specifier',
local: { type: 'Identifier', name: x.name }, local: { type: 'Identifier', name: x.name },
exported: { type: 'Identifier', name: x.as } exported: { type: 'Identifier', name: x.as }
@ -142,27 +157,29 @@ function cjs(
const internal_requires = { const internal_requires = {
type: 'VariableDeclaration', type: 'VariableDeclaration',
kind: 'const', kind: 'const',
declarations: [{ declarations: [
type: 'VariableDeclarator', {
id: { type: 'VariableDeclarator',
type: 'ObjectPattern', id: {
properties: helpers.map(h => ({ type: 'ObjectPattern',
type: 'Property', properties: helpers.map((h) => ({
method: false, type: 'Property',
shorthand: false, method: false,
computed: false, shorthand: false,
key: { type: 'Identifier', name: h.name }, computed: false,
value: h.alias, key: { type: 'Identifier', name: h.name },
kind: 'init' value: h.alias,
})) kind: 'init'
}, }))
init: x`require("${internal_path}")` },
}] init: x`require("${internal_path}")`
}
]
}; };
const internal_globals = get_internal_globals(globals, helpers); const internal_globals = get_internal_globals(globals, helpers);
const user_requires = imports.map(node => { const user_requires = imports.map((node) => {
const init = x`require("${edit_source(node.source.value, sveltePath)}")`; const init = x`require("${edit_source(node.source.value, sveltePath)}")`;
if (node.specifiers.length === 0) { if (node.specifiers.length === 0) {
return b`${init};`; return b`${init};`;
@ -170,32 +187,41 @@ function cjs(
return { return {
type: 'VariableDeclaration', type: 'VariableDeclaration',
kind: 'const', kind: 'const',
declarations: [{ declarations: [
type: 'VariableDeclarator', {
id: node.specifiers[0].type === 'ImportNamespaceSpecifier' type: 'VariableDeclarator',
? { type: 'Identifier', name: node.specifiers[0].local.name } id:
: { node.specifiers[0].type === 'ImportNamespaceSpecifier'
type: 'ObjectPattern', ? { type: 'Identifier', name: node.specifiers[0].local.name }
properties: node.specifiers.map(s => ({ : {
type: 'Property', type: 'ObjectPattern',
method: false, properties: node.specifiers.map((s) => ({
shorthand: false, type: 'Property',
computed: false, method: false,
key: s.type === 'ImportSpecifier' ? s.imported : { type: 'Identifier', name: 'default' }, shorthand: false,
value: s.local, computed: false,
kind: 'init' key:
})) s.type === 'ImportSpecifier'
}, ? s.imported
init : { type: 'Identifier', name: 'default' },
}] value: s.local,
kind: 'init'
}))
},
init
}
]
}; };
}); });
const exports = module_exports.map(x => b`exports.${{ type: 'Identifier', name: x.as }} = ${{ type: 'Identifier', name: x.name }};`); const exports = module_exports.map(
(x) =>
b`exports.${{ type: 'Identifier', name: x.as }} = ${{ type: 'Identifier', name: x.name }};`
);
const user_exports_from = exports_from.map(node => { const user_exports_from = exports_from.map((node) => {
const init = x`require("${edit_source(node.source.value, sveltePath)}")`; const init = x`require("${edit_source(node.source.value, sveltePath)}")`;
return node.specifiers.map(specifier => { return node.specifiers.map((specifier) => {
return b`exports.${specifier.exported} = ${init}.${specifier.local};`; return b`exports.${specifier.exported} = ${init}.${specifier.local};`;
}); });
}); });

@ -18,7 +18,7 @@ enum BlockAppliesToNode {
} }
enum NodeExist { enum NodeExist {
Probably = 1, Probably = 1,
Definitely = 2, Definitely = 2
} }
const whitelist_attribute_selector = new Map([ const whitelist_attribute_selector = new Map([
@ -57,7 +57,7 @@ export default class Selector {
} }
apply(node: Element) { apply(node: Element) {
const to_encapsulate: Array<{ node: Element, block: Block }> = []; const to_encapsulate: Array<{ node: Element; block: Block }> = [];
apply_selector(this.local_blocks.slice(), node, to_encapsulate); apply_selector(this.local_blocks.slice(), node, to_encapsulate);
@ -85,7 +85,9 @@ export default class Selector {
} }
transform(code: MagicString, attr: string, max_amount_class_specificity_increased: number) { transform(code: MagicString, attr: string, max_amount_class_specificity_increased: number) {
const amount_class_specificity_to_increase = max_amount_class_specificity_increased - this.blocks.filter(block => block.should_encapsulate).length; const amount_class_specificity_to_increase =
max_amount_class_specificity_increased -
this.blocks.filter((block) => block.should_encapsulate).length;
function remove_global_pseudo_class(selector: CssNode) { function remove_global_pseudo_class(selector: CssNode) {
const first = selector.children[0]; const first = selector.children[0];
@ -125,7 +127,13 @@ export default class Selector {
if (block.global) { if (block.global) {
remove_global_pseudo_class(block.selectors[0]); remove_global_pseudo_class(block.selectors[0]);
} }
if (block.should_encapsulate) encapsulate_block(block, index === this.blocks.length - 1 ? attr.repeat(amount_class_specificity_to_increase + 1) : attr); if (block.should_encapsulate)
encapsulate_block(
block,
index === this.blocks.length - 1
? attr.repeat(amount_class_specificity_to_increase + 1)
: attr
);
}); });
} }
@ -172,10 +180,20 @@ export default class Selector {
for (let i = 0; i < this.blocks.length; i++) { for (let i = 0; i < this.blocks.length; i++) {
const block = this.blocks[i]; const block = this.blocks[i];
if (block.combinator && block.selectors.length === 0) { if (block.combinator && block.selectors.length === 0) {
component.error(this.node, compiler_errors.css_invalid_selector(component.source.slice(this.node.start, this.node.end))); component.error(
this.node,
compiler_errors.css_invalid_selector(
component.source.slice(this.node.start, this.node.end)
)
);
} }
if (!block.combinator && block.selectors.length === 0) { if (!block.combinator && block.selectors.length === 0) {
component.error(this.node, compiler_errors.css_invalid_selector(component.source.slice(this.node.start, this.node.end))); component.error(
this.node,
compiler_errors.css_invalid_selector(
component.source.slice(this.node.start, this.node.end)
)
);
} }
} }
} }
@ -184,7 +202,8 @@ export default class Selector {
for (const block of this.blocks) { for (const block of this.blocks) {
for (let index = 0; index < block.selectors.length; index++) { for (let index = 0; index < block.selectors.length; index++) {
const selector = block.selectors[index]; const selector = block.selectors[index];
if (selector.type === 'PseudoClassSelector' && if (
selector.type === 'PseudoClassSelector' &&
selector.name === 'global' && selector.name === 'global' &&
index !== 0 && index !== 0 &&
selector.children && selector.children &&
@ -201,21 +220,24 @@ export default class Selector {
let count = 0; let count = 0;
for (const block of this.blocks) { for (const block of this.blocks) {
if (block.should_encapsulate) { if (block.should_encapsulate) {
count ++; count++;
} }
} }
return count; return count;
} }
} }
function apply_selector(blocks: Block[], node: Element, to_encapsulate: Array<{ node: Element, block: Block }>): boolean { function apply_selector(
blocks: Block[],
node: Element,
to_encapsulate: Array<{ node: Element; block: Block }>
): boolean {
const block = blocks.pop(); const block = blocks.pop();
if (!block) return false; if (!block) return false;
if (!node) { if (!node) {
return ( return (
(block.global && blocks.every(block => block.global)) || (block.global && blocks.every((block) => block.global)) || (block.host && blocks.length === 0)
(block.host && blocks.length === 0)
); );
} }
@ -224,7 +246,7 @@ function apply_selector(blocks: Block[], node: Element, to_encapsulate: Array<{
return false; return false;
case BlockAppliesToNode.UnknownSelectorType: case BlockAppliesToNode.UnknownSelectorType:
// bail. TODO figure out what these could be // bail. TODO figure out what these could be
to_encapsulate.push({ node, block }); to_encapsulate.push({ node, block });
return true; return true;
} }
@ -242,8 +264,10 @@ function apply_selector(blocks: Block[], node: Element, to_encapsulate: Array<{
} }
let parent = node; let parent = node;
while (parent = get_element_parent(parent)) { while ((parent = get_element_parent(parent))) {
if (block_might_apply_to_node(ancestor_block, parent) !== BlockAppliesToNode.NotPossible) { if (
block_might_apply_to_node(ancestor_block, parent) !== BlockAppliesToNode.NotPossible
) {
to_encapsulate.push({ node: parent, block: ancestor_block }); to_encapsulate.push({ node: parent, block: ancestor_block });
} }
} }
@ -254,14 +278,14 @@ function apply_selector(blocks: Block[], node: Element, to_encapsulate: Array<{
} }
} }
if (blocks.every(block => block.global)) { if (blocks.every((block) => block.global)) {
to_encapsulate.push({ node, block }); to_encapsulate.push({ node, block });
return true; return true;
} }
return false; return false;
} else if (block.combinator.name === '>') { } else if (block.combinator.name === '>') {
const has_global_parent = blocks.every(block => block.global); const has_global_parent = blocks.every((block) => block.global);
if (has_global_parent || apply_selector(blocks, get_element_parent(node), to_encapsulate)) { if (has_global_parent || apply_selector(blocks, get_element_parent(node), to_encapsulate)) {
to_encapsulate.push({ node, block }); to_encapsulate.push({ node, block });
return true; return true;
@ -275,7 +299,7 @@ function apply_selector(blocks: Block[], node: Element, to_encapsulate: Array<{
// NOTE: if we have :global(), we couldn't figure out what is selected within `:global` due to the // NOTE: if we have :global(), we couldn't figure out what is selected within `:global` due to the
// css-tree limitation that does not parse the inner selector of :global // css-tree limitation that does not parse the inner selector of :global
// so unless we are sure there will be no sibling to match, we will consider it as matched // so unless we are sure there will be no sibling to match, we will consider it as matched
const has_global = blocks.some(block => block.global); const has_global = blocks.some((block) => block.global);
if (has_global) { if (has_global) {
if (siblings.size === 0 && get_element_parent(node) !== null) { if (siblings.size === 0 && get_element_parent(node) !== null) {
return false; return false;
@ -309,13 +333,19 @@ function block_might_apply_to_node(block: Block, node: Element): BlockAppliesToN
while (i--) { while (i--) {
const selector = block.selectors[i]; const selector = block.selectors[i];
const name = typeof selector.name === 'string' && selector.name.replace(regex_backslash_and_following_character, '$1'); const name =
typeof selector.name === 'string' &&
selector.name.replace(regex_backslash_and_following_character, '$1');
if (selector.type === 'PseudoClassSelector' && (name === 'host' || name === 'root')) { if (selector.type === 'PseudoClassSelector' && (name === 'host' || name === 'root')) {
return BlockAppliesToNode.NotPossible; return BlockAppliesToNode.NotPossible;
} }
if (block.selectors.length === 1 && selector.type === 'PseudoClassSelector' && name === 'global') { if (
block.selectors.length === 1 &&
selector.type === 'PseudoClassSelector' &&
name === 'global'
) {
return BlockAppliesToNode.NotPossible; return BlockAppliesToNode.NotPossible;
} }
@ -324,17 +354,38 @@ function block_might_apply_to_node(block: Block, node: Element): BlockAppliesToN
} }
if (selector.type === 'ClassSelector') { if (selector.type === 'ClassSelector') {
if (!attribute_matches(node, 'class', name, '~=', false) && !node.classes.some(c => c.name === name)) return BlockAppliesToNode.NotPossible; if (
!attribute_matches(node, 'class', name, '~=', false) &&
!node.classes.some((c) => c.name === name)
)
return BlockAppliesToNode.NotPossible;
} else if (selector.type === 'IdSelector') { } else if (selector.type === 'IdSelector') {
if (!attribute_matches(node, 'id', name, '=', false)) return BlockAppliesToNode.NotPossible; if (!attribute_matches(node, 'id', name, '=', false)) return BlockAppliesToNode.NotPossible;
} else if (selector.type === 'AttributeSelector') { } else if (selector.type === 'AttributeSelector') {
if ( if (
!(whitelist_attribute_selector.has(node.name.toLowerCase()) && whitelist_attribute_selector.get(node.name.toLowerCase()).has(selector.name.name.toLowerCase())) && !(
!attribute_matches(node, selector.name.name, selector.value && unquote(selector.value), selector.matcher, selector.flags)) { whitelist_attribute_selector.has(node.name.toLowerCase()) &&
whitelist_attribute_selector
.get(node.name.toLowerCase())
.has(selector.name.name.toLowerCase())
) &&
!attribute_matches(
node,
selector.name.name,
selector.value && unquote(selector.value),
selector.matcher,
selector.flags
)
) {
return BlockAppliesToNode.NotPossible; return BlockAppliesToNode.NotPossible;
} }
} else if (selector.type === 'TypeSelector') { } else if (selector.type === 'TypeSelector') {
if (node.name.toLowerCase() !== name.toLowerCase() && name !== '*' && !node.is_dynamic_element) return BlockAppliesToNode.NotPossible; if (
node.name.toLowerCase() !== name.toLowerCase() &&
name !== '*' &&
!node.is_dynamic_element
)
return BlockAppliesToNode.NotPossible;
} else { } else {
return BlockAppliesToNode.UnknownSelectorType; return BlockAppliesToNode.UnknownSelectorType;
} }
@ -349,18 +400,31 @@ function test_attribute(operator, expected_value, case_insensitive, value) {
value = value.toLowerCase(); value = value.toLowerCase();
} }
switch (operator) { switch (operator) {
case '=': return value === expected_value; case '=':
case '~=': return value.split(/\s/).includes(expected_value); return value === expected_value;
case '|=': return `${value}-`.startsWith(`${expected_value}-`); case '~=':
case '^=': return value.startsWith(expected_value); return value.split(/\s/).includes(expected_value);
case '$=': return value.endsWith(expected_value); case '|=':
case '*=': return value.includes(expected_value); return `${value}-`.startsWith(`${expected_value}-`);
default: throw new Error("this shouldn't happen"); 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: CssNode, name: string, expected_value: string, operator: string, case_insensitive: boolean) { function attribute_matches(
const spread = node.attributes.find(attr => attr.type === 'Spread'); 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 (spread) return true;
if (node.bindings.some((binding: CssNode) => binding.name === name)) return true; if (node.bindings.some((binding: CssNode) => binding.name === name)) return true;
@ -373,7 +437,8 @@ function attribute_matches(node: CssNode, name: string, expected_value: string,
if (attr.chunks.length === 1) { if (attr.chunks.length === 1) {
const value = attr.chunks[0]; const value = attr.chunks[0];
if (!value) return false; if (!value) return false;
if (value.type === 'Text') return test_attribute(operator, expected_value, case_insensitive, value.data); if (value.type === 'Text')
return test_attribute(operator, expected_value, case_insensitive, value.data);
} }
const possible_values = new Set(); const possible_values = new Set();
@ -403,7 +468,7 @@ function attribute_matches(node: CssNode, name: string, expected_value: string,
if (remaining.length > 0) { if (remaining.length > 0) {
if (start_with_space.length > 0) { if (start_with_space.length > 0) {
prev_values.forEach(prev_value => possible_values.add(prev_value)); prev_values.forEach((prev_value) => possible_values.add(prev_value));
} }
const combined = []; const combined = [];
@ -423,7 +488,7 @@ function attribute_matches(node: CssNode, name: string, expected_value: string,
}); });
continue; continue;
} else { } else {
prev_values.forEach(prev_value => possible_values.add(prev_value)); prev_values.forEach((prev_value) => possible_values.add(prev_value));
prev_values = []; prev_values = [];
} }
} }
@ -444,7 +509,7 @@ function attribute_matches(node: CssNode, name: string, expected_value: string,
return true; return true;
} }
} }
prev_values.forEach(prev_value => possible_values.add(prev_value)); prev_values.forEach((prev_value) => possible_values.add(prev_value));
if (possible_values.has(UNKNOWN)) return true; if (possible_values.has(UNKNOWN)) return true;
@ -458,7 +523,7 @@ function attribute_matches(node: CssNode, name: string, expected_value: string,
function unquote(value: CssNode) { function unquote(value: CssNode) {
if (value.type === 'Identifier') return value.name; if (value.type === 'Identifier') return value.name;
const str = value.value; const str = value.value;
if (str[0] === str[str.length - 1] && str[0] === "'" || str[0] === '"') { if ((str[0] === str[str.length - 1] && str[0] === "'") || str[0] === '"') {
return str.slice(1, str.length - 1); return str.slice(1, str.length - 1);
} }
return str; return str;
@ -470,12 +535,55 @@ function get_element_parent(node: Element): Element | null {
return parent as Element | null; return parent as Element | null;
} }
function get_possible_element_siblings(node: INode, adjacent_only: boolean): Map<Element, NodeExist> { /**
* Finds the given node's previous sibling in the DOM
*
* The Svelte <slot> is just a placeholder and is not actually real. Any children nodes
* in <slot> are 'flattened' and considered as the same level as the <slot>'s siblings
*
* e.g.
* <h1>Heading 1</h1>
* <slot>
* <h2>Heading 2</h2>
* </slot>
*
* is considered to look like:
* <h1>Heading 1</h1>
* <h2>Heading 2</h2>
*/
function find_previous_sibling(node: INode): INode {
let current_node: INode = node;
do {
if (current_node.type === 'Slot') {
const slot_children = current_node.children;
if (slot_children.length > 0) {
current_node = slot_children.slice(-1)[0]; // go to its last child first
continue;
}
}
while (!current_node.prev && current_node.parent && current_node.parent.type === 'Slot') {
current_node = current_node.parent;
}
current_node = current_node.prev;
} while (current_node && current_node.type === 'Slot');
return current_node;
}
function get_possible_element_siblings(
node: INode,
adjacent_only: boolean
): Map<Element, NodeExist> {
const result: Map<Element, NodeExist> = new Map(); const result: Map<Element, NodeExist> = new Map();
let prev: INode = node; let prev: INode = node;
while (prev = prev.prev) { while ((prev = find_previous_sibling(prev))) {
if (prev.type === 'Element') { if (prev.type === 'Element') {
if (!prev.attributes.find(attr => attr.type === 'Attribute' && attr.name.toLowerCase() === 'slot')) { if (
!prev.attributes.find(
(attr) => attr.type === 'Attribute' && attr.name.toLowerCase() === 'slot'
)
) {
result.set(prev, NodeExist.Definitely); result.set(prev, NodeExist.Definitely);
} }
@ -495,7 +603,13 @@ function get_possible_element_siblings(node: INode, adjacent_only: boolean): Map
if (!prev || !adjacent_only) { if (!prev || !adjacent_only) {
let parent: INode = node; let parent: INode = node;
let skip_each_for_last_child = node.type === 'ElseBlock'; let skip_each_for_last_child = node.type === 'ElseBlock';
while ((parent = parent.parent) && (parent.type === 'EachBlock' || parent.type === 'IfBlock' || parent.type === 'ElseBlock' || parent.type === 'AwaitBlock')) { while (
(parent = parent.parent) &&
(parent.type === 'EachBlock' ||
parent.type === 'IfBlock' ||
parent.type === 'ElseBlock' ||
parent.type === 'AwaitBlock')
) {
const possible_siblings = get_possible_element_siblings(parent, adjacent_only); const possible_siblings = get_possible_element_siblings(parent, adjacent_only);
add_to_map(possible_siblings, result); add_to_map(possible_siblings, result);
@ -520,12 +634,17 @@ function get_possible_element_siblings(node: INode, adjacent_only: boolean): Map
return result; return result;
} }
function get_possible_last_child(block: EachBlock | IfBlock | AwaitBlock, adjacent_only: boolean): Map<Element, NodeExist> { function get_possible_last_child(
block: EachBlock | IfBlock | AwaitBlock,
adjacent_only: boolean
): Map<Element, NodeExist> {
const result: Map<Element, NodeExist> = new Map(); const result: Map<Element, NodeExist> = new Map();
if (block.type === 'EachBlock') { if (block.type === 'EachBlock') {
const each_result: Map<Element, NodeExist> = loop_child(block.children, adjacent_only); const each_result: Map<Element, NodeExist> = loop_child(block.children, adjacent_only);
const else_result: Map<Element, NodeExist> = block.else ? loop_child(block.else.children, adjacent_only) : new Map(); const else_result: Map<Element, NodeExist> = block.else
? loop_child(block.else.children, adjacent_only)
: new Map();
const not_exhaustive = !has_definite_elements(else_result); const not_exhaustive = !has_definite_elements(else_result);
@ -537,7 +656,9 @@ function get_possible_last_child(block: EachBlock | IfBlock | AwaitBlock, adjace
add_to_map(else_result, result); add_to_map(else_result, result);
} else if (block.type === 'IfBlock') { } else if (block.type === 'IfBlock') {
const if_result: Map<Element, NodeExist> = loop_child(block.children, adjacent_only); const if_result: Map<Element, NodeExist> = loop_child(block.children, adjacent_only);
const else_result: Map<Element, NodeExist> = block.else ? loop_child(block.else.children, adjacent_only) : new Map(); const else_result: Map<Element, NodeExist> = block.else
? loop_child(block.else.children, adjacent_only)
: new Map();
const not_exhaustive = !has_definite_elements(if_result) || !has_definite_elements(else_result); const not_exhaustive = !has_definite_elements(if_result) || !has_definite_elements(else_result);
@ -549,11 +670,20 @@ function get_possible_last_child(block: EachBlock | IfBlock | AwaitBlock, adjace
add_to_map(if_result, result); add_to_map(if_result, result);
add_to_map(else_result, result); add_to_map(else_result, result);
} else if (block.type === 'AwaitBlock') { } else if (block.type === 'AwaitBlock') {
const pending_result: Map<Element, NodeExist> = block.pending ? loop_child(block.pending.children, adjacent_only) : new Map(); const pending_result: Map<Element, NodeExist> = block.pending
const then_result: Map<Element, NodeExist> = block.then ? loop_child(block.then.children, adjacent_only) : new Map(); ? loop_child(block.pending.children, adjacent_only)
const catch_result: Map<Element, NodeExist> = block.catch ? loop_child(block.catch.children, adjacent_only) : new Map(); : new Map();
const then_result: Map<Element, NodeExist> = block.then
const not_exhaustive = !has_definite_elements(pending_result) || !has_definite_elements(then_result) || !has_definite_elements(catch_result); ? loop_child(block.then.children, adjacent_only)
: new Map();
const catch_result: Map<Element, NodeExist> = block.catch
? loop_child(block.catch.children, adjacent_only)
: new Map();
const not_exhaustive =
!has_definite_elements(pending_result) ||
!has_definite_elements(then_result) ||
!has_definite_elements(catch_result);
if (not_exhaustive) { if (not_exhaustive) {
mark_as_probably(pending_result); mark_as_probably(pending_result);
@ -605,7 +735,11 @@ function loop_child(children: INode[], adjacent_only: boolean) {
if (adjacent_only) { if (adjacent_only) {
break; break;
} }
} else if (child.type === 'EachBlock' || child.type === 'IfBlock' || child.type === 'AwaitBlock') { } else if (
child.type === 'EachBlock' ||
child.type === 'IfBlock' ||
child.type === 'AwaitBlock'
) {
const child_result = get_possible_last_child(child, adjacent_only); const child_result = get_possible_last_child(child, adjacent_only);
add_to_map(child_result, result); add_to_map(child_result, result);
if (adjacent_only && has_definite_elements(child_result)) { if (adjacent_only && has_definite_elements(child_result)) {
@ -642,7 +776,7 @@ class Block {
this.start = selector.start; this.start = selector.start;
this.host = selector.type === 'PseudoClassSelector' && selector.name === 'host'; this.host = selector.type === 'PseudoClassSelector' && selector.name === 'host';
} }
this.root = this.root || selector.type === 'PseudoClassSelector' && selector.name === 'root'; this.root = this.root || (selector.type === 'PseudoClassSelector' && selector.name === 'root');
this.selectors.push(selector); this.selectors.push(selector);
this.end = selector.end; this.end = selector.end;
@ -653,7 +787,10 @@ class Block {
this.selectors.length >= 1 && this.selectors.length >= 1 &&
this.selectors[0].type === 'PseudoClassSelector' && this.selectors[0].type === 'PseudoClassSelector' &&
this.selectors[0].name === 'global' && this.selectors[0].name === 'global' &&
this.selectors.every((selector) => selector.type === 'PseudoClassSelector' || selector.type === 'PseudoElementSelector') this.selectors.every(
(selector) =>
selector.type === 'PseudoClassSelector' || selector.type === 'PseudoElementSelector'
)
); );
} }
} }

@ -17,13 +17,10 @@ function remove_css_prefix(name: string): string {
return name.replace(regex_css_browser_prefix, ''); return name.replace(regex_css_browser_prefix, '');
} }
const is_keyframes_node = (node: CssNode) => const is_keyframes_node = (node: CssNode) => remove_css_prefix(node.name) === 'keyframes';
remove_css_prefix(node.name) === 'keyframes';
const at_rule_has_declaration = ({ block }: CssNode): true => const at_rule_has_declaration = ({ block }: CssNode): true =>
block && block && block.children && block.children.find((node: CssNode) => node.type === 'Declaration');
block.children &&
block.children.find((node: CssNode) => node.type === 'Declaration');
function minify_declarations( function minify_declarations(
code: MagicString, code: MagicString,
@ -34,7 +31,7 @@ function minify_declarations(
declarations.forEach((declaration, i) => { declarations.forEach((declaration, i) => {
const separator = i > 0 ? ';' : ''; const separator = i > 0 ? ';' : '';
if ((declaration.node.start - c) > separator.length) { if (declaration.node.start - c > separator.length) {
code.update(c, declaration.node.start, separator); code.update(c, declaration.node.start, separator);
} }
declaration.minify(code); declaration.minify(code);
@ -58,13 +55,14 @@ class Rule {
} }
apply(node: Element) { apply(node: Element) {
this.selectors.forEach(selector => selector.apply(node)); // TODO move the logic in here? this.selectors.forEach((selector) => selector.apply(node)); // TODO move the logic in here?
} }
is_used(dev: boolean) { is_used(dev: boolean) {
if (this.parent && this.parent.node.type === 'Atrule' && is_keyframes_node(this.parent.node)) return true; if (this.parent && this.parent.node.type === 'Atrule' && is_keyframes_node(this.parent.node))
return true;
if (this.declarations.length === 0) return dev; if (this.declarations.length === 0) return dev;
return this.selectors.some(s => s.used); return this.selectors.some((s) => s.used);
} }
minify(code: MagicString, _dev: boolean) { minify(code: MagicString, _dev: boolean) {
@ -74,7 +72,7 @@ class Rule {
this.selectors.forEach((selector) => { this.selectors.forEach((selector) => {
if (selector.used) { if (selector.used) {
const separator = started ? ',' : ''; const separator = started ? ',' : '';
if ((selector.node.start - c) > separator.length) { if (selector.node.start - c > separator.length) {
code.update(c, selector.node.start, separator); code.update(c, selector.node.start, separator);
} }
@ -93,29 +91,39 @@ class Rule {
code.remove(c, this.node.block.end - 1); code.remove(c, this.node.block.end - 1);
} }
transform(code: MagicString, id: string, keyframes: Map<string, string>, max_amount_class_specificity_increased: number) { transform(
if (this.parent && this.parent.node.type === 'Atrule' && is_keyframes_node(this.parent.node)) return true; code: MagicString,
id: string,
keyframes: Map<string, string>,
max_amount_class_specificity_increased: number
) {
if (this.parent && this.parent.node.type === 'Atrule' && is_keyframes_node(this.parent.node))
return true;
const attr = `.${id}`; const attr = `.${id}`;
this.selectors.forEach(selector => selector.transform(code, attr, max_amount_class_specificity_increased)); this.selectors.forEach((selector) =>
this.declarations.forEach(declaration => declaration.transform(code, keyframes)); selector.transform(code, attr, max_amount_class_specificity_increased)
);
this.declarations.forEach((declaration) => declaration.transform(code, keyframes));
} }
validate(component: Component) { validate(component: Component) {
this.selectors.forEach(selector => { this.selectors.forEach((selector) => {
selector.validate(component); selector.validate(component);
}); });
} }
warn_on_unused_selector(handler: (selector: Selector) => void) { warn_on_unused_selector(handler: (selector: Selector) => void) {
this.selectors.forEach(selector => { this.selectors.forEach((selector) => {
if (!selector.used) handler(selector); if (!selector.used) handler(selector);
}); });
} }
get_max_amount_class_specificity_increased() { get_max_amount_class_specificity_increased() {
return Math.max(...this.selectors.map(selector => selector.get_amount_class_specificity_increased())); return Math.max(
...this.selectors.map((selector) => selector.get_amount_class_specificity_increased())
);
} }
} }
@ -144,9 +152,7 @@ class Declaration {
if (!this.node.property) return; // @apply, and possibly other weird cases? if (!this.node.property) return; // @apply, and possibly other weird cases?
const c = this.node.start + this.node.property.length; const c = this.node.start + this.node.property.length;
const first = this.node.value.children const first = this.node.value.children ? this.node.value.children[0] : this.node.value;
? this.node.value.children[0]
: this.node.value;
// Don't minify whitespace in custom properties, since some browsers (Chromium < 99) // Don't minify whitespace in custom properties, since some browsers (Chromium < 99)
// treat --foo: ; and --foo:; differently // treat --foo: ; and --foo:; differently
@ -173,13 +179,18 @@ class Atrule {
} }
apply(node: Element) { apply(node: Element) {
if (this.node.name === 'container' || this.node.name === 'media' || this.node.name === 'supports' || this.node.name === 'layer') { if (
this.children.forEach(child => { this.node.name === 'container' ||
this.node.name === 'media' ||
this.node.name === 'supports' ||
this.node.name === 'layer'
) {
this.children.forEach((child) => {
child.apply(node); child.apply(node);
}); });
} else if (is_keyframes_node(this.node)) { } else if (is_keyframes_node(this.node)) {
this.children.forEach((rule: Rule) => { this.children.forEach((rule: Rule) => {
rule.selectors.forEach(selector => { rule.selectors.forEach((selector) => {
selector.used = true; selector.used = true;
}); });
}); });
@ -231,7 +242,7 @@ class Atrule {
if (this.children.length) c++; if (this.children.length) c++;
} }
this.children.forEach(child => { this.children.forEach((child) => {
if (child.is_used(dev)) { if (child.is_used(dev)) {
code.remove(c, child.node.start); code.remove(c, child.node.start);
child.minify(code, dev); child.minify(code, dev);
@ -243,14 +254,19 @@ class Atrule {
} }
} }
transform(code: MagicString, id: string, keyframes: Map<string, string>, max_amount_class_specificity_increased: number) { transform(
code: MagicString,
id: string,
keyframes: Map<string, string>,
max_amount_class_specificity_increased: number
) {
if (is_keyframes_node(this.node)) { if (is_keyframes_node(this.node)) {
this.node.prelude.children.forEach(({ type, name, start, end }: CssNode) => { this.node.prelude.children.forEach(({ type, name, start, end }: CssNode) => {
if (type === 'Identifier') { if (type === 'Identifier') {
if (name.startsWith('-global-')) { if (name.startsWith('-global-')) {
code.remove(start, start + 8); code.remove(start, start + 8);
this.children.forEach((rule: Rule) => { this.children.forEach((rule: Rule) => {
rule.selectors.forEach(selector => { rule.selectors.forEach((selector) => {
selector.used = true; selector.used = true;
}); });
}); });
@ -261,13 +277,13 @@ class Atrule {
}); });
} }
this.children.forEach(child => { this.children.forEach((child) => {
child.transform(code, id, keyframes, max_amount_class_specificity_increased); child.transform(code, id, keyframes, max_amount_class_specificity_increased);
}); });
} }
validate(component: Component) { validate(component: Component) {
this.children.forEach(child => { this.children.forEach((child) => {
child.validate(component); child.validate(component);
}); });
} }
@ -275,13 +291,15 @@ class Atrule {
warn_on_unused_selector(handler: (selector: Selector) => void) { warn_on_unused_selector(handler: (selector: Selector) => void) {
if (this.node.name !== 'media') return; if (this.node.name !== 'media') return;
this.children.forEach(child => { this.children.forEach((child) => {
child.warn_on_unused_selector(handler); child.warn_on_unused_selector(handler);
}); });
} }
get_max_amount_class_specificity_increased() { get_max_amount_class_specificity_increased() {
return Math.max(...this.children.map(rule => rule.get_max_amount_class_specificity_increased())); return Math.max(
...this.children.map((rule) => rule.get_max_amount_class_specificity_increased())
);
} }
} }
@ -357,8 +375,8 @@ export default class Stylesheet {
}); });
} else if (at_rule_has_declaration(node)) { } else if (at_rule_has_declaration(node)) {
const at_rule_declarations = node.block.children const at_rule_declarations = node.block.children
.filter(node => node.type === 'Declaration') .filter((node) => node.type === 'Declaration')
.map(node => new Declaration(node)); .map((node) => new Declaration(node));
push_array(atrule.declarations, at_rule_declarations); push_array(atrule.declarations, at_rule_declarations);
} }
@ -407,7 +425,7 @@ export default class Stylesheet {
}); });
} }
render(file: string, should_transform_selectors: boolean) { render(file: string) {
if (!this.has_styles) { if (!this.has_styles) {
return { code: null, map: null }; return { code: null, map: null };
} }
@ -421,15 +439,15 @@ export default class Stylesheet {
} }
}); });
if (should_transform_selectors) { const max = Math.max(
const max = Math.max(...this.children.map(rule => rule.get_max_amount_class_specificity_increased())); ...this.children.map((rule) => rule.get_max_amount_class_specificity_increased())
this.children.forEach((child: (Atrule | Rule)) => { );
child.transform(code, this.id, this.keyframes, max); this.children.forEach((child: Atrule | Rule) => {
}); child.transform(code, this.id, this.keyframes, max);
} });
let c = 0; let c = 0;
this.children.forEach(child => { this.children.forEach((child) => {
if (child.is_used(this.dev)) { if (child.is_used(this.dev)) {
code.remove(c, child.node.start); code.remove(c, child.node.start);
child.minify(code, this.dev); child.minify(code, this.dev);
@ -450,17 +468,24 @@ export default class Stylesheet {
} }
validate(component: Component) { validate(component: Component) {
this.children.forEach(child => { this.children.forEach((child) => {
child.validate(component); child.validate(component);
}); });
} }
warn_on_unused_selectors(component: Component) { warn_on_unused_selectors(component: Component) {
const ignores = !this.ast.css ? [] : extract_ignores_above_position(this.ast.css.start, this.ast.html.children); const ignores = !this.ast.css
? []
: extract_ignores_above_position(this.ast.css.start, this.ast.html.children);
component.push_ignores(ignores); component.push_ignores(ignores);
this.children.forEach(child => { this.children.forEach((child) => {
child.warn_on_unused_selector((selector: Selector) => { child.warn_on_unused_selector((selector: Selector) => {
component.warn(selector.node, compiler_warnings.css_unused_selector(this.source.slice(selector.node.start, selector.node.end))); component.warn(
selector.node,
compiler_warnings.css_unused_selector(
this.source.slice(selector.node.start, selector.node.end)
)
);
}); });
}); });
component.pop_ignores(); component.pop_ignores();

@ -35,13 +35,7 @@ const valid_options = [
'cssHash' 'cssHash'
]; ];
const valid_css_values = [ const valid_css_values = [true, false, 'injected', 'external', 'none'];
true,
false,
'injected',
'external',
'none'
];
const regex_valid_identifier = /^[a-zA-Z_$][a-zA-Z_$0-9]*$/; const regex_valid_identifier = /^[a-zA-Z_$][a-zA-Z_$0-9]*$/;
const regex_starts_with_lowercase_character = /^[a-z]/; const regex_starts_with_lowercase_character = /^[a-z]/;
@ -49,7 +43,7 @@ const regex_starts_with_lowercase_character = /^[a-z]/;
function validate_options(options: CompileOptions, warnings: Warning[]) { function validate_options(options: CompileOptions, warnings: Warning[]) {
const { name, filename, loopGuardTimeout, dev, namespace, css } = options; const { name, filename, loopGuardTimeout, dev, namespace, css } = options;
Object.keys(options).forEach(key => { Object.keys(options).forEach((key) => {
if (!valid_options.includes(key)) { if (!valid_options.includes(key)) {
const match = fuzzymatch(key, valid_options); const match = fuzzymatch(key, valid_options);
let message = `Unrecognized option '${key}'`; let message = `Unrecognized option '${key}'`;
@ -84,7 +78,9 @@ function validate_options(options: CompileOptions, warnings: Warning[]) {
} }
if (valid_css_values.indexOf(css) === -1) { if (valid_css_values.indexOf(css) === -1) {
throw new Error(`options.css must be true, false, 'injected', 'external', or 'none' (got '${css}')`); throw new Error(
`options.css must be true, false, 'injected', 'external', or 'none' (got '${css}')`
);
} }
if (css === true || css === false) { if (css === true || css === false) {
@ -111,7 +107,10 @@ function validate_options(options: CompileOptions, warnings: Warning[]) {
} }
export default function compile(source: string, options: CompileOptions = {}) { export default function compile(source: string, options: CompileOptions = {}) {
options = Object.assign({ generate: 'dom', dev: false, enableSourcemap: true, css: 'injected' }, options); options = Object.assign(
{ generate: 'dom', dev: false, enableSourcemap: true, css: 'injected' },
options
);
const stats = new Stats(); const stats = new Stats();
const warnings = []; const warnings = [];
@ -133,9 +132,10 @@ export default function compile(source: string, options: CompileOptions = {}) {
); );
stats.stop('create component'); stats.stop('create component');
const result = options.generate === false const result =
? null options.generate === false
: options.generate === 'ssr' ? null
: options.generate === 'ssr'
? render_ssr(component, options) ? render_ssr(component, options)
: render_dom(component, options); : render_dom(component, options);

@ -48,16 +48,21 @@ export default class Attribute extends Node {
this.chunks = this.is_true this.chunks = this.is_true
? [] ? []
: info.value.map(node => { : info.value.map((node) => {
if (node.type === 'Text') return node; if (node.type === 'Text') return node;
this.is_static = false; this.is_static = false;
const expression = new Expression(component, this, scope, node.expression); const expression = new Expression(component, this, scope, node.expression);
add_to_set(this.dependencies, expression.dependencies); add_to_set(this.dependencies, expression.dependencies);
return expression; return expression;
}); });
}
if (this.dependencies.size > 0) {
parent.cannot_use_innerhtml();
parent.not_static_content();
} }
} }
@ -65,7 +70,7 @@ export default class Attribute extends Node {
if (this.is_spread) return this.expression.dynamic_dependencies(); if (this.is_spread) return this.expression.dynamic_dependencies();
const dependencies: Set<string> = new Set(); const dependencies: Set<string> = new Set();
this.chunks.forEach(chunk => { this.chunks.forEach((chunk) => {
if (chunk.type === 'Expression') { if (chunk.type === 'Expression') {
add_to_set(dependencies, chunk.dynamic_dependencies()); add_to_set(dependencies, chunk.dynamic_dependencies());
} }
@ -85,7 +90,9 @@ export default class Attribute extends Node {
} }
let expression = this.chunks let expression = this.chunks
.map(chunk => chunk.type === 'Text' ? string_literal(chunk.data) : chunk.manipulate(block)) .map((chunk) =>
chunk.type === 'Text' ? string_literal(chunk.data) : chunk.manipulate(block)
)
.reduce((lhs, rhs) => x`${lhs} + ${rhs}`); .reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
if (this.chunks[0].type !== 'Text') { if (this.chunks[0].type !== 'Text') {
@ -101,17 +108,17 @@ export default class Attribute extends Node {
return this.is_true return this.is_true
? true ? true
: this.chunks[0] : this.chunks[0]
// method should be called only when `is_static = true` ? // method should be called only when `is_static = true`
? (this.chunks[0] as Text).data (this.chunks[0] as Text).data
: ''; : '';
} }
should_cache() { should_cache() {
return this.is_static return this.is_static
? false ? false
: this.chunks.length === 1 : this.chunks.length === 1
// @ts-ignore todo: probably error ? // @ts-ignore todo: probably error
? this.chunks[0].node.type !== 'Identifier' || this.scope.names.has(this.chunks[0].node.name) this.chunks[0].node.type !== 'Identifier' || this.scope.names.has(this.chunks[0].node.name)
: true; : true;
} }
} }

@ -27,6 +27,8 @@ export default class AwaitBlock extends Node {
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.cannot_use_innerhtml();
this.not_static_content();
this.expression = new Expression(component, this, scope, info.expression); this.expression = new Expression(component, this, scope, info.expression);
@ -35,12 +37,24 @@ export default class AwaitBlock extends Node {
if (this.then_node) { if (this.then_node) {
this.then_contexts = []; this.then_contexts = [];
unpack_destructuring({ contexts: this.then_contexts, node: info.value, scope, component, context_rest_properties: this.context_rest_properties }); unpack_destructuring({
contexts: this.then_contexts,
node: info.value,
scope,
component,
context_rest_properties: this.context_rest_properties
});
} }
if (this.catch_node) { if (this.catch_node) {
this.catch_contexts = []; this.catch_contexts = [];
unpack_destructuring({ contexts: this.catch_contexts, node: info.error, scope, component, context_rest_properties: this.context_rest_properties }); unpack_destructuring({
contexts: this.catch_contexts,
node: info.error,
scope,
component,
context_rest_properties: this.context_rest_properties
});
} }
this.pending = new PendingBlock(component, this, scope, info.pending); this.pending = new PendingBlock(component, this, scope, info.pending);

@ -37,7 +37,12 @@ export default class Binding extends Node {
is_contextual: boolean; is_contextual: boolean;
is_readonly: boolean; is_readonly: boolean;
constructor(component: Component, parent: Element | InlineComponent | Window | Document, scope: TemplateScope, info: TemplateNode) { constructor(
component: Component,
parent: Element | InlineComponent | Window | Document,
scope: TemplateScope,
info: TemplateNode
) {
super(component, parent, scope, info); super(component, parent, scope, info);
if (info.expression.type !== 'Identifier' && info.expression.type !== 'MemberExpression') { if (info.expression.type !== 'Identifier' && info.expression.type !== 'MemberExpression') {
@ -51,7 +56,9 @@ export default class Binding extends Node {
const { name } = get_object(this.expression.node); const { name } = get_object(this.expression.node);
this.is_contextual = Array.from(this.expression.references).some(name => scope.names.has(name)); this.is_contextual = Array.from(this.expression.references).some((name) =>
scope.names.has(name)
);
if (this.is_contextual) this.validate_binding_rest_properties(scope); if (this.is_contextual) this.validate_binding_rest_properties(scope);
// make sure we track this as a mutable ref // make sure we track this as a mutable ref
@ -67,7 +74,7 @@ export default class Binding extends Node {
component.error(this, compiler_errors.invalid_binding_const); component.error(this, compiler_errors.invalid_binding_const);
} }
scope.dependencies_for_name.get(name).forEach(name => { scope.dependencies_for_name.get(name).forEach((name) => {
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);
if (variable) { if (variable) {
variable.mutated = true; variable.mutated = true;
@ -96,7 +103,7 @@ export default class Binding extends Node {
regex_box_size.test(this.name) || regex_box_size.test(this.name) ||
(isElement(parent) && (isElement(parent) &&
((parent.is_media_node() && read_only_media_attributes.has(this.name)) || ((parent.is_media_node() && read_only_media_attributes.has(this.name)) ||
(parent.name === 'input' && type === 'file')) /* TODO others? */); (parent.name === 'input' && type === 'file'))) /* TODO others? */;
} }
is_readonly_media_attribute() { is_readonly_media_attribute() {
@ -104,12 +111,15 @@ export default class Binding extends Node {
} }
validate_binding_rest_properties(scope: TemplateScope) { validate_binding_rest_properties(scope: TemplateScope) {
this.expression.references.forEach(name => { this.expression.references.forEach((name) => {
const each_block = scope.get_owner(name); const each_block = scope.get_owner(name);
if (each_block && each_block.type === 'EachBlock') { if (each_block && each_block.type === 'EachBlock') {
const rest_node = each_block.context_rest_properties.get(name); const rest_node = each_block.context_rest_properties.get(name);
if (rest_node) { if (rest_node) {
this.component.warn(rest_node as any, compiler_warnings.invalid_rest_eachblock_binding(name)); this.component.warn(
rest_node as any,
compiler_warnings.invalid_rest_eachblock_binding(name)
);
} }
} }
}); });

@ -16,13 +16,13 @@ export default class CatchBlock extends AbstractBlock {
this.scope = scope.child(); this.scope = scope.child();
if (parent.catch_node) { if (parent.catch_node) {
parent.catch_contexts.forEach(context => { parent.catch_contexts.forEach((context) => {
if (context.type !== 'DestructuredVariable') return; if (context.type !== 'DestructuredVariable') return;
this.scope.add(context.key.name, parent.expression.dependencies, this); this.scope.add(context.key.name, parent.expression.dependencies, this);
}); });
} }
([this.const_tags, this.children] = get_const_tags(info.children, component, this, parent)); [this.const_tags, this.children] = get_const_tags(info.children, component, this, parent);
if (!info.skip) { if (!info.skip) {
this.warn_if_empty_block(); this.warn_if_empty_block();

@ -12,7 +12,15 @@ import get_object from '../utils/get_object';
import compiler_errors from '../compiler_errors'; import compiler_errors from '../compiler_errors';
import { Node as ESTreeNode } from 'estree'; import { Node as ESTreeNode } from 'estree';
const allowed_parents = new Set(['EachBlock', 'CatchBlock', 'ThenBlock', 'InlineComponent', 'SlotTemplate', 'IfBlock', 'ElseBlock']); const allowed_parents = new Set([
'EachBlock',
'CatchBlock',
'ThenBlock',
'InlineComponent',
'SlotTemplate',
'IfBlock',
'ElseBlock'
]);
export default class ConstTag extends Node { export default class ConstTag extends Node {
type: 'ConstTag'; type: 'ConstTag';
@ -23,9 +31,14 @@ export default class ConstTag extends Node {
context_rest_properties: Map<string, ESTreeNode> = new Map(); context_rest_properties: Map<string, ESTreeNode> = new Map();
assignees: Set<string> = new Set(); assignees: Set<string> = new Set();
dependencies: Set<string> = new Set(); dependencies: Set<string> = new Set();
constructor(component: Component, parent: INodeAllowConstTag, scope: TemplateScope, info: ConstTagType) { constructor(
component: Component,
parent: INodeAllowConstTag,
scope: TemplateScope,
info: ConstTagType
) {
super(component, parent, scope, info); super(component, parent, scope, info);
if (!allowed_parents.has(parent.type)) { if (!allowed_parents.has(parent.type)) {
@ -37,22 +50,24 @@ export default class ConstTag extends Node {
const { assignees, dependencies } = this; const { assignees, dependencies } = this;
extract_identifiers(info.expression.left).forEach(({ name }) => { extract_identifiers(info.expression.left).forEach(({ name }) => {
assignees.add(name); assignees.add(name);
const owner = this.scope.get_owner(name); const owner = this.scope.get_owner(name);
if (owner === parent) { if (owner === parent) {
component.error(info, compiler_errors.invalid_const_declaration(name)); component.error(info, compiler_errors.invalid_const_declaration(name));
} }
}); });
walk(info.expression.right, { walk(info.expression.right, {
enter(node, parent) { enter(node, parent) {
if (is_reference(node as NodeWithPropertyDefinition, parent as NodeWithPropertyDefinition)) { if (
const identifier = get_object(node as any); is_reference(node as NodeWithPropertyDefinition, parent as NodeWithPropertyDefinition)
const { name } = identifier; ) {
dependencies.add(name); const identifier = get_object(node as any);
} const { name } = identifier;
} dependencies.add(name);
}); }
}
});
} }
parse_expression() { parse_expression() {
@ -64,11 +79,14 @@ export default class ConstTag extends Node {
context_rest_properties: this.context_rest_properties context_rest_properties: this.context_rest_properties
}); });
this.expression = new Expression(this.component, this, this.scope, this.node.expression.right); this.expression = new Expression(this.component, this, this.scope, this.node.expression.right);
this.contexts.forEach(context => { this.contexts.forEach((context) => {
if (context.type !== 'DestructuredVariable') return; if (context.type !== 'DestructuredVariable') return;
const owner = this.scope.get_owner(context.key.name); const owner = this.scope.get_owner(context.key.name);
if (owner && owner.type === 'ConstTag' && owner.parent === this.parent) { if (owner && owner.type === 'ConstTag' && owner.parent === this.parent) {
this.component.error(this.node, compiler_errors.invalid_const_declaration(context.key.name)); this.component.error(
this.node,
compiler_errors.invalid_const_declaration(context.key.name)
);
} }
this.scope.add(context.key.name, this.expression.dependencies, this); this.scope.add(context.key.name, this.expression.dependencies, this);
}); });

@ -10,10 +10,7 @@ import { Element } from '../../interfaces';
import compiler_warnings from '../compiler_warnings'; import compiler_warnings from '../compiler_warnings';
import compiler_errors from '../compiler_errors'; import compiler_errors from '../compiler_errors';
const valid_bindings = [ const valid_bindings = ['fullscreenElement', 'visibilityState'];
'fullscreenElement',
'visibilityState'
];
export default class Document extends Node { export default class Document extends Node {
type: 'Document'; type: 'Document';
@ -31,9 +28,23 @@ export default class Document extends Node {
if (!~valid_bindings.indexOf(node.name)) { if (!~valid_bindings.indexOf(node.name)) {
const match = fuzzymatch(node.name, valid_bindings); const match = fuzzymatch(node.name, valid_bindings);
if (match) { if (match) {
return component.error(node, compiler_errors.invalid_binding_on(node.name, '<svelte:document>', ` (did you mean '${match}'?)`)); return component.error(
node,
compiler_errors.invalid_binding_on(
node.name,
'<svelte:document>',
` (did you mean '${match}'?)`
)
);
} else { } else {
return component.error(node, compiler_errors.invalid_binding_on(node.name, '<svelte:document>', ` — valid bindings are ${list(valid_bindings)}`)); return component.error(
node,
compiler_errors.invalid_binding_on(
node.name,
'<svelte:document>',
` — valid bindings are ${list(valid_bindings)}`
)
);
} }
} }
@ -51,9 +62,7 @@ export default class Document extends Node {
private validate() { private validate() {
const handlers_map = new Set(); const handlers_map = new Set();
this.handlers.forEach(handler => ( this.handlers.forEach((handler) => handlers_map.add(handler.name));
handlers_map.add(handler.name)
));
if (handlers_map.has('mouseenter') || handlers_map.has('mouseleave')) { if (handlers_map.has('mouseenter') || handlers_map.has('mouseleave')) {
this.component.warn(this, compiler_warnings.avoid_mouse_events_on_document); this.component.warn(this, compiler_warnings.avoid_mouse_events_on_document);

@ -33,6 +33,8 @@ export default class EachBlock extends AbstractBlock {
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.cannot_use_innerhtml();
this.not_static_content();
this.expression = new Expression(component, this, scope, info.expression); this.expression = new Expression(component, this, scope, info.expression);
this.context = info.context.name || 'each'; // TODO this is used to facilitate binding; currently fails with destructuring this.context = info.context.name || 'each'; // TODO this is used to facilitate binding; currently fails with destructuring
@ -42,9 +44,15 @@ export default class EachBlock extends AbstractBlock {
this.scope = scope.child(); this.scope = scope.child();
this.context_rest_properties = new Map(); this.context_rest_properties = new Map();
this.contexts = []; this.contexts = [];
unpack_destructuring({ contexts: this.contexts, node: info.context, scope, component, context_rest_properties: this.context_rest_properties }); unpack_destructuring({
contexts: this.contexts,
node: info.context,
scope,
component,
context_rest_properties: this.context_rest_properties
});
this.contexts.forEach(context => { this.contexts.forEach((context) => {
if (context.type !== 'DestructuredVariable') return; if (context.type !== 'DestructuredVariable') return;
this.scope.add(context.key.name, this.expression.dependencies, this); this.scope.add(context.key.name, this.expression.dependencies, this);
}); });
@ -55,19 +63,17 @@ export default class EachBlock extends AbstractBlock {
this.scope.add(this.index, dependencies, this); this.scope.add(this.index, dependencies, this);
} }
this.key = info.key this.key = info.key ? new Expression(component, this, this.scope, info.key) : null;
? new Expression(component, this, this.scope, info.key)
: null;
this.has_animation = false; this.has_animation = false;
([this.const_tags, this.children] = get_const_tags(info.children, component, this, this)); [this.const_tags, this.children] = get_const_tags(info.children, component, this, this);
if (this.has_animation) { if (this.has_animation) {
this.children = this.children.filter(child => !isEmptyNode(child) && !isCommentNode(child)); this.children = this.children.filter((child) => !isEmptyNode(child) && !isCommentNode(child));
if (this.children.length !== 1) { if (this.children.length !== 1) {
const child = this.children.find(child => !!(child as Element).animation); const child = this.children.find((child) => !!(child as Element).animation);
component.error((child as Element).animation, compiler_errors.invalid_animation_sole); component.error((child as Element).animation, compiler_errors.invalid_animation_sole);
return; return;
} }
@ -75,9 +81,7 @@ export default class EachBlock extends AbstractBlock {
this.warn_if_empty_block(); this.warn_if_empty_block();
this.else = info.else this.else = info.else ? new ElseBlock(component, this, this.scope, info.else) : null;
? new ElseBlock(component, this, this.scope, info.else)
: null;
} }
} }

File diff suppressed because it is too large Load Diff

@ -15,7 +15,7 @@ export default class ElseBlock extends AbstractBlock {
super(component, parent, scope, info); super(component, parent, scope, info);
this.scope = scope.child(); this.scope = scope.child();
([this.const_tags, this.children] = get_const_tags(info.children, component, this, this)); [this.const_tags, this.children] = get_const_tags(info.children, component, this, this);
this.warn_if_empty_block(); this.warn_if_empty_block();
} }

@ -17,7 +17,12 @@ export default class EventHandler extends Node {
uses_context = false; uses_context = false;
can_make_passive = false; can_make_passive = false;
constructor(component: Component, parent: Node, template_scope: TemplateScope, info: TemplateNode) { constructor(
component: Component,
parent: Node,
template_scope: TemplateScope,
info: TemplateNode
) {
super(component, parent, template_scope, info); super(component, parent, template_scope, info);
this.name = info.name; this.name = info.name;
@ -27,7 +32,10 @@ export default class EventHandler extends Node {
this.expression = new Expression(component, this, template_scope, info.expression); this.expression = new Expression(component, this, template_scope, info.expression);
this.uses_context = this.expression.uses_context; this.uses_context = this.expression.uses_context;
if (regex_contains_term_function_expression.test(info.expression.type) && info.expression.params.length === 0) { if (
regex_contains_term_function_expression.test(info.expression.type) &&
info.expression.params.length === 0
) {
// TODO make this detection more accurate — if `event.preventDefault` isn't called, and // TODO make this detection more accurate — if `event.preventDefault` isn't called, and
// `event` is passed to another function, we can make it passive // `event` is passed to another function, we can make it passive
this.can_make_passive = true; this.can_make_passive = true;
@ -37,11 +45,19 @@ export default class EventHandler extends Node {
if (node) { if (node) {
if (node.type === 'VariableDeclaration') { if (node.type === 'VariableDeclaration') {
// for `const handleClick = () => {...}`, we want the [arrow] function expression node // for `const handleClick = () => {...}`, we want the [arrow] function expression node
const declarator = node.declarations.find(d => (d.id as Identifier).name === info.expression.name); const declarator = node.declarations.find(
(d) => (d.id as Identifier).name === info.expression.name
);
node = declarator && declarator.init; node = declarator && declarator.init;
} }
if (node && (node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration' || node.type === 'ArrowFunctionExpression') && node.params.length === 0) { if (
node &&
(node.type === 'FunctionExpression' ||
node.type === 'FunctionDeclaration' ||
node.type === 'ArrowFunctionExpression') &&
node.params.length === 0
) {
this.can_make_passive = true; this.can_make_passive = true;
} }
} }

@ -15,14 +15,21 @@ export default class Head extends Node {
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.cannot_use_innerhtml();
if (info.attributes.length) { if (info.attributes.length) {
component.error(info.attributes[0], compiler_errors.invalid_attribute_head); component.error(info.attributes[0], compiler_errors.invalid_attribute_head);
return; return;
} }
this.children = map_children(component, parent, scope, info.children.filter(child => { this.children = map_children(
return (child.type !== 'Text' || regex_non_whitespace_character.test(child.data)); component,
})); parent,
scope,
info.children.filter((child) => {
return child.type !== 'Text' || regex_non_whitespace_character.test(child.data);
})
);
if (this.children.length > 0) { if (this.children.length > 0) {
this.id = `svelte-${hash(this.component.source.slice(this.start, this.end))}`; this.id = `svelte-${hash(this.component.source.slice(this.start, this.end))}`;

@ -18,13 +18,13 @@ export default class IfBlock extends AbstractBlock {
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.scope = scope.child(); this.scope = scope.child();
this.cannot_use_innerhtml();
this.not_static_content();
this.expression = new Expression(component, this, this.scope, info.expression); this.expression = new Expression(component, this, this.scope, info.expression);
([this.const_tags, this.children] = get_const_tags(info.children, component, this, this)); [this.const_tags, this.children] = get_const_tags(info.children, component, this, this);
this.else = info.else this.else = info.else ? new ElseBlock(component, this, scope, info.else) : null;
? new ElseBlock(component, this, scope, info.else)
: null;
this.warn_if_empty_block(); this.warn_if_empty_block();
} }

@ -28,6 +28,9 @@ export default class InlineComponent extends Node {
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.cannot_use_innerhtml();
this.not_static_content();
if (info.name !== 'svelte:component' && info.name !== 'svelte:self') { if (info.name !== 'svelte:component' && info.name !== 'svelte:self') {
const name = info.name.split('.')[0]; // accommodate namespaces const name = info.name.split('.')[0]; // accommodate namespaces
component.warn_if_undefined(name, info, scope); component.warn_if_undefined(name, info, scope);
@ -37,11 +40,12 @@ export default class InlineComponent extends Node {
this.name = info.name; this.name = info.name;
this.namespace = get_namespace(parent, component.namespace); this.namespace = get_namespace(parent, component.namespace);
this.expression = this.name === 'svelte:component' this.expression =
? new Expression(component, this, scope, info.expression) this.name === 'svelte:component'
: null; ? new Expression(component, this, scope, info.expression)
: null;
info.attributes.forEach(node => { info.attributes.forEach((node) => {
/* eslint-disable no-fallthrough */ /* eslint-disable no-fallthrough */
switch (node.type) { switch (node.type) {
case 'Action': case 'Action':
@ -52,7 +56,7 @@ export default class InlineComponent extends Node {
this.css_custom_properties.push(new Attribute(component, this, scope, node)); this.css_custom_properties.push(new Attribute(component, this, scope, node));
break; break;
} }
// fallthrough // fallthrough
case 'Spread': case 'Spread':
this.attributes.push(new Attribute(component, this, scope, node)); this.attributes.push(new Attribute(component, this, scope, node));
break; break;
@ -87,10 +91,10 @@ export default class InlineComponent extends Node {
if (this.lets.length > 0) { if (this.lets.length > 0) {
this.scope = scope.child(); this.scope = scope.child();
this.lets.forEach(l => { this.lets.forEach((l) => {
const dependencies = new Set([l.name.name]); const dependencies = new Set([l.name.name]);
l.names.forEach(name => { l.names.forEach((name) => {
this.scope.add(name, dependencies, this); this.scope.add(name, dependencies, this);
}); });
}); });
@ -98,8 +102,8 @@ export default class InlineComponent extends Node {
this.scope = scope; this.scope = scope;
} }
this.handlers.forEach(handler => { this.handlers.forEach((handler) => {
handler.modifiers.forEach(modifier => { handler.modifiers.forEach((modifier) => {
if (modifier !== 'once') { if (modifier !== 'once') {
return component.error(handler, compiler_errors.invalid_event_modifier_component); return component.error(handler, compiler_errors.invalid_event_modifier_component);
} }
@ -112,7 +116,10 @@ export default class InlineComponent extends Node {
if (child.type === 'SlotTemplate') { if (child.type === 'SlotTemplate') {
children.push(child); children.push(child);
info.children.splice(i, 1); info.children.splice(i, 1);
} else if ((child.type === 'Element' || child.type === 'InlineComponent' || child.type === 'Slot') && child.attributes.find(attribute => attribute.name === 'slot')) { } else if (
(child.type === 'Element' || child.type === 'InlineComponent' || child.type === 'Slot') &&
child.attributes.find((attribute) => attribute.name === 'slot')
) {
const slot_template = { const slot_template = {
start: child.start, start: child.start,
end: child.end, end: child.end,
@ -148,7 +155,7 @@ export default class InlineComponent extends Node {
} }
} }
if (info.children.some(node => not_whitespace_text(node))) { if (info.children.some((node) => not_whitespace_text(node))) {
children.push({ children.push({
start: info.start, start: info.start,
end: info.end, end: info.end,
@ -163,7 +170,9 @@ export default class InlineComponent extends Node {
} }
get slot_template_name() { get slot_template_name() {
return this.attributes.find(attribute => attribute.name === 'slot').get_static_value() as string; return this.attributes
.find((attribute) => attribute.name === 'slot')
.get_static_value() as string;
} }
} }

@ -13,6 +13,8 @@ export default class KeyBlock extends AbstractBlock {
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.cannot_use_innerhtml();
this.not_static_content();
this.expression = new Expression(component, this, scope, info.expression); this.expression = new Expression(component, this, scope, info.expression);

@ -2,4 +2,9 @@ import Tag from './shared/Tag';
export default class RawMustacheTag extends Tag { export default class RawMustacheTag extends Tag {
type: 'RawMustacheTag'; type: 'RawMustacheTag';
constructor(component, parent, scope, info) {
super(component, parent, scope, info);
this.cannot_use_innerhtml();
this.not_static_content();
}
} }

@ -7,7 +7,8 @@ import { TemplateNode } from '../../interfaces';
import compiler_errors from '../compiler_errors'; import compiler_errors from '../compiler_errors';
export default class Slot extends Element { export default class Slot extends Element {
type: 'Element'; // @ts-ignore Slot elements have the 'Slot' type, but TypeScript doesn't allow us to have 'Slot' when it extends Element
type: 'Slot';
name: string; name: string;
children: INode[]; children: INode[];
slot_name: string; slot_name: string;
@ -16,7 +17,7 @@ export default class Slot extends Element {
constructor(component: Component, parent: INode, scope: TemplateScope, info: TemplateNode) { constructor(component: Component, parent: INode, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info); super(component, parent, scope, info);
info.attributes.forEach(attr => { info.attributes.forEach((attr) => {
if (attr.type !== 'Attribute' && attr.type !== 'Spread') { if (attr.type !== 'Attribute' && attr.type !== 'Spread') {
return component.error(attr, compiler_errors.invalid_slot_directive); return component.error(attr, compiler_errors.invalid_slot_directive);
} }
@ -60,5 +61,8 @@ export default class Slot extends Element {
} }
component.slots.set(this.slot_name, this); component.slots.set(this.slot_name, this);
this.cannot_use_innerhtml();
this.not_static_content();
} }
} }

@ -17,12 +17,7 @@ export default class SlotTemplate extends Node {
slot_attribute: Attribute; slot_attribute: Attribute;
slot_template_name: string = 'default'; slot_template_name: string = 'default';
constructor( constructor(component: Component, parent: INode, scope: TemplateScope, info: any) {
component: Component,
parent: INode,
scope: TemplateScope,
info: any
) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.validate_slot_template_placement(); this.validate_slot_template_placement();
@ -62,7 +57,7 @@ export default class SlotTemplate extends Node {
}); });
this.scope = scope; this.scope = scope;
([this.const_tags, this.children] = get_const_tags(info.children, component, this, this)); [this.const_tags, this.children] = get_const_tags(info.children, component, this, this);
} }
validate_slot_template_placement() { validate_slot_template_placement() {

@ -16,12 +16,7 @@ export default class StyleDirective extends Node {
expression: Expression; expression: Expression;
should_cache: boolean; should_cache: boolean;
constructor( constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
component: Component,
parent: Node,
scope: TemplateScope,
info: TemplateNode
) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.name = info.name; this.name = info.name;
@ -31,9 +26,7 @@ export default class StyleDirective extends Node {
if (!valid_modifiers.has(modifier)) { if (!valid_modifiers.has(modifier)) {
component.error( component.error(
this, this,
compiler_errors.invalid_style_directive_modifier( compiler_errors.invalid_style_directive_modifier(list([...valid_modifiers]))
list([...valid_modifiers])
)
); );
} }
} }
@ -41,14 +34,15 @@ export default class StyleDirective extends Node {
// Convert the value array to an expression so it's easier to handle // Convert the value array to an expression so it's easier to handle
// the StyleDirective going forward. // the StyleDirective going forward.
if (info.value === true || (info.value.length === 1 && info.value[0].type === 'MustacheTag')) { if (info.value === true || (info.value.length === 1 && info.value[0].type === 'MustacheTag')) {
const identifier = info.value === true const identifier =
? { info.value === true
type: 'Identifier', ? ({
start: info.end - info.name.length, type: 'Identifier',
end: info.end, start: info.end - info.name.length,
name: info.name end: info.end,
} as any name: info.name
: info.value[0].expression; } as any)
: info.value[0].expression;
this.expression = new Expression(component, this, scope, identifier); this.expression = new Expression(component, this, scope, identifier);
this.should_cache = false; this.should_cache = false;
} else { } else {

@ -8,16 +8,10 @@ import { regex_non_whitespace_character } from '../../utils/patterns';
// Whitespace inside one of these elements will not result in // Whitespace inside one of these elements will not result in
// a whitespace node being created in any circumstances. (This // a whitespace node being created in any circumstances. (This
// list is almost certainly very incomplete) // list is almost certainly very incomplete)
const elements_without_text = new Set([ const elements_without_text = new Set(['audio', 'datalist', 'dl', 'optgroup', 'select', 'video']);
'audio',
'datalist',
'dl',
'optgroup',
'select',
'video'
]);
const regex_ends_with_svg = /svg$/; const regex_ends_with_svg = /svg$/;
const regex_non_whitespace_characters = /[\S\u00A0]/;
export default class Text extends Node { export default class Text extends Node {
type: 'Text'; type: 'Text';
@ -37,7 +31,8 @@ export default class Text extends Node {
if (!parent_element) return false; if (!parent_element) return false;
if (parent_element.type === 'Head') return true; if (parent_element.type === 'Head') return true;
if (parent_element.type === 'InlineComponent') return parent_element.children.length === 1 && this === parent_element.children[0]; if (parent_element.type === 'InlineComponent')
return parent_element.children.length === 1 && this === parent_element.children[0];
// svg namespace exclusions // svg namespace exclusions
if (regex_ends_with_svg.test(parent_element.namespace)) { if (regex_ends_with_svg.test(parent_element.namespace)) {
@ -63,4 +58,11 @@ export default class Text extends Node {
return false; return false;
} }
use_space(): boolean {
if (this.component.compile_options.preserveWhitespace) return false;
if (regex_non_whitespace_characters.test(this.data)) return false;
return !this.within_pre();
}
} }

@ -16,13 +16,13 @@ export default class ThenBlock extends AbstractBlock {
this.scope = scope.child(); this.scope = scope.child();
if (parent.then_node) { if (parent.then_node) {
parent.then_contexts.forEach(context => { parent.then_contexts.forEach((context) => {
if (context.type !== 'DestructuredVariable') return; if (context.type !== 'DestructuredVariable') return;
this.scope.add(context.key.name, parent.expression.dependencies, this); this.scope.add(context.key.name, parent.expression.dependencies, this);
}); });
} }
([this.const_tags, this.children] = get_const_tags(info.children, component, this, parent)); [this.const_tags, this.children] = get_const_tags(info.children, component, this, parent);
if (!info.skip) { if (!info.skip) {
this.warn_if_empty_block(); this.warn_if_empty_block();

@ -19,17 +19,15 @@ export default class Title extends Node {
return; return;
} }
info.children.forEach(child => { info.children.forEach((child) => {
if (child.type !== 'Text' && child.type !== 'MustacheTag') { if (child.type !== 'Text' && child.type !== 'MustacheTag') {
return component.error(child, compiler_errors.illegal_structure_title); return component.error(child, compiler_errors.illegal_structure_title);
} }
}); });
this.should_cache = info.children.length === 1 this.should_cache =
? ( info.children.length === 1
info.children[0].type !== 'Identifier' || ? info.children[0].type !== 'Identifier' || scope.names.has(info.children[0].name)
scope.names.has(info.children[0].name) : true;
)
: true;
} }
} }

@ -25,8 +25,11 @@ export default class Transition extends Node {
this.is_local = info.modifiers.includes('local'); this.is_local = info.modifiers.includes('local');
if ((info.intro && parent.intro) || (info.outro && parent.outro)) { if ((info.intro && parent.intro) || (info.outro && parent.outro)) {
const parent_transition = (parent.intro || parent.outro); const parent_transition = parent.intro || parent.outro;
component.error(info, compiler_errors.duplicate_transition(this.directive, parent_transition.directive)); component.error(
info,
compiler_errors.duplicate_transition(this.directive, parent_transition.directive)
);
return; return;
} }

@ -30,7 +30,7 @@ export default class Window extends Node {
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info); super(component, parent, scope, info);
info.attributes.forEach(node => { info.attributes.forEach((node) => {
if (node.type === 'EventHandler') { if (node.type === 'EventHandler') {
this.handlers.push(new EventHandler(component, this, scope, node)); this.handlers.push(new EventHandler(component, this, scope, node));
} else if (node.type === 'Binding') { } else if (node.type === 'Binding') {
@ -42,16 +42,31 @@ export default class Window extends Node {
} }
if (!~valid_bindings.indexOf(node.name)) { if (!~valid_bindings.indexOf(node.name)) {
const match = ( const match =
node.name === 'width' ? 'innerWidth' : node.name === 'width'
node.name === 'height' ? 'innerHeight' : ? 'innerWidth'
fuzzymatch(node.name, valid_bindings) : node.name === 'height'
); ? 'innerHeight'
: fuzzymatch(node.name, valid_bindings);
if (match) { if (match) {
return component.error(node, compiler_errors.invalid_binding_on(node.name, '<svelte:window>', ` (did you mean '${match}'?)`)); return component.error(
node,
compiler_errors.invalid_binding_on(
node.name,
'<svelte:window>',
` (did you mean '${match}'?)`
)
);
} else { } else {
return component.error(node, compiler_errors.invalid_binding_on(node.name, '<svelte:window>', ` — valid bindings are ${list(valid_bindings)}`)); return component.error(
node,
compiler_errors.invalid_binding_on(
node.name,
'<svelte:window>',
` — valid bindings are ${list(valid_bindings)}`
)
);
} }
} }

@ -37,47 +37,48 @@ import Window from './Window';
// note: to write less types each of types in union below should have type defined as literal // note: to write less types each of types in union below should have type defined as literal
// https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#discriminating-unions // https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#discriminating-unions
export type INode = Action export type INode =
| Animation | Action
| Attribute | Animation
| AwaitBlock | Attribute
| Binding | AwaitBlock
| Body | Binding
| CatchBlock | Body
| Class | CatchBlock
| Comment | Class
| ConstTag | Comment
| DebugTag | ConstTag
| Document | DebugTag
| EachBlock | Document
| Element | EachBlock
| ElseBlock | Element
| EventHandler | ElseBlock
| Fragment | EventHandler
| Head | Fragment
| IfBlock | Head
| InlineComponent | IfBlock
| KeyBlock | InlineComponent
| Let | KeyBlock
| MustacheTag | Let
| Options | MustacheTag
| PendingBlock | Options
| RawMustacheTag | PendingBlock
| Slot | RawMustacheTag
| SlotTemplate | Slot
| StyleDirective | SlotTemplate
| Tag | StyleDirective
| Text | Tag
| ThenBlock | Text
| Title | ThenBlock
| Transition | Title
| Window; | Transition
| Window;
export type INodeAllowConstTag = export type INodeAllowConstTag =
| IfBlock | IfBlock
| ElseBlock | ElseBlock
| EachBlock | EachBlock
| CatchBlock | CatchBlock
| ThenBlock | ThenBlock
| InlineComponent | InlineComponent
| SlotTemplate; | SlotTemplate;

@ -1,5 +1,5 @@
import { x } from 'code-red'; import { x } from 'code-red';
import { Node, Identifier, Expression, PrivateIdentifier } from 'estree'; import { Node, Identifier, Expression, PrivateIdentifier, Pattern } from 'estree';
import { walk } from 'estree-walker'; import { walk } from 'estree-walker';
import is_reference, { NodeWithPropertyDefinition } from 'is-reference'; import is_reference, { NodeWithPropertyDefinition } from 'is-reference';
import { clone } from '../../../utils/clone'; import { clone } from '../../../utils/clone';
@ -11,12 +11,12 @@ export type Context = DestructuredVariable | ComputedProperty;
interface ComputedProperty { interface ComputedProperty {
type: 'ComputedProperty'; type: 'ComputedProperty';
property_name: Identifier; property_name: Identifier;
key: Expression | PrivateIdentifier; key: Expression | PrivateIdentifier;
} }
interface DestructuredVariable { interface DestructuredVariable {
type: 'DestructuredVariable' type: 'DestructuredVariable';
key: Identifier; key: Identifier;
name?: string; name?: string;
modifier: (node: Node) => Node; modifier: (node: Node) => Node;
@ -30,15 +30,17 @@ export function unpack_destructuring({
default_modifier = (node) => node, default_modifier = (node) => node,
scope, scope,
component, component,
context_rest_properties context_rest_properties,
in_rest_element = false
}: { }: {
contexts: Context[]; contexts: Context[];
node: Node; node: Pattern;
modifier?: DestructuredVariable['modifier']; modifier?: DestructuredVariable['modifier'];
default_modifier?: DestructuredVariable['default_modifier']; default_modifier?: DestructuredVariable['default_modifier'];
scope: TemplateScope; scope: TemplateScope;
component: Component; component: Component;
context_rest_properties: Map<string, Node>; context_rest_properties: Map<string, Node>;
in_rest_element?: boolean;
}) { }) {
if (!node) return; if (!node) return;
@ -49,28 +51,26 @@ export function unpack_destructuring({
modifier, modifier,
default_modifier default_modifier
}); });
} else if (node.type === 'RestElement') {
contexts.push({ if (in_rest_element) {
type: 'DestructuredVariable', context_rest_properties.set(node.name, node);
key: node.argument as Identifier, }
modifier,
default_modifier
});
context_rest_properties.set((node.argument as Identifier).name, node);
} else if (node.type === 'ArrayPattern') { } else if (node.type === 'ArrayPattern') {
node.elements.forEach((element, i) => { node.elements.forEach((element: Pattern | null, i: number) => {
if (element && element.type === 'RestElement') { if (!element) {
return;
} else if (element.type === 'RestElement') {
unpack_destructuring({ unpack_destructuring({
contexts, contexts,
node: element, node: element.argument,
modifier: (node) => x`${modifier(node)}.slice(${i})` as Node, modifier: (node) => x`${modifier(node)}.slice(${i})` as Node,
default_modifier, default_modifier,
scope, scope,
component, component,
context_rest_properties context_rest_properties,
in_rest_element: true
}); });
context_rest_properties.set((element.argument as Identifier).name, element); } else if (element.type === 'AssignmentPattern') {
} else if (element && element.type === 'AssignmentPattern') {
const n = contexts.length; const n = contexts.length;
mark_referenced(element.right, scope, component); mark_referenced(element.right, scope, component);
@ -87,7 +87,8 @@ export function unpack_destructuring({
)}` as Node, )}` as Node,
scope, scope,
component, component,
context_rest_properties context_rest_properties,
in_rest_element
}); });
} else { } else {
unpack_destructuring({ unpack_destructuring({
@ -97,7 +98,8 @@ export function unpack_destructuring({
default_modifier, default_modifier,
scope, scope,
component, component,
context_rest_properties context_rest_properties,
in_rest_element
}); });
} }
}); });
@ -110,15 +112,13 @@ export function unpack_destructuring({
contexts, contexts,
node: property.argument, node: property.argument,
modifier: (node) => modifier: (node) =>
x`@object_without_properties(${modifier( x`@object_without_properties(${modifier(node)}, [${used_properties}])` as Node,
node
)}, [${used_properties}])` as Node,
default_modifier, default_modifier,
scope, scope,
component, component,
context_rest_properties context_rest_properties,
in_rest_element: true
}); });
context_rest_properties.set((property.argument as Identifier).name, property);
} else if (property.type === 'Property') { } else if (property.type === 'Property') {
const key = property.key; const key = property.key;
const value = property.value; const value = property.value;
@ -168,7 +168,8 @@ export function unpack_destructuring({
)}` as Node, )}` as Node,
scope, scope,
component, component,
context_rest_properties context_rest_properties,
in_rest_element
}); });
} else { } else {
// e.g. { property } or { property: newName } // e.g. { property } or { property: newName }
@ -179,7 +180,8 @@ export function unpack_destructuring({
default_modifier, default_modifier,
scope, scope,
component, component,
context_rest_properties context_rest_properties,
in_rest_element
}); });
} }
} }
@ -213,12 +215,7 @@ function update_reference(
expression = clone(expression) as Expression; expression = clone(expression) as Expression;
walk(expression, { walk(expression, {
enter(node, parent: Node) { enter(node, parent: Node) {
if ( if (is_reference(node as NodeWithPropertyDefinition, parent as NodeWithPropertyDefinition)) {
is_reference(
node as NodeWithPropertyDefinition,
parent as NodeWithPropertyDefinition
)
) {
this.replace(find_from_context(node as Identifier)); this.replace(find_from_context(node as Identifier));
this.skip(); this.skip();
} }
@ -228,11 +225,7 @@ function update_reference(
return expression; return expression;
} }
function mark_referenced( function mark_referenced(node: Node, scope: TemplateScope, component: Component) {
node: Node,
scope: TemplateScope,
component: Component
) {
walk(node, { walk(node, {
enter(node: any, parent: any) { enter(node: any, parent: any) {
if (is_reference(node, parent)) { if (is_reference(node, parent)) {

@ -24,7 +24,7 @@ type Owner = INode;
const regex_contains_term_function_expression = /FunctionExpression/; const regex_contains_term_function_expression = /FunctionExpression/;
export default class Expression { export default class Expression {
type: 'Expression' = 'Expression'; type: 'Expression' = 'Expression' as const;
component: Component; component: Component;
owner: Owner; owner: Owner;
node: Node; node: Node;
@ -36,12 +36,18 @@ export default class Expression {
scope: Scope; scope: Scope;
scope_map: WeakMap<Node, Scope>; scope_map: WeakMap<Node, Scope>;
declarations: Array<(Node | Node[])> = []; declarations: Array<Node | Node[]> = [];
uses_context = false; uses_context = false;
manipulated: Node; manipulated: Node;
constructor(component: Component, owner: Owner, template_scope: TemplateScope, info: Node, lazy?: boolean) { constructor(
component: Component,
owner: Owner,
template_scope: TemplateScope,
info: Node,
lazy?: boolean
) {
// TODO revert to direct property access in prod? // TODO revert to direct property access in prod?
Object.defineProperties(this, { Object.defineProperties(this, {
component: { component: {
@ -105,7 +111,9 @@ export default class Expression {
const is_index = owner.type === 'EachBlock' && owner.key && name === owner.index; const is_index = owner.type === 'EachBlock' && owner.key && name === owner.index;
if (!lazy || is_index) { if (!lazy || is_index) {
template_scope.dependencies_for_name.get(name).forEach(name => dependencies.add(name)); template_scope.dependencies_for_name
.get(name)
.forEach((name) => dependencies.add(name));
} }
} else { } else {
if (!lazy) { if (!lazy) {
@ -128,19 +136,19 @@ export default class Expression {
deep = node.left.type === 'MemberExpression'; deep = node.left.type === 'MemberExpression';
names = extract_names(deep ? get_object(node.left) : node.left); names = extract_names(deep ? get_object(node.left) : node.left);
} else if (node.type === 'UpdateExpression') { } else if (node.type === 'UpdateExpression') {
deep = node.argument.type === 'MemberExpression'; deep = node.argument.type === 'MemberExpression';
names = extract_names(get_object(node.argument)); names = extract_names(get_object(node.argument));
} }
} }
if (names) { if (names) {
names.forEach(name => { names.forEach((name) => {
if (template_scope.names.has(name)) { if (template_scope.names.has(name)) {
if (template_scope.is_const(name)) { if (template_scope.is_const(name)) {
component.error(node, compiler_errors.invalid_const_update(name)); component.error(node, compiler_errors.invalid_const_update(name));
} }
template_scope.dependencies_for_name.get(name).forEach(name => { template_scope.dependencies_for_name.get(name).forEach((name) => {
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);
if (variable) variable[deep ? 'mutated' : 'reassigned'] = true; if (variable) variable[deep ? 'mutated' : 'reassigned'] = true;
}); });
@ -188,7 +196,7 @@ export default class Expression {
} }
dynamic_dependencies() { dynamic_dependencies() {
return Array.from(this.dependencies).filter(name => { return Array.from(this.dependencies).filter((name) => {
if (this.template_scope.is_let(name)) return true; if (this.template_scope.is_let(name)) return true;
if (is_reserved_keyword(name)) return true; if (is_reserved_keyword(name)) return true;
@ -197,19 +205,24 @@ export default class Expression {
}); });
} }
dynamic_contextual_dependencies() {
return Array.from(this.contextual_dependencies).filter((name) => {
return Array.from(this.template_scope.dependencies_for_name.get(name)).some(
(variable_name) => {
const variable = this.component.var_lookup.get(variable_name);
return is_dynamic(variable);
}
);
});
}
// TODO move this into a render-dom wrapper? // TODO move this into a render-dom wrapper?
manipulate(block?: Block, ctx?: string | void) { manipulate(block?: Block, ctx?: string | void) {
// TODO ideally we wouldn't end up calling this method // TODO ideally we wouldn't end up calling this method
// multiple times // multiple times
if (this.manipulated) return this.manipulated; if (this.manipulated) return this.manipulated;
const { const { component, declarations, scope_map: map, template_scope, owner } = this;
component,
declarations,
scope_map: map,
template_scope,
owner
} = this;
let scope = this.scope; let scope = this.scope;
let function_expression; let function_expression;
@ -237,7 +250,7 @@ export default class Expression {
if (template_scope.names.has(name)) { if (template_scope.names.has(name)) {
contextual_dependencies.add(name); contextual_dependencies.add(name);
template_scope.dependencies_for_name.get(name).forEach(dependency => { template_scope.dependencies_for_name.get(name).forEach((dependency) => {
dependencies.add(dependency); dependencies.add(dependency);
}); });
} else { } else {
@ -269,9 +282,7 @@ export default class Expression {
if (map.has(node)) scope = scope.parent; if (map.has(node)) scope = scope.parent;
if (node === function_expression) { if (node === function_expression) {
const id = component.get_unique_name( const id = component.get_unique_name(sanitize(get_function_name(node, owner)));
sanitize(get_function_name(node, owner))
);
const declaration = b`const ${id} = ${node}`; const declaration = b`const ${id} = ${node}`;
const extract_functions = () => { const extract_functions = () => {
@ -280,11 +291,11 @@ export default class Expression {
const has_args = function_expression.params.length > 0; const has_args = function_expression.params.length > 0;
function_expression.params = [ function_expression.params = [
...deps.map(name => ({ type: 'Identifier', name } as Identifier)), ...deps.map((name) => ({ type: 'Identifier', name } as Identifier)),
...function_expression.params ...function_expression.params
]; ];
const context_args = deps.map(name => block.renderer.reference(name, ctx)); const context_args = deps.map((name) => block.renderer.reference(name, ctx));
component.partly_hoisted.push(declaration); component.partly_hoisted.push(declaration);
@ -346,7 +357,7 @@ export default class Expression {
const { deps, func_declaration } = extract_functions(); const { deps, func_declaration } = extract_functions();
if (owner.type === 'Attribute' && owner.parent.name === 'slot') { if (owner.type === 'Attribute' && owner.parent.name === 'slot') {
const dep_scopes = new Set<INode>(deps.map(name => template_scope.get_owner(name))); const dep_scopes = new Set<INode>(deps.map((name) => template_scope.get_owner(name)));
// find the nearest scopes // find the nearest scopes
let node: INode = owner.parent; let node: INode = owner.parent;
while (node && !dep_scopes.has(node)) { while (node && !dep_scopes.has(node)) {
@ -376,7 +387,7 @@ export default class Expression {
type: 'DestructuredVariable', type: 'DestructuredVariable',
key: func_id, key: func_id,
modifier: () => func_expression, modifier: () => func_expression,
default_modifier: node => node default_modifier: (node) => node
}); });
this.replace(block.renderer.reference(func_id)); this.replace(block.renderer.reference(func_id));
} }
@ -408,10 +419,10 @@ export default class Expression {
const names = new Set(extract_names(assignee as Node)); const names = new Set(extract_names(assignee as Node));
const traced: Set<string> = new Set(); const traced: Set<string> = new Set();
names.forEach(name => { names.forEach((name) => {
const dependencies = template_scope.dependencies_for_name.get(name); const dependencies = template_scope.dependencies_for_name.get(name);
if (dependencies) { if (dependencies) {
dependencies.forEach(name => traced.add(name)); dependencies.forEach((name) => traced.add(name));
} else { } else {
traced.add(name); traced.add(name);
} }
@ -441,7 +452,7 @@ export default class Expression {
if (declarations.length > 0) { if (declarations.length > 0) {
block.maintain_context = true; block.maintain_context = true;
declarations.forEach(declaration => { declarations.forEach((declaration) => {
block.chunks.init.push(declaration); block.chunks.init.push(declaration);
}); });
} }

@ -15,6 +15,7 @@ export default class Node {
next?: INode; next?: INode;
can_use_innerhtml: boolean; can_use_innerhtml: boolean;
is_static_content: boolean;
var: string; var: string;
attributes: Attribute[]; attributes: Attribute[];
@ -33,6 +34,9 @@ export default class Node {
value: parent value: parent
} }
}); });
this.can_use_innerhtml = true;
this.is_static_content = true;
} }
cannot_use_innerhtml() { cannot_use_innerhtml() {
@ -42,15 +46,22 @@ export default class Node {
} }
} }
not_static_content() {
this.is_static_content = false;
if (this.parent) this.parent.not_static_content();
}
find_nearest(selector: RegExp) { find_nearest(selector: RegExp) {
if (selector.test(this.type)) return this; if (selector.test(this.type)) return this;
if (this.parent) return this.parent.find_nearest(selector); if (this.parent) return this.parent.find_nearest(selector);
} }
get_static_attribute_value(name: string) { get_static_attribute_value(name: string) {
const attribute = this.attributes && this.attributes.find( const attribute =
(attr: Attribute) => attr.type === 'Attribute' && attr.name.toLowerCase() === name this.attributes &&
); this.attributes.find(
(attr: Attribute) => attr.type === 'Attribute' && attr.name.toLowerCase() === name
);
if (!attribute) return null; if (!attribute) return null;
@ -65,8 +76,6 @@ export default class Node {
} }
has_ancestor(type: string) { has_ancestor(type: string) {
return this.parent ? return this.parent ? this.parent.type === type || this.parent.has_ancestor(type) : false;
this.parent.type === type || this.parent.has_ancestor(type) :
false;
} }
} }

@ -8,11 +8,24 @@ export default class Tag extends Node {
constructor(component, parent, scope, info) { constructor(component, parent, scope, info) {
super(component, parent, scope, info); super(component, parent, scope, info);
component.tags.push(this);
this.cannot_use_innerhtml();
this.expression = new Expression(component, this, scope, info.expression); this.expression = new Expression(component, this, scope, info.expression);
this.should_cache = ( this.should_cache =
info.expression.type !== 'Identifier' || info.expression.type !== 'Identifier' ||
(this.expression.dependencies.size && scope.names.has(info.expression.name)) (this.expression.dependencies.size && scope.names.has(info.expression.name));
}
is_dependencies_static() {
return (
this.expression.dynamic_contextual_dependencies().length === 0 &&
this.expression.dynamic_dependencies().length === 0
); );
} }
check_if_content_dynamic() {
if (!this.is_dependencies_static()) {
this.not_static_content();
}
}
} }

@ -6,7 +6,14 @@ import Element from '../Element';
import SlotTemplate from '../SlotTemplate'; import SlotTemplate from '../SlotTemplate';
import ConstTag from '../ConstTag'; import ConstTag from '../ConstTag';
type NodeWithScope = EachBlock | ThenBlock | CatchBlock | InlineComponent | Element | SlotTemplate | ConstTag; type NodeWithScope =
| EachBlock
| ThenBlock
| CatchBlock
| InlineComponent
| Element
| SlotTemplate
| ConstTag;
export default class TemplateScope { export default class TemplateScope {
names: Set<string>; names: Set<string>;
@ -33,7 +40,7 @@ export default class TemplateScope {
} }
is_top_level(name: string) { is_top_level(name: string) {
return !this.parent || !this.names.has(name) && this.parent.is_top_level(name); return !this.parent || (!this.names.has(name) && this.parent.is_top_level(name));
} }
get_owner(name: string): NodeWithScope { get_owner(name: string): NodeWithScope {
@ -42,7 +49,12 @@ export default class TemplateScope {
is_let(name: string) { is_let(name: string) {
const owner = this.get_owner(name); const owner = this.get_owner(name);
return owner && (owner.type === 'Element' || owner.type === 'InlineComponent' || owner.type === 'SlotTemplate'); return (
owner &&
(owner.type === 'Element' ||
owner.type === 'InlineComponent' ||
owner.type === 'SlotTemplate')
);
} }
is_await(name: string) { is_await(name: string) {

@ -6,86 +6,93 @@ import { INodeAllowConstTag, INode } from '../interfaces';
import check_graph_for_cycles from '../../utils/check_graph_for_cycles'; import check_graph_for_cycles from '../../utils/check_graph_for_cycles';
import compiler_errors from '../../compiler_errors'; import compiler_errors from '../../compiler_errors';
export default function get_const_tags(children: TemplateNode[], component: Component, node: INodeAllowConstTag, parent: INode): [ConstTag[], Array<Exclude<INode, ConstTag>>] { export default function get_const_tags(
const const_tags: ConstTagType[] = []; children: TemplateNode[],
const others: Array<Exclude<TemplateNode, ConstTagType>> = []; component: Component,
node: INodeAllowConstTag,
for (const child of children) { parent: INode
if (child.type === 'ConstTag') { ): [ConstTag[], Array<Exclude<INode, ConstTag>>] {
const_tags.push(child as ConstTagType); const const_tags: ConstTagType[] = [];
} else { const others: Array<Exclude<TemplateNode, ConstTagType>> = [];
others.push(child);
} for (const child of children) {
} if (child.type === 'ConstTag') {
const_tags.push(child as ConstTagType);
const consts_nodes = const_tags.map(tag => new ConstTag(component, node, node.scope, tag)); } else {
const sorted_consts_nodes = sort_consts_nodes(consts_nodes, component); others.push(child);
sorted_consts_nodes.forEach(node => node.parse_expression()); }
}
const children_nodes = map_children(component, parent, node.scope, others);
const consts_nodes = const_tags.map((tag) => new ConstTag(component, node, node.scope, tag));
return [sorted_consts_nodes, children_nodes as Array<Exclude<INode, ConstTag>>]; const sorted_consts_nodes = sort_consts_nodes(consts_nodes, component);
sorted_consts_nodes.forEach((node) => node.parse_expression());
const children_nodes = map_children(component, parent, node.scope, others);
return [sorted_consts_nodes, children_nodes as Array<Exclude<INode, ConstTag>>];
} }
function sort_consts_nodes(consts_nodes: ConstTag[], component: Component) { function sort_consts_nodes(consts_nodes: ConstTag[], component: Component) {
type ConstNode = { type ConstNode = {
assignees: Set<string>; assignees: Set<string>;
dependencies: Set<string>; dependencies: Set<string>;
node: ConstTag; node: ConstTag;
}; };
const sorted_consts_nodes: ConstNode[] = []; const sorted_consts_nodes: ConstNode[] = [];
const unsorted_consts_nodes: ConstNode[] = consts_nodes.map(node => { const unsorted_consts_nodes: ConstNode[] = consts_nodes.map((node) => {
return { return {
assignees: node.assignees, assignees: node.assignees,
dependencies: node.dependencies, dependencies: node.dependencies,
node node
}; };
}); });
const lookup = new Map(); const lookup = new Map();
unsorted_consts_nodes.forEach(node => { unsorted_consts_nodes.forEach((node) => {
node.assignees.forEach(name => { node.assignees.forEach((name) => {
if (!lookup.has(name)) { if (!lookup.has(name)) {
lookup.set(name, []); lookup.set(name, []);
} }
lookup.get(name).push(node); lookup.get(name).push(node);
}); });
}); });
const cycle = check_graph_for_cycles(unsorted_consts_nodes.reduce((acc, node) => { const cycle = check_graph_for_cycles(
node.assignees.forEach(v => { unsorted_consts_nodes.reduce((acc, node) => {
node.dependencies.forEach(w => { node.assignees.forEach((v) => {
if (!node.assignees.has(w)) { node.dependencies.forEach((w) => {
acc.push([v, w]); if (!node.assignees.has(w)) {
} acc.push([v, w]);
}); }
}); });
return acc; });
}, [])); return acc;
}, [])
if (cycle && cycle.length) { );
const nodeList = lookup.get(cycle[0]);
const node = nodeList[0]; if (cycle && cycle.length) {
component.error(node.node, compiler_errors.cyclical_const_tags(cycle)); const nodeList = lookup.get(cycle[0]);
} const node = nodeList[0];
component.error(node.node, compiler_errors.cyclical_const_tags(cycle));
const add_node = (node: ConstNode) => { }
if (sorted_consts_nodes.includes(node)) return;
const add_node = (node: ConstNode) => {
node.dependencies.forEach(name => { if (sorted_consts_nodes.includes(node)) return;
if (node.assignees.has(name)) return;
const earlier_nodes = lookup.get(name); node.dependencies.forEach((name) => {
if (earlier_nodes) { if (node.assignees.has(name)) return;
earlier_nodes.forEach(add_node); const earlier_nodes = lookup.get(name);
} if (earlier_nodes) {
}); earlier_nodes.forEach(add_node);
}
sorted_consts_nodes.push(node); });
};
sorted_consts_nodes.push(node);
unsorted_consts_nodes.forEach(add_node); };
return sorted_consts_nodes.map(node => node.node); unsorted_consts_nodes.forEach(add_node);
return sorted_consts_nodes.map((node) => node.node);
} }

@ -25,27 +25,48 @@ export type Children = ReturnType<typeof map_children>;
function get_constructor(type) { function get_constructor(type) {
switch (type) { switch (type) {
case 'AwaitBlock': return AwaitBlock; case 'AwaitBlock':
case 'Body': return Body; return AwaitBlock;
case 'Comment': return Comment; case 'Body':
case 'ConstTag': return ConstTag; return Body;
case 'Document': return Document; case 'Comment':
case 'EachBlock': return EachBlock; return Comment;
case 'Element': return Element; case 'ConstTag':
case 'Head': return Head; return ConstTag;
case 'IfBlock': return IfBlock; case 'Document':
case 'InlineComponent': return InlineComponent; return Document;
case 'KeyBlock': return KeyBlock; case 'EachBlock':
case 'MustacheTag': return MustacheTag; return EachBlock;
case 'Options': return Options; case 'Element':
case 'RawMustacheTag': return RawMustacheTag; return Element;
case 'DebugTag': return DebugTag; case 'Head':
case 'Slot': return Slot; return Head;
case 'SlotTemplate': return SlotTemplate; case 'IfBlock':
case 'Text': return Text; return IfBlock;
case 'Title': return Title; case 'InlineComponent':
case 'Window': return Window; return InlineComponent;
default: throw new Error(`Not implemented: ${type}`); case 'KeyBlock':
return KeyBlock;
case 'MustacheTag':
return MustacheTag;
case 'Options':
return Options;
case 'RawMustacheTag':
return RawMustacheTag;
case 'DebugTag':
return DebugTag;
case 'Slot':
return Slot;
case 'SlotTemplate':
return SlotTemplate;
case 'Text':
return Text;
case 'Title':
return Title;
case 'Window':
return Window;
default:
throw new Error(`Not implemented: ${type}`);
} }
} }
@ -53,14 +74,14 @@ export default function map_children(component, parent, scope, children: Templat
let last = null; let last = null;
let ignores = []; let ignores = [];
return children.map(child => { return children.map((child) => {
const constructor = get_constructor(child.type); const constructor = get_constructor(child.type);
const use_ignores = child.type !== 'Text' && child.type !== 'Comment' && ignores.length; const use_ignores = child.type !== 'Text' && child.type !== 'Comment' && ignores.length;
if (use_ignores) component.push_ignores(ignores); if (use_ignores) component.push_ignores(ignores);
const node = new constructor(component, parent, scope, child); const node = new constructor(component, parent, scope, child);
if (use_ignores) component.pop_ignores(), ignores = []; if (use_ignores) component.pop_ignores(), (ignores = []);
if (node.type === 'Comment' && node.ignores.length) { if (node.type === 'Comment' && node.ignores.length) {
push_array(ignores, node.ignores); push_array(ignores, node.ignores);

@ -74,7 +74,7 @@ export default class Block {
get_unique_name: (name: string) => Identifier; get_unique_name: (name: string) => Identifier;
has_update_method = false; has_update_method = false;
autofocus?: { element_var: string, condition_expression?: any }; autofocus?: { element_var: string; condition_expression?: any };
constructor(options: BlockOptions) { constructor(options: BlockOptions) {
this.parent = options.parent; this.parent = options.parent;
@ -156,7 +156,7 @@ export default class Block {
} }
add_dependencies(dependencies: Set<string>) { add_dependencies(dependencies: Set<string>) {
dependencies.forEach(dependency => { dependencies.forEach((dependency) => {
this.dependencies.add(dependency); this.dependencies.add(dependency);
}); });
@ -206,9 +206,7 @@ export default class Block {
add_variable(id: Identifier, init?: Node) { add_variable(id: Identifier, init?: Node) {
if (this.variables.has(id.name)) { if (this.variables.has(id.name)) {
throw new Error( throw new Error(`Variable '${id.name}' already initialised with a different value`);
`Variable '${id.name}' already initialised with a different value`
);
} }
this.variables.set(id.name, { id, init }); this.variables.set(id.name, { id, init });
@ -244,7 +242,9 @@ export default class Block {
if (this.autofocus) { if (this.autofocus) {
if (this.autofocus.condition_expression) { if (this.autofocus.condition_expression) {
this.chunks.mount.push(b`if (${this.autofocus.condition_expression}) ${this.autofocus.element_var}.focus();`); this.chunks.mount.push(
b`if (${this.autofocus.condition_expression}) ${this.autofocus.element_var}.focus();`
);
} else { } else {
this.chunks.mount.push(b`${this.autofocus.element_var}.focus();`); this.chunks.mount.push(b`${this.autofocus.element_var}.focus();`);
} }
@ -267,11 +267,9 @@ export default class Block {
if (this.chunks.create.length === 0 && this.chunks.hydrate.length === 0) { if (this.chunks.create.length === 0 && this.chunks.hydrate.length === 0) {
properties.create = noop; properties.create = noop;
} else { } else {
const hydrate = this.chunks.hydrate.length > 0 && ( const hydrate =
this.renderer.options.hydratable this.chunks.hydrate.length > 0 &&
? b`this.h();` (this.renderer.options.hydratable ? b`this.h();` : this.chunks.hydrate);
: this.chunks.hydrate
);
properties.create = x`function #create() { properties.create = x`function #create() {
${this.chunks.create} ${this.chunks.create}
@ -404,15 +402,14 @@ export default class Block {
${this.chunks.declarations} ${this.chunks.declarations}
${Array.from(this.variables.values()).map(({ id, init }) => { ${Array.from(this.variables.values()).map(({ id, init }) => {
return init return init ? b`let ${id} = ${init}` : b`let ${id}`;
? b`let ${id} = ${init}`
: b`let ${id}`;
})} })}
${this.chunks.init} ${this.chunks.init}
${dev ${
? b` dev
? b`
const ${block} = ${return_value}; const ${block} = ${return_value};
@dispatch_dev("SvelteRegisterBlock", { @dispatch_dev("SvelteRegisterBlock", {
block: ${block}, block: ${block},
@ -422,7 +419,7 @@ export default class Block {
ctx: #ctx ctx: #ctx
}); });
return ${block};` return ${block};`
: b` : b`
return ${return_value};` return ${return_value};`
} }
`; `;
@ -431,17 +428,19 @@ export default class Block {
} }
has_content(): boolean { has_content(): boolean {
return !!this.first || return (
!!this.first ||
this.event_listeners.length > 0 || this.event_listeners.length > 0 ||
this.chunks.intro.length > 0 || this.chunks.intro.length > 0 ||
this.chunks.outro.length > 0 || this.chunks.outro.length > 0 ||
this.chunks.create.length > 0 || this.chunks.create.length > 0 ||
this.chunks.hydrate.length > 0 || this.chunks.hydrate.length > 0 ||
this.chunks.claim.length > 0 || this.chunks.claim.length > 0 ||
this.chunks.mount.length > 0 || this.chunks.mount.length > 0 ||
this.chunks.update.length > 0 || this.chunks.update.length > 0 ||
this.chunks.destroy.length > 0 || this.chunks.destroy.length > 0 ||
this.has_animation; this.has_animation
);
} }
render() { render() {
@ -483,9 +482,7 @@ export default class Block {
` `
); );
this.chunks.destroy.push( this.chunks.destroy.push(b`${dispose}();`);
b`${dispose}();`
);
} else { } else {
this.chunks.mount.push(b` this.chunks.mount.push(b`
if (!#mounted) { if (!#mounted) {
@ -496,9 +493,7 @@ export default class Block {
} }
`); `);
this.chunks.destroy.push( this.chunks.destroy.push(b`@run_all(${dispose});`);
b`@run_all(${dispose});`
);
} }
} }
} }

@ -3,7 +3,16 @@ import { CompileOptions, Var } from '../../interfaces';
import Component from '../Component'; import Component from '../Component';
import FragmentWrapper from './wrappers/Fragment'; import FragmentWrapper from './wrappers/Fragment';
import { x } from 'code-red'; import { x } from 'code-red';
import { Node, Identifier, MemberExpression, Literal, Expression, BinaryExpression, UnaryExpression, ArrayExpression } from 'estree'; import {
Node,
Identifier,
MemberExpression,
Literal,
Expression,
BinaryExpression,
UnaryExpression,
ArrayExpression
} from 'estree';
import flatten_reference from '../utils/flatten_reference'; import flatten_reference from '../utils/flatten_reference';
import { reserved_keywords } from '../utils/reserved_keywords'; import { reserved_keywords } from '../utils/reserved_keywords';
import { renderer_invalidate } from './invalidate'; import { renderer_invalidate } from './invalidate';
@ -57,12 +66,14 @@ export default class Renderer {
this.file_var = options.dev && this.component.get_unique_name('file'); this.file_var = options.dev && this.component.get_unique_name('file');
component.vars.filter(v => !v.hoistable || (v.export_name && !v.module)).forEach(v => this.add_to_context(v.name)); component.vars
.filter((v) => !v.hoistable || (v.export_name && !v.module))
.forEach((v) => this.add_to_context(v.name));
// ensure store values are included in context // ensure store values are included in context
component.vars.filter(v => v.subscribable).forEach(v => this.add_to_context(`$${v.name}`)); component.vars.filter((v) => v.subscribable).forEach((v) => this.add_to_context(`$${v.name}`));
reserved_keywords.forEach(keyword => { reserved_keywords.forEach((keyword) => {
if (component.var_lookup.has(keyword)) { if (component.var_lookup.has(keyword)) {
this.add_to_context(keyword); this.add_to_context(keyword);
} }
@ -97,7 +108,7 @@ export default class Renderer {
); );
// TODO messy // TODO messy
this.blocks.forEach(block => { this.blocks.forEach((block) => {
if (block instanceof Block) { if (block instanceof Block) {
block.assign_variable_names(); block.assign_variable_names();
} }
@ -109,7 +120,7 @@ export default class Renderer {
this.context_overflow = this.context.length > 31; this.context_overflow = this.context.length > 31;
this.context.forEach(member => { this.context.forEach((member) => {
const { variable } = member; const { variable } = member;
if (variable) { if (variable) {
member.priority += 2; member.priority += 2;
@ -117,7 +128,8 @@ export default class Renderer {
// these determine whether variable is included in initial context // these determine whether variable is included in initial context
// array, so must have the highest priority // array, so must have the highest priority
if (variable.is_reactive_dependency && (variable.mutated || variable.reassigned)) member.priority += 16; if (variable.is_reactive_dependency && (variable.mutated || variable.reassigned))
member.priority += 16;
if (variable.export_name) member.priority += 32; if (variable.export_name) member.priority += 32;
if (variable.referenced) member.priority += 64; if (variable.referenced) member.priority += 64;
} else if (member.is_non_contextual) { } else if (member.is_non_contextual) {
@ -131,14 +143,22 @@ export default class Renderer {
} }
}); });
this.context.sort((a, b) => (b.priority - a.priority) || ((a.index.value as number) - (b.index.value as number))); this.context.sort(
this.context.forEach((member, i) => member.index.value = i); (a, b) => b.priority - a.priority || (a.index.value as number) - (b.index.value as number)
);
this.context.forEach((member, i) => (member.index.value = i));
let i = this.context.length; let i = this.context.length;
while (i--) { while (i--) {
const member = this.context[i]; const member = this.context[i];
if (member.variable) { if (member.variable) {
if (member.variable.referenced || member.variable.export_name || (member.variable.is_reactive_dependency && (member.variable.mutated || member.variable.reassigned))) break; if (
member.variable.referenced ||
member.variable.export_name ||
(member.variable.is_reactive_dependency &&
(member.variable.mutated || member.variable.reassigned))
)
break;
} else if (member.is_non_contextual) { } else if (member.is_non_contextual) {
break; break;
} }
@ -180,9 +200,9 @@ export default class Renderer {
dirty(names: string[], is_reactive_declaration = false): Expression { dirty(names: string[], is_reactive_declaration = false): Expression {
const renderer = this; const renderer = this;
const dirty = (is_reactive_declaration const dirty = (is_reactive_declaration ? x`$$self.$$.dirty` : x`#dirty`) as
? x`$$self.$$.dirty` | Identifier
: x`#dirty`) as Identifier | MemberExpression; | MemberExpression;
const get_bitmask = () => { const get_bitmask = () => {
const bitmask: BitMasks = []; const bitmask: BitMasks = [];
@ -197,7 +217,7 @@ export default class Renderer {
const value = member.index.value as number; const value = member.index.value as number;
const i = (value / 31) | 0; const i = (value / 31) | 0;
const n = 1 << (value % 31); const n = 1 << value % 31;
if (!bitmask[i]) bitmask[i] = { n: 0, names: [] }; if (!bitmask[i]) bitmask[i] = { n: 0, names: [] };

@ -1,3 +1,4 @@
import type { RawSourceMap, DecodedSourceMap } from '@ampproject/remapping';
import { b, x, p } from 'code-red'; import { b, x, p } from 'code-red';
import Component from '../Component'; import Component from '../Component';
import Renderer from './Renderer'; import Renderer from './Renderer';
@ -6,13 +7,18 @@ import { walk } from 'estree-walker';
import { extract_names, Scope } from 'periscopic'; import { extract_names, Scope } from 'periscopic';
import { invalidate } from './invalidate'; import { invalidate } from './invalidate';
import Block from './Block'; import Block from './Block';
import { ImportDeclaration, ClassDeclaration, FunctionExpression, Node, Statement, ObjectExpression, Expression } from 'estree'; import {
ImportDeclaration,
ClassDeclaration,
Node,
Statement,
ObjectExpression,
Expression
} from 'estree';
import { apply_preprocessor_sourcemap } from '../../utils/mapped_code'; import { apply_preprocessor_sourcemap } from '../../utils/mapped_code';
import { RawSourceMap, DecodedSourceMap } from '@ampproject/remapping/dist/types/types';
import { flatten } from '../../utils/flatten'; import { flatten } from '../../utils/flatten';
import check_enable_sourcemap from '../utils/check_enable_sourcemap'; import check_enable_sourcemap from '../utils/check_enable_sourcemap';
import { push_array } from '../../utils/push_array'; import { push_array } from '../../utils/push_array';
import { regex_backslashes } from '../../utils/patterns';
export default function dom( export default function dom(
component: Component, component: Component,
@ -25,9 +31,6 @@ export default function dom(
block.has_outro_method = true; block.has_outro_method = true;
// prevent fragment being created twice (#1063)
if (options.customElement) block.chunks.create.push(b`this.c = @noop;`);
const body = []; const body = [];
if (renderer.file_var) { if (renderer.file_var) {
@ -35,27 +38,28 @@ export default function dom(
body.push(b`const ${renderer.file_var} = ${file};`); body.push(b`const ${renderer.file_var} = ${file};`);
} }
const css = component.stylesheet.render(options.filename, !options.customElement); const css = component.stylesheet.render(options.filename);
const css_sourcemap_enabled = check_enable_sourcemap(options.enableSourcemap, 'css'); const css_sourcemap_enabled = check_enable_sourcemap(options.enableSourcemap, 'css');
if (css_sourcemap_enabled) { if (css_sourcemap_enabled) {
css.map = apply_preprocessor_sourcemap(options.filename, css.map, options.sourcemap as string | RawSourceMap | DecodedSourceMap); css.map = apply_preprocessor_sourcemap(
options.filename,
css.map,
options.sourcemap as string | RawSourceMap | DecodedSourceMap
);
} else { } else {
css.map = null; css.map = null;
} }
const styles = css_sourcemap_enabled && component.stylesheet.has_styles && options.dev const styles =
? `${css.code}\n/*# sourceMappingURL=${css.map.toUrl()} */` css_sourcemap_enabled && component.stylesheet.has_styles && options.dev
: css.code; ? `${css.code}\n/*# sourceMappingURL=${css.map.toUrl()} */`
: css.code;
const add_css = component.get_unique_name('add_css'); const add_css = component.get_unique_name('add_css');
const should_add_css = ( const should_add_css = !!styles && (options.customElement || options.css === 'injected');
!options.customElement &&
!!styles &&
options.css === 'injected'
);
if (should_add_css) { if (should_add_css) {
body.push(b` body.push(b`
@ -69,12 +73,15 @@ export default function dom(
// TODO the deconflicted names of blocks are reversed... should set them here // TODO the deconflicted names of blocks are reversed... should set them here
const blocks = renderer.blocks.slice().reverse(); const blocks = renderer.blocks.slice().reverse();
push_array(body, blocks.map(block => { push_array(
// TODO this is a horrible mess — renderer.blocks body,
// contains a mixture of Blocks and Nodes blocks.map((block) => {
if ((block as Block).render) return (block as Block).render(); // TODO this is a horrible mess — renderer.blocks
return block; // contains a mixture of Blocks and Nodes
})); if ((block as Block).render) return (block as Block).render();
return block;
})
);
if (options.dev && !options.hydratable) { if (options.dev && !options.hydratable) {
block.chunks.claim.push( block.chunks.claim.push(
@ -90,34 +97,55 @@ export default function dom(
`; `;
} }
const uses_props = component.var_lookup.has('$$props'); const uses_props = component.var_lookup.has('$$props');
const uses_rest = component.var_lookup.has('$$restProps'); const uses_rest = component.var_lookup.has('$$restProps');
const $$props = uses_props || uses_rest ? '$$new_props' : '$$props'; const $$props = uses_props || uses_rest ? '$$new_props' : '$$props';
const props = component.vars.filter(variable => !variable.module && variable.export_name); const props = component.vars.filter((variable) => !variable.module && variable.export_name);
const writable_props = props.filter(variable => variable.writable); const writable_props = props.filter((variable) => variable.writable);
const omit_props_names = component.get_unique_name('omit_props_names'); const omit_props_names = component.get_unique_name('omit_props_names');
const compute_rest = x`@compute_rest_props($$props, ${omit_props_names.name})`; const compute_rest = x`@compute_rest_props($$props, ${omit_props_names.name})`;
const rest = uses_rest ? b` const rest = uses_rest
const ${omit_props_names.name} = [${props.map(prop => `"${prop.export_name}"`).join(',')}]; ? b`
const ${omit_props_names.name} = [${props.map((prop) => `"${prop.export_name}"`).join(',')}];
let $$restProps = ${compute_rest}; let $$restProps = ${compute_rest};
` : null; `
: null;
const set = (uses_props || uses_rest || writable_props.length > 0 || component.slots.size > 0) const set =
? x` uses_props || uses_rest || writable_props.length > 0 || component.slots.size > 0
? x`
${$$props} => { ${$$props} => {
${uses_props && renderer.invalidate('$$props', x`$$props = @assign(@assign({}, $$props), @exclude_internal_props($$new_props))`)} ${
${uses_rest && !uses_props && x`$$props = @assign(@assign({}, $$props), @exclude_internal_props($$new_props))`} uses_props &&
renderer.invalidate(
'$$props',
x`$$props = @assign(@assign({}, $$props), @exclude_internal_props($$new_props))`
)
}
${
uses_rest &&
!uses_props &&
x`$$props = @assign(@assign({}, $$props), @exclude_internal_props($$new_props))`
}
${uses_rest && renderer.invalidate('$$restProps', x`$$restProps = ${compute_rest}`)} ${uses_rest && renderer.invalidate('$$restProps', x`$$restProps = ${compute_rest}`)}
${writable_props.map(prop => ${writable_props.map(
b`if ('${prop.export_name}' in ${$$props}) ${renderer.invalidate(prop.name, x`${prop.name} = ${$$props}.${prop.export_name}`)};` (prop) =>
b`if ('${prop.export_name}' in ${$$props}) ${renderer.invalidate(
prop.name,
x`${prop.name} = ${$$props}.${prop.export_name}`
)};`
)} )}
${component.slots.size > 0 && ${
b`if ('$$scope' in ${$$props}) ${renderer.invalidate('$$scope', x`$$scope = ${$$props}.$$scope`)};`} component.slots.size > 0 &&
b`if ('$$scope' in ${$$props}) ${renderer.invalidate(
'$$scope',
x`$$scope = ${$$props}.$$scope`
)};`
}
} }
` `
: null; : null;
const accessors = []; const accessors = [];
@ -127,7 +155,7 @@ export default function dom(
let capture_state: Expression; let capture_state: Expression;
let props_inject: Node[] | Node; let props_inject: Node[] | Node;
props.forEach(prop => { props.forEach((prop) => {
const variable = component.var_lookup.get(prop.name); const variable = component.var_lookup.get(prop.name);
if (!variable.writable || component.component_options.accessors) { if (!variable.writable || component.component_options.accessors) {
@ -136,7 +164,11 @@ export default function dom(
kind: 'get', kind: 'get',
key: { type: 'Identifier', name: prop.export_name }, key: { type: 'Identifier', name: prop.export_name },
value: x`function() { value: x`function() {
return ${prop.hoistable ? prop.name : x`this.$$.ctx[${renderer.context_lookup.get(prop.name).index}]`} return ${
prop.hoistable
? prop.name
: x`this.$$.ctx[${renderer.context_lookup.get(prop.name).index}]`
}
}` }`
}); });
} else if (component.compile_options.dev) { } else if (component.compile_options.dev) {
@ -183,7 +215,7 @@ export default function dom(
} }
}); });
component.instance_exports_from.forEach(exports_from => { component.instance_exports_from.forEach((exports_from) => {
const import_declaration = { const import_declaration = {
...exports_from, ...exports_from,
type: 'ImportDeclaration', type: 'ImportDeclaration',
@ -192,7 +224,7 @@ export default function dom(
}; };
component.imports.push(import_declaration as ImportDeclaration); component.imports.push(import_declaration as ImportDeclaration);
exports_from.specifiers.forEach(specifier => { exports_from.specifiers.forEach((specifier) => {
if (component.component_options.accessors) { if (component.component_options.accessors) {
const name = component.get_unique_name(specifier.exported.name); const name = component.get_unique_name(specifier.exported.name);
import_declaration.specifiers.push({ import_declaration.specifiers.push({
@ -225,33 +257,46 @@ export default function dom(
if (component.compile_options.dev) { if (component.compile_options.dev) {
// checking that expected ones were passed // checking that expected ones were passed
const expected = props.filter(prop => prop.writable && !prop.initialised); const expected = props.filter((prop) => prop.writable && !prop.initialised);
if (expected.length) { if (expected.length) {
missing_props_check = b` missing_props_check = b`
$$self.$$.on_mount.push(function () { $$self.$$.on_mount.push(function () {
${expected.map(prop => b` ${expected.map(
(prop) => b`
if (${prop.name} === undefined && !(('${prop.export_name}' in $$props) || $$self.$$.bound[$$self.$$.props['${prop.export_name}']])) { if (${prop.name} === undefined && !(('${prop.export_name}' in $$props) || $$self.$$.bound[$$self.$$.props['${prop.export_name}']])) {
@_console.warn("<${component.tag}> was created without expected prop '${prop.export_name}'"); @_console.warn("<${component.tag}> was created without expected prop '${prop.export_name}'");
}`)} }`
)}
}); });
`; `;
} }
const capturable_vars = component.vars.filter(v => !v.internal && !v.global && !v.name.startsWith('$$')); const capturable_vars = component.vars.filter(
(v) => !v.internal && !v.global && !v.name.startsWith('$$')
);
if (capturable_vars.length > 0) { if (capturable_vars.length > 0) {
capture_state = x`() => ({ ${capturable_vars.map(prop => p`${prop.name}`)} })`; capture_state = x`() => ({ ${capturable_vars.map((prop) => p`${prop.name}`)} })`;
} }
const injectable_vars = capturable_vars.filter(v => !v.module && v.writable && v.name[0] !== '$'); const injectable_vars = capturable_vars.filter(
(v) => !v.module && v.writable && v.name[0] !== '$'
);
if (uses_props || injectable_vars.length > 0) { if (uses_props || injectable_vars.length > 0) {
inject_state = x` inject_state = x`
${$$props} => { ${$$props} => {
${uses_props && renderer.invalidate('$$props', x`$$props = @assign(@assign({}, $$props), $$new_props)`)} ${
uses_props &&
renderer.invalidate('$$props', x`$$props = @assign(@assign({}, $$props), $$new_props)`)
}
${injectable_vars.map( ${injectable_vars.map(
v => b`if ('${v.name}' in $$props) ${renderer.invalidate(v.name, x`${v.name} = ${$$props}.${v.name}`)};` (v) =>
b`if ('${v.name}' in $$props) ${renderer.invalidate(
v.name,
x`${v.name} = ${$$props}.${v.name}`
)};`
)} )}
} }
`; `;
@ -278,7 +323,11 @@ export default function dom(
if (!execution_context && !scope.block) { if (!execution_context && !scope.block) {
execution_context = node; execution_context = node;
} }
} else if (!execution_context && node.type === 'LabeledStatement' && node.label.name === '$') { } else if (
!execution_context &&
node.type === 'LabeledStatement' &&
node.label.name === '$'
) {
execution_context = node; execution_context = node;
} }
}, },
@ -310,9 +359,10 @@ export default function dom(
const value = `$${name}`; const value = `$${name}`;
const i = renderer.context_lookup.get(`$${name}`).index; const i = renderer.context_lookup.get(`$${name}`).index;
const insert = (reassigned || export_name) const insert =
? b`${`$$subscribe_${name}`}()` reassigned || export_name
: b`@component_subscribe($$self, ${name}, #value => $$invalidate(${i}, ${value} = #value))`; ? b`${`$$subscribe_${name}`}()`
: b`@component_subscribe($$self, ${name}, #value => $$invalidate(${i}, ${value} = #value))`;
if (component.compile_options.dev) { if (component.compile_options.dev) {
return b`@validate_store(${name}, '${name}'); ${insert}`; return b`@validate_store(${name}, '${name}'); ${insert}`;
@ -323,7 +373,8 @@ export default function dom(
} }
const args = [x`$$self`]; const args = [x`$$self`];
const has_invalidate = props.length > 0 || const has_invalidate =
props.length > 0 ||
component.has_reactive_assignments || component.has_reactive_assignments ||
component.slots.size > 0 || component.slots.size > 0 ||
capture_state || capture_state ||
@ -350,18 +401,20 @@ export default function dom(
${component.fully_hoisted} ${component.fully_hoisted}
`); `);
const filtered_props = props.filter(prop => { const filtered_props = props.filter((prop) => {
const variable = component.var_lookup.get(prop.name); const variable = component.var_lookup.get(prop.name);
if (variable.hoistable) return false; if (variable.hoistable) return false;
return prop.name[0] !== '$'; return prop.name[0] !== '$';
}); });
const reactive_stores = component.vars.filter(variable => variable.name[0] === '$' && variable.name[1] !== '$'); const reactive_stores = component.vars.filter(
(variable) => variable.name[0] === '$' && variable.name[1] !== '$'
);
const instance_javascript = component.extract_javascript(component.ast.instance); const instance_javascript = component.extract_javascript(component.ast.instance);
const has_definition = ( const has_definition =
component.compile_options.dev || component.compile_options.dev ||
(instance_javascript && instance_javascript.length > 0) || (instance_javascript && instance_javascript.length > 0) ||
filtered_props.length > 0 || filtered_props.length > 0 ||
@ -370,44 +423,48 @@ export default function dom(
renderer.initial_context.length > 0 || renderer.initial_context.length > 0 ||
component.reactive_declarations.length > 0 || component.reactive_declarations.length > 0 ||
capture_state || capture_state ||
inject_state inject_state;
);
const definition = has_definition const definition = has_definition
? component.alias('instance') ? component.alias('instance')
: { type: 'Literal', value: null }; : { type: 'Literal', value: null };
const reactive_store_subscriptions = reactive_stores const reactive_store_subscriptions = reactive_stores
.filter(store => { .filter((store) => {
const variable = component.var_lookup.get(store.name.slice(1)); const variable = component.var_lookup.get(store.name.slice(1));
return !variable || variable.hoistable; return !variable || variable.hoistable;
}) })
.map(({ name }) => b` .map(
({ name }) => b`
${component.compile_options.dev && b`@validate_store(${name.slice(1)}, '${name.slice(1)}');`} ${component.compile_options.dev && b`@validate_store(${name.slice(1)}, '${name.slice(1)}');`}
@component_subscribe($$self, ${name.slice(1)}, $$value => $$invalidate(${renderer.context_lookup.get(name).index}, ${name} = $$value)); @component_subscribe($$self, ${name.slice(1)}, $$value => $$invalidate(${
`); renderer.context_lookup.get(name).index
}, ${name} = $$value));
`
);
const resubscribable_reactive_store_unsubscribers = reactive_stores const resubscribable_reactive_store_unsubscribers = reactive_stores
.filter(store => { .filter((store) => {
const variable = component.var_lookup.get(store.name.slice(1)); const variable = component.var_lookup.get(store.name.slice(1));
return variable && (variable.reassigned || variable.export_name); return variable && (variable.reassigned || variable.export_name);
}) })
.map(({ name }) => b`$$self.$$.on_destroy.push(() => ${`$$unsubscribe_${name.slice(1)}`}());`); .map(({ name }) => b`$$self.$$.on_destroy.push(() => ${`$$unsubscribe_${name.slice(1)}`}());`);
if (has_definition) { if (has_definition) {
const reactive_declarations: (Node | Node[]) = []; const reactive_declarations: Node | Node[] = [];
const fixed_reactive_declarations: Node[] = []; // not really 'reactive' but whatever const fixed_reactive_declarations: Node[] = []; // not really 'reactive' but whatever
component.reactive_declarations.forEach(d => { component.reactive_declarations.forEach((d) => {
const dependencies = Array.from(d.dependencies); const dependencies = Array.from(d.dependencies);
const uses_rest_or_props = !!dependencies.find(n => n === '$$props' || n === '$$restProps'); const uses_rest_or_props = !!dependencies.find((n) => n === '$$props' || n === '$$restProps');
const writable = dependencies.filter(n => { const writable = dependencies.filter((n) => {
const variable = component.var_lookup.get(n); const variable = component.var_lookup.get(n);
return variable && (variable.export_name || variable.mutated || variable.reassigned); return variable && (variable.export_name || variable.mutated || variable.reassigned);
}); });
const condition = !uses_rest_or_props && writable.length > 0 && renderer.dirty(writable, true); const condition =
!uses_rest_or_props && writable.length > 0 && renderer.dirty(writable, true);
let statement = d.node; // TODO remove label (use d.node.body) if it's not referenced let statement = d.node; // TODO remove label (use d.node.body) if it's not referenced
@ -420,12 +477,12 @@ export default function dom(
} }
}); });
const injected = Array.from(component.injected_reactive_declaration_vars).filter(name => { const injected = Array.from(component.injected_reactive_declaration_vars).filter((name) => {
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);
return variable.injected && variable.name[0] !== '$'; return variable.injected && variable.name[0] !== '$';
}); });
const reactive_store_declarations = reactive_stores.map(variable => { const reactive_store_declarations = reactive_stores.map((variable) => {
const $name = variable.name; const $name = variable.name;
const name = $name.slice(1); const name = $name.slice(1);
@ -444,24 +501,29 @@ export default function dom(
let unknown_props_check: Node[] | undefined; let unknown_props_check: Node[] | undefined;
if (component.compile_options.dev && !(uses_props || uses_rest)) { if (component.compile_options.dev && !(uses_props || uses_rest)) {
unknown_props_check = b` unknown_props_check = b`
const writable_props = [${writable_props.map(prop => x`'${prop.export_name}'`)}]; const writable_props = [${writable_props.map((prop) => x`'${prop.export_name}'`)}];
@_Object.keys($$props).forEach(key => { @_Object.keys($$props).forEach(key => {
if (!~writable_props.indexOf(key) && key.slice(0, 2) !== '$$' && key !== 'slot') @_console.warn(\`<${component.tag}> was created with unknown prop '\${key}'\`); if (!~writable_props.indexOf(key) && key.slice(0, 2) !== '$$' && key !== 'slot') @_console.warn(\`<${
component.tag
}> was created with unknown prop '\${key}'\`);
}); });
`; `;
} }
const return_value = { const return_value = {
type: 'ArrayExpression', type: 'ArrayExpression',
elements: renderer.initial_context.map(member => ({ elements: renderer.initial_context.map(
type: 'Identifier', (member) =>
name: member.name ({
}) as Expression) type: 'Identifier',
name: member.name
} as Expression)
)
}; };
body.push(b` body.push(b`
function ${definition}(${args}) { function ${definition}(${args}) {
${injected.map(name => b`let ${name};`)} ${injected.map((name) => b`let ${name};`)}
${rest} ${rest}
@ -471,8 +533,17 @@ export default function dom(
${resubscribable_reactive_store_unsubscribers} ${resubscribable_reactive_store_unsubscribers}
${component.slots.size || component.compile_options.dev || uses_slots ? b`let { $$slots: #slots = {}, $$scope } = $$props;` : null} ${
${component.compile_options.dev && b`@validate_slots('${component.tag}', #slots, [${[...component.slots.keys()].map(key => `'${key}'`).join(',')}]);`} component.slots.size || component.compile_options.dev || uses_slots
? b`let { $$slots: #slots = {}, $$scope } = $$props;`
: null
}
${
component.compile_options.dev &&
b`@validate_slots('${component.tag}', #slots, [${[...component.slots.keys()]
.map((key) => `'${key}'`)
.join(',')}]);`
}
${compute_slots} ${compute_slots}
${instance_javascript} ${instance_javascript}
@ -480,7 +551,10 @@ export default function dom(
${missing_props_check} ${missing_props_check}
${unknown_props_check} ${unknown_props_check}
${renderer.binding_groups.size > 0 && b`const $$binding_groups = [${[...renderer.binding_groups.keys()].map(_ => x`[]`)}];`} ${
renderer.binding_groups.size > 0 &&
b`const $$binding_groups = [${[...renderer.binding_groups.keys()].map((_) => x`[]`)}];`
}
${component.partly_hoisted} ${component.partly_hoisted}
@ -492,11 +566,14 @@ export default function dom(
${/* before reactive declarations */ props_inject} ${/* before reactive declarations */ props_inject}
${reactive_declarations.length > 0 && b` ${
reactive_declarations.length > 0 &&
b`
$$self.$$.update = () => { $$self.$$.update = () => {
${reactive_declarations} ${reactive_declarations}
}; };
`} `
}
${fixed_reactive_declarations} ${fixed_reactive_declarations}
@ -508,7 +585,9 @@ export default function dom(
} }
const prop_indexes = x`{ const prop_indexes = x`{
${props.filter(v => v.export_name && !v.module).map(v => p`${v.export_name}: ${renderer.context_lookup.get(v.name).index}`)} ${props
.filter((v) => v.export_name && !v.module)
.map((v) => p`${v.export_name}: ${renderer.context_lookup.get(v.name).index}`)}
}` as ObjectExpression; }` as ObjectExpression;
let dirty; let dirty;
@ -519,91 +598,71 @@ export default function dom(
} }
} }
if (options.customElement) { const superclass = {
type: 'Identifier',
name: options.dev ? '@SvelteComponentDev' : '@SvelteComponent'
};
let init_props = x`@attribute_to_object(this.attributes)`; const optional_parameters = [];
if (uses_slots) { if (should_add_css) {
init_props = x`{ ...${init_props}, $$slots: @get_custom_elements_slots(this) }`; optional_parameters.push(add_css);
} } else if (dirty) {
optional_parameters.push(x`null`);
const declaration = b` }
class ${name} extends @SvelteElement { if (dirty) {
constructor(options) { optional_parameters.push(dirty);
super(); }
${css.code && b`
const style = document.createElement('style');
style.textContent = \`${css.code.replace(regex_backslashes, '\\\\')}${css_sourcemap_enabled && options.dev ? `\n/*# sourceMappingURL=${css.map.toUrl()} */` : ''}\`
this.shadowRoot.appendChild(style)`}
@init(this, { target: this.shadowRoot, props: ${init_props}, customElement: true }, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, null, ${dirty});
if (options) {
if (options.target) {
@insert(options.target, this, options.anchor);
}
${(props.length > 0 || uses_props || uses_rest) && b` const declaration = b`
if (options.props) { class ${name} extends ${superclass} {
this.$set(options.props); constructor(options) {
@flush(); super(${options.dev && 'options'});
}`} @init(this, options, ${definition}, ${
} has_create_fragment ? 'create_fragment' : 'null'
}, ${not_equal}, ${prop_indexes}, ${optional_parameters});
${
options.dev &&
b`@dispatch_dev("SvelteRegisterComponent", { component: this, tagName: "${name.name}", options, id: create_fragment.name });`
} }
} }
`[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
});
} }
`[0] as ClassDeclaration;
push_array(declaration.body.body, accessors); push_array(declaration.body.body, accessors);
body.push(declaration);
body.push(declaration); if (options.customElement) {
const props_str = writable_props.reduce((def, prop) => {
if (component.tag != null) { def[prop.export_name] =
body.push(b` component.component_options.customElement?.props?.[prop.export_name] || {};
@_customElements.define("${component.tag}", ${name}); if (prop.is_boolean && !def[prop.export_name].type) {
`); def[prop.export_name].type = 'Boolean';
}
} else {
const superclass = {
type: 'Identifier',
name: options.dev ? '@SvelteComponentDev' : '@SvelteComponent'
};
const optional_parameters = [];
if (should_add_css) {
optional_parameters.push(add_css);
} else if (dirty) {
optional_parameters.push(x`null`);
}
if (dirty) {
optional_parameters.push(dirty);
}
const declaration = b`
class ${name} extends ${superclass} {
constructor(options) {
super(${options.dev && 'options'});
@init(this, options, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, ${optional_parameters});
${options.dev && b`@dispatch_dev("SvelteRegisterComponent", { component: this, tagName: "${name.name}", options, id: create_fragment.name });`}
}
} }
`[0] as ClassDeclaration; return def;
}, {});
push_array(declaration.body.body, accessors); const slots_str = [...component.slots.keys()].map((key) => `"${key}"`).join(',');
const accessors_str = accessors
body.push(declaration); .filter((accessor) => !writable_props.some((prop) => prop.export_name === accessor.key.name))
.map((accessor) => `"${accessor.key.name}"`)
.join(',');
const use_shadow_dom =
component.component_options.customElement?.shadow !== 'none' ? 'true' : 'false';
if (component.component_options.customElement?.tag) {
body.push(
b`@_customElements.define("${
component.component_options.customElement.tag
}", @create_custom_element(${name}, ${JSON.stringify(
props_str
)}, [${slots_str}], [${accessors_str}], ${use_shadow_dom}));`
);
} else {
body.push(
b`@create_custom_element(${name}, ${JSON.stringify(
props_str
)}, [${slots_str}], [${accessors_str}], ${use_shadow_dom});`
);
}
} }
return { js: flatten(body), css }; return { js: flatten(body), css };

@ -5,27 +5,32 @@ import { Node, Expression } from 'estree';
import Renderer from './Renderer'; import Renderer from './Renderer';
import { Var } from '../../interfaces'; import { Var } from '../../interfaces';
export function invalidate(renderer: Renderer, scope: Scope, node: Node, names: Set<string>, main_execution_context: boolean = false) { export function invalidate(
renderer: Renderer,
scope: Scope,
node: Node,
names: Set<string>,
main_execution_context: boolean = false
) {
const { component } = renderer; const { component } = renderer;
const [head, ...tail] = Array.from(names) const [head, ...tail] = Array.from(names)
.filter(name => { .filter((name) => {
const owner = scope.find_owner(name); const owner = scope.find_owner(name);
return !owner || owner === component.instance_scope; return !owner || owner === component.instance_scope;
}) })
.map(name => component.var_lookup.get(name)) .map((name) => component.var_lookup.get(name))
.filter(variable => { .filter((variable) => {
return variable && ( return (
variable &&
!variable.hoistable && !variable.hoistable &&
!variable.global && !variable.global &&
!variable.module && !variable.module &&
( (variable.referenced ||
variable.referenced ||
variable.subscribable || variable.subscribable ||
variable.is_reactive_dependency || variable.is_reactive_dependency ||
variable.export_name || variable.export_name ||
variable.name[0] === '$' variable.name[0] === '$')
)
); );
}) as Var[]; }) as Var[];
@ -42,12 +47,17 @@ export function invalidate(renderer: Renderer, scope: Scope, node: Node, names:
component.has_reactive_assignments = true; component.has_reactive_assignments = true;
if (node.type === 'AssignmentExpression' && node.operator === '=' && nodes_match(node.left, node.right) && tail.length === 0) { if (
node.type === 'AssignmentExpression' &&
node.operator === '=' &&
nodes_match(node.left, node.right) &&
tail.length === 0
) {
return get_invalidated(head, node); return get_invalidated(head, node);
} }
const is_store_value = head.name[0] === '$' && head.name[1] !== '$'; const is_store_value = head.name[0] === '$' && head.name[1] !== '$';
const extra_args = tail.map(variable => get_invalidated(variable)).filter(Boolean); const extra_args = tail.map((variable) => get_invalidated(variable)).filter(Boolean);
if (is_store_value) { if (is_store_value) {
return x`@set_store_value(${head.name.slice(1)}, ${node}, ${head.name}, ${extra_args})`; return x`@set_store_value(${head.name.slice(1)}, ${node}, ${head.name}, ${extra_args})`;
@ -55,18 +65,19 @@ export function invalidate(renderer: Renderer, scope: Scope, node: Node, names:
let invalidate; let invalidate;
if (!main_execution_context) { if (!main_execution_context) {
const pass_value = ( const pass_value =
extra_args.length > 0 || extra_args.length > 0 ||
(node.type === 'AssignmentExpression' && node.left.type !== 'Identifier') || (node.type === 'AssignmentExpression' && node.left.type !== 'Identifier') ||
(node.type === 'UpdateExpression' && (!node.prefix || node.argument.type !== 'Identifier')) (node.type === 'UpdateExpression' && (!node.prefix || node.argument.type !== 'Identifier'));
);
if (pass_value) { if (pass_value) {
extra_args.unshift({ extra_args.unshift({
type: 'Identifier', type: 'Identifier',
name: head.name name: head.name
}); });
} }
invalidate = x`$$invalidate(${renderer.context_lookup.get(head.name).index}, ${node}, ${extra_args})`; invalidate = x`$$invalidate(${
renderer.context_lookup.get(head.name).index
}, ${node}, ${extra_args})`;
} else { } else {
// skip `$$invalidate` if it is in the main execution context // skip `$$invalidate` if it is in the main execution context
invalidate = extra_args.length ? [node, ...extra_args] : node; invalidate = extra_args.length ? [node, ...extra_args] : node;
@ -80,10 +91,15 @@ export function invalidate(renderer: Renderer, scope: Scope, node: Node, names:
return invalidate; return invalidate;
} }
export function renderer_invalidate(renderer: Renderer, name: string, value?: unknown, main_execution_context: boolean = false) { export function renderer_invalidate(
renderer: Renderer,
name: string,
value?: unknown,
main_execution_context: boolean = false
) {
const variable = renderer.component.var_lookup.get(name); const variable = renderer.component.var_lookup.get(name);
if (variable && (variable.subscribable && (variable.reassigned || variable.export_name))) { if (variable && variable.subscribable && (variable.reassigned || variable.export_name)) {
if (main_execution_context) { if (main_execution_context) {
return x`${`$$subscribe_${name}`}(${value || name})`; return x`${`$$subscribe_${name}`}(${value || name})`;
} else { } else {
@ -97,14 +113,12 @@ export function renderer_invalidate(renderer: Renderer, name: string, value?: un
} }
if ( if (
variable && ( variable &&
variable.module || ( (variable.module ||
!variable.referenced && (!variable.referenced &&
!variable.is_reactive_dependency && !variable.is_reactive_dependency &&
!variable.export_name && !variable.export_name &&
!name.startsWith('$$') !name.startsWith('$$')))
)
)
) { ) {
return value || name; return value || name;
} }
@ -123,22 +137,22 @@ export function renderer_invalidate(renderer: Renderer, name: string, value?: un
// if this is a reactive declaration, invalidate dependencies recursively // if this is a reactive declaration, invalidate dependencies recursively
const deps = new Set([name]); const deps = new Set([name]);
deps.forEach(name => { deps.forEach((name) => {
const reactive_declarations = renderer.component.reactive_declarations.filter(x => const reactive_declarations = renderer.component.reactive_declarations.filter((x) =>
x.assignees.has(name) x.assignees.has(name)
); );
reactive_declarations.forEach(declaration => { reactive_declarations.forEach((declaration) => {
declaration.dependencies.forEach(name => { declaration.dependencies.forEach((name) => {
deps.add(name); deps.add(name);
}); });
}); });
}); });
// TODO ideally globals etc wouldn't be here in the first place // TODO ideally globals etc wouldn't be here in the first place
const filtered = Array.from(deps).filter(n => renderer.context_lookup.has(n)); const filtered = Array.from(deps).filter((n) => renderer.context_lookup.has(n));
if (!filtered.length) return null; if (!filtered.length) return null;
return filtered return filtered
.map(n => x`$$invalidate(${renderer.context_lookup.get(n).index}, ${n})`) .map((n) => x`$$invalidate(${renderer.context_lookup.get(n).index}, ${n})`)
.reduce((lhs, rhs) => x`${lhs}, ${rhs}`); .reduce((lhs, rhs) => x`${lhs}, ${rhs}`);
} }

@ -69,7 +69,7 @@ class AwaitBlockBranch extends Wrapper {
this.value = node.name; this.value = node.name;
this.renderer.add_to_context(this.value, true); this.renderer.add_to_context(this.value, true);
} else { } else {
contexts.forEach(context => { contexts.forEach((context) => {
if (context.type !== 'DestructuredVariable') return; if (context.type !== 'DestructuredVariable') return;
this.renderer.add_to_context(context.key.name, true); this.renderer.add_to_context(context.key.name, true);
}); });
@ -98,17 +98,28 @@ class AwaitBlockBranch extends Wrapper {
} }
render_get_context() { render_get_context() {
const props = this.is_destructured ? this.value_contexts.map(prop => { const props = this.is_destructured
if (prop.type === 'ComputedProperty') { ? this.value_contexts.map((prop) => {
const expression = new Expression(this.renderer.component, this.node, this.has_consts(this.node) ? this.node.scope : null, prop.key); if (prop.type === 'ComputedProperty') {
return b`const ${prop.property_name} = ${expression.manipulate(this.block, '#ctx')};`; const expression = new Expression(
} else { this.renderer.component,
const to_ctx = name => this.renderer.reference(name); this.node,
return b`#ctx[${this.block.renderer.context_lookup.get(prop.key.name).index}] = ${prop.default_modifier(prop.modifier(x`#ctx[${this.value_index}]`), to_ctx)};`; this.has_consts(this.node) ? this.node.scope : null,
} prop.key
}) : null; );
return b`const ${prop.property_name} = ${expression.manipulate(this.block, '#ctx')};`;
} else {
const to_ctx = (name) => this.renderer.reference(name);
return b`#ctx[${
this.block.renderer.context_lookup.get(prop.key.name).index
}] = ${prop.default_modifier(prop.modifier(x`#ctx[${this.value_index}]`), to_ctx)};`;
}
})
: null;
const const_tags_props = this.has_consts(this.node) ? add_const_tags(this.block, this.node.const_tags, '#ctx') : null; const const_tags_props = this.has_consts(this.node)
? add_const_tags(this.block, this.node.const_tags, '#ctx')
: null;
const get_context = this.block.renderer.component.get_unique_name(`get_${this.status}_context`); const get_context = this.block.renderer.component.get_unique_name(`get_${this.status}_context`);
this.block.renderer.blocks.push(b` this.block.renderer.blocks.push(b`
@ -143,9 +154,6 @@ export default class AwaitBlockWrapper extends Wrapper {
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
block.add_dependencies(this.node.expression.dependencies); block.add_dependencies(this.node.expression.dependencies);
let is_dynamic = false; let is_dynamic = false;
@ -179,7 +187,7 @@ export default class AwaitBlockWrapper extends Wrapper {
this[status] = branch; this[status] = branch;
}); });
['pending', 'then', 'catch'].forEach(status => { ['pending', 'then', 'catch'].forEach((status) => {
this[status].block.has_update_method = is_dynamic; this[status].block.has_update_method = is_dynamic;
this[status].block.has_intro_method = has_intros; this[status].block.has_intro_method = has_intros;
this[status].block.has_outro_method = has_outros; this[status].block.has_outro_method = has_outros;
@ -190,11 +198,7 @@ export default class AwaitBlockWrapper extends Wrapper {
} }
} }
render( render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
block: Block,
parent_node: Identifier,
parent_nodes: Identifier
) {
const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes); const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes);
const update_mount_node = this.get_update_mount_node(anchor); const update_mount_node = this.get_update_mount_node(anchor);
@ -241,7 +245,8 @@ export default class AwaitBlockWrapper extends Wrapper {
const initial_mount_node = parent_node || '#target'; const initial_mount_node = parent_node || '#target';
const anchor_node = parent_node ? 'null' : '#anchor'; const anchor_node = parent_node ? 'null' : '#anchor';
const has_transitions = this.pending.block.has_intro_method || this.pending.block.has_outro_method; const has_transitions =
this.pending.block.has_intro_method || this.pending.block.has_outro_method;
block.chunks.mount.push(b` block.chunks.mount.push(b`
${info}.block.m(${initial_mount_node}, ${info}.anchor = ${anchor_node}); ${info}.block.m(${initial_mount_node}, ${info}.anchor = ${anchor_node});
@ -263,9 +268,7 @@ export default class AwaitBlockWrapper extends Wrapper {
${promise} !== (${promise} = ${snippet}) && ${promise} !== (${promise} = ${snippet}) &&
@handle_promise(${promise}, ${info})`; @handle_promise(${promise}, ${info})`;
block.chunks.update.push( block.chunks.update.push(b`${info}.ctx = #ctx;`);
b`${info}.ctx = #ctx;`
);
if (this.pending.block.has_update_method) { if (this.pending.block.has_update_method) {
block.chunks.update.push(b` block.chunks.update.push(b`
@ -303,7 +306,7 @@ export default class AwaitBlockWrapper extends Wrapper {
${info} = null; ${info} = null;
`); `);
[this.pending, this.then, this.catch].forEach(branch => { [this.pending, this.then, this.catch].forEach((branch) => {
branch.render(branch.block, null, x`#nodes` as Identifier); branch.render(branch.block, null, x`#nodes` as Identifier);
}); });
} }

@ -15,7 +15,7 @@ export default class BodyWrapper extends Wrapper {
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: TemplateNode) { constructor(renderer: Renderer, block: Block, parent: Wrapper, node: TemplateNode) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.handlers = this.node.handlers.map(handler => new EventHandler(handler, this)); this.handlers = this.node.handlers.map((handler) => new EventHandler(handler, this));
} }
render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) { render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) {

@ -9,18 +9,13 @@ export default class CommentWrapper extends Wrapper {
node: Comment; node: Comment;
var: Identifier; var: Identifier;
constructor( constructor(renderer: Renderer, block: Block, parent: Wrapper, node: Comment) {
renderer: Renderer,
block: Block,
parent: Wrapper,
node: Comment
) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.var = x`c` as Identifier; this.var = x`c` as Identifier;
} }
render(block: Block, parent_node: Identifier, parent_nodes: Identifier) { render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
if (!this.renderer.options.preserveComments) return; if (!this.renderer.options.preserveComments) return;
const string_literal = { const string_literal = {
type: 'Literal', type: 'Literal',
@ -38,4 +33,10 @@ export default class CommentWrapper extends Wrapper {
parent_node parent_node
); );
} }
text() {
if (!this.renderer.options.preserveComments) return '';
return `<!--${this.node.data}-->`;
}
} }

@ -50,21 +50,23 @@ export default class DebugTagWrapper extends Wrapper {
}; };
const dependencies: Set<string> = new Set(); const dependencies: Set<string> = new Set();
this.node.expressions.forEach(expression => { this.node.expressions.forEach((expression) => {
add_to_set(dependencies, expression.dependencies); add_to_set(dependencies, expression.dependencies);
}); });
const contextual_identifiers = this.node.expressions const contextual_identifiers = this.node.expressions
.filter(e => { .filter((e) => {
const variable = var_lookup.get((e.node as Identifier).name); const variable = var_lookup.get((e.node as Identifier).name);
return !(variable && variable.hoistable); return !(variable && variable.hoistable);
}) })
.map(e => (e.node as Identifier).name); .map((e) => (e.node as Identifier).name);
const logged_identifiers = this.node.expressions.map(e => p`${(e.node as Identifier).name}`); const logged_identifiers = this.node.expressions.map(
(e) => p`${(e.node as Identifier).name}`
);
const debug_statements = b` const debug_statements = b`
${contextual_identifiers.map(name => b`const ${name} = ${renderer.reference(name)};`)} ${contextual_identifiers.map((name) => b`const ${name} = ${renderer.reference(name)};`)}
@_console.${log}({ ${logged_identifiers} }); @_console.${log}({ ${logged_identifiers} });
debugger;`; debugger;`;

@ -14,10 +14,7 @@ const associated_events = {
visibilityState: ['visibilitychange'] visibilityState: ['visibilitychange']
}; };
const readonly = new Set([ const readonly = new Set(['fullscreenElement', 'visibilityState']);
'fullscreenElement',
'visibilityState'
]);
export default class DocumentWrapper extends Wrapper { export default class DocumentWrapper extends Wrapper {
node: Document; node: Document;
@ -25,7 +22,7 @@ export default class DocumentWrapper extends Wrapper {
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: TemplateNode) { constructor(renderer: Renderer, block: Block, parent: Wrapper, node: TemplateNode) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.handlers = this.node.handlers.map(handler => new EventHandler(handler, this)); this.handlers = this.node.handlers.map((handler) => new EventHandler(handler, this));
} }
render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) { render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) {
@ -38,7 +35,7 @@ export default class DocumentWrapper extends Wrapper {
add_event_handlers(block, x`@_document`, this.handlers); add_event_handlers(block, x`@_document`, this.handlers);
add_actions(block, x`@_document`, this.node.actions); add_actions(block, x`@_document`, this.node.actions);
this.node.bindings.forEach(binding => { this.node.bindings.forEach((binding) => {
// TODO: what if it's a MemberExpression? // TODO: what if it's a MemberExpression?
const binding_name = (binding.expression.node as Identifier).name; const binding_name = (binding.expression.node as Identifier).name;
@ -52,7 +49,7 @@ export default class DocumentWrapper extends Wrapper {
const binding_events = associated_events[binding.name]; const binding_events = associated_events[binding.name];
const property = binding.name; const property = binding.name;
binding_events.forEach(associated_event => { binding_events.forEach((associated_event) => {
if (!events[associated_event]) events[associated_event] = []; if (!events[associated_event]) events[associated_event] = [];
events[associated_event].push({ events[associated_event].push({
name: binding_name, name: binding_name,
@ -61,17 +58,15 @@ export default class DocumentWrapper extends Wrapper {
}); });
}); });
Object.keys(events).forEach(event => { Object.keys(events).forEach((event) => {
const id = block.get_unique_name(`ondocument${event}`); const id = block.get_unique_name(`ondocument${event}`);
const props = events[event]; const props = events[event];
renderer.add_to_context(id.name); renderer.add_to_context(id.name);
const fn = renderer.reference(id.name); const fn = renderer.reference(id.name);
props.forEach(prop => { props.forEach((prop) => {
renderer.meta_bindings.push( renderer.meta_bindings.push(b`this._state.${prop.name} = @_document.${prop.value};`);
b`this._state.${prop.name} = @_document.${prop.value};`
);
}); });
block.event_listeners.push(x` block.event_listeners.push(x`
@ -80,7 +75,7 @@ export default class DocumentWrapper extends Wrapper {
component.partly_hoisted.push(b` component.partly_hoisted.push(b`
function ${id}() { function ${id}() {
${props.map(prop => renderer.invalidate(prop.name, x`${prop.name} = @_document.${prop.value}`))} ${props.map((prop) => renderer.invalidate(prop.name, x`${prop.name} = @_document.${prop.value}`))}
} }
`); `);

@ -80,13 +80,11 @@ export default class EachBlockWrapper extends Wrapper {
next_sibling: Wrapper next_sibling: Wrapper
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
const { dependencies } = node.expression; const { dependencies } = node.expression;
block.add_dependencies(dependencies); block.add_dependencies(dependencies);
this.node.contexts.forEach(context => { this.node.contexts.forEach((context) => {
if (context.type !== 'DestructuredVariable') return; if (context.type !== 'DestructuredVariable') return;
renderer.add_to_context(context.key.name, true); renderer.add_to_context(context.key.name, true);
}); });
@ -111,7 +109,7 @@ export default class EachBlockWrapper extends Wrapper {
const fixed_length = const fixed_length =
node.expression.node.type === 'ArrayExpression' && node.expression.node.type === 'ArrayExpression' &&
node.expression.node.elements.every(element => element.type !== 'SpreadElement') node.expression.node.elements.every((element) => element.type !== 'SpreadElement')
? node.expression.node.elements.length ? node.expression.node.elements.length
: null; : null;
@ -146,9 +144,10 @@ export default class EachBlockWrapper extends Wrapper {
}; };
const object = get_object(node.expression.node); const object = get_object(node.expression.node);
const store = object.type === 'Identifier' && object.name[0] === '$' ? object.name.slice(1) : null; const store =
object.type === 'Identifier' && object.name[0] === '$' ? object.name.slice(1) : null;
node.contexts.forEach(prop => { node.contexts.forEach((prop) => {
if (prop.type !== 'DestructuredVariable') return; if (prop.type !== 'DestructuredVariable') return;
this.block.bindings.set(prop.key.name, { this.block.bindings.set(prop.key.name, {
object: this.vars.each_block_value, object: this.vars.each_block_value,
@ -165,7 +164,14 @@ export default class EachBlockWrapper extends Wrapper {
renderer.blocks.push(this.block); renderer.blocks.push(this.block);
this.fragment = new FragmentWrapper(renderer, this.block, node.children, this, strip_whitespace, next_sibling); this.fragment = new FragmentWrapper(
renderer,
this.block,
node.children,
this,
strip_whitespace,
next_sibling
);
if (this.node.else) { if (this.node.else) {
this.else = new ElseBlockWrapper( this.else = new ElseBlockWrapper(
@ -198,8 +204,8 @@ export default class EachBlockWrapper extends Wrapper {
const { component } = renderer; const { component } = renderer;
const needs_anchor = this.next const needs_anchor = this.next
? !this.next.is_dom_node() : ? !this.next.is_dom_node()
!parent_node || !this.parent.is_dom_node(); : !parent_node || !this.parent.is_dom_node();
const snippet = this.node.expression.manipulate(block); const snippet = this.node.expression.manipulate(block);
@ -208,12 +214,17 @@ export default class EachBlockWrapper extends Wrapper {
block.chunks.init.push(b`@validate_each_argument(${this.vars.each_block_value});`); block.chunks.init.push(b`@validate_each_argument(${this.vars.each_block_value});`);
} }
const initial_anchor_node: Identifier = { type: 'Identifier', name: parent_node ? 'null' : '#anchor' }; const initial_anchor_node: Identifier = {
type: 'Identifier',
name: parent_node ? 'null' : '#anchor'
};
const initial_mount_node: Identifier = parent_node || { type: 'Identifier', name: '#target' }; const initial_mount_node: Identifier = parent_node || { type: 'Identifier', name: '#target' };
const update_anchor_node = needs_anchor const update_anchor_node = needs_anchor
? block.get_unique_name(`${this.var.name}_anchor`) ? block.get_unique_name(`${this.var.name}_anchor`)
: (this.next && this.next.var) || { type: 'Identifier', name: 'null' }; : (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 update_mount_node: Identifier = this.get_update_mount_node(
update_anchor_node as Identifier
);
const args = { const args = {
block, block,
@ -304,7 +315,9 @@ export default class EachBlockWrapper extends Wrapper {
} }
`); `);
const has_transitions = !!(this.else.block.has_intro_method || this.else.block.has_outro_method); const has_transitions = !!(
this.else.block.has_intro_method || this.else.block.has_outro_method
);
const destroy_block_else = this.else.block.has_outro_method const destroy_block_else = this.else.block.has_outro_method
? b` ? b`
@ -364,18 +377,34 @@ export default class EachBlockWrapper extends Wrapper {
this.else.fragment.render(this.else.block, null, x`#nodes` as Identifier); this.else.fragment.render(this.else.block, null, x`#nodes` as Identifier);
} }
this.context_props = this.node.contexts.map(prop => { this.context_props = this.node.contexts.map((prop) => {
if (prop.type === 'DestructuredVariable') { if (prop.type === 'DestructuredVariable') {
const to_ctx = (name: string) => renderer.context_lookup.has(name) ? x`child_ctx[${renderer.context_lookup.get(name).index}]` : { type: 'Identifier', name } as Node; const to_ctx = (name: string) =>
return b`child_ctx[${renderer.context_lookup.get(prop.key.name).index}] = ${prop.default_modifier(prop.modifier(x`list[i]`), to_ctx)};`; renderer.context_lookup.has(name)
? x`child_ctx[${renderer.context_lookup.get(name).index}]`
: ({ type: 'Identifier', name } as Node);
return b`child_ctx[${
renderer.context_lookup.get(prop.key.name).index
}] = ${prop.default_modifier(prop.modifier(x`list[i]`), to_ctx)};`;
} else { } else {
const expression = new Expression(this.renderer.component, this.node, this.node.scope, prop.key); const expression = new Expression(
this.renderer.component,
this.node,
this.node.scope,
prop.key
);
return b`const ${prop.property_name} = ${expression.manipulate(block, 'child_ctx')};`; return b`const ${prop.property_name} = ${expression.manipulate(block, 'child_ctx')};`;
} }
}); });
if (this.node.has_binding) this.context_props.push(b`child_ctx[${renderer.context_lookup.get(this.vars.each_block_value.name).index}] = list;`); if (this.node.has_binding)
if (this.node.has_binding || this.node.has_index_binding || this.node.index) this.context_props.push(b`child_ctx[${renderer.context_lookup.get(this.index_name.name).index}] = i;`); this.context_props.push(
b`child_ctx[${renderer.context_lookup.get(this.vars.each_block_value.name).index}] = list;`
);
if (this.node.has_binding || this.node.has_index_binding || this.node.index)
this.context_props.push(
b`child_ctx[${renderer.context_lookup.get(this.index_name.name).index}] = i;`
);
// TODO which is better — Object.create(array) or array.slice()? // TODO which is better — Object.create(array) or array.slice()?
renderer.blocks.push(b` renderer.blocks.push(b`
@ -407,12 +436,7 @@ export default class EachBlockWrapper extends Wrapper {
update_anchor_node: Identifier; update_anchor_node: Identifier;
update_mount_node: Identifier; update_mount_node: Identifier;
}) { }) {
const { const { create_each_block, iterations, data_length, view_length } = this.vars;
create_each_block,
iterations,
data_length,
view_length
} = this.vars;
const get_key = block.get_unique_name('get_key'); const get_key = block.get_unique_name('get_key');
const lookup = block.get_unique_name(`${this.var.name}_lookup`); const lookup = block.get_unique_name(`${this.var.name}_lookup`);
@ -424,18 +448,16 @@ export default class EachBlockWrapper extends Wrapper {
this.block.first = this.fragment.nodes[0].var; this.block.first = this.fragment.nodes[0].var;
} else { } else {
this.block.first = this.block.get_unique_name('first'); this.block.first = this.block.get_unique_name('first');
this.block.add_element( this.block.add_element(this.block.first, x`@empty()`, parent_nodes && x`@empty()`, null);
this.block.first,
x`@empty()`,
parent_nodes && x`@empty()`,
null
);
} }
block.chunks.init.push(b` block.chunks.init.push(b`
const ${get_key} = #ctx => ${this.node.key.manipulate(block)}; const ${get_key} = #ctx => ${this.node.key.manipulate(block)};
${this.renderer.options.dev && b`@validate_each_keys(#ctx, ${this.vars.each_block_value}, ${this.vars.get_each_context}, ${get_key});`} ${
this.renderer.options.dev &&
b`@validate_each_keys(#ctx, ${this.vars.each_block_value}, ${this.vars.get_each_context}, ${get_key});`
}
for (let #i = 0; #i < ${data_length}; #i += 1) { 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 child_ctx = ${this.vars.get_each_context}(#ctx, ${this.vars.each_block_value}, #i);
let key = ${get_key}(child_ctx); let key = ${get_key}(child_ctx);
@ -468,12 +490,12 @@ export default class EachBlockWrapper extends Wrapper {
const dynamic = this.block.has_update_method; const dynamic = this.block.has_update_method;
const destroy = this.node.has_animation const destroy = this.node.has_animation
? (this.block.has_outros ? this.block.has_outros
? '@fix_and_outro_and_destroy_block' ? '@fix_and_outro_and_destroy_block'
: '@fix_and_destroy_block') : '@fix_and_destroy_block'
: this.block.has_outros : this.block.has_outros
? '@outro_and_destroy_block' ? '@outro_and_destroy_block'
: '@destroy_block'; : '@destroy_block';
if (this.dependencies.size) { if (this.dependencies.size) {
this.block.maintain_context = true; this.block.maintain_context = true;
@ -483,10 +505,23 @@ export default class EachBlockWrapper extends Wrapper {
${this.renderer.options.dev && b`@validate_each_argument(${this.vars.each_block_value});`} ${this.renderer.options.dev && b`@validate_each_argument(${this.vars.each_block_value});`}
${this.block.has_outros && b`@group_outros();`} ${this.block.has_outros && b`@group_outros();`}
${this.node.has_animation && b`for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].r();`} ${
${this.renderer.options.dev && b`@validate_each_keys(#ctx, ${this.vars.each_block_value}, ${this.vars.get_each_context}, ${get_key});`} this.node.has_animation &&
${iterations} = @update_keyed_each(${iterations}, #dirty, ${get_key}, ${dynamic ? 1 : 0}, #ctx, ${this.vars.each_block_value}, ${lookup}, ${update_mount_node}, ${destroy}, ${create_each_block}, ${update_anchor_node}, ${this.vars.get_each_context}); b`for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].r();`
${this.node.has_animation && b`for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].a();`} }
${
this.renderer.options.dev &&
b`@validate_each_keys(#ctx, ${this.vars.each_block_value}, ${this.vars.get_each_context}, ${get_key});`
}
${iterations} = @update_keyed_each(${iterations}, #dirty, ${get_key}, ${dynamic ? 1 : 0}, #ctx, ${
this.vars.each_block_value
}, ${lookup}, ${update_mount_node}, ${destroy}, ${create_each_block}, ${update_anchor_node}, ${
this.vars.get_each_context
});
${
this.node.has_animation &&
b`for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].a();`
}
${this.block.has_outros && b`@check_outros();`} ${this.block.has_outros && b`@check_outros();`}
`); `);
} }
@ -523,13 +558,7 @@ export default class EachBlockWrapper extends Wrapper {
update_anchor_node: Identifier; update_anchor_node: Identifier;
update_mount_node: Identifier; update_mount_node: Identifier;
}) { }) {
const { const { create_each_block, iterations, fixed_length, data_length, view_length } = this.vars;
create_each_block,
iterations,
fixed_length,
data_length,
view_length
} = this.vars;
block.chunks.init.push(b` block.chunks.init.push(b`
let ${iterations} = []; let ${iterations} = [];
@ -577,7 +606,7 @@ export default class EachBlockWrapper extends Wrapper {
} }
` `
: has_transitions : has_transitions
? b` ? b`
if (${iterations}[#i]) { if (${iterations}[#i]) {
@transition_in(${this.vars.iterations}[#i], 1); @transition_in(${this.vars.iterations}[#i], 1);
} else { } else {
@ -587,7 +616,7 @@ export default class EachBlockWrapper extends Wrapper {
${iterations}[#i].m(${update_mount_node}, ${update_anchor_node}); ${iterations}[#i].m(${update_mount_node}, ${update_anchor_node});
} }
` `
: b` : b`
if (!${iterations}[#i]) { if (!${iterations}[#i]) {
${iterations}[#i] = ${create_each_block}(child_ctx); ${iterations}[#i] = ${create_each_block}(child_ctx);
${iterations}[#i].c(); ${iterations}[#i].c();
@ -616,7 +645,9 @@ export default class EachBlockWrapper extends Wrapper {
`; `;
} else { } else {
remove_old_blocks = b` 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) { 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); ${iterations}[#i].d(1);
} }
${!fixed_length && b`${view_length} = ${data_length};`} ${!fixed_length && b`${view_length} = ${data_length};`}

@ -36,14 +36,11 @@ export class BaseAttributeWrapper {
this.parent = parent; this.parent = parent;
if (node.dependencies.size > 0) { if (node.dependencies.size > 0) {
parent.cannot_use_innerhtml();
parent.not_static_content();
block.add_dependencies(node.dependencies); block.add_dependencies(node.dependencies);
} }
} }
render(_block: Block) { } render(_block: Block) {}
} }
const regex_minus_sign = /-/; const regex_minus_sign = /-/;
@ -75,7 +72,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
} }
if (select && select.select_binding_dependencies) { if (select && select.select_binding_dependencies) {
select.select_binding_dependencies.forEach(prop => { select.select_binding_dependencies.forEach((prop) => {
this.node.dependencies.forEach((dependency: string) => { this.node.dependencies.forEach((dependency: string) => {
this.parent.renderer.component.indirect_dependencies.get(prop).add(dependency); this.parent.renderer.component.indirect_dependencies.get(prop).add(dependency);
}); });
@ -109,7 +106,9 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
} }
// TODO retire this exception in favour of https://github.com/sveltejs/svelte/issues/3750 // TODO retire this exception in favour of https://github.com/sveltejs/svelte/issues/3750
this.is_src = this.name === 'src' && (!this.parent.node.namespace || this.parent.node.namespace === namespaces.html); this.is_src =
this.name === 'src' &&
(!this.parent.node.namespace || this.parent.node.namespace === namespaces.html);
this.should_cache = should_cache(this); this.should_cache = should_cache(this);
} }
@ -123,10 +122,13 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
const method = regex_minus_sign.test(element.node.name) const method = regex_minus_sign.test(element.node.name)
? '@set_custom_element_data' ? '@set_custom_element_data'
: name.slice(0, 6) === 'xlink:' : name.slice(0, 6) === 'xlink:'
? '@xlink_attr' ? '@xlink_attr'
: '@attr'; : '@attr';
const is_legacy_input_type = element.renderer.component.compile_options.legacy && name === 'type' && this.parent.node.name === 'input'; const is_legacy_input_type =
element.renderer.component.compile_options.legacy &&
name === 'type' &&
this.parent.node.name === 'input';
const dependencies = this.get_dependencies(); const dependencies = this.get_dependencies();
const value = this.get_value(block); const value = this.get_value(block);
@ -135,9 +137,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
const init = this.get_init(block, value); const init = this.get_init(block, value);
if (is_legacy_input_type) { if (is_legacy_input_type) {
block.chunks.hydrate.push( block.chunks.hydrate.push(b`@set_input_type(${element.var}, ${init});`);
b`@set_input_type(${element.var}, ${init});`
);
updater = b`@set_input_type(${element.var}, ${should_cache ? this.last : value});`; updater = b`@set_input_type(${element.var}, ${should_cache ? this.last : value});`;
} else if (this.is_select_value_attribute) { } else if (this.is_select_value_attribute) {
// annoying special case // annoying special case
@ -158,21 +158,17 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
); );
updater = b`${method}(${element.var}, "${name}", ${should_cache ? this.last : value});`; updater = b`${method}(${element.var}, "${name}", ${should_cache ? this.last : value});`;
} else if (property_name) { } else if (property_name) {
block.chunks.hydrate.push( block.chunks.hydrate.push(b`${element.var}.${property_name} = ${init};`);
b`${element.var}.${property_name} = ${init};`
);
updater = block.renderer.options.dev updater = block.renderer.options.dev
? b`@prop_dev(${element.var}, "${property_name}", ${should_cache ? this.last : value});` ? b`@prop_dev(${element.var}, "${property_name}", ${should_cache ? this.last : value});`
: b`${element.var}.${property_name} = ${should_cache ? this.last : value};`; : b`${element.var}.${property_name} = ${should_cache ? this.last : value};`;
} else { } else {
block.chunks.hydrate.push( block.chunks.hydrate.push(b`${method}(${element.var}, "${name}", ${init});`);
b`${method}(${element.var}, "${name}", ${init});`
);
updater = b`${method}(${element.var}, "${name}", ${should_cache ? this.last : value});`; updater = b`${method}(${element.var}, "${name}", ${should_cache ? this.last : value});`;
} }
if (is_indirectly_bound_value) { if (is_indirectly_bound_value) {
const update_value = b`${element.var}.value = ${element.var}.__value;`; const update_value = b`@set_input_value(${element.var}, ${element.var}.__value);`;
block.chunks.hydrate.push(update_value); block.chunks.hydrate.push(update_value);
updater = b` updater = b`
@ -182,7 +178,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
} }
if (this.node.name === 'value' && dependencies.length > 0) { if (this.node.name === 'value' && dependencies.length > 0) {
if (this.parent.bindings.some(binding => binding.node.name === 'group')) { if (this.parent.bindings.some((binding) => binding.node.name === 'group')) {
this.parent.dynamic_value_condition = block.get_unique_name('value_has_changed'); this.parent.dynamic_value_condition = block.get_unique_name('value_has_changed');
block.add_variable(this.parent.dynamic_value_condition, x`false`); block.add_variable(this.parent.dynamic_value_condition, x`false`);
updater = b` updater = b`
@ -211,9 +207,14 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
} }
get_init(block: Block, value) { get_init(block: Block, value) {
this.last = this.should_cache && block.get_unique_name( this.last =
`${this.parent.var.name}_${this.name.replace(regex_invalid_variable_identifier_characters, '_')}_value` this.should_cache &&
); block.get_unique_name(
`${this.parent.var.name}_${this.name.replace(
regex_invalid_variable_identifier_characters,
'_'
)}_value`
);
if (this.should_cache) block.add_variable(this.last); if (this.should_cache) block.add_variable(this.last);
@ -236,7 +237,9 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
if (this.is_input_value) { if (this.is_input_value) {
const type = element.node.get_static_attribute_value('type'); const type = element.node.get_static_attribute_value('type');
if (type !== true && !non_textlike_input_types.has(type)) { if (type !== true && !non_textlike_input_types.has(type)) {
condition = x`${condition} && ${element.var}.${property_name} !== ${should_cache ? last : value}`; condition = x`${condition} && ${element.var}.${property_name} !== ${
should_cache ? last : value
}`;
} }
} }
@ -254,7 +257,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
node_dependencies.forEach((prop: string) => { node_dependencies.forEach((prop: string) => {
const indirect_dependencies = this.parent.renderer.component.indirect_dependencies.get(prop); const indirect_dependencies = this.parent.renderer.component.indirect_dependencies.get(prop);
if (indirect_dependencies) { if (indirect_dependencies) {
indirect_dependencies.forEach(indirect_dependency => { indirect_dependencies.forEach((indirect_dependency) => {
dependencies.add(indirect_dependency); dependencies.add(indirect_dependency);
}); });
} }
@ -266,7 +269,8 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
get_metadata() { get_metadata() {
if (this.parent.node.namespace) return null; if (this.parent.node.namespace) return null;
const metadata = attribute_lookup[this.name]; const metadata = attribute_lookup[this.name];
if (metadata && metadata.applies_to && !metadata.applies_to.includes(this.parent.node.name)) return null; if (metadata && metadata.applies_to && !metadata.applies_to.includes(this.parent.node.name))
return null;
return metadata; return metadata;
} }
@ -287,9 +291,10 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
: (this.node.chunks[0] as Expression).manipulate(block); : (this.node.chunks[0] as Expression).manipulate(block);
} }
let value = this.node.name === 'class' let value =
? this.get_class_name_text(block) this.node.name === 'class'
: this.render_chunks(block).reduce((lhs, rhs) => x`${lhs} + ${rhs}`); ? this.get_class_name_text(block)
: this.render_chunks(block).reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
// '{foo} {bar}' — treat as string concatenation // '{foo} {bar}' — treat as string concatenation
if (this.node.chunks[0].type !== 'Text') { if (this.node.chunks[0].type !== 'Text') {
@ -327,17 +332,21 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
const value = this.node.chunks; const value = this.node.chunks;
if (value.length === 0) return '=""'; if (value.length === 0) return '=""';
return `="${value.map(chunk => { return `="${value
return chunk.type === 'Text' .map((chunk) => {
? chunk.data.replace(regex_double_quotes, '\\"') return chunk.type === 'Text'
: `\${${chunk.manipulate()}}`; ? chunk.data.replace(regex_double_quotes, '\\"')
}).join('')}"`; : `\${${chunk.manipulate()}}`;
})
.join('')}"`;
} }
} }
// source: https://html.spec.whatwg.org/multipage/indices.html // source: https://html.spec.whatwg.org/multipage/indices.html
type AttributeMetadata = { property_name?: string, applies_to?: string[] }; type AttributeMetadata = { property_name?: string; applies_to?: string[] };
const attribute_lookup: { [key in BooleanAttributes]: AttributeMetadata } & { [key in string]: AttributeMetadata } = { const attribute_lookup: { [key in BooleanAttributes]: AttributeMetadata } & {
[key in string]: AttributeMetadata;
} = {
allowfullscreen: { property_name: 'allowFullscreen', applies_to: ['iframe'] }, allowfullscreen: { property_name: 'allowFullscreen', applies_to: ['iframe'] },
allowpaymentrequest: { property_name: 'allowPaymentRequest', applies_to: ['iframe'] }, allowpaymentrequest: { property_name: 'allowPaymentRequest', applies_to: ['iframe'] },
async: { applies_to: ['script'] }, async: { applies_to: ['script'] },
@ -390,7 +399,7 @@ const attribute_lookup: { [key in BooleanAttributes]: AttributeMetadata } & { [k
} }
}; };
Object.keys(attribute_lookup).forEach(name => { Object.keys(attribute_lookup).forEach((name) => {
const metadata = attribute_lookup[name]; const metadata = attribute_lookup[name];
if (!metadata.property_name) metadata.property_name = name; if (!metadata.property_name) metadata.property_name = name;
}); });
@ -403,10 +412,12 @@ const regex_contains_checked_or_group = /checked|group/;
function is_indirectly_bound_value(attribute: AttributeWrapper) { function is_indirectly_bound_value(attribute: AttributeWrapper) {
const element = attribute.parent; const element = attribute.parent;
return attribute.name === 'value' && return (
attribute.name === 'value' &&
(element.node.name === 'option' || // TODO check it's actually bound (element.node.name === 'option' || // TODO check it's actually bound
(element.node.name === 'input' && (element.node.name === 'input' &&
element.node.bindings.some( element.node.bindings.some((binding) =>
(binding) => regex_contains_checked_or_group.test(binding.name) regex_contains_checked_or_group.test(binding.name)
))); )))
);
} }

@ -20,7 +20,7 @@ export default class BindingWrapper {
object: string; object: string;
handler: { handler: {
uses_context: boolean; uses_context: boolean;
mutation: (Node | Node[]); mutation: Node | Node[];
contextual_dependencies: Set<string>; contextual_dependencies: Set<string>;
lhs?: Node; lhs?: Node;
}; };
@ -52,13 +52,19 @@ export default class BindingWrapper {
} }
// view to model // view to model
this.handler = get_event_handler(this, parent.renderer, block, this.object, this.node.raw_expression); this.handler = get_event_handler(
this,
parent.renderer,
block,
this.object,
this.node.raw_expression
);
this.snippet = this.node.expression.manipulate(block); this.snippet = this.node.expression.manipulate(block);
this.is_readonly = this.node.is_readonly; this.is_readonly = this.node.is_readonly;
this.needs_lock = this.node.name === 'currentTime'; // TODO others? this.needs_lock = this.node.name === 'currentTime'; // TODO others?
} }
get_dependencies() { get_dependencies() {
@ -67,14 +73,14 @@ export default class BindingWrapper {
this.node.expression.dependencies.forEach((prop: string) => { this.node.expression.dependencies.forEach((prop: string) => {
const indirect_dependencies = this.parent.renderer.component.indirect_dependencies.get(prop); const indirect_dependencies = this.parent.renderer.component.indirect_dependencies.get(prop);
if (indirect_dependencies) { if (indirect_dependencies) {
indirect_dependencies.forEach(indirect_dependency => { indirect_dependencies.forEach((indirect_dependency) => {
dependencies.add(indirect_dependency); dependencies.add(indirect_dependency);
}); });
} }
}); });
if (this.binding_group) { if (this.binding_group) {
this.binding_group.list_dependencies.forEach(dep => dependencies.add(dep)); this.binding_group.list_dependencies.forEach((dep) => dependencies.add(dep));
} }
return dependencies; return dependencies;
@ -93,9 +99,10 @@ export default class BindingWrapper {
const result = new Set(dependencies); const result = new Set(dependencies);
dependencies.forEach((dependency) => { dependencies.forEach((dependency) => {
const indirect_dependencies = this.parent.renderer.component.indirect_dependencies.get(dependency); const indirect_dependencies =
this.parent.renderer.component.indirect_dependencies.get(dependency);
if (indirect_dependencies) { if (indirect_dependencies) {
indirect_dependencies.forEach(indirect_dependency => { indirect_dependencies.forEach((indirect_dependency) => {
result.add(indirect_dependency); result.add(indirect_dependency);
}); });
} }
@ -135,13 +142,9 @@ export default class BindingWrapper {
type === 'search' || type === 'search' ||
type === 'url' type === 'url'
) { ) {
update_conditions.push( update_conditions.push(x`${parent.var}.${this.node.name} !== ${this.snippet}`);
x`${parent.var}.${this.node.name} !== ${this.snippet}`
);
} else if (type === 'number') { } else if (type === 'number') {
update_conditions.push( update_conditions.push(x`@to_number(${parent.var}.${this.node.name}) !== ${this.snippet}`);
x`@to_number(${parent.var}.${this.node.name}) !== ${this.snippet}`
);
} }
} }
@ -151,8 +154,7 @@ export default class BindingWrapper {
// special cases // special cases
switch (this.node.name) { switch (this.node.name) {
case 'group': case 'group': {
{
block.renderer.add_to_context('$$binding_groups'); block.renderer.add_to_context('$$binding_groups');
this.binding_group.add_element(block, this.parent.var); this.binding_group.add_element(block, this.parent.var);
@ -188,8 +190,7 @@ export default class BindingWrapper {
mount_conditions.push(x`!@_isNaN(${this.snippet})`); mount_conditions.push(x`!@_isNaN(${this.snippet})`);
break; break;
case 'paused': case 'paused': {
{
// this is necessary to prevent audio restarting by itself // this is necessary to prevent audio restarting by itself
const last = block.get_unique_name(`${parent.var.name}_is_paused`); const last = block.get_unique_name(`${parent.var.name}_is_paused`);
block.add_variable(last, x`true`); block.add_variable(last, x`true`);
@ -254,18 +255,20 @@ function get_dom_updater(
} }
if (node.name === 'select') { if (node.name === 'select') {
return node.get_static_attribute_value('multiple') === true ? return node.get_static_attribute_value('multiple') === true
b`@select_options(${element.var}, ${binding.snippet})` : ? b`@select_options(${element.var}, ${binding.snippet})`
mounting ? b`@select_option(${element.var}, ${binding.snippet}, true)` : : mounting
b`@select_option(${element.var}, ${binding.snippet})`; ? b`@select_option(${element.var}, ${binding.snippet}, true)`
: b`@select_option(${element.var}, ${binding.snippet})`;
} }
if (binding.node.name === 'group') { if (binding.node.name === 'group') {
const type = node.get_static_attribute_value('type'); const type = node.get_static_attribute_value('type');
const condition = type === 'checkbox' const condition =
? x`~(${binding.snippet} || []).indexOf(${element.var}.__value)` type === 'checkbox'
: x`${element.var}.__value === ${binding.snippet}`; ? x`~(${binding.snippet} || []).indexOf(${element.var}.__value)`
: x`${element.var}.__value === ${binding.snippet}`;
return b`${element.var}.checked = ${condition};`; return b`${element.var}.checked = ${condition};`;
} }
@ -335,7 +338,7 @@ function get_binding_group(renderer: Renderer, binding: BindingWrapper, block: B
*/ */
const elements = new Map<Block, any>(); const elements = new Map<Block, any>();
contexts.forEach(context => { contexts.forEach((context) => {
renderer.add_to_context(context, true); renderer.add_to_context(context, true);
}); });
@ -344,7 +347,7 @@ function get_binding_group(renderer: Renderer, binding: BindingWrapper, block: B
let obj = x`$$binding_groups[${index}]`; let obj = x`$$binding_groups[${index}]`;
if (contexts.length > 0) { if (contexts.length > 0) {
contexts.forEach(secondary_index => { contexts.forEach((secondary_index) => {
obj = x`${obj}[${secondary_index}]`; obj = x`${obj}[${secondary_index}]`;
}); });
} }
@ -364,24 +367,25 @@ function get_binding_group(renderer: Renderer, binding: BindingWrapper, block: B
const binding_group = block.renderer.reference('$$binding_groups'); const binding_group = block.renderer.reference('$$binding_groups');
block.add_variable(local_name); block.add_variable(local_name);
if (contexts.length > 0) { if (contexts.length > 0) {
const indexes = { type: 'ArrayExpression', elements: contexts.map(name => block.renderer.reference(name)) }; const indexes = {
type: 'ArrayExpression',
elements: contexts.map((name) => block.renderer.reference(name))
};
block.chunks.init.push( block.chunks.init.push(
b`${local_name} = @init_binding_group_dynamic(${binding_group}[${index}], ${indexes})` b`${local_name} = @init_binding_group_dynamic(${binding_group}[${index}], ${indexes})`
); );
block.chunks.update.push( block.chunks.update.push(
b`if (${block.renderer.dirty(Array.from(list_dependencies))}) ${local_name}.u(${indexes})` b`if (${block.renderer.dirty(
Array.from(list_dependencies)
)}) ${local_name}.u(${indexes})`
); );
} else { } else {
block.chunks.init.push( block.chunks.init.push(
b`${local_name} = @init_binding_group(${binding_group}[${index}])` b`${local_name} = @init_binding_group(${binding_group}[${index}])`
); );
} }
block.chunks.hydrate.push( block.chunks.hydrate.push(b`${local_name}.p(${elements.get(block)})`);
b`${local_name}.p(${elements.get(block)})` block.chunks.destroy.push(b`${local_name}.r()`);
);
block.chunks.destroy.push(
b`${local_name}.r()`
);
} }
}); });
} }
@ -401,7 +405,7 @@ function get_event_handler(
lhs: Node lhs: Node
): { ): {
uses_context: boolean; uses_context: boolean;
mutation: (Node | Node[]); mutation: Node | Node[];
contextual_dependencies: Set<string>; contextual_dependencies: Set<string>;
lhs?: Node; lhs?: Node;
} { } {
@ -463,9 +467,9 @@ function get_value_from_dom(
// <select bind:value='selected> // <select bind:value='selected>
if (node.name === 'select') { if (node.name === 'select') {
return node.get_static_attribute_value('multiple') === true ? return node.get_static_attribute_value('multiple') === true
x`@select_multiple_value(this)` : ? x`@select_multiple_value(this)`
x`@select_value(this)`; : x`@select_value(this)`;
} }
const type = node.get_static_attribute_value('type'); const type = node.get_static_attribute_value('type');
@ -486,7 +490,7 @@ function get_value_from_dom(
return x`@to_number(this.${name})`; return x`@to_number(this.${name})`;
} }
if ((name === 'buffered' || name === 'seekable' || name === 'played')) { if (name === 'buffered' || name === 'seekable' || name === 'played') {
return x`@time_ranges_to_array(this.${name})`; return x`@time_ranges_to_array(this.${name})`;
} }

@ -27,7 +27,9 @@ export default class EventHandlerWrapper {
} }
get_snippet(block: Block) { get_snippet(block: Block) {
const snippet = this.node.expression ? this.node.expression.manipulate(block) : block.renderer.reference(this.node.handler_name); const snippet = this.node.expression
? this.node.expression.manipulate(block)
: block.renderer.reference(this.node.handler_name);
if (this.node.reassigned) { if (this.node.reassigned) {
block.maintain_context = true; block.maintain_context = true;
@ -41,22 +43,23 @@ export default class EventHandlerWrapper {
if (this.node.modifiers.has('preventDefault')) snippet = x`@prevent_default(${snippet})`; 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('stopPropagation')) snippet = x`@stop_propagation(${snippet})`;
if (this.node.modifiers.has('stopImmediatePropagation')) snippet = x`@stop_immediate_propagation(${snippet})`; if (this.node.modifiers.has('stopImmediatePropagation'))
snippet = x`@stop_immediate_propagation(${snippet})`;
if (this.node.modifiers.has('self')) snippet = x`@self(${snippet})`; if (this.node.modifiers.has('self')) snippet = x`@self(${snippet})`;
if (this.node.modifiers.has('trusted')) snippet = x`@trusted(${snippet})`; if (this.node.modifiers.has('trusted')) snippet = x`@trusted(${snippet})`;
const args = []; const args = [];
const opts = ['nonpassive', 'passive', 'once', 'capture'].filter(mod => this.node.modifiers.has(mod)); const opts = ['nonpassive', 'passive', 'once', 'capture'].filter((mod) =>
this.node.modifiers.has(mod)
);
if (opts.length) { if (opts.length) {
if (opts.length === 1 && opts[0] === 'capture') { if (opts.length === 1 && opts[0] === 'capture') {
args.push(TRUE); args.push(TRUE);
} else { } else {
args.push(x`{ ${ opts.map(opt => args.push(
opt === 'nonpassive' x`{ ${opts.map((opt) => (opt === 'nonpassive' ? p`passive: false` : p`${opt}: true`))} }`
? p`passive: false` );
: p`${opt}: true`
) } }`);
} }
} else if (block.renderer.options.dev) { } else if (block.renderer.options.dev) {
args.push(FALSE); args.push(FALSE);
@ -68,8 +71,6 @@ export default class EventHandlerWrapper {
args.push(this.node.modifiers.has('stopImmediatePropagation') ? TRUE : FALSE); args.push(this.node.modifiers.has('stopImmediatePropagation') ? TRUE : FALSE);
} }
block.event_listeners.push( block.event_listeners.push(x`@listen(${target}, "${this.node.name}", ${snippet}, ${args})`);
x`@listen(${target}, "${this.node.name}", ${snippet}, ${args})`
);
} }
} }

@ -29,7 +29,7 @@ export default class StyleAttributeWrapper extends AttributeWrapper {
const prop_dependencies: Set<string> = new Set(); const prop_dependencies: Set<string> = new Set();
value = prop.value value = prop.value
.map(chunk => { .map((chunk) => {
if (chunk.type === 'Text') { if (chunk.type === 'Text') {
return string_literal(chunk.data); return string_literal(chunk.data);
} else { } else {

@ -1,8 +1,11 @@
const svg_attributes = 'accent-height accumulate additive alignment-baseline allowReorder alphabetic amplitude arabic-form ascent attributeName attributeType autoReverse azimuth baseFrequency baseline-shift baseProfile bbox begin bias by calcMode cap-height class clip clipPathUnits clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering contentScriptType contentStyleType cursor cx cy d decelerate descent diffuseConstant direction display divisor dominant-baseline dur dx dy edgeMode elevation enable-background end exponent externalResourcesRequired fill fill-opacity fill-rule filter filterRes filterUnits flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight format from fr fx fy g1 g2 glyph-name glyph-orientation-horizontal glyph-orientation-vertical glyphRef gradientTransform gradientUnits hanging height href horiz-adv-x horiz-origin-x id ideographic image-rendering in in2 intercept k k1 k2 k3 k4 kernelMatrix kernelUnitLength kerning keyPoints keySplines keyTimes lang lengthAdjust letter-spacing lighting-color limitingConeAngle local marker-end marker-mid marker-start markerHeight markerUnits markerWidth mask maskContentUnits maskUnits mathematical max media method min mode name numOctaves offset onabort onactivate onbegin onclick onend onerror onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup onrepeat onresize onscroll onunload opacity operator order orient orientation origin overflow overline-position overline-thickness panose-1 paint-order pathLength patternContentUnits patternTransform patternUnits pointer-events points pointsAtX pointsAtY pointsAtZ preserveAlpha preserveAspectRatio primitiveUnits r radius refX refY rendering-intent repeatCount repeatDur requiredExtensions requiredFeatures restart result rotate rx ry scale seed shape-rendering slope spacing specularConstant specularExponent speed spreadMethod startOffset stdDeviation stemh stemv stitchTiles stop-color stop-opacity strikethrough-position strikethrough-thickness string stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style surfaceScale systemLanguage tabindex tableValues target targetX targetY text-anchor text-decoration text-rendering textLength to transform type u1 u2 underline-position underline-thickness unicode unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical values version vert-adv-y vert-origin-x vert-origin-y viewBox viewTarget visibility width widths word-spacing writing-mode x x-height x1 x2 xChannelSelector xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space y y1 y2 yChannelSelector z zoomAndPan'.split(' '); const svg_attributes =
'accent-height accumulate additive alignment-baseline allowReorder alphabetic amplitude arabic-form ascent attributeName attributeType autoReverse azimuth baseFrequency baseline-shift baseProfile bbox begin bias by calcMode cap-height class clip clipPathUnits clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering contentScriptType contentStyleType cursor cx cy d decelerate descent diffuseConstant direction display divisor dominant-baseline dur dx dy edgeMode elevation enable-background end exponent externalResourcesRequired fill fill-opacity fill-rule filter filterRes filterUnits flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight format from fr fx fy g1 g2 glyph-name glyph-orientation-horizontal glyph-orientation-vertical glyphRef gradientTransform gradientUnits hanging height href horiz-adv-x horiz-origin-x id ideographic image-rendering in in2 intercept k k1 k2 k3 k4 kernelMatrix kernelUnitLength kerning keyPoints keySplines keyTimes lang lengthAdjust letter-spacing lighting-color limitingConeAngle local marker-end marker-mid marker-start markerHeight markerUnits markerWidth mask maskContentUnits maskUnits mathematical max media method min mode name numOctaves offset onabort onactivate onbegin onclick onend onerror onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup onrepeat onresize onscroll onunload opacity operator order orient orientation origin overflow overline-position overline-thickness panose-1 paint-order pathLength patternContentUnits patternTransform patternUnits pointer-events points pointsAtX pointsAtY pointsAtZ preserveAlpha preserveAspectRatio primitiveUnits r radius refX refY rendering-intent repeatCount repeatDur requiredExtensions requiredFeatures restart result rotate rx ry scale seed shape-rendering slope spacing specularConstant specularExponent speed spreadMethod startOffset stdDeviation stemh stemv stitchTiles stop-color stop-opacity strikethrough-position strikethrough-thickness string stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style surfaceScale systemLanguage tabindex tableValues target targetX targetY text-anchor text-decoration text-rendering textLength to transform type u1 u2 underline-position underline-thickness unicode unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical values version vert-adv-y vert-origin-x vert-origin-y viewBox viewTarget visibility width widths word-spacing writing-mode x x-height x1 x2 xChannelSelector xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space y y1 y2 yChannelSelector z zoomAndPan'.split(
' '
);
const svg_attribute_lookup = new Map(); const svg_attribute_lookup = new Map();
svg_attributes.forEach(name => { svg_attributes.forEach((name) => {
svg_attribute_lookup.set(name.toLowerCase(), name); svg_attribute_lookup.set(name.toLowerCase(), name);
}); });

File diff suppressed because it is too large Load Diff

@ -51,7 +51,10 @@ function trimmable_at(child: INode, next_sibling: Wrapper): boolean {
// Whitespace is trimmable if one of the following is true: // Whitespace is trimmable if one of the following is true:
// The child and its sibling share a common nearest each block (not at an each block boundary) // The child and its sibling share a common nearest each block (not at an each block boundary)
// The next sibling's previous node is an each block // The next sibling's previous node is an each block
return (next_sibling.node.find_nearest(/EachBlock/) === child.find_nearest(/EachBlock/)) || next_sibling.node.prev.type === 'EachBlock'; return (
next_sibling.node.find_nearest(/EachBlock/) === child.find_nearest(/EachBlock/) ||
next_sibling.node.prev.type === 'EachBlock'
);
} }
export default class FragmentWrapper { export default class FragmentWrapper {
@ -95,9 +98,11 @@ export default class FragmentWrapper {
// We want to remove trailing whitespace inside an element/component/block, // We want to remove trailing whitespace inside an element/component/block,
// *unless* there is no whitespace between this node and its next sibling // *unless* there is no whitespace between this node and its next sibling
if (this.nodes.length === 0) { if (this.nodes.length === 0) {
const should_trim = ( const should_trim = next_sibling
next_sibling ? (next_sibling.node.type === 'Text' && regex_starts_with_whitespace.test(next_sibling.node.data) && trimmable_at(child, next_sibling)) : !child.has_ancestor('EachBlock') ? next_sibling.node.type === 'Text' &&
); regex_starts_with_whitespace.test(next_sibling.node.data) &&
trimmable_at(child, next_sibling)
: !child.has_ancestor('EachBlock');
if (should_trim && !child.keep_space()) { if (should_trim && !child.keep_space()) {
data = trim_end(data); data = trim_end(data);
@ -116,15 +121,22 @@ export default class FragmentWrapper {
this.nodes.unshift(wrapper); this.nodes.unshift(wrapper);
link(last_child, last_child = wrapper); link(last_child, (last_child = wrapper));
} else { } else {
const Wrapper = wrappers[child.type]; const Wrapper = wrappers[child.type];
if (!Wrapper || (child.type === 'Comment' && !renderer.options.preserveComments)) continue; if (!Wrapper || (child.type === 'Comment' && !renderer.options.preserveComments)) continue;
const wrapper = new Wrapper(renderer, block, parent, child, strip_whitespace, last_child || next_sibling); const wrapper = new Wrapper(
renderer,
block,
parent,
child,
strip_whitespace,
last_child || next_sibling
);
this.nodes.unshift(wrapper); this.nodes.unshift(wrapper);
link(last_child, last_child = wrapper); link(last_child, (last_child = wrapper));
} }
} }

@ -20,8 +20,6 @@ export default class HeadWrapper extends Wrapper {
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.can_use_innerhtml = false;
this.fragment = new FragmentWrapper( this.fragment = new FragmentWrapper(
renderer, renderer,
block, block,
@ -36,15 +34,15 @@ export default class HeadWrapper extends Wrapper {
let nodes: Identifier; let nodes: Identifier;
if (this.renderer.options.hydratable && this.fragment.nodes.length) { if (this.renderer.options.hydratable && this.fragment.nodes.length) {
nodes = block.get_unique_name('head_nodes'); nodes = block.get_unique_name('head_nodes');
block.chunks.claim.push(b`const ${nodes} = @head_selector('${this.node.id}', @_document.head);`); block.chunks.claim.push(
b`const ${nodes} = @head_selector('${this.node.id}', @_document.head);`
);
} }
this.fragment.render(block, x`@_document.head` as unknown as Identifier, nodes); this.fragment.render(block, x`@_document.head` as unknown as Identifier, nodes);
if (nodes && this.renderer.options.hydratable) { if (nodes && this.renderer.options.hydratable) {
block.chunks.claim.push( block.chunks.claim.push(b`${nodes}.forEach(@detach);`);
b`${nodes}.forEach(@detach);`
);
} }
} }
} }

@ -16,9 +16,7 @@ import { add_const_tags, add_const_tags_context } from './shared/add_const_tags'
type DetachingOrNull = 'detaching' | null; type DetachingOrNull = 'detaching' | null;
function is_else_if(node: ElseBlock) { function is_else_if(node: ElseBlock) {
return ( return node && node.children.length === 1 && node.children[0].type === 'IfBlock';
node && node.children.length === 1 && node.children[0].type === 'IfBlock'
);
} }
class IfBlockBranch extends Wrapper { class IfBlockBranch extends Wrapper {
@ -43,7 +41,7 @@ class IfBlockBranch extends Wrapper {
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
const { expression } = (node as IfBlock); const { expression } = node as IfBlock;
const is_else = !expression; const is_else = !expression;
if (expression) { if (expression) {
@ -62,7 +60,7 @@ class IfBlockBranch extends Wrapper {
if (should_cache) { if (should_cache) {
this.condition = block.get_unique_name('show_if'); this.condition = block.get_unique_name('show_if');
this.snippet = (expression.manipulate(block) as Node); this.snippet = expression.manipulate(block) as Node;
} else { } else {
this.condition = expression.manipulate(block); this.condition = expression.manipulate(block);
} }
@ -78,12 +76,21 @@ class IfBlockBranch extends Wrapper {
type: (node as IfBlock).expression ? 'if' : 'else' type: (node as IfBlock).expression ? 'if' : 'else'
}); });
this.fragment = new FragmentWrapper(renderer, this.block, node.children, parent, strip_whitespace, next_sibling); this.fragment = new FragmentWrapper(
renderer,
this.block,
node.children,
parent,
strip_whitespace,
next_sibling
);
this.is_dynamic = this.block.dependencies.size > 0; this.is_dynamic = this.block.dependencies.size > 0;
if (node.const_tags.length > 0) { if (node.const_tags.length > 0) {
this.get_ctx_name = parent.renderer.component.get_unique_name(is_else ? 'get_else_ctx' : 'get_if_ctx'); this.get_ctx_name = parent.renderer.component.get_unique_name(
is_else ? 'get_else_ctx' : 'get_if_ctx'
);
} }
} }
} }
@ -105,9 +112,6 @@ export default class IfBlockWrapper extends Wrapper {
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
this.branches = []; this.branches = [];
const blocks: Block[] = []; const blocks: Block[] = [];
@ -116,14 +120,7 @@ export default class IfBlockWrapper extends Wrapper {
let has_outros = false; let has_outros = false;
const create_branches = (node: IfBlock) => { const create_branches = (node: IfBlock) => {
const branch = new IfBlockBranch( const branch = new IfBlockBranch(renderer, block, this, node, strip_whitespace, next_sibling);
renderer,
block,
this,
node,
strip_whitespace,
next_sibling
);
this.branches.push(branch); this.branches.push(branch);
@ -172,7 +169,7 @@ export default class IfBlockWrapper extends Wrapper {
create_branches(this.node); create_branches(this.node);
blocks.forEach(block => { blocks.forEach((block) => {
block.has_update_method = is_dynamic; block.has_update_method = is_dynamic;
block.has_intro_method = has_intros; block.has_intro_method = has_intros;
block.has_outro_method = has_outros; block.has_outro_method = has_outros;
@ -181,19 +178,17 @@ export default class IfBlockWrapper extends Wrapper {
push_array(renderer.blocks, blocks); push_array(renderer.blocks, blocks);
} }
render( render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
block: Block,
parent_node: Identifier,
parent_nodes: Identifier
) {
const name = this.var; const name = this.var;
const needs_anchor = this.next ? !this.next.is_dom_node() : !parent_node || !this.parent.is_dom_node(); const needs_anchor = this.next
? !this.next.is_dom_node()
: !parent_node || !this.parent.is_dom_node();
const anchor = needs_anchor const anchor = needs_anchor
? block.get_unique_name(`${this.var.name}_anchor`) ? block.get_unique_name(`${this.var.name}_anchor`)
: (this.next && this.next.var) || 'null'; : (this.next && this.next.var) || 'null';
const has_else = !(this.branches[this.branches.length - 1].condition); const has_else = !this.branches[this.branches.length - 1].condition;
const if_exists_condition = has_else ? null : 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 dynamic = this.branches[0].block.has_update_method; // can use [0] as proxy for all, since they necessarily have the same value
@ -201,7 +196,7 @@ export default class IfBlockWrapper extends Wrapper {
const has_outros = this.branches[0].block.has_outro_method; const has_outros = this.branches[0].block.has_outro_method;
const has_transitions = has_intros || has_outros; const has_transitions = has_intros || has_outros;
this.branches.forEach(branch => { this.branches.forEach((branch) => {
if (branch.get_ctx_name) { if (branch.get_ctx_name) {
this.renderer.blocks.push(b` this.renderer.blocks.push(b`
function ${branch.get_ctx_name}(#ctx) { function ${branch.get_ctx_name}(#ctx) {
@ -218,12 +213,19 @@ export default class IfBlockWrapper extends Wrapper {
const detaching: DetachingOrNull = parent_node && !is_head(parent_node) ? null : 'detaching'; const detaching: DetachingOrNull = parent_node && !is_head(parent_node) ? null : 'detaching';
if (this.node.else) { if (this.node.else) {
this.branches.forEach(branch => { this.branches.forEach((branch) => {
if (branch.snippet) block.add_variable(branch.condition); if (branch.snippet) block.add_variable(branch.condition);
}); });
if (has_outros) { if (has_outros) {
this.render_compound_with_outros(block, parent_node, parent_nodes, dynamic, vars, detaching); this.render_compound_with_outros(
block,
parent_node,
parent_nodes,
dynamic,
vars,
detaching
);
block.chunks.outro.push(b`@transition_out(${name});`); block.chunks.outro.push(b`@transition_out(${name});`);
} else { } else {
@ -245,13 +247,9 @@ export default class IfBlockWrapper extends Wrapper {
if (parent_nodes && this.renderer.options.hydratable) { if (parent_nodes && this.renderer.options.hydratable) {
if (if_exists_condition) { if (if_exists_condition) {
block.chunks.claim.push( block.chunks.claim.push(b`if (${if_exists_condition}) ${name}.l(${parent_nodes});`);
b`if (${if_exists_condition}) ${name}.l(${parent_nodes});`
);
} else { } else {
block.chunks.claim.push( block.chunks.claim.push(b`${name}.l(${parent_nodes});`);
b`${name}.l(${parent_nodes});`
);
} }
} }
@ -268,7 +266,7 @@ export default class IfBlockWrapper extends Wrapper {
); );
} }
this.branches.forEach(branch => { this.branches.forEach((branch) => {
branch.fragment.render(branch.block, null, x`#nodes` as unknown as Identifier); branch.fragment.render(branch.block, null, x`#nodes` as unknown as Identifier);
}); });
} }
@ -283,8 +281,10 @@ export default class IfBlockWrapper extends Wrapper {
) { ) {
const select_block_type = this.renderer.component.get_unique_name('select_block_type'); 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 = block.get_unique_name('current_block_type');
const need_select_block_ctx = this.branches.some(branch => branch.get_ctx_name); const need_select_block_ctx = this.branches.some((branch) => branch.get_ctx_name);
const select_block_ctx = need_select_block_ctx ? block.get_unique_name('select_block_ctx') : null; const select_block_ctx = need_select_block_ctx
? block.get_unique_name('select_block_ctx')
: null;
const if_ctx = select_block_ctx ? x`${select_block_ctx}(#ctx, ${current_block_type})` : x`#ctx`; const if_ctx = select_block_ctx ? x`${select_block_ctx}(#ctx, ${current_block_type})` : x`#ctx`;
const get_block = has_else const get_block = has_else
@ -295,36 +295,45 @@ export default class IfBlockWrapper extends Wrapper {
block.chunks.init.push(b` block.chunks.init.push(b`
function ${select_block_type}(#ctx, #dirty) { function ${select_block_type}(#ctx, #dirty) {
${this.branches.map(({ dependencies, condition, snippet }) => { ${this.branches.map(({ dependencies, condition, snippet }) => {
return b`${snippet && dependencies.length > 0 ? b`if (${block.renderer.dirty(dependencies)}) ${condition} = null;` : null}`; return b`${
snippet && dependencies.length > 0
? b`if (${block.renderer.dirty(dependencies)}) ${condition} = null;`
: null
}`;
})} })}
${this.branches.map(({ condition, snippet, block }) => condition ${this.branches.map(({ condition, snippet, block }) =>
? b` condition
? b`
${snippet && b`if (${condition} == null) ${condition} = !!${snippet}`} ${snippet && b`if (${condition} == null) ${condition} = !!${snippet}`}
if (${condition}) return ${block.name};` if (${condition}) return ${block.name};`
: b`return ${block.name};` : b`return ${block.name};`
)} )}
} }
`); `);
} else { } else {
block.chunks.init.push(b` block.chunks.init.push(b`
function ${select_block_type}(#ctx, #dirty) { function ${select_block_type}(#ctx, #dirty) {
${this.branches.map(({ condition, snippet, block }) => condition ${this.branches.map(({ condition, snippet, block }) =>
? b`if (${snippet || condition}) return ${block.name};` condition
: b`return ${block.name};`)} ? b`if (${snippet || condition}) return ${block.name};`
: b`return ${block.name};`
)}
} }
`); `);
} }
if (need_select_block_ctx) { if (need_select_block_ctx) {
// if all branches needs create a context // if all branches needs create a context
if (this.branches.every(branch => branch.get_ctx_name)) { if (this.branches.every((branch) => branch.get_ctx_name)) {
block.chunks.init.push(b` block.chunks.init.push(b`
function ${select_block_ctx}(#ctx, #type) { function ${select_block_ctx}(#ctx, #type) {
${this.branches.map(({ condition, get_ctx_name, block }) => { ${this.branches
return condition .map(({ condition, get_ctx_name, block }) => {
? b`if (#type === ${block.name}) return ${get_ctx_name}(#ctx);` return condition
: b`return ${get_ctx_name}(#ctx);`; ? b`if (#type === ${block.name}) return ${get_ctx_name}(#ctx);`
}).filter(Boolean)} : b`return ${get_ctx_name}(#ctx);`;
})
.filter(Boolean)}
} }
`); `);
} else { } else {
@ -332,11 +341,13 @@ export default class IfBlockWrapper extends Wrapper {
// this code is simpler // this code is simpler
block.chunks.init.push(b` block.chunks.init.push(b`
function ${select_block_ctx}(#ctx, #type) { function ${select_block_ctx}(#ctx, #type) {
${this.branches.map(({ get_ctx_name, block }) => { ${this.branches
return get_ctx_name .map(({ get_ctx_name, block }) => {
? b`if (#type === ${block.name}) return ${get_ctx_name}(#ctx);` return get_ctx_name
: null; ? b`if (#type === ${block.name}) return ${get_ctx_name}(#ctx);`
}).filter(Boolean)} : null;
})
.filter(Boolean)}
return #ctx; return #ctx;
} }
`); `);
@ -356,9 +367,7 @@ export default class IfBlockWrapper extends Wrapper {
b`if (${if_exists_condition}) ${name}.m(${initial_mount_node}, ${anchor_node});` b`if (${if_exists_condition}) ${name}.m(${initial_mount_node}, ${anchor_node});`
); );
} else { } else {
block.chunks.mount.push( block.chunks.mount.push(b`${name}.m(${initial_mount_node}, ${anchor_node});`);
b`${name}.m(${initial_mount_node}, ${anchor_node});`
);
} }
if (this.needs_update) { if (this.needs_update) {
@ -425,9 +434,13 @@ export default class IfBlockWrapper extends Wrapper {
const previous_block_index = block.get_unique_name('previous_block_index'); const previous_block_index = block.get_unique_name('previous_block_index');
const if_block_creators = block.get_unique_name('if_block_creators'); const if_block_creators = block.get_unique_name('if_block_creators');
const if_blocks = block.get_unique_name('if_blocks'); const if_blocks = block.get_unique_name('if_blocks');
const need_select_block_ctx = this.branches.some(branch => branch.get_ctx_name); const need_select_block_ctx = this.branches.some((branch) => branch.get_ctx_name);
const select_block_ctx = need_select_block_ctx ? block.get_unique_name('select_block_ctx') : null; const select_block_ctx = need_select_block_ctx
const if_ctx = select_block_ctx ? x`${select_block_ctx}(#ctx, ${current_block_type_index})` : x`#ctx`; ? block.get_unique_name('select_block_ctx')
: null;
const if_ctx = select_block_ctx
? x`${select_block_ctx}(#ctx, ${current_block_type_index})`
: x`#ctx`;
const if_current_block_type_index = has_else const if_current_block_type_index = has_else
? (nodes: Node[]) => nodes ? (nodes: Node[]) => nodes
@ -438,45 +451,55 @@ export default class IfBlockWrapper extends Wrapper {
block.chunks.init.push(b` block.chunks.init.push(b`
const ${if_block_creators} = [ const ${if_block_creators} = [
${this.branches.map(branch => branch.block.name)} ${this.branches.map((branch) => branch.block.name)}
]; ];
const ${if_blocks} = []; const ${if_blocks} = [];
${this.needs_update ${
? b` this.needs_update
? b`
function ${select_block_type}(#ctx, #dirty) { function ${select_block_type}(#ctx, #dirty) {
${this.branches.map(({ dependencies, condition, snippet }) => { ${this.branches.map(({ dependencies, condition, snippet }) => {
return b`${snippet && dependencies.length > 0 ? b`if (${block.renderer.dirty(dependencies)}) ${condition} = null;` : null}`; return b`${
snippet && dependencies.length > 0
? b`if (${block.renderer.dirty(dependencies)}) ${condition} = null;`
: null
}`;
})} })}
${this.branches.map(({ condition, snippet }, i) => condition ${this.branches.map(({ condition, snippet }, i) =>
? b` condition
? b`
${snippet && b`if (${condition} == null) ${condition} = !!${snippet}`} ${snippet && b`if (${condition} == null) ${condition} = !!${snippet}`}
if (${condition}) return ${i};` if (${condition}) return ${i};`
: b`return ${i};`)} : b`return ${i};`
)}
${!has_else && b`return -1;`} ${!has_else && b`return -1;`}
} }
` `
: b` : b`
function ${select_block_type}(#ctx, #dirty) { function ${select_block_type}(#ctx, #dirty) {
${this.branches.map(({ condition, snippet }, i) => condition ${this.branches.map(({ condition, snippet }, i) =>
? b`if (${snippet || condition}) return ${i};` condition ? b`if (${snippet || condition}) return ${i};` : b`return ${i};`
: b`return ${i};`)} )}
${!has_else && b`return -1;`} ${!has_else && b`return -1;`}
} }
`} `
}
`); `);
if (need_select_block_ctx) { if (need_select_block_ctx) {
// if all branches needs create a context // if all branches needs create a context
if (this.branches.every(branch => branch.get_ctx_name)) { if (this.branches.every((branch) => branch.get_ctx_name)) {
block.chunks.init.push(b` block.chunks.init.push(b`
function ${select_block_ctx}(#ctx, #index) { function ${select_block_ctx}(#ctx, #index) {
${this.branches.map(({ condition, get_ctx_name }, i) => { ${this.branches
return condition .map(({ condition, get_ctx_name }, i) => {
? b`if (#index === ${i}) return ${get_ctx_name}(#ctx);` return condition
: b`return ${get_ctx_name}(#ctx);`; ? b`if (#index === ${i}) return ${get_ctx_name}(#ctx);`
}).filter(Boolean)} : b`return ${get_ctx_name}(#ctx);`;
})
.filter(Boolean)}
} }
`); `);
} else { } else {
@ -484,11 +507,11 @@ export default class IfBlockWrapper extends Wrapper {
// this code is simpler // this code is simpler
block.chunks.init.push(b` block.chunks.init.push(b`
function ${select_block_ctx}(#ctx, #index) { function ${select_block_ctx}(#ctx, #index) {
${this.branches.map(({ get_ctx_name }, i) => { ${this.branches
return get_ctx_name .map(({ get_ctx_name }, i) => {
? b`if (#index === ${i}) return ${get_ctx_name}(#ctx);` return get_ctx_name ? b`if (#index === ${i}) return ${get_ctx_name}(#ctx);` : null;
: null; })
}).filter(Boolean)} .filter(Boolean)}
return #ctx; return #ctx;
} }
`); `);
@ -611,9 +634,7 @@ export default class IfBlockWrapper extends Wrapper {
const initial_mount_node = parent_node || '#target'; const initial_mount_node = parent_node || '#target';
const anchor_node = parent_node ? 'null' : '#anchor'; const anchor_node = parent_node ? 'null' : '#anchor';
block.chunks.mount.push( block.chunks.mount.push(b`if (${name}) ${name}.m(${initial_mount_node}, ${anchor_node});`);
b`if (${name}) ${name}.m(${initial_mount_node}, ${anchor_node});`
);
if (branch.dependencies.length > 0) { if (branch.dependencies.length > 0) {
const update_mount_node = this.get_update_mount_node(anchor); const update_mount_node = this.get_update_mount_node(anchor);
@ -621,11 +642,12 @@ export default class IfBlockWrapper extends Wrapper {
const enter = b` const enter = b`
if (${name}) { if (${name}) {
${dynamic && b`${name}.p(${if_ctx}, #dirty);`} ${dynamic && b`${name}.p(${if_ctx}, #dirty);`}
${has_transitions && ${
has_transitions &&
b`if (${block.renderer.dirty(branch.dependencies)}) { b`if (${block.renderer.dirty(branch.dependencies)}) {
@transition_in(${name}, 1); @transition_in(${name}, 1);
}` }`
} }
} else { } else {
${name} = ${branch.block.name}(${if_ctx}); ${name} = ${branch.block.name}(${if_ctx});
${name}.c(); ${name}.c();
@ -635,7 +657,11 @@ export default class IfBlockWrapper extends Wrapper {
`; `;
if (branch.snippet) { if (branch.snippet) {
block.chunks.update.push(b`if (${block.renderer.dirty(branch.dependencies)}) ${branch.condition} = ${branch.snippet}`); block.chunks.update.push(
b`if (${block.renderer.dirty(branch.dependencies)}) ${branch.condition} = ${
branch.snippet
}`
);
} }
// no `p()` here — we don't want to update outroing nodes, // no `p()` here — we don't want to update outroing nodes,

@ -23,7 +23,12 @@ import compiler_warnings from '../../../compiler_warnings';
import { namespaces } from '../../../../utils/namespaces'; import { namespaces } from '../../../../utils/namespaces';
import { extract_ignores_above_node } from '../../../../utils/extract_svelte_ignore'; import { extract_ignores_above_node } from '../../../../utils/extract_svelte_ignore';
type SlotDefinition = { block: Block; scope: TemplateScope; get_context?: Node; get_changes?: Node }; type SlotDefinition = {
block: Block;
scope: TemplateScope;
get_context?: Node;
get_changes?: Node;
};
const regex_invalid_variable_identifier_characters = /[^a-zA-Z_$]/g; const regex_invalid_variable_identifier_characters = /[^a-zA-Z_$]/g;
@ -44,18 +49,15 @@ export default class InlineComponentWrapper extends Wrapper {
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
if (this.node.expression) { if (this.node.expression) {
block.add_dependencies(this.node.expression.dependencies); block.add_dependencies(this.node.expression.dependencies);
} }
this.node.attributes.forEach(attr => { this.node.attributes.forEach((attr) => {
block.add_dependencies(attr.dependencies); block.add_dependencies(attr.dependencies);
}); });
this.node.bindings.forEach(binding => { this.node.bindings.forEach((binding) => {
if (binding.is_contextual) { if (binding.is_contextual) {
mark_each_block_bindings(this, binding); mark_each_block_bindings(this, binding);
} }
@ -63,33 +65,44 @@ export default class InlineComponentWrapper extends Wrapper {
block.add_dependencies(binding.expression.dependencies); block.add_dependencies(binding.expression.dependencies);
}); });
this.node.handlers.forEach(handler => { this.node.handlers.forEach((handler) => {
if (handler.expression) { if (handler.expression) {
block.add_dependencies(handler.expression.dependencies); block.add_dependencies(handler.expression.dependencies);
} }
}); });
this.node.css_custom_properties.forEach(attr => { this.node.css_custom_properties.forEach((attr) => {
block.add_dependencies(attr.dependencies); block.add_dependencies(attr.dependencies);
}); });
this.var = { this.var = {
type: 'Identifier', type: 'Identifier',
name: ( name: (this.node.name === 'svelte:self'
this.node.name === 'svelte:self' ? renderer.component.name.name : ? renderer.component.name.name
this.node.name === 'svelte:component' ? 'switch_instance' : : this.node.name === 'svelte:component'
sanitize(this.node.name) ? 'switch_instance'
: sanitize(this.node.name)
).toLowerCase() ).toLowerCase()
}; };
if (this.node.children.length) { if (this.node.children.length) {
this.node.lets.forEach(l => { this.node.lets.forEach((l) => {
extract_names(l.value || l.name).forEach(name => { extract_names(l.value || l.name).forEach((name) => {
renderer.add_to_context(name, true); renderer.add_to_context(name, true);
}); });
}); });
this.children = this.node.children.map(child => new SlotTemplateWrapper(renderer, block, this, child as SlotTemplate, strip_whitespace, next_sibling)); this.children = this.node.children.map(
(child) =>
new SlotTemplateWrapper(
renderer,
block,
this,
child as SlotTemplate,
strip_whitespace,
next_sibling
)
);
} }
block.add_outro(); block.add_outro();
@ -112,19 +125,15 @@ export default class InlineComponentWrapper extends Wrapper {
return; return;
} }
const ignores = extract_ignores_above_node(this.node); const ignores = extract_ignores_above_node(this.node);
this.renderer.component.push_ignores(ignores); this.renderer.component.push_ignores(ignores);
if (variable.reassigned || variable.export_name || variable.is_reactive_dependency) { if (variable.reassigned || variable.export_name || variable.is_reactive_dependency) {
this.renderer.component.warn(this.node, compiler_warnings.reactive_component(name)); this.renderer.component.warn(this.node, compiler_warnings.reactive_component(name));
} }
this.renderer.component.pop_ignores(); this.renderer.component.pop_ignores();
} }
render( render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
block: Block,
parent_node: Identifier,
parent_nodes: Identifier
) {
this.warn_if_reactive(); this.warn_if_reactive();
const { renderer } = this; const { renderer } = this;
@ -146,7 +155,7 @@ export default class InlineComponentWrapper extends Wrapper {
let props: Identifier | undefined; let props: Identifier | undefined;
const name_changes = block.get_unique_name(`${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 uses_spread = !!this.node.attributes.find((a) => a.is_spread);
// removing empty slot // removing empty slot
for (const slot of this.slots.keys()) { for (const slot of this.slots.keys()) {
@ -159,28 +168,33 @@ export default class InlineComponentWrapper extends Wrapper {
const has_css_custom_properties = this.node.css_custom_properties.length > 0; const has_css_custom_properties = this.node.css_custom_properties.length > 0;
const is_svg_namespace = this.node.namespace === namespaces.svg; const is_svg_namespace = this.node.namespace === namespaces.svg;
const css_custom_properties_wrapper_element = is_svg_namespace ? 'g' : 'div'; const css_custom_properties_wrapper_element = is_svg_namespace ? 'g' : 'div';
const css_custom_properties_wrapper = has_css_custom_properties ? block.get_unique_name(css_custom_properties_wrapper_element) : null; const css_custom_properties_wrapper = has_css_custom_properties
? block.get_unique_name(css_custom_properties_wrapper_element)
: null;
if (has_css_custom_properties) { if (has_css_custom_properties) {
block.add_variable(css_custom_properties_wrapper); block.add_variable(css_custom_properties_wrapper);
} }
const initial_props = this.slots.size > 0 const initial_props =
? [ this.slots.size > 0
p`$$slots: { ? [
p`$$slots: {
${Array.from(this.slots).map(([name, slot]) => { ${Array.from(this.slots).map(([name, slot]) => {
return p`${name}: [${slot.block.name}, ${slot.get_context || null}, ${slot.get_changes || null}]`; return p`${name}: [${slot.block.name}, ${slot.get_context || null}, ${
slot.get_changes || null
}]`;
})} })}
}`, }`,
p`$$scope: { p`$$scope: {
ctx: #ctx ctx: #ctx
}` }`
] ]
: []; : [];
const attribute_object = uses_spread const attribute_object = uses_spread
? x`{ ${initial_props} }` ? x`{ ${initial_props} }`
: x`{ : x`{
${this.node.attributes.map(attr => p`${attr.name}: ${attr.get_value(block)}`)}, ${this.node.attributes.map((attr) => p`${attr.name}: ${attr.get_value(block)}`)},
${initial_props} ${initial_props}
}`; }`;
@ -202,8 +216,8 @@ export default class InlineComponentWrapper extends Wrapper {
} }
const fragment_dependencies = new Set(this.slots.size ? ['$$scope'] : []); const fragment_dependencies = new Set(this.slots.size ? ['$$scope'] : []);
this.slots.forEach(slot => { this.slots.forEach((slot) => {
slot.block.dependencies.forEach(name => { slot.block.dependencies.forEach((name) => {
const is_let = slot.scope.is_let(name); const is_let = slot.scope.is_let(name);
const variable = renderer.component.var_lookup.get(name); const variable = renderer.component.var_lookup.get(name);
@ -211,9 +225,14 @@ export default class InlineComponentWrapper extends Wrapper {
}); });
}); });
const dynamic_attributes = this.node.attributes.filter(a => a.get_dependencies().length > 0); 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 || fragment_dependencies.size > 0)) { if (
!uses_spread &&
(dynamic_attributes.length > 0 ||
this.node.bindings.length > 0 ||
fragment_dependencies.size > 0)
) {
updates.push(b`const ${name_changes} = {};`); updates.push(b`const ${name_changes} = {};`);
} }
@ -226,16 +245,17 @@ export default class InlineComponentWrapper extends Wrapper {
const all_dependencies: Set<string> = new Set(); const all_dependencies: Set<string> = new Set();
this.node.attributes.forEach(attr => { this.node.attributes.forEach((attr) => {
add_to_set(all_dependencies, attr.dependencies); add_to_set(all_dependencies, attr.dependencies);
}); });
this.node.attributes.forEach((attr, i) => { this.node.attributes.forEach((attr, i) => {
const { name, dependencies } = attr; const { name, dependencies } = attr;
const condition = dependencies.size > 0 && (dependencies.size !== all_dependencies.size) const condition =
? renderer.dirty(Array.from(dependencies)) dependencies.size > 0 && dependencies.size !== all_dependencies.size
: null; ? renderer.dirty(Array.from(dependencies))
: null;
const unchanged = dependencies.size === 0; const unchanged = dependencies.size === 0;
let change_object: Node | ReturnType<typeof x>; let change_object: Node | ReturnType<typeof x>;
@ -309,7 +329,7 @@ export default class InlineComponentWrapper extends Wrapper {
}`); }`);
} }
const munged_bindings = this.node.bindings.map(binding => { const munged_bindings = this.node.bindings.map((binding) => {
component.has_reactive_assignments = true; component.has_reactive_assignments = true;
if (binding.name === 'this') { if (binding.name === 'this') {
@ -328,8 +348,7 @@ export default class InlineComponentWrapper extends Wrapper {
statements.push(b` statements.push(b`
if (${snippet} !== void 0) { if (${snippet} !== void 0) {
${props}.${binding.name} = ${snippet}; ${props}.${binding.name} = ${snippet};
}` }`);
);
updates.push(b` updates.push(b`
if (!${updating} && ${renderer.dirty(Array.from(binding.expression.dependencies))}) { if (!${updating} && ${renderer.dirty(Array.from(binding.expression.dependencies))}) {
@ -356,8 +375,7 @@ export default class InlineComponentWrapper extends Wrapper {
const params: Identifier[] = [x`#value` as Identifier]; const params: Identifier[] = [x`#value` as Identifier];
const args = [x`#value`]; const args = [x`#value`];
if (contextual_dependencies.length > 0) { if (contextual_dependencies.length > 0) {
contextual_dependencies.forEach((name) => {
contextual_dependencies.forEach(name => {
params.push({ params.push({
type: 'Identifier', type: 'Identifier',
name name
@ -367,7 +385,6 @@ export default class InlineComponentWrapper extends Wrapper {
args.push(renderer.reference(name)); args.push(renderer.reference(name));
}); });
block.maintain_context = true; // TODO put this somewhere more logical block.maintain_context = true; // TODO put this somewhere more logical
} }
@ -400,7 +417,7 @@ export default class InlineComponentWrapper extends Wrapper {
return b`@binding_callbacks.push(() => @bind(${this.var}, '${binding.name}', ${id}));`; return b`@binding_callbacks.push(() => @bind(${this.var}, '${binding.name}', ${id}));`;
}); });
const munged_handlers = this.node.handlers.map(handler => { const munged_handlers = this.node.handlers.map((handler) => {
const event_handler = new EventHandler(handler, this); const event_handler = new EventHandler(handler, this);
let snippet = event_handler.get_snippet(block); let snippet = event_handler.get_snippet(block);
if (handler.modifiers.has('once')) snippet = x`@once(${snippet})`; if (handler.modifiers.has('once')) snippet = x`@once(${snippet})`;
@ -408,8 +425,10 @@ export default class InlineComponentWrapper extends Wrapper {
return b`${name}.$on("${handler.name}", ${snippet});`; return b`${name}.$on("${handler.name}", ${snippet});`;
}); });
const mount_target = has_css_custom_properties ? css_custom_properties_wrapper : (parent_node || '#target'); const mount_target = has_css_custom_properties
const mount_anchor = has_css_custom_properties ? 'null' : (parent_node ? 'null' : '#anchor'); ? css_custom_properties_wrapper
: parent_node || '#target';
const mount_anchor = has_css_custom_properties ? 'null' : parent_node ? 'null' : '#anchor';
const to_claim = parent_nodes && this.renderer.options.hydratable; const to_claim = parent_nodes && this.renderer.options.hydratable;
let claim_nodes = parent_nodes; let claim_nodes = parent_nodes;
@ -421,15 +440,23 @@ export default class InlineComponentWrapper extends Wrapper {
const dependencies = this.node.expression.dynamic_dependencies(); const dependencies = this.node.expression.dynamic_dependencies();
if (has_css_custom_properties) { if (has_css_custom_properties) {
this.set_css_custom_properties(block, css_custom_properties_wrapper, css_custom_properties_wrapper_element, is_svg_namespace); this.set_css_custom_properties(
block,
css_custom_properties_wrapper,
css_custom_properties_wrapper_element,
is_svg_namespace
);
} }
block.chunks.init.push(b` block.chunks.init.push(b`
var ${switch_value} = ${snippet}; var ${switch_value} = ${snippet};
function ${switch_props}(#ctx) { function ${switch_props}(#ctx) {
${(this.node.attributes.length > 0 || this.node.bindings.length > 0) && b` ${
${props && b`let ${props} = ${attribute_object};`}`} (this.node.attributes.length > 0 || this.node.bindings.length > 0) &&
b`
${props && b`let ${props} = ${attribute_object};`}`
}
${statements} ${statements}
return ${component_opts}; return ${component_opts};
} }
@ -442,16 +469,30 @@ export default class InlineComponentWrapper extends Wrapper {
} }
`); `);
block.chunks.create.push( block.chunks.create.push(b`if (${name}) @create_component(${name}.$$.fragment);`);
b`if (${name}) @create_component(${name}.$$.fragment);`
);
if (css_custom_properties_wrapper) this.create_css_custom_properties_wrapper_mount_chunk(block, parent_node, css_custom_properties_wrapper); if (css_custom_properties_wrapper)
block.chunks.mount.push(b`if (${name}) @mount_component(${name}, ${mount_target}, ${mount_anchor});`); this.create_css_custom_properties_wrapper_mount_chunk(
block,
parent_node,
css_custom_properties_wrapper
);
block.chunks.mount.push(
b`if (${name}) @mount_component(${name}, ${mount_target}, ${mount_anchor});`
);
if (to_claim) { if (to_claim) {
if (css_custom_properties_wrapper) claim_nodes = this.create_css_custom_properties_wrapper_claim_chunk(block, claim_nodes, css_custom_properties_wrapper, css_custom_properties_wrapper_element, is_svg_namespace); if (css_custom_properties_wrapper)
block.chunks.claim.push(b`if (${name}) @claim_component(${name}.$$.fragment, ${claim_nodes});`); claim_nodes = this.create_css_custom_properties_wrapper_claim_chunk(
block,
claim_nodes,
css_custom_properties_wrapper,
css_custom_properties_wrapper_element,
is_svg_namespace
);
block.chunks.claim.push(
b`if (${name}) @claim_component(${name}.$$.fragment, ${claim_nodes});`
);
} }
if (updates.length) { if (updates.length) {
@ -462,7 +503,9 @@ export default class InlineComponentWrapper extends Wrapper {
const tmp_anchor = this.get_or_create_anchor(block, parent_node, parent_nodes); const tmp_anchor = this.get_or_create_anchor(block, parent_node, parent_nodes);
const anchor = has_css_custom_properties ? 'null' : tmp_anchor; const anchor = has_css_custom_properties ? 'null' : tmp_anchor;
const update_mount_node = has_css_custom_properties ? css_custom_properties_wrapper : this.get_update_mount_node(tmp_anchor); const update_mount_node = has_css_custom_properties
? css_custom_properties_wrapper
: this.get_update_mount_node(tmp_anchor);
const update_insert = const update_insert =
css_custom_properties_wrapper && css_custom_properties_wrapper &&
(tmp_anchor.name !== 'null' (tmp_anchor.name !== 'null'
@ -508,19 +551,23 @@ export default class InlineComponentWrapper extends Wrapper {
if (${name}) @transition_in(${name}.$$.fragment, #local); if (${name}) @transition_in(${name}.$$.fragment, #local);
`); `);
block.chunks.outro.push( block.chunks.outro.push(b`if (${name}) @transition_out(${name}.$$.fragment, #local);`);
b`if (${name}) @transition_out(${name}.$$.fragment, #local);`
);
block.chunks.destroy.push(b`if (${name}) @destroy_component(${name}, ${parent_node ? null : 'detaching'});`); block.chunks.destroy.push(
b`if (${name}) @destroy_component(${name}, ${parent_node ? null : 'detaching'});`
);
} else { } else {
const expression = this.node.name === 'svelte:self' const expression =
? component.name this.node.name === 'svelte:self'
: this.renderer.reference(string_to_member_expression(this.node.name)); ? component.name
: this.renderer.reference(string_to_member_expression(this.node.name));
block.chunks.init.push(b` block.chunks.init.push(b`
${(this.node.attributes.length > 0 || this.node.bindings.length > 0) && b` ${
${props && b`let ${props} = ${attribute_object};`}`} (this.node.attributes.length > 0 || this.node.bindings.length > 0) &&
b`
${props && b`let ${props} = ${attribute_object};`}`
}
${statements} ${statements}
${name} = new ${expression}(${component_opts}); ${name} = new ${expression}(${component_opts});
@ -529,15 +576,32 @@ export default class InlineComponentWrapper extends Wrapper {
`); `);
if (has_css_custom_properties) { if (has_css_custom_properties) {
this.set_css_custom_properties(block, css_custom_properties_wrapper, css_custom_properties_wrapper_element, is_svg_namespace); this.set_css_custom_properties(
block,
css_custom_properties_wrapper,
css_custom_properties_wrapper_element,
is_svg_namespace
);
} }
block.chunks.create.push(b`@create_component(${name}.$$.fragment);`); block.chunks.create.push(b`@create_component(${name}.$$.fragment);`);
if (css_custom_properties_wrapper) this.create_css_custom_properties_wrapper_mount_chunk(block, parent_node, css_custom_properties_wrapper); if (css_custom_properties_wrapper)
this.create_css_custom_properties_wrapper_mount_chunk(
block,
parent_node,
css_custom_properties_wrapper
);
block.chunks.mount.push(b`@mount_component(${name}, ${mount_target}, ${mount_anchor});`); block.chunks.mount.push(b`@mount_component(${name}, ${mount_target}, ${mount_anchor});`);
if (to_claim) { if (to_claim) {
if (css_custom_properties_wrapper) claim_nodes = this.create_css_custom_properties_wrapper_claim_chunk(block, claim_nodes, css_custom_properties_wrapper, css_custom_properties_wrapper_element, is_svg_namespace); if (css_custom_properties_wrapper)
claim_nodes = this.create_css_custom_properties_wrapper_claim_chunk(
block,
claim_nodes,
css_custom_properties_wrapper,
css_custom_properties_wrapper_element,
is_svg_namespace
);
block.chunks.claim.push(b`@claim_component(${name}.$$.fragment, ${claim_nodes});`); block.chunks.claim.push(b`@claim_component(${name}.$$.fragment, ${claim_nodes});`);
} }
@ -556,9 +620,7 @@ export default class InlineComponentWrapper extends Wrapper {
@destroy_component(${name}, ${parent_node ? null : 'detaching'}); @destroy_component(${name}, ${parent_node ? null : 'detaching'});
`); `);
block.chunks.outro.push( block.chunks.outro.push(b`@transition_out(${name}.$$.fragment, #local);`);
b`@transition_out(${name}.$$.fragment, #local);`
);
} }
} }
@ -567,17 +629,19 @@ export default class InlineComponentWrapper extends Wrapper {
parent_node: Identifier, parent_node: Identifier,
css_custom_properties_wrapper: Identifier | null css_custom_properties_wrapper: Identifier | null
) { ) {
if (parent_node) { if (parent_node) {
block.chunks.mount.push(b`@append(${parent_node}, ${css_custom_properties_wrapper})`); block.chunks.mount.push(b`@append(${parent_node}, ${css_custom_properties_wrapper})`);
if (is_head(parent_node)) { if (is_head(parent_node)) {
block.chunks.destroy.push(b`@detach(${css_custom_properties_wrapper});`); block.chunks.destroy.push(b`@detach(${css_custom_properties_wrapper});`);
}
} else {
block.chunks.mount.push(b`@insert(#target, ${css_custom_properties_wrapper}, #anchor);`);
// TODO we eventually need to consider what happens to elements
// that belong to the same outgroup as an outroing element...
block.chunks.destroy.push(b`if (detaching && ${this.var}) @detach(${css_custom_properties_wrapper});`);
} }
} else {
block.chunks.mount.push(b`@insert(#target, ${css_custom_properties_wrapper}, #anchor);`);
// TODO we eventually need to consider what happens to elements
// that belong to the same outgroup as an outroing element...
block.chunks.destroy.push(
b`if (detaching && ${this.var}) @detach(${css_custom_properties_wrapper});`
);
}
} }
private create_css_custom_properties_wrapper_claim_chunk( private create_css_custom_properties_wrapper_claim_chunk(
@ -603,12 +667,21 @@ export default class InlineComponentWrapper extends Wrapper {
is_svg_namespace: boolean is_svg_namespace: boolean
) { ) {
const element = is_svg_namespace ? x`@svg_element` : x`@element`; const element = is_svg_namespace ? x`@svg_element` : x`@element`;
block.chunks.create.push(b`${css_custom_properties_wrapper} = ${element}("${css_custom_properties_wrapper_element}");`); block.chunks.create.push(
if (!is_svg_namespace) block.chunks.hydrate.push(b`@set_style(${css_custom_properties_wrapper}, "display", "contents");`); b`${css_custom_properties_wrapper} = ${element}("${css_custom_properties_wrapper_element}");`
);
if (!is_svg_namespace)
block.chunks.hydrate.push(
b`@set_style(${css_custom_properties_wrapper}, "display", "contents");`
);
this.node.css_custom_properties.forEach((attr) => { this.node.css_custom_properties.forEach((attr) => {
const dependencies = attr.get_dependencies(); const dependencies = attr.get_dependencies();
const should_cache = attr.should_cache(); const should_cache = attr.should_cache();
const last = should_cache && block.get_unique_name(`${attr.name.replace(regex_invalid_variable_identifier_characters, '_')}_last`); const last =
should_cache &&
block.get_unique_name(
`${attr.name.replace(regex_invalid_variable_identifier_characters, '_')}_last`
);
if (should_cache) block.add_variable(last); if (should_cache) block.add_variable(last);
const value = attr.get_value(block); const value = attr.get_value(block);
const init = should_cache ? x`${last} = ${value}` : value; const init = should_cache ? x`${last} = ${value}` : value;

@ -24,9 +24,6 @@ export default class KeyBlockWrapper extends Wrapper {
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
this.dependencies = node.expression.dynamic_dependencies(); this.dependencies = node.expression.dynamic_dependencies();
if (this.dependencies.length) { if (this.dependencies.length) {
@ -63,23 +60,21 @@ export default class KeyBlockWrapper extends Wrapper {
} }
render_dynamic_key(block: Block, parent_node: Identifier, parent_nodes: Identifier) { render_dynamic_key(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
this.fragment.render( this.fragment.render(this.block, null, x`#nodes` as unknown as Identifier);
this.block,
null,
(x`#nodes` as unknown) as Identifier
);
const has_transitions = !!( const has_transitions = !!(this.block.has_intro_method || this.block.has_outro_method);
this.block.has_intro_method || this.block.has_outro_method
);
const dynamic = this.block.has_update_method; const dynamic = this.block.has_update_method;
const previous_key = block.get_unique_name('previous_key'); const previous_key = block.get_unique_name('previous_key');
const snippet = this.node.expression.manipulate(block); const snippet = this.node.expression.manipulate(block);
block.add_variable(previous_key, snippet); block.add_variable(previous_key, snippet);
const not_equal = this.renderer.component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`; const not_equal = this.renderer.component.component_options.immutable
const condition = x`${this.renderer.dirty(this.dependencies)} && ${not_equal}(${previous_key}, ${previous_key} = ${snippet})`; ? x`@not_equal`
: x`@safe_not_equal`;
const condition = x`${this.renderer.dirty(
this.dependencies
)} && ${not_equal}(${previous_key}, ${previous_key} = ${snippet})`;
block.chunks.init.push(b` block.chunks.init.push(b`
let ${this.var} = ${this.block.name}(#ctx); let ${this.var} = ${this.block.name}(#ctx);
@ -89,9 +84,7 @@ export default class KeyBlockWrapper extends Wrapper {
block.chunks.claim.push(b`${this.var}.l(${parent_nodes});`); block.chunks.claim.push(b`${this.var}.l(${parent_nodes});`);
} }
block.chunks.mount.push( block.chunks.mount.push(
b`${this.var}.m(${parent_node || '#target'}, ${ b`${this.var}.m(${parent_node || '#target'}, ${parent_node ? 'null' : '#anchor'});`
parent_node ? 'null' : '#anchor'
});`
); );
const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes); const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes);
const body = b` const body = b`

@ -12,11 +12,21 @@ import AttributeWrapper from './Element/Attribute';
export default class MustacheTagWrapper extends Tag { export default class MustacheTagWrapper extends Tag {
var: Identifier = { type: 'Identifier', name: 't' }; var: Identifier = { type: 'Identifier', name: 't' };
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: MustacheTag | RawMustacheTag) { constructor(
renderer: Renderer,
block: Block,
parent: Wrapper,
node: MustacheTag | RawMustacheTag
) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
} }
render(block: Block, parent_node: Identifier, parent_nodes: Identifier, data: Record<string, unknown> | undefined) { render(
block: Block,
parent_node: Identifier,
parent_nodes: Identifier,
data: Record<string, unknown> | undefined
) {
const contenteditable_attributes = const contenteditable_attributes =
this.parent instanceof ElementWrapper && this.parent instanceof ElementWrapper &&
this.parent.attributes.filter((a) => a.node.name === 'contenteditable'); this.parent.attributes.filter((a) => a.node.name === 'contenteditable');
@ -33,24 +43,21 @@ export default class MustacheTagWrapper extends Tag {
} else { } else {
contenteditable_attr_value = x`${attribute.get_value(block)}`; contenteditable_attr_value = x`${attribute.get_value(block)}`;
} }
} else if (spread_attributes.length > 0 && data.element_data_name) { } else if (spread_attributes.length > 0 && data.element_data_name) {
contenteditable_attr_value = x`${data.element_data_name}['contenteditable']`; contenteditable_attr_value = x`${data.element_data_name}['contenteditable']`;
} }
const { init } = this.rename_this_method( const { init } = this.rename_this_method(block, (value) => {
block, if (contenteditable_attr_value) {
value => { if (contenteditable_attr_value === true) {
if (contenteditable_attr_value) { return x`@set_data_contenteditable(${this.var}, ${value})`;
if (contenteditable_attr_value === true) {
return x`@set_data_contenteditable(${this.var}, ${value})`;
} else {
return x`@set_data_maybe_contenteditable(${this.var}, ${value}, ${contenteditable_attr_value})`;
}
} else { } else {
return x`@set_data(${this.var}, ${value})`; return x`@set_data_maybe_contenteditable(${this.var}, ${value}, ${contenteditable_attr_value})`;
} }
} else {
return x`@set_data(${this.var}, ${value})`;
} }
); });
block.add_element( block.add_element(
this.var, this.var,

@ -20,8 +20,6 @@ export default class RawMustacheTagWrapper extends Tag {
node: MustacheTag | RawMustacheTag node: MustacheTag | RawMustacheTag
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
} }
render(block: Block, parent_node: Identifier, _parent_nodes: Identifier) { render(block: Block, parent_node: Identifier, _parent_nodes: Identifier) {
@ -32,24 +30,20 @@ export default class RawMustacheTagWrapper extends Tag {
if (can_use_innerhtml) { if (can_use_innerhtml) {
const insert = (content: Node) => b`${parent_node}.innerHTML = ${content};`[0]; const insert = (content: Node) => b`${parent_node}.innerHTML = ${content};`[0];
const { init } = this.rename_this_method( const { init } = this.rename_this_method(block, (content) => insert(content));
block,
content => insert(content)
);
block.chunks.mount.push(insert(init)); block.chunks.mount.push(insert(init));
} else { } else {
const needs_anchor = in_head || (this.next ? !this.next.is_dom_node() : (!this.parent || !this.parent.is_dom_node())); const needs_anchor =
in_head ||
(this.next ? !this.next.is_dom_node() : !this.parent || !this.parent.is_dom_node());
const html_tag = block.get_unique_name('html_tag'); const html_tag = block.get_unique_name('html_tag');
const html_anchor = needs_anchor && block.get_unique_name('html_anchor'); const html_anchor = needs_anchor && block.get_unique_name('html_anchor');
block.add_variable(html_tag); block.add_variable(html_tag);
const { init } = this.rename_this_method( const { init } = this.rename_this_method(block, (content) => x`${html_tag}.p(${content})`);
block,
content => x`${html_tag}.p(${content})`
);
const update_anchor = needs_anchor ? html_anchor : this.next ? this.next.var : 'null'; const update_anchor = needs_anchor ? html_anchor : this.next ? this.next.var : 'null';
@ -58,10 +52,14 @@ export default class RawMustacheTagWrapper extends Tag {
block.chunks.create.push(b`${html_tag} = new @HtmlTag(${is_svg ? 'true' : 'false'});`); block.chunks.create.push(b`${html_tag} = new @HtmlTag(${is_svg ? 'true' : 'false'});`);
if (this.renderer.options.hydratable) { if (this.renderer.options.hydratable) {
block.chunks.claim.push(b`${html_tag} = @claim_html_tag(${_parent_nodes}, ${is_svg ? 'true' : 'false'});`); block.chunks.claim.push(
b`${html_tag} = @claim_html_tag(${_parent_nodes}, ${is_svg ? 'true' : 'false'});`
);
} }
block.chunks.hydrate.push(b`${html_tag}.a = ${update_anchor};`); block.chunks.hydrate.push(b`${html_tag}.a = ${update_anchor};`);
block.chunks.mount.push(b`${html_tag}.m(${init}, ${parent_node || '#target'}, ${parent_node ? null : '#anchor'});`); block.chunks.mount.push(
b`${html_tag}.m(${init}, ${parent_node || '#target'}, ${parent_node ? null : '#anchor'});`
);
if (needs_anchor) { if (needs_anchor) {
block.add_element(html_anchor, x`@empty()`, x`@empty()`, parent_node); block.add_element(html_anchor, x`@empty()`, x`@empty()`, parent_node);

@ -30,8 +30,6 @@ export default class SlotWrapper extends Wrapper {
next_sibling: Wrapper next_sibling: Wrapper
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
if (this.node.children.length) { if (this.node.children.length) {
this.fallback = block.child({ this.fallback = block.child({
@ -51,7 +49,7 @@ export default class SlotWrapper extends Wrapper {
next_sibling next_sibling
); );
this.node.values.forEach(attribute => { this.node.values.forEach((attribute) => {
add_to_set(this.dependencies, attribute.dependencies); add_to_set(this.dependencies, attribute.dependencies);
}); });
@ -62,11 +60,7 @@ export default class SlotWrapper extends Wrapper {
block.add_outro(); block.add_outro();
} }
render( render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
block: Block,
parent_node: Identifier,
parent_nodes: Identifier
) {
const { renderer } = this; const { renderer } = this;
const { slot_name } = this.node; const { slot_name } = this.node;
@ -80,18 +74,27 @@ export default class SlotWrapper extends Wrapper {
let get_slot_context_fn: Identifier | 'null'; let get_slot_context_fn: Identifier | 'null';
if (this.node.values.size > 0) { if (this.node.values.size > 0) {
get_slot_changes_fn = renderer.component.get_unique_name(`get_${sanitize(slot_name)}_slot_changes`); get_slot_changes_fn = renderer.component.get_unique_name(
get_slot_context_fn = renderer.component.get_unique_name(`get_${sanitize(slot_name)}_slot_context`); `get_${sanitize(slot_name)}_slot_changes`
);
get_slot_context_fn = renderer.component.get_unique_name(
`get_${sanitize(slot_name)}_slot_context`
);
const changes = x`{}` as ObjectExpression; const changes = x`{}` as ObjectExpression;
const spread_dynamic_dependencies = new Set<string>(); const spread_dynamic_dependencies = new Set<string>();
this.node.values.forEach(attribute => { this.node.values.forEach((attribute) => {
if (attribute.type === 'Spread') { if (attribute.type === 'Spread') {
add_to_set(spread_dynamic_dependencies, Array.from(attribute.dependencies).filter((name) => this.is_dependency_dynamic(name))); add_to_set(
spread_dynamic_dependencies,
Array.from(attribute.dependencies).filter((name) => this.is_dependency_dynamic(name))
);
} else { } else {
const dynamic_dependencies = Array.from(attribute.dependencies).filter((name) => this.is_dependency_dynamic(name)); const dynamic_dependencies = Array.from(attribute.dependencies).filter((name) =>
this.is_dependency_dynamic(name)
);
if (dynamic_dependencies.length > 0) { if (dynamic_dependencies.length > 0) {
changes.properties.push(p`${attribute.name}: ${renderer.dirty(dynamic_dependencies)}`); changes.properties.push(p`${attribute.name}: ${renderer.dirty(dynamic_dependencies)}`);
@ -105,9 +108,13 @@ export default class SlotWrapper extends Wrapper {
`); `);
if (spread_dynamic_dependencies.size) { if (spread_dynamic_dependencies.size) {
get_slot_spread_changes_fn = renderer.component.get_unique_name(`get_${sanitize(slot_name)}_slot_spread_changes`); get_slot_spread_changes_fn = renderer.component.get_unique_name(
`get_${sanitize(slot_name)}_slot_spread_changes`
);
renderer.blocks.push(b` renderer.blocks.push(b`
const ${get_slot_spread_changes_fn} = #dirty => ${renderer.dirty(Array.from(spread_dynamic_dependencies))}; const ${get_slot_spread_changes_fn} = #dirty => ${renderer.dirty(
Array.from(spread_dynamic_dependencies)
)};
`); `);
} }
} else { } else {
@ -126,22 +133,22 @@ export default class SlotWrapper extends Wrapper {
const slot = block.get_unique_name(`${sanitize(slot_name)}_slot`); const slot = block.get_unique_name(`${sanitize(slot_name)}_slot`);
const slot_definition = block.get_unique_name(`${sanitize(slot_name)}_slot_template`); const slot_definition = block.get_unique_name(`${sanitize(slot_name)}_slot_template`);
const slot_or_fallback = has_fallback ? block.get_unique_name(`${sanitize(slot_name)}_slot_or_fallback`) : slot; const slot_or_fallback = has_fallback
? block.get_unique_name(`${sanitize(slot_name)}_slot_or_fallback`)
: slot;
block.chunks.init.push(b` block.chunks.init.push(b`
const ${slot_definition} = ${renderer.reference('#slots')}.${slot_name}; const ${slot_definition} = ${renderer.reference('#slots')}.${slot_name};
const ${slot} = @create_slot(${slot_definition}, #ctx, ${renderer.reference('$$scope')}, ${get_slot_context_fn}); const ${slot} = @create_slot(${slot_definition}, #ctx, ${renderer.reference(
'$$scope'
)}, ${get_slot_context_fn});
${has_fallback ? b`const ${slot_or_fallback} = ${slot} || ${this.fallback.name}(#ctx);` : null} ${has_fallback ? b`const ${slot_or_fallback} = ${slot} || ${this.fallback.name}(#ctx);` : null}
`); `);
block.chunks.create.push( block.chunks.create.push(b`if (${slot_or_fallback}) ${slot_or_fallback}.c();`);
b`if (${slot_or_fallback}) ${slot_or_fallback}.c();`
);
if (renderer.options.hydratable) { if (renderer.options.hydratable) {
block.chunks.claim.push( block.chunks.claim.push(b`if (${slot_or_fallback}) ${slot_or_fallback}.l(${parent_nodes});`);
b`if (${slot_or_fallback}) ${slot_or_fallback}.l(${parent_nodes});`
);
} }
block.chunks.mount.push(b` block.chunks.mount.push(b`
@ -150,15 +157,13 @@ export default class SlotWrapper extends Wrapper {
} }
`); `);
block.chunks.intro.push( block.chunks.intro.push(b`@transition_in(${slot_or_fallback}, #local);`);
b`@transition_in(${slot_or_fallback}, #local);`
);
block.chunks.outro.push( block.chunks.outro.push(b`@transition_out(${slot_or_fallback}, #local);`);
b`@transition_out(${slot_or_fallback}, #local);`
);
const dynamic_dependencies = Array.from(this.dependencies).filter((name) => this.is_dependency_dynamic(name)); const dynamic_dependencies = Array.from(this.dependencies).filter((name) =>
this.is_dependency_dynamic(name)
);
const fallback_dynamic_dependencies = has_fallback const fallback_dynamic_dependencies = has_fallback
? Array.from(this.fallback.dependencies).filter((name) => this.is_dependency_dynamic(name)) ? Array.from(this.fallback.dependencies).filter((name) => this.is_dependency_dynamic(name))
@ -174,21 +179,31 @@ export default class SlotWrapper extends Wrapper {
get_slot_spread_changes_fn ? x`${get_slot_spread_changes_fn}(#dirty)` : null, get_slot_spread_changes_fn ? x`${get_slot_spread_changes_fn}(#dirty)` : null,
block.has_outros ? x`!#current` : null block.has_outros ? x`!#current` : null
].filter(Boolean); ].filter(Boolean);
const all_dirty_condition = all_dirty_conditions.length ? all_dirty_conditions.reduce((condition1, condition2) => x`${condition1} || ${condition2}`) : null; const all_dirty_condition = all_dirty_conditions.length
? all_dirty_conditions.reduce((condition1, condition2) => x`${condition1} || ${condition2}`)
: null;
let slot_update: Node[]; let slot_update: Node[];
if (all_dirty_condition) { if (all_dirty_condition) {
const dirty = x`${all_dirty_condition} ? @get_all_dirty_from_scope(${renderer.reference('$$scope')}) : @get_slot_changes(${slot_definition}, ${renderer.reference('$$scope')}, #dirty, ${get_slot_changes_fn})`; const dirty = x`${all_dirty_condition} ? @get_all_dirty_from_scope(${renderer.reference(
'$$scope'
)}) : @get_slot_changes(${slot_definition}, ${renderer.reference(
'$$scope'
)}, #dirty, ${get_slot_changes_fn})`;
slot_update = b` slot_update = b`
if (${slot}.p && ${condition}) { if (${slot}.p && ${condition}) {
@update_slot_base(${slot}, ${slot_definition}, #ctx, ${renderer.reference('$$scope')}, ${dirty}, ${get_slot_context_fn}); @update_slot_base(${slot}, ${slot_definition}, #ctx, ${renderer.reference(
'$$scope'
)}, ${dirty}, ${get_slot_context_fn});
} }
`; `;
} else { } else {
slot_update = b` slot_update = b`
if (${slot}.p && ${condition}) { if (${slot}.p && ${condition}) {
@update_slot(${slot}, ${slot_definition}, #ctx, ${renderer.reference('$$scope')}, #dirty, ${get_slot_changes_fn}, ${get_slot_context_fn}); @update_slot(${slot}, ${slot_definition}, #ctx, ${renderer.reference(
'$$scope'
)}, #dirty, ${get_slot_changes_fn}, ${get_slot_context_fn});
} }
`; `;
} }
@ -200,7 +215,10 @@ export default class SlotWrapper extends Wrapper {
fallback_dirty = x`!#current ? ${renderer.get_initial_dirty()} : ${fallback_dirty}`; fallback_dirty = x`!#current ? ${renderer.get_initial_dirty()} : ${fallback_dirty}`;
} }
const fallback_update = has_fallback && fallback_dynamic_dependencies.length > 0 && b` const fallback_update =
has_fallback &&
fallback_dynamic_dependencies.length > 0 &&
b`
if (${slot_or_fallback} && ${slot_or_fallback}.p && ${fallback_condition}) { if (${slot_or_fallback} && ${slot_or_fallback}.p && ${fallback_condition}) {
${slot_or_fallback}.p(#ctx, ${fallback_dirty}); ${slot_or_fallback}.p(#ctx, ${fallback_dirty});
} }
@ -222,9 +240,7 @@ export default class SlotWrapper extends Wrapper {
`); `);
} }
block.chunks.destroy.push( block.chunks.destroy.push(b`if (${slot_or_fallback}) ${slot_or_fallback}.d(detaching);`);
b`if (${slot_or_fallback}) ${slot_or_fallback}.d(detaching);`
);
} }
is_dependency_dynamic(name: string) { is_dependency_dynamic(name: string) {

@ -30,8 +30,8 @@ export default class SlotTemplateWrapper extends Wrapper {
const { scope, lets, const_tags, slot_template_name } = this.node; const { scope, lets, const_tags, slot_template_name } = this.node;
lets.forEach(l => { lets.forEach((l) => {
extract_names(l.value || l.name).forEach(name => { extract_names(l.value || l.name).forEach((name) => {
renderer.add_to_context(name, true); renderer.add_to_context(name, true);
}); });
}); });
@ -40,22 +40,17 @@ export default class SlotTemplateWrapper extends Wrapper {
this.block = block.child({ this.block = block.child({
comment: create_debugging_comment(this.node, this.renderer.component), comment: create_debugging_comment(this.node, this.renderer.component),
name: this.renderer.component.get_unique_name( name: this.renderer.component.get_unique_name(`create_${sanitize(slot_template_name)}_slot`),
`create_${sanitize(slot_template_name)}_slot`
),
type: 'slot' type: 'slot'
}); });
this.renderer.blocks.push(this.block); this.renderer.blocks.push(this.block);
const seen = new Set(lets.map(l => l.name.name)); const seen = new Set(lets.map((l) => l.name.name));
this.parent.node.lets.forEach(l => { this.parent.node.lets.forEach((l) => {
if (!seen.has(l.name.name)) lets.push(l); if (!seen.has(l.name.name)) lets.push(l);
}); });
this.parent.set_slot( this.parent.set_slot(slot_template_name, get_slot_definition(this.block, scope, lets));
slot_template_name,
get_slot_definition(this.block, scope, lets)
);
this.fragment = new FragmentWrapper( this.fragment = new FragmentWrapper(
renderer, renderer,

@ -5,33 +5,32 @@ import Wrapper from './shared/Wrapper';
import { x } from 'code-red'; import { x } from 'code-red';
import { Identifier } from 'estree'; import { Identifier } from 'estree';
const regex_non_whitespace_characters = /[\S\u00A0]/;
export default class TextWrapper extends Wrapper { export default class TextWrapper extends Wrapper {
node: Text; node: Text;
data: string; _data: string;
skip: boolean; skip: boolean;
var: Identifier; var: Identifier;
constructor( constructor(renderer: Renderer, block: Block, parent: Wrapper, node: Text, data: string) {
renderer: Renderer,
block: Block,
parent: Wrapper,
node: Text,
data: string
) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.skip = this.node.should_skip(); this.skip = this.node.should_skip();
this.data = data; this._data = data;
this.var = (this.skip ? null : x`t`) as unknown as Identifier; this.var = (this.skip ? null : x`t`) as unknown as Identifier;
} }
use_space() { use_space() {
if (this.renderer.component.component_options.preserveWhitespace) return false; return this.node.use_space();
if (regex_non_whitespace_characters.test(this.data)) return false; }
return !this.node.within_pre(); set data(value: string) {
// when updating `this.data` during optimisation
// propagate the changes over to the underlying node
// so that the node.use_space reflects on the latest `data` value
this.node.data = this._data = value;
}
get data() {
return this._data;
} }
render(block: Block, parent_node: Identifier, parent_nodes: Identifier) { render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
@ -50,7 +49,10 @@ export default class TextWrapper extends Wrapper {
block.add_element( block.add_element(
this.var, this.var,
use_space ? x`@space()` : x`@text(${string_literal})`, use_space ? x`@space()` : x`@text(${string_literal})`,
parent_nodes && (use_space ? x`@claim_space(${parent_nodes})` : x`@claim_text(${parent_nodes}, ${string_literal})`), parent_nodes &&
(use_space
? x`@claim_space(${parent_nodes})`
: x`@claim_text(${parent_nodes}, ${string_literal})`),
parent_node as Identifier parent_node as Identifier
); );
} }

@ -24,7 +24,7 @@ export default class TitleWrapper extends Wrapper {
} }
render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) { render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) {
const is_dynamic = !!this.node.children.find(node => node.type !== 'Text'); const is_dynamic = !!this.node.children.find((node) => node.type !== 'Text');
if (is_dynamic) { if (is_dynamic) {
let value; let value;
@ -42,10 +42,10 @@ export default class TitleWrapper extends Wrapper {
} else { } else {
// '{foo} {bar}' — treat as string concatenation // '{foo} {bar}' — treat as string concatenation
value = this.node.children value = this.node.children
.map(chunk => { .map((chunk) => {
if (chunk.type === 'Text') return string_literal(chunk.data); if (chunk.type === 'Text') return string_literal(chunk.data);
(chunk as MustacheTag).expression.dependencies.forEach(d => { (chunk as MustacheTag).expression.dependencies.forEach((d) => {
all_dependencies.add(d); all_dependencies.add(d);
}); });
@ -58,17 +58,13 @@ export default class TitleWrapper extends Wrapper {
} }
} }
const last = this.node.should_cache && block.get_unique_name( const last = this.node.should_cache && block.get_unique_name('title_value');
'title_value'
);
if (this.node.should_cache) block.add_variable(last); if (this.node.should_cache) block.add_variable(last);
const init = this.node.should_cache ? x`${last} = ${value}` : value; const init = this.node.should_cache ? x`${last} = ${value}` : value;
block.chunks.init.push( block.chunks.init.push(b`@_document.title = ${init};`);
b`@_document.title = ${init};`
);
const updater = b`@_document.title = ${this.node.should_cache ? last : value};`; const updater = b`@_document.title = ${this.node.should_cache ? last : value};`;
@ -91,9 +87,10 @@ export default class TitleWrapper extends Wrapper {
}`); }`);
} }
} else { } else {
const value = this.node.children.length > 0 const value =
? string_literal((this.node.children[0] as Text).data) this.node.children.length > 0
: x`""`; ? string_literal((this.node.children[0] as Text).data)
: x`""`;
block.chunks.hydrate.push(b`@_document.title = ${value};`); block.chunks.hydrate.push(b`@_document.title = ${value};`);
} }

@ -40,7 +40,7 @@ export default class WindowWrapper extends Wrapper {
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: TemplateNode) { constructor(renderer: Renderer, block: Block, parent: Wrapper, node: TemplateNode) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.handlers = this.node.handlers.map(handler => new EventHandler(handler, this)); this.handlers = this.node.handlers.map((handler) => new EventHandler(handler, this));
} }
render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) { render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) {
@ -53,7 +53,7 @@ export default class WindowWrapper extends Wrapper {
add_actions(block, '@_window', this.node.actions); add_actions(block, '@_window', this.node.actions);
add_event_handlers(block, '@_window', this.handlers); add_event_handlers(block, '@_window', this.handlers);
this.node.bindings.forEach(binding => { this.node.bindings.forEach((binding) => {
// TODO: what if it's a MemberExpression? // TODO: what if it's a MemberExpression?
const binding_name = (binding.expression.node as Identifier).name; const binding_name = (binding.expression.node as Identifier).name;
@ -81,7 +81,7 @@ export default class WindowWrapper extends Wrapper {
const clear_scrolling = block.get_unique_name('clear_scrolling'); const clear_scrolling = block.get_unique_name('clear_scrolling');
const scrolling_timeout = block.get_unique_name('scrolling_timeout'); const scrolling_timeout = block.get_unique_name('scrolling_timeout');
Object.keys(events).forEach(event => { Object.keys(events).forEach((event) => {
const id = block.get_unique_name(`onwindow${event}`); const id = block.get_unique_name(`onwindow${event}`);
const props = events[event]; const props = events[event];
@ -94,9 +94,10 @@ export default class WindowWrapper extends Wrapper {
block.add_variable(clear_scrolling, x`() => { ${scrolling} = false }`); block.add_variable(clear_scrolling, x`() => { ${scrolling} = false }`);
block.add_variable(scrolling_timeout); block.add_variable(scrolling_timeout);
const condition = bindings.scrollX && bindings.scrollY const condition =
? x`"${bindings.scrollX}" in this._state || "${bindings.scrollY}" in this._state` bindings.scrollX && bindings.scrollY
: x`"${bindings.scrollX || bindings.scrollY}" in this._state`; ? x`"${bindings.scrollX}" in this._state || "${bindings.scrollY}" in this._state`
: x`"${bindings.scrollX || bindings.scrollY}" in this._state`;
const scrollX = bindings.scrollX && x`this._state.${bindings.scrollX}`; const scrollX = bindings.scrollX && x`this._state.${bindings.scrollX}`;
const scrollY = bindings.scrollY && x`this._state.${bindings.scrollY}`; const scrollY = bindings.scrollY && x`this._state.${bindings.scrollY}`;
@ -118,10 +119,8 @@ export default class WindowWrapper extends Wrapper {
}) })
`); `);
} else { } else {
props.forEach(prop => { props.forEach((prop) => {
renderer.meta_bindings.push( renderer.meta_bindings.push(b`this._state.${prop.name} = @_window.${prop.value};`);
b`this._state.${prop.name} = @_window.${prop.value};`
);
}); });
block.event_listeners.push(x` block.event_listeners.push(x`
@ -131,7 +130,7 @@ export default class WindowWrapper extends Wrapper {
component.partly_hoisted.push(b` component.partly_hoisted.push(b`
function ${id}() { function ${id}() {
${props.map(prop => renderer.invalidate(prop.name, x`${prop.name} = @_window.${prop.value}`))} ${props.map((prop) => renderer.invalidate(prop.name, x`${prop.name} = @_window.${prop.value}`))}
} }
`); `);
@ -146,8 +145,12 @@ export default class WindowWrapper extends Wrapper {
if (bindings.scrollX || bindings.scrollY) { if (bindings.scrollX || bindings.scrollY) {
const condition = renderer.dirty([bindings.scrollX, bindings.scrollY].filter(Boolean)); const condition = renderer.dirty([bindings.scrollX, bindings.scrollY].filter(Boolean));
const scrollX = bindings.scrollX ? renderer.reference(bindings.scrollX) : x`@_window.pageXOffset`; const scrollX = bindings.scrollX
const scrollY = bindings.scrollY ? renderer.reference(bindings.scrollY) : x`@_window.pageYOffset`; ? renderer.reference(bindings.scrollX)
: x`@_window.pageXOffset`;
const scrollY = bindings.scrollY
? renderer.reference(bindings.scrollY)
: x`@_window.pageYOffset`;
block.chunks.update.push(b` block.chunks.update.push(b`
if (${condition} && !${scrolling}) { if (${condition} && !${scrolling}) {

@ -9,25 +9,18 @@ import { Node } from 'estree';
export default class Tag extends Wrapper { export default class Tag extends Wrapper {
node: MustacheTag | RawMustacheTag; node: MustacheTag | RawMustacheTag;
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: MustacheTag | RawMustacheTag) { constructor(
renderer: Renderer,
block: Block,
parent: Wrapper,
node: MustacheTag | RawMustacheTag
) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.cannot_use_innerhtml();
if (!this.is_dependencies_static()) {
this.not_static_content();
}
block.add_dependencies(node.expression.dependencies); block.add_dependencies(node.expression.dependencies);
} }
is_dependencies_static() { rename_this_method(block: Block, update: (value: Node) => Node | Node[]) {
return this.node.expression.contextual_dependencies.size === 0 && this.node.expression.dynamic_dependencies().length === 0;
}
rename_this_method(
block: Block,
update: ((value: Node) => (Node | Node[]))
) {
const dependencies = this.node.expression.dynamic_dependencies(); const dependencies = this.node.expression.dynamic_dependencies();
let snippet = this.node.expression.manipulate(block); let snippet = this.node.expression.manipulate(block);

@ -13,15 +13,8 @@ export default class Wrapper {
next: Wrapper | null; next: Wrapper | null;
var: Identifier; var: Identifier;
can_use_innerhtml: boolean;
is_static_content: boolean;
constructor( constructor(renderer: Renderer, block: Block, parent: Wrapper, node: TemplateNode) {
renderer: Renderer,
block: Block,
parent: Wrapper,
node: TemplateNode
) {
this.node = node; this.node = node;
// make these non-enumerable so that they can be logged sensibly // make these non-enumerable so that they can be logged sensibly
@ -35,26 +28,15 @@ export default class Wrapper {
} }
}); });
this.can_use_innerhtml = !renderer.options.hydratable;
this.is_static_content = !renderer.options.hydratable;
block.wrappers.push(this); block.wrappers.push(this);
} }
cannot_use_innerhtml() {
this.can_use_innerhtml = false;
if (this.parent) this.parent.cannot_use_innerhtml();
}
not_static_content() {
this.is_static_content = false;
if (this.parent) this.parent.not_static_content();
}
get_or_create_anchor(block: Block, parent_node: Identifier, parent_nodes: Identifier) { get_or_create_anchor(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
// TODO use this in EachBlock and IfBlock — tricky because // TODO use this in EachBlock and IfBlock — tricky because
// children need to be created first // children need to be created first
const needs_anchor = this.next ? !this.next.is_dom_node() : !parent_node || !this.parent.is_dom_node(); const needs_anchor = this.next
? !this.next.is_dom_node()
: !parent_node || !this.parent.is_dom_node();
const anchor = needs_anchor const anchor = needs_anchor
? block.get_unique_name(`${this.var.name}_anchor`) ? block.get_unique_name(`${this.var.name}_anchor`)
: (this.next && this.next.var) || { type: 'Identifier', name: 'null' }; : (this.next && this.next.var) || { type: 'Identifier', name: 'null' };
@ -72,20 +54,23 @@ export default class Wrapper {
} }
get_update_mount_node(anchor: Identifier): Identifier { get_update_mount_node(anchor: Identifier): Identifier {
return ((this.parent && this.parent.is_dom_node()) return (
? this.parent.var this.parent && this.parent.is_dom_node() ? this.parent.var : x`${anchor}.parentNode`
: x`${anchor}.parentNode`) as Identifier; ) as Identifier;
} }
is_dom_node() { is_dom_node() {
return ( return (
this.node.type === 'Element' || this.node.type === 'Element' || this.node.type === 'Text' || this.node.type === 'MustacheTag'
this.node.type === 'Text' ||
this.node.type === 'MustacheTag'
); );
} }
render(_block: Block, _parent_node: Identifier, _parent_nodes: Identifier, _data: Record<string, any> = undefined) { render(
_block: Block,
_parent_node: Identifier,
_parent_nodes: Identifier,
_data: Record<string, any> = undefined
) {
throw Error('Wrapper class is not renderable'); throw Error('Wrapper class is not renderable');
} }
} }

@ -4,12 +4,8 @@ import Action from '../../../nodes/Action';
import { Expression, Node } from 'estree'; import { Expression, Node } from 'estree';
import is_contextual from '../../../nodes/shared/is_contextual'; import is_contextual from '../../../nodes/shared/is_contextual';
export default function add_actions( export default function add_actions(block: Block, target: string | Expression, actions: Action[]) {
block: Block, actions.forEach((action) => add_action(block, target, action));
target: string | Expression,
actions: Action[]
) {
actions.forEach(action => add_action(block, target, action));
} }
const regex_invalid_variable_identifier_characters = /[^a-zA-Z0-9_$]/g; const regex_invalid_variable_identifier_characters = /[^a-zA-Z0-9_$]/g;
@ -54,8 +50,6 @@ export function add_action(block: Block, target: string | Expression, action: Ac
condition = x`${condition} && ${block.renderer.dirty(dependencies)}`; condition = x`${condition} && ${block.renderer.dirty(dependencies)}`;
} }
block.chunks.update.push( block.chunks.update.push(b`if (${condition}) ${id}.update.call(null, ${snippet});`);
b`if (${condition}) ${id}.update.call(null, ${snippet});`
);
} }
} }

@ -9,14 +9,28 @@ export function add_const_tags(block: Block, const_tags: ConstTag[], ctx: string
const_tags.forEach((const_tag, i) => { const_tags.forEach((const_tag, i) => {
const name = `#constants_${i}`; const name = `#constants_${i}`;
const_tags_props.push(b`const ${name} = ${const_tag.expression.manipulate(block, ctx)}`); const_tags_props.push(b`const ${name} = ${const_tag.expression.manipulate(block, ctx)}`);
const to_ctx = (name: string) => block.renderer.context_lookup.has(name) ? x`${ctx}[${block.renderer.context_lookup.get(name).index}]` : { type: 'Identifier', name } as Node; const to_ctx = (name: string) =>
block.renderer.context_lookup.has(name)
? x`${ctx}[${block.renderer.context_lookup.get(name).index}]`
: ({ type: 'Identifier', name } as Node);
const_tag.contexts.forEach(context => { const_tag.contexts.forEach((context) => {
if (context.type === 'DestructuredVariable') { if (context.type === 'DestructuredVariable') {
const_tags_props.push(b`${ctx}[${block.renderer.context_lookup.get(context.key.name).index}] = ${context.default_modifier(context.modifier({ type: 'Identifier', name }), to_ctx)}`); const_tags_props.push(
b`${ctx}[${
block.renderer.context_lookup.get(context.key.name).index
}] = ${context.default_modifier(context.modifier({ type: 'Identifier', name }), to_ctx)}`
);
} else { } else {
const expression = new Expression(block.renderer.component, const_tag, const_tag.scope, context.key); const expression = new Expression(
const_tags_props.push(b`const ${context.property_name} = ${expression.manipulate(block, ctx)}`); block.renderer.component,
const_tag,
const_tag.scope,
context.key
);
const_tags_props.push(
b`const ${context.property_name} = ${expression.manipulate(block, ctx)}`
);
} }
}); });
}); });
@ -24,8 +38,8 @@ export function add_const_tags(block: Block, const_tags: ConstTag[], ctx: string
} }
export function add_const_tags_context(renderer: Renderer, const_tags: ConstTag[]) { export function add_const_tags_context(renderer: Renderer, const_tags: ConstTag[]) {
const_tags.forEach(const_tag => { const_tags.forEach((const_tag) => {
const_tag.contexts.forEach(context => { const_tag.contexts.forEach((context) => {
if (context.type !== 'DestructuredVariable') return; if (context.type !== 'DestructuredVariable') return;
renderer.add_to_context(context.key.name, true); renderer.add_to_context(context.key.name, true);
}); });

@ -7,7 +7,7 @@ export default function add_event_handlers(
target: string | Expression, target: string | Expression,
handlers: EventHandler[] handlers: EventHandler[]
) { ) {
handlers.forEach(handler => add_event_handler(block, target, handler)); handlers.forEach((handler) => add_event_handler(block, target, handler));
} }
export function add_event_handler( export function add_event_handler(

@ -5,7 +5,12 @@ import BindingWrapper from '../Element/Binding';
import { Identifier } from 'estree'; import { Identifier } from 'estree';
import { compare_node } from '../../../utils/compare_node'; import { compare_node } from '../../../utils/compare_node';
export default function bind_this(component: Component, block: Block, binding: BindingWrapper, variable: Identifier) { export default function bind_this(
component: Component,
block: Block,
binding: BindingWrapper,
variable: Identifier
) {
const fn = component.get_unique_name(`${variable.name}_binding`); const fn = component.get_unique_name(`${variable.name}_binding`);
block.renderer.add_to_context(fn.name); block.renderer.add_to_context(fn.name);
@ -17,13 +22,13 @@ export default function bind_this(component: Component, block: Block, binding: B
const body = b` const body = b`
${mutation} ${mutation}
${Array.from(dependencies) ${Array.from(dependencies)
.filter(dep => dep[0] !== '$') .filter((dep) => dep[0] !== '$')
.filter(dep => !contextual_dependencies.has(dep)) .filter((dep) => !contextual_dependencies.has(dep))
.map(dep => b`${block.renderer.invalidate(dep)};`)} .map((dep) => b`${block.renderer.invalidate(dep)};`)}
`; `;
if (contextual_dependencies.size) { if (contextual_dependencies.size) {
const params: Identifier[] = Array.from(contextual_dependencies).map(name => ({ const params: Identifier[] = Array.from(contextual_dependencies).map((name) => ({
type: 'Identifier', type: 'Identifier',
name name
})); }));
@ -66,7 +71,9 @@ export default function bind_this(component: Component, block: Block, binding: B
`); `);
const condition = Array.from(args) const condition = Array.from(args)
.map(name => x`${name} !== ${block.renderer.reference(alias_map.get(name.name) || name.name)}`) .map(
(name) => x`${name} !== ${block.renderer.reference(alias_map.get(name.name) || name.name)}`
)
.reduce((lhs, rhs) => x`${lhs} || ${rhs}`); .reduce((lhs, rhs) => x`${lhs} || ${rhs}`);
// we push unassign and unshift assign so that references are // we push unassign and unshift assign so that references are
@ -75,10 +82,9 @@ export default function bind_this(component: Component, block: Block, binding: B
block.chunks.update.push(b` block.chunks.update.push(b`
if (${condition}) { if (${condition}) {
${unassign}(); ${unassign}();
${args.map(a => b`${a} = ${block.renderer.reference(alias_map.get(a.name) || a.name)}`)}; ${args.map((a) => b`${a} = ${block.renderer.reference(alias_map.get(a.name) || a.name)}`)};
${assign}(); ${assign}();
}` }`);
);
block.chunks.destroy.push(b`${unassign}();`); block.chunks.destroy.push(b`${unassign}();`);
return b`${assign}();`; return b`${assign}();`;

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

Loading…
Cancel
Save