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 = {
root: true,
extends: '@sveltejs',
extends: ['@sveltejs', 'prettier'],
settings: {
'import/core-modules': [
'svelte',
@ -10,5 +10,8 @@ module.exports = {
'estree'
],
'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
- [ ] 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:
contents: read # to fetch code (actions/checkout)
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:
needs: Setup
runs-on: ${{ matrix.os }}
timeout-minutes: 15
strategy:
matrix:
node-version: [8, 10, 12, 14, 16, 18]
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:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2.2.4
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: npm
- name: Download build assets
uses: actions/download-artifact@v3
id: download-artifact
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
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: node node_modules/puppeteer/install.js
- run: pnpm test:integration
env:
CI: true
Lint:
@ -80,22 +39,34 @@ jobs:
timeout-minutes: 5
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2.2.4
- uses: actions/setup-node@v3
with:
cache: npm
- run: 'npm i && npm run lint'
node-version: 16
cache: pnpm
- run: 'pnpm i && pnpm format:check && pnpm lint'
Unit:
runs-on: ${{ matrix.os }}
timeout-minutes: 10
strategy:
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:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2.2.4
- uses: actions/setup-node@v3
with:
cache: npm
- run: npm install
env:
SKIP_PREPARE: true
- run: npm run test:unit
node-version: ${{ matrix.node-version }}
cache: pnpm
- run: pnpm install
- run: pnpm test:unit

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

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

@ -1,8 +1,23 @@
{
"singleQuote": true,
"printWidth": 100,
"useTabs": true,
"tabWidth": 2,
"singleQuote": true,
"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
## Unreleased
* Handle `width`/`height` attributes when spreading ([#6752](https://github.com/sveltejs/svelte/issues/6752))
* Add support for resize observer bindings (`<div bind:contentRect|contentBoxSize|borderBoxSize|devicePixelContentBoxSize>`) ([#8022](https://github.com/sveltejs/svelte/pull/8022))
* Update interpolated style directive properly 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))
* Ensure version is typed as `string` instead of the literal `__VERSION__` ([#8498](https://github.com/sveltejs/svelte/issues/8498))
## Unreleased (4.0)
* **breaking** Minimum supported Node version is now Node 16 ([#8566](https://github.com/sveltejs/svelte/pull/8566))
* **breaking** Minimum supported webpack version is now webpack 5 ([#8515](https://github.com/sveltejs/svelte/pull/8515))
* **breaking** Bundlers must specify the `browser` condition when building a frontend bundle for the browser ([#8516](https://github.com/sveltejs/svelte/pull/8516))
* **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))
* **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

@ -62,6 +62,8 @@ When [opening a new issue](https://github.com/sveltejs/svelte/issues/new/choose)
## 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
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
1. Ensure you have [npm](https://www.npmjs.com/get-npm) installed.
1. After cloning the repository, run `npm install` in the root of the repository.
1. To start a development server, run `npm run dev`.
1. Ensure you have [pnpm](https://pnpm.io/installation) installed.
1. After cloning the repository, run `pnpm install` in the root of the repository.
1. To compile in watch mode, run `pnpm dev`.
### Creating a branch
@ -94,8 +96,8 @@ Test samples are kept in `/test/xxx/samples` folder.
#### Running tests
1. To run test, run `npm run test`.
1. To run test for a specific feature, you can use the `-g` (aka `--grep`) option. For example, to only run test involving transitions, run `npm run test -- -g transition`.
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 `pnpm test -- -g transition`.
##### Running solo test
@ -106,11 +108,11 @@ Test samples are kept in `/test/xxx/samples` folder.
##### Updating `.expected` files
1. Tests suites like `css`, `js`, `server-side-rendering` asserts that the generated output has to match the content in the `.expected` file. For example, in the `js` test suites, the generated js code is compared against the content in `expected.js`.
1. To update the content of the `.expected` file, run the test with `--update` flag. (`npm run test --update`)
1. To update the content of the `.expected` file, run the test with `--update` flag. (`pnpm test --update`)
### 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
@ -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:
1. Describe your **test plan** in your pull request description. Make sure to test your changes.
1. Make sure your code lints (`npm run lint`).
1. Make sure your tests pass (`npm run test`).
1. Make sure your code lints (`pnpm lint`).
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.

@ -29,7 +29,7 @@ To install and work on Svelte locally:
```bash
git clone https://github.com/sveltejs/svelte.git
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.
@ -37,13 +37,13 @@ npm install
To build the compiler and all the other modules included in the package:
```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
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.
@ -51,13 +51,13 @@ The compiler is written in [TypeScript](https://www.typescriptlang.org/), but do
### Running Tests
```bash
npm run test
pnpm test
```
To filter tests, use `-g` (aka `--grep`). For example, to only run tests involving transitions:
```bash
npm run test -- -g transition
pnpm test -- -g transition
```
## svelte.dev

129
elements/index.d.ts vendored

@ -39,8 +39,9 @@ type Booleanish = boolean | 'true' | 'false';
// Event Handler Types
// ----------------------------------------------------------------------
type EventHandler<E extends Event = Event, T extends EventTarget = Element> =
(event: E & { currentTarget: EventTarget & T}) => any;
type EventHandler<E extends Event = Event, T extends EventTarget = Element> = (
event: E & { currentTarget: EventTarget & T }
) => any;
export type ClipboardEventHandler<T extends EventTarget> = EventHandler<ClipboardEvent, 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:input'?: 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: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
'on:load'?: EventHandler | undefined | null;
@ -196,7 +197,7 @@ export interface DOMAttributes<T extends EventTarget> {
// Message Events
'on:message'?: MessageEventHandler<T> | undefined | null;
'on:messageerror'?: MessageEventHandler<T> | undefined | null;
// Document Events
'on:visibilitychange'?: EventHandler<Event, T> | 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.
* @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. */
'aria-required'?: Booleanish | undefined | null;
/** 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> {
// Standard HTML Attributes
accesskey?: string | undefined | null;
accesskey?: string | undefined | null;
autofocus?: boolean | undefined | null;
class?: string | undefined | null;
contenteditable?: Booleanish | 'inherit' | undefined | null;
contextmenu?: string | undefined | null;
dir?: string | 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;
id?: 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
* @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
* @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;
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:borderBoxSize'?: Array<{ blockSize: number; inlineSize: number }> | undefined | null; // TODO make this ResizeObserverSize once we require TS>=4.4
readonly 'bind:devicePixelContentBoxSize'?: 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<ResizeObserverSize> | undefined | null;
readonly 'bind:devicePixelContentBoxSize'?: Array<ResizeObserverSize> | undefined | null;
// SvelteKit
'data-sveltekit-keepfocus'?: 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-reload'?: true | '' | 'off' | undefined | null;
'data-sveltekit-replacestate'?: true | '' | 'off' | undefined | null;
// allow any data- attribute
[key: `data-${string}`]: any;
}
export type HTMLAttributeAnchorTarget =
| '_self'
| '_blank'
| '_parent'
| '_top'
| (string & {});
export type HTMLAttributeAnchorTarget = '_self' | '_blank' | '_parent' | '_top' | (string & {});
export interface HTMLAnchorAttributes extends HTMLAttributes<HTMLAnchorElement> {
download?: any;
@ -841,7 +880,14 @@ export interface HTMLMenuAttributes extends HTMLAttributes<HTMLMenuElement> {
export interface HTMLMediaAttributes<T extends HTMLMediaElement> extends HTMLAttributes<T> {
autoplay?: 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;
currenttime?: number | 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;
accumulate?: 'none' | 'sum' | undefined | null;
additive?: 'replace' | 'sum' | undefined | null;
'alignment-baseline'?: 'auto' | 'baseline' | 'before-edge' | 'text-before-edge' | 'middle' |
'central' | 'after-edge' | 'text-after-edge' | 'ideographic' | 'alphabetic' | 'hanging' |
'mathematical' | 'inherit' | undefined | null;
'alignment-baseline'?:
| 'auto'
| '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;
alphabetic?: number | string | undefined | null;
amplitude?: number | string | undefined | null;
@ -1600,7 +1659,29 @@ export interface SvelteHTMLElements {
'svelte:document': SvelteDocumentAttributes;
'svelte:body': HTMLAttributes<HTMLElement>;
'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 };
[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",
"version": "3.58.0",
"version": "4.0.0-next.0",
"description": "Cybernetically enhanced web apps",
"module": "index.mjs",
"main": "index",
@ -28,12 +28,8 @@
"import": "./index.mjs",
"require": "./index.js"
},
"node": {
"import": "./ssr.mjs",
"require": "./ssr.js"
},
"import": "./index.mjs",
"require": "./index.js"
"import": "./ssr.mjs",
"require": "./ssr.js"
},
"./compiler": {
"types": "./types/compiler/index.d.ts",
@ -78,28 +74,25 @@
},
"./elements": {
"types": "./elements/index.d.ts"
},
"./ssr": {
"types": "./types/runtime/index.d.ts",
"import": "./ssr.mjs",
"require": "./ssr.js"
}
},
"engines": {
"node": ">= 8"
"node": ">=16"
},
"types": "types/runtime/index.d.ts",
"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:unit": "mocha --config .mocharc.unit.js --exit",
"quicktest": "mocha --exit",
"build": "rollup -c && npm run tsd",
"prepare": "node scripts/skip_in_ci.js npm run build",
"prepare": "npm run build",
"dev": "rollup -cw",
"posttest": "agadoo internal/index.mjs",
"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"
},
"repository": {
@ -119,46 +112,50 @@
},
"homepage": "https://svelte.dev",
"devDependencies": {
"@ampproject/remapping": "^0.3.0",
"@ampproject/remapping": "^2.2.1",
"@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-node-resolve": "^11.2.1",
"@rollup/plugin-node-resolve": "^15.0.2",
"@rollup/plugin-replace": "^5.0.2",
"@rollup/plugin-sucrase": "^3.1.0",
"@rollup/plugin-typescript": "^2.0.1",
"@rollup/plugin-sucrase": "^5.0.1",
"@rollup/plugin-typescript": "^11.1.0",
"@rollup/plugin-virtual": "^3.0.1",
"@sveltejs/eslint-config": "github:sveltejs/eslint-config#v5.8.0",
"@types/aria-query": "^5.0.1",
"@types/mocha": "^7.0.0",
"@types/node": "^8.10.53",
"@typescript-eslint/eslint-plugin": "^5.29.0",
"@typescript-eslint/parser": "^5.29.0",
"acorn": "^8.8.1",
"@types/estree": "^1.0.0",
"@types/mocha": "^10.0.1",
"@types/node": "^14.14.31",
"@typescript-eslint/eslint-plugin": "^5.58.0",
"@typescript-eslint/parser": "^5.58.0",
"acorn": "^8.8.2",
"agadoo": "^3.0.0",
"aria-query": "^5.1.3",
"axobject-query": "^3.1.1",
"code-red": "^1.0.0",
"css-tree": "^2.3.1",
"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",
"estree-walker": "^3.0.3",
"is-reference": "^3.0.1",
"jsdom": "^15.2.1",
"jsdom": "^21.1.1",
"kleur": "^4.1.5",
"locate-character": "^2.0.5",
"magic-string": "^0.30.0",
"mocha": "^7.0.0",
"mocha": "^10.2.0",
"periscopic": "^3.1.0",
"prettier": "^2.8.8",
"prettier-plugin-svelte": "^2.10.0",
"puppeteer": "^2.0.0",
"rollup": "^1.27.14",
"puppeteer": "^19.8.5",
"rollup": "^3.20.2",
"source-map": "^0.7.4",
"source-map-support": "^0.5.21",
"tiny-glob": "^0.2.9",
"tslib": "^2.5.0",
"typescript": "^3.7.5",
"typescript": "^5.0.4",
"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.
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_functions_and_references = (name, data) => {
const functions = [];
const references = [];
data.split('\n').forEach(line => {
data.split('\n').forEach((line) => {
const trimmed = line.trim();
const split = trimmed.replace(/[\s+]/, ' ').split(' ');
if (split[0] === 'declare' && split[1] !== 'type') {
@ -35,17 +36,20 @@ const extract_functions_and_references = (name, data) => {
return { functions, references };
};
const do_get = (url) => new Promise((resolve, reject) => {
http.get(url, (res) => {
let body = '';
res.setEncoding('utf8');
res.on('data', (chunk) => body += chunk);
res.on('end', () => resolve(body));
}).on('error', (e) => {
console.error(e.message);
reject(e);
const do_get = (url) =>
new Promise((resolve, reject) => {
http
.get(url, (res) => {
let body = '';
res.setEncoding('utf8');
res.on('data', (chunk) => (body += chunk));
res.on('end', () => resolve(body));
})
.on('error', (e) => {
console.error(e.message);
reject(e);
});
});
});
const fetched_names = new Set();
const get_functions = async (name) => {
@ -56,7 +60,7 @@ const get_functions = async (name) => {
const { functions, references } = extract_functions_and_references(name, body);
res.push(...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;
};
@ -76,10 +80,11 @@ ${sorted.map((i) => `\t'${i}'`).join(',\n')}
const get_exists_globals = () => {
const regexp = /^\s*["'](.+)["'],?\s*$/;
return fs.readFileSync(GLOBAL_TS_PATH, 'utf8')
return fs
.readFileSync(GLOBAL_TS_PATH, 'utf8')
.split('\n')
.filter(line => line.match(regexp))
.map(line => line.match(regexp)[1]);
.filter((line) => line.match(regexp))
.map((line) => line.match(regexp)[1]);
};
(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={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
- `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:options tag="my-custom-element" />
<svelte:options customElement="my-custom-element" />
```
## `<svelte:fragment>`

@ -33,7 +33,7 @@ count.set(1); // logs '1'
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
/// 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`.
```js
/// file: store.js
// @errors: 7006 2769
// ---cut---
import { readable } from 'svelte/store';
const time = readable(new Date(), (set) => {
@ -74,6 +75,14 @@ const time = readable(new Date(), (set) => {
return () => clearInterval(interval);
});
const ticktock = readable('tick', (set, update) => {
const interval = setInterval(() => {
update((sound) => (sound === 'tick' ? 'tock' : 'tick'));
}, 1000);
return () => clearInterval(interval);
});
```
## `derived`
@ -101,9 +110,9 @@ import { derived } from 'svelte/store';
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
// @filename: ambient.d.ts
@ -116,16 +125,20 @@ declare global {
export {};
// @filename: index.ts
// @errors: 2769 7006
// ---cut---
import { derived } from 'svelte/store';
const delayed = derived(
a,
($a, set) => {
setTimeout(() => set($a), 1000);
},
2000
);
const delayed = derived(a, ($a, set) => {
setTimeout(() => set($a), 1000);
}, 2000);
const delayedIncrement = derived(a, ($a, set, update) => {
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.

@ -15,7 +15,7 @@ Svelte components can also be compiled to custom elements (aka web components) u
<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
// @noErrors
@ -49,11 +49,37 @@ console.log(el.name);
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:
- 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
- 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
- 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
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" />
```
## `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`.

@ -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:
- `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
- `accessors={true}` — adds getters and setters for the component's props
- `accessors={false}` — the default
- `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
* `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
* `accessors={true}` — adds getters and setters for the component's props
* `accessors={false}` — the default
* `namespace="..."` — the namespace where this component will be used, most commonly `"svg"`
* `customElement="..."` — the name to use when compiling this component as a custom element
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",
"scripts": {
"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",
"update": "node scripts/update.js --force=true",
"preview": "vite preview",
"start": "node build",
"check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
"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=.",
"test": "uvu -r ts-node/register src/lib/server/markdown"
},
@ -34,14 +34,14 @@
"degit": "^2.8.4",
"dotenv": "^16.0.3",
"jimp": "^0.22.8",
"marked": "^5.0.1",
"marked": "^5.0.2",
"node-fetch": "^3.3.1",
"prettier": "^2.8.8",
"prettier-plugin-svelte": "^2.10.0",
"rollup": "^3.21.6",
"rollup-plugin-dts": "^5.3.0",
"sass": "^1.62.1",
"satori": "^0.8.0",
"satori": "^0.8.1",
"satori-html": "^0.3.2",
"shelljs": "^0.8.5",
"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 t = process.hrtime();
return t[0] * 1e3 + t[1] / 1e6;
}
: () => self.performance.now();
const now =
typeof process !== 'undefined' && process.hrtime
? () => {
const t = process.hrtime();
return t[0] * 1e3 + t[1] / 1e6;
}
: () => self.performance.now();
interface Timing {
label: string;
@ -14,10 +15,13 @@ interface Timing {
function collapse_timings(timings) {
const result = {};
timings.forEach(timing => {
result[timing.label] = Object.assign({
total: timing.end - timing.start
}, timing.children && collapse_timings(timing.children));
timings.forEach((timing) => {
result[timing.label] = Object.assign(
{
total: timing.end - timing.start
},
timing.children && collapse_timings(timing.children)
);
});
return result;
}
@ -52,7 +56,9 @@ export default class Stats {
stop(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();
@ -62,9 +68,12 @@ export default class Stats {
}
render() {
const timings = Object.assign({
total: now() - this.start_time
}, collapse_timings(this.timings));
const timings = Object.assign(
{
total: now() - this.start_time
},
collapse_timings(this.timings)
);
return {
timings

@ -1,3 +1,4 @@
import type { DecodedSourceMap, RawSourceMap } from '@ampproject/remapping';
import { walk } from 'estree-walker';
import { getLocator } from 'locate-character';
import Stats from '../Stats';
@ -5,17 +6,12 @@ import { reserved, is_valid } from '../utils/names';
import globals from '../utils/globals';
import { namespaces, valid_namespaces } from '../utils/namespaces';
import create_module from './create_module';
import {
create_scopes,
extract_names,
Scope,
extract_identifiers
} from './utils/scope';
import { create_scopes, extract_names, Scope, extract_identifiers } from './utils/scope';
import Stylesheet from './css/Stylesheet';
import { test } from '../config';
import Fragment from './nodes/Fragment';
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 get_code_frame from '../utils/get_code_frame';
import flatten_reference from './utils/flatten_reference';
@ -25,26 +21,58 @@ import TemplateScope from './nodes/shared/TemplateScope';
import fuzzymatch from '../utils/fuzzymatch';
import get_object from './utils/get_object';
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 check_graph_for_cycles from './utils/check_graph_for_cycles';
import { print, b } from 'code-red';
import { is_reserved_keyword } from './utils/reserved_keywords';
import { apply_preprocessor_sourcemap } from '../utils/mapped_code';
import Element from './nodes/Element';
import { DecodedSourceMap, RawSourceMap } from '@ampproject/remapping/dist/types/types';
import { clone } from '../utils/clone';
import compiler_warnings from './compiler_warnings';
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 Tag from './nodes/shared/Tag';
interface ComponentOptions {
namespace?: string;
tag?: string;
immutable?: boolean;
accessors?: 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 = /^[/\\]/;
@ -81,8 +109,8 @@ export default class Component {
hoistable_nodes: Set<Node> = new Set();
node_for_declaration: Map<string, Node> = new Map();
partly_hoisted: Array<(Node | Node[])> = [];
fully_hoisted: Array<(Node | Node[])> = [];
partly_hoisted: Array<Node | Node[]> = [];
fully_hoisted: Array<Node | Node[]> = [];
reactive_declarations: Array<{
assignees: Set<string>;
dependencies: Set<string>;
@ -110,6 +138,8 @@ export default class Component {
slots: Map<string, Slot> = new Map();
slot_outlets: Set<string> = new Set();
tags: Tag[] = [];
constructor(
ast: Ast,
source: string,
@ -139,8 +169,8 @@ export default class Component {
compile_options.filename &&
(typeof process !== 'undefined'
? compile_options.filename
.replace(process.cwd(), '')
.replace(regex_leading_directory_separator, '')
.replace(process.cwd(), '')
.replace(regex_leading_directory_separator, '')
: compile_options.filename);
this.locate = getLocator(this.source, { offsetLine: 1 });
@ -155,44 +185,39 @@ export default class Component {
});
this.stylesheet.validate(this);
this.component_options = process_component_options(
this,
this.ast.html.children
);
this.component_options = process_component_options(this, this.ast.html.children);
this.namespace =
namespaces[this.component_options.namespace] ||
this.component_options.namespace;
namespaces[this.component_options.namespace] || this.component_options.namespace;
if (compile_options.customElement) {
if (
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;
this.tag = this.component_options.customElement?.tag || compile_options.tag || this.name.name;
} else {
this.tag = this.name.name;
}
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.pop_ignores();
this.fragment = new Fragment(this, ast.html);
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.pop_ignores();
this.elements.forEach(element => this.stylesheet.apply(element));
if (!compile_options.customElement) this.stylesheet.reify();
this.elements.forEach((element) => this.stylesheet.apply(element));
this.stylesheet.reify();
this.stylesheet.warn_on_unused_selectors(this);
}
@ -343,17 +368,15 @@ export default class Component {
referenced_globals,
this.imports,
this.vars
.filter(variable => variable.module && variable.export_name)
.map(variable => ({
.filter((variable) => variable.module && variable.export_name)
.map((variable) => ({
name: variable.name,
as: variable.export_name
})),
this.exports_from
);
css = compile_options.customElement
? { code: null, map: null }
: result.css;
css = compile_options.customElement ? { code: null, map: null } : result.css;
const js_sourcemap_enabled = check_enable_sourcemap(compile_options.enableSourcemap, 'js');
@ -367,15 +390,15 @@ export default class Component {
sourceMapSource: sourcemap_source_filename
});
js.map.sources = [
sourcemap_source_filename
];
js.map.sources = [sourcemap_source_filename];
js.map.sourcesContent = [
this.source
];
js.map.sourcesContent = [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[] {
const { compile_options, vars } = this;
const vars_report = compile_options.varsReport === false
? []
: compile_options.varsReport === 'full'
const vars_report =
compile_options.varsReport === false
? []
: compile_options.varsReport === 'full'
? 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,
export_name: v.export_name || null,
injected: v.injected || false,
@ -507,8 +531,7 @@ export default class Component {
end,
pos: pos.start,
filename: this.compile_options.filename,
toString: () =>
`${warning.message} (${start.line}:${start.column})\n${frame}`
toString: () => `${warning.message} (${start.line}:${start.column})\n${frame}`
});
}
@ -524,7 +547,10 @@ export default class Component {
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') {
return this.error(node as any, compiler_errors.default_export);
}
@ -540,12 +566,25 @@ export default class Component {
}
if (node.declaration) {
if (node.declaration.type === 'VariableDeclaration') {
node.declaration.declarations.forEach(declarator => {
extract_names(declarator.id).forEach(name => {
node.declaration.declarations.forEach((declarator) => {
extract_names(declarator.id).forEach((name) => {
const variable = this.var_lookup.get(name);
variable.export_name = name;
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));
if (
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;
} else {
node.specifiers.forEach(specifier => {
node.specifiers.forEach((specifier) => {
const variable = this.var_lookup.get(specifier.local.name);
if (variable) {
variable.export_name = specifier.exported.name;
if (!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));
if (
!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) {
if (!script) return null;
return script.content.body.filter(node => {
return script.content.body.filter((node) => {
if (!node) return false;
if (this.hoistable_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);
}
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');
this.add_var(node, {
@ -658,7 +705,7 @@ export default class Component {
if (!script) return;
// inject vars for reactive declarations
script.content.body.forEach(node => {
script.content.body.forEach((node) => {
if (node.type !== 'LabeledStatement') return;
if (node.body.type !== 'ExpressionStatement') return;
@ -666,16 +713,14 @@ export default class Component {
if (expression.type !== 'AssignmentExpression') 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] !== '$') {
this.injected_reactive_declaration_vars.add(name);
}
});
});
const { scope: instance_scope, map, globals } = create_scopes(
script.content
);
const { scope: instance_scope, map, globals } = create_scopes(script.content);
this.instance_scope = instance_scope;
this.instance_scope_map = map;
@ -684,7 +729,8 @@ export default class Component {
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');
this.add_var(node, {
@ -701,11 +747,11 @@ export default class Component {
// as `$store` will mark `store` variable as referenced and subscribable
const global_keys = Array.from(globals.keys());
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;
const node = globals.get(name);
@ -761,6 +807,7 @@ export default class Component {
this.hoist_instance_declarations();
this.extract_reactive_declarations();
this.check_if_tags_content_dynamic();
}
post_template_walk() {
@ -784,8 +831,8 @@ export default class Component {
walk(content, {
enter(node: Node, parent: Node, prop, index) {
if ((node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression')) {
current_function_stack.push(current_function = node);
if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') {
current_function_stack.push((current_function = node));
}
if (map.has(node)) {
@ -799,18 +846,18 @@ export default class Component {
if (node.left.type === 'ArrayPattern') {
walk(node.left, {
enter(node: Node, parent: Node) {
if (node.type === 'Identifier' &&
if (
node.type === 'Identifier' &&
parent.type !== 'MemberExpression' &&
(parent.type !== 'AssignmentPattern' || parent.right !== node)) {
names.push(node.name);
(parent.type !== 'AssignmentPattern' || parent.right !== node)
) {
names.push(node.name);
}
}
});
} else {
deep = node.left.type === 'MemberExpression';
names = deep
? [get_object(node.left).name]
: extract_names(node.left);
names = deep ? [get_object(node.left).name] : extract_names(node.left);
}
} else if (node.type === 'UpdateExpression') {
deep = node.argument.type === 'MemberExpression';
@ -818,7 +865,7 @@ export default class Component {
names.push(name);
}
if (names.length > 0) {
names.forEach(name => {
names.forEach((name) => {
let current_scope = scope;
let declaration;
@ -861,14 +908,22 @@ export default class Component {
},
leave(node: Node) {
if ((node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression')) {
if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') {
current_function_stack.pop();
current_function = current_function_stack[current_function_stack.length - 1];
}
// 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))) {
const to_replace_for_loop_protect = component.loop_protect(node, scope, component.compile_options.loopGuardTimeout);
if (
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) {
this.replace(to_replace_for_loop_protect);
scope_updated = true;
@ -920,7 +975,7 @@ export default class Component {
const deep = assignee.type === 'MemberExpression';
names.forEach(name => {
names.forEach((name) => {
const scope_owner = scope.find_owner(name);
if (
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) {
if (
node.type === 'LabeledStatement' &&
node.label.name === '$' &&
parent.type !== 'Program'
) {
warn_on_undefined_store_value_references(
node: Node,
parent: Node,
prop: string | number | symbol,
scope: Scope
) {
if (node.type === 'LabeledStatement' && node.label.name === '$' && parent.type !== 'Program') {
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);
}
if (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'))) {
if (
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);
}
}
@ -978,9 +1043,11 @@ export default class Component {
}
loop_protect(node, scope: Scope, timeout: number): Node | null {
if (node.type === 'WhileStatement' ||
if (
node.type === 'WhileStatement' ||
node.type === 'ForStatement' ||
node.type === 'DoWhileStatement') {
node.type === 'DoWhileStatement'
) {
const guard = this.get_unique_name('guard', scope);
this.used_names.add(guard.name);
@ -998,10 +1065,7 @@ export default class Component {
return {
type: 'BlockStatement',
body: [
before[0],
node
]
body: [before[0], node]
};
}
return null;
@ -1034,7 +1098,11 @@ export default class Component {
const inserts = [];
const props = [];
function add_new_props(exported, local, default_value) {
function add_new_props(
exported: Identifier,
local: Pattern,
default_value: Expression
) {
props.push({
type: 'Property',
method: false,
@ -1044,10 +1112,10 @@ export default class Component {
key: exported,
value: default_value
? {
type: 'AssignmentPattern',
left: local,
right: default_value
}
type: 'AssignmentPattern',
left: local,
right: default_value
}
: local
});
}
@ -1064,7 +1132,7 @@ export default class Component {
for (let index = 0; index < node.declarations.length; index++) {
const declarator = node.declarations[index];
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);
if (variable.subscribable) {
inserts.push(get_insert(variable));
@ -1072,13 +1140,17 @@ export default class Component {
if (variable.export_name && variable.writable) {
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 local;
}
function rename_identifiers(param: Node) {
function rename_identifiers(param: Pattern) {
switch (param.type) {
case 'ObjectPattern': {
const handle_prop = (prop: Property | RestElement) => {
@ -1087,7 +1159,7 @@ export default class Component {
} else if (prop.value.type === 'Identifier') {
prop.value = get_new_name(prop.value);
} else {
rename_identifiers(prop.value);
rename_identifiers(prop.value as Pattern);
}
};
@ -1095,7 +1167,11 @@ export default class Component {
break;
}
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.type === 'Identifier') {
array[index] = get_new_name(element);
@ -1110,7 +1186,11 @@ export default class Component {
}
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;
case 'AssignmentPattern':
@ -1129,7 +1209,11 @@ export default class Component {
const variable = component.var_lookup.get(name);
const is_props = variable.export_name && variable.writable;
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);
}
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}
${ props.length > 0 && b`let { ${props} } = $$props;`}
${props.length > 0 && b`let { ${props} } = $$props;`}
${inserts}
` as any);
` as any
);
return this.skip();
}
}
@ -1162,12 +1248,7 @@ export default class Component {
// reference instance variables other than other
// hoistable functions. TODO others?
const {
hoistable_nodes,
var_lookup,
injected_reactive_declaration_vars,
imports
} = this;
const { hoistable_nodes, var_lookup, injected_reactive_declaration_vars, imports } = this;
const top_level_function_declarations = new Map();
@ -1177,7 +1258,7 @@ export default class Component {
const node = body[i];
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.type !== 'Literal') return false;
@ -1192,11 +1273,7 @@ export default class Component {
if (v.export_name) return false;
if (this.var_lookup.get(name).reassigned) return false;
if (
this.vars.find(
variable => variable.name === name && variable.module
)
) {
if (this.vars.find((variable) => variable.name === name && variable.module)) {
return false;
}
@ -1204,7 +1281,7 @@ export default class Component {
});
if (all_hoistable) {
node.declarations.forEach(d => {
node.declarations.forEach((d) => {
const variable = this.var_lookup.get((d.id as Identifier).name);
variable.hoistable = true;
});
@ -1232,7 +1309,7 @@ export default class Component {
const checked = new Set();
const walking = new Set();
const is_hoistable = fn_declaration => {
const is_hoistable = (fn_declaration) => {
if (fn_declaration.type === 'ExportNamedDeclaration') {
fn_declaration = fn_declaration.declaration;
}
@ -1254,7 +1331,9 @@ export default class Component {
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 owner = scope.find_owner(name);
@ -1272,9 +1351,7 @@ export default class Component {
if (variable.hoistable) return;
if (top_level_function_declarations.has(name)) {
const other_declaration = top_level_function_declarations.get(
name
);
const other_declaration = top_level_function_declarations.get(name);
if (walking.has(other_declaration)) {
hoistable = false;
@ -1341,7 +1418,7 @@ export default class Component {
declaration: Node;
}> = [];
this.ast.instance.content.body.forEach(node => {
this.ast.instance.content.body.forEach((node) => {
const ignores = extract_svelte_ignore_from_comments(node);
if (ignores.length) this.push_ignores(ignores);
@ -1354,10 +1431,23 @@ export default class Component {
const module_dependencies = new Set<string>();
let scope = this.instance_scope;
const { declarations: outset_scope_decalarations } = this.instance_scope;
const map = this.instance_scope_map;
walk(node.body, {
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)) {
scope = map.get(node);
}
@ -1365,7 +1455,7 @@ export default class Component {
if (node.type === 'AssignmentExpression') {
const left = get_object(node.left);
extract_identifiers(left).forEach(node => {
extract_identifiers(left).forEach((node) => {
assignee_nodes.add(node);
assignees.add(node.name);
});
@ -1376,7 +1466,9 @@ export default class Component {
} else if (node.type === 'UpdateExpression') {
const identifier = get_object(node.argument);
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);
if (!assignee_nodes.has(identifier)) {
const { name } = identifier;
@ -1391,8 +1483,7 @@ export default class Component {
module_dependencies.add(name);
}
}
const is_writable_or_mutated =
variable && (variable.writable || variable.mutated);
const is_writable_or_mutated = variable && (variable.writable || variable.mutated);
if (
should_add_as_dependency &&
(!owner || owner === component.instance_scope) &&
@ -1414,7 +1505,12 @@ export default class Component {
});
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;
@ -1433,8 +1529,8 @@ export default class Component {
const lookup = new Map();
unsorted_reactive_declarations.forEach(declaration => {
declaration.assignees.forEach(name => {
unsorted_reactive_declarations.forEach((declaration) => {
declaration.assignees.forEach((name) => {
if (!lookup.has(name)) {
lookup.set(name, []);
}
@ -1445,16 +1541,18 @@ export default class Component {
});
});
const cycle = check_graph_for_cycles(unsorted_reactive_declarations.reduce((acc, declaration) => {
declaration.assignees.forEach(v => {
declaration.dependencies.forEach(w => {
if (!declaration.assignees.has(w)) {
acc.push([v, w]);
}
const cycle = check_graph_for_cycles(
unsorted_reactive_declarations.reduce((acc, declaration) => {
declaration.assignees.forEach((v) => {
declaration.dependencies.forEach((w) => {
if (!declaration.assignees.has(w)) {
acc.push([v, w]);
}
});
});
});
return acc;
}, []));
return acc;
}, [])
);
if (cycle && cycle.length) {
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));
}
const add_declaration = declaration => {
const add_declaration = (declaration) => {
if (this.reactive_declarations.includes(declaration)) return;
declaration.dependencies.forEach(name => {
declaration.dependencies.forEach((name) => {
if (declaration.assignees.has(name)) return;
const earlier_declarations = lookup.get(name);
if (earlier_declarations) {
@ -1479,9 +1577,15 @@ export default class Component {
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) {
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));
}
@ -1524,7 +1628,7 @@ function process_component_options(component: Component, nodes) {
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 }) {
const { value } = attribute;
@ -1546,27 +1650,117 @@ function process_component_options(component: Component, nodes) {
}
if (node) {
node.attributes.forEach(attribute => {
node.attributes.forEach((attribute) => {
if (attribute.type === '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) {
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) {
return component.error(attribute, compiler_errors.invalid_tag_attribute);
const { value } = 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)) {
return component.error(attribute, compiler_errors.invalid_tag_property);
const tag = value[0].expression.properties.find((prop: any) => prop.key.name === 'tag');
if (tag) {
parse_tag(tag, tag.value?.value);
} else {
return component.error(attribute, compiler_errors.invalid_customElement_attribute);
}
if (tag && !component.compile_options.customElement) {
component.warn(attribute, compiler_warnings.missing_custom_element_compile_options);
const props = value[0].expression.properties.find(
(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;
}
@ -1579,7 +1773,10 @@ function process_component_options(component: Component, nodes) {
if (valid_namespaces.indexOf(ns) === -1) {
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;
@ -1600,7 +1797,10 @@ function process_component_options(component: Component, nodes) {
}
default:
return component.error(attribute, compiler_errors.invalid_options_attribute_unknown);
return component.error(
attribute,
compiler_errors.invalid_options_attribute_unknown(name)
);
}
} else {
return component.error(attribute, compiler_errors.invalid_options_attribute);

@ -22,7 +22,9 @@ export default {
}),
invalid_binding_no_checkbox: (binding: string, is_radio: boolean) => ({
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) => ({
code: 'invalid-binding',
@ -30,7 +32,9 @@ export default {
}),
invalid_binding_window: (parts: string[]) => ({
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: {
code: 'invalid-binding',
@ -54,23 +58,24 @@ export default {
}),
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: {
code: 'missing-type',
message: '\'type\' attribute must be specified'
message: "'type' attribute must be specified"
},
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: {
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: {
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) => ({
code: 'invalid-event-modifier',
@ -90,7 +95,8 @@ export default {
},
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) => ({
code: 'illegal-attribute',
@ -106,7 +112,8 @@ export default {
}),
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: {
code: 'invalid-attribute',
@ -158,13 +165,14 @@ export default {
},
duplicate_transition: (directive: string, parent_directive: string) => {
function describe(_directive: string) {
return _directive === 'transition'
? "a 'transition'"
: `an '${_directive}'`;
return _directive === 'transition' ? "a 'transition'" : `an '${_directive}'`;
}
const message = directive === parent_directive
? `An element can only have one '${directive}' directive`
: `An element cannot have both ${describe(parent_directive)} directive and ${describe(directive)} directive`;
const message =
directive === parent_directive
? `An element can only have one '${directive}' directive`
: `An element cannot have both ${describe(parent_directive)} directive and ${describe(
directive
)} directive`;
return {
code: 'duplicate-transition',
message
@ -172,7 +180,8 @@ export default {
},
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: {
code: 'default-export',
@ -202,13 +211,30 @@ export default {
code: 'invalid-tag-property',
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: {
code: 'invalid-tag-attribute',
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) => ({
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: {
code: 'invalid-namespace-attribute',
@ -218,13 +244,14 @@ export default {
code: `invalid-${name}-value`,
message: `${name} attribute must be true or false`
}),
invalid_options_attribute_unknown: {
invalid_options_attribute_unknown: (name: string) => ({
code: 'invalid-options-attribute',
message: '<svelte:options> unknown attribute'
},
message: `<svelte:options> unknown attribute '${name}'`
}),
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: {
code: 'css-invalid-global',
@ -236,7 +263,8 @@ export default {
},
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) => ({
code: 'css-invalid-selector',
@ -248,15 +276,18 @@ export default {
},
invalid_animation_immediate: {
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: {
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: {
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: {
code: 'invalid-animation',
@ -264,11 +295,13 @@ export default {
},
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: {
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) => ({
code: 'invalid-const-declaration',
@ -286,6 +319,10 @@ export default {
code: 'invalid-component-style-directive',
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) => ({
code: 'invalid-style-directive-modifier',
message: `Valid modifiers for style directives are: ${valid}`

@ -6,9 +6,9 @@ import { ARIAPropertyDefinition } from 'aria-query';
* @internal
*/
export default {
custom_element_no_tag: {
code: 'custom-element-no-tag',
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}/>'
tag_option_deprecated: {
code: 'tag-option-deprecated',
message: "'tag' option is deprecated — use 'customElement' instead"
},
unused_export_let: (component: string, property: string) => ({
code: 'unused-export-let',
@ -24,15 +24,22 @@ export default {
},
module_script_variable_reactive_declaration: (names: string[]) => ({
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) => ({
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: {
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) => ({
code: 'css-unused-selector',
@ -52,7 +59,7 @@ export default {
}),
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) => ({
code: 'invalid-html-attribute',
@ -78,10 +85,14 @@ export default {
message = `The value of '${attribute}' must be exactly one of true, false, or mixed`;
break;
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;
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;
default:
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) => ({
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) => ({
code: 'a11y-hidden',
@ -115,29 +128,52 @@ export default {
code: 'a11y-no-redundant-roles',
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',
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',
message: `A11y: Non-interactive element <${element}> cannot have interactive role '${role}'`
}),
a11y_role_has_required_aria_props: (role: string, props: string[]) => ({
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) => {
let message = `The attribute '${attribute}' is not supported by the role '${role}'.`;
if (is_implicit) {
message += ` This role is implicit on the element <${name}>.`;
}
a11y_role_supports_aria_props: (
attribute: string,
role: string,
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 {
code: 'a11y-role-supports-aria-props',
message: `A11y: ${message}`
};
},
return {
code: 'a11y-role-supports-aria-props',
message: `A11y: ${message}`
};
},
a11y_accesskey: {
code: 'a11y-accesskey',
message: 'A11y: Avoid using accesskey'
@ -162,6 +198,12 @@ export default {
code: 'a11y-missing-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: {
code: 'a11y-img-redundant-alt',
message: 'A11y: Screenreaders already announce <img> elements as an image.'
@ -196,7 +238,8 @@ export default {
}),
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) => ({
code: 'a11y-missing-content',
@ -212,7 +255,7 @@ export default {
},
redundant_event_modifier_for_touch: {
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: {
code: 'redundant-event-modifier',
@ -220,10 +263,11 @@ export default {
},
invalid_rest_eachblock_binding: (rest_element_name: string) => ({
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: {
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`;
helpers.sort((a, b) => (a.name < b.name) ? -1 : 1);
globals.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));
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))})`);
}
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) {
@ -46,26 +57,30 @@ function get_internal_globals(
globals: Array<{ name: string; alias: Identifier }>,
helpers: Array<{ name: string; alias: Identifier }>
) {
return globals.length > 0 && {
type: 'VariableDeclaration',
kind: 'const',
declarations: [{
type: 'VariableDeclarator',
id: {
type: 'ObjectPattern',
properties: globals.map(g => ({
type: 'Property',
method: false,
shorthand: false,
computed: false,
key: { type: 'Identifier', name: g.name },
value: g.alias,
kind: 'init'
}))
},
init: helpers.find(({ name }) => name === 'globals').alias
}]
};
return (
globals.length > 0 && {
type: 'VariableDeclaration',
kind: 'const',
declarations: [
{
type: 'VariableDeclarator',
id: {
type: 'ObjectPattern',
properties: globals.map((g) => ({
type: 'Property',
method: false,
shorthand: false,
computed: false,
key: { type: 'Identifier', name: g.name },
value: g.alias,
kind: 'init'
}))
},
init: helpers.find(({ name }) => name === 'globals').alias
}
]
}
);
}
function esm(
@ -82,7 +97,7 @@ function esm(
) {
const import_declaration = {
type: 'ImportDeclaration',
specifiers: helpers.map(h => ({
specifiers: helpers.map((h) => ({
type: 'ImportSpecifier',
local: h.alias,
imported: { type: 'Identifier', name: h.name }
@ -105,7 +120,7 @@ function esm(
const exports = module_exports.length > 0 && {
type: 'ExportNamedDeclaration',
specifiers: module_exports.map(x => ({
specifiers: module_exports.map((x) => ({
type: 'Specifier',
local: { type: 'Identifier', name: x.name },
exported: { type: 'Identifier', name: x.as }
@ -142,27 +157,29 @@ function cjs(
const internal_requires = {
type: 'VariableDeclaration',
kind: 'const',
declarations: [{
type: 'VariableDeclarator',
id: {
type: 'ObjectPattern',
properties: helpers.map(h => ({
type: 'Property',
method: false,
shorthand: false,
computed: false,
key: { type: 'Identifier', name: h.name },
value: h.alias,
kind: 'init'
}))
},
init: x`require("${internal_path}")`
}]
declarations: [
{
type: 'VariableDeclarator',
id: {
type: 'ObjectPattern',
properties: helpers.map((h) => ({
type: 'Property',
method: false,
shorthand: false,
computed: false,
key: { type: 'Identifier', name: h.name },
value: h.alias,
kind: 'init'
}))
},
init: x`require("${internal_path}")`
}
]
};
const internal_globals = 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)}")`;
if (node.specifiers.length === 0) {
return b`${init};`;
@ -170,32 +187,41 @@ function cjs(
return {
type: 'VariableDeclaration',
kind: 'const',
declarations: [{
type: 'VariableDeclarator',
id: node.specifiers[0].type === 'ImportNamespaceSpecifier'
? { type: 'Identifier', name: node.specifiers[0].local.name }
: {
type: 'ObjectPattern',
properties: node.specifiers.map(s => ({
type: 'Property',
method: false,
shorthand: false,
computed: false,
key: s.type === 'ImportSpecifier' ? s.imported : { type: 'Identifier', name: 'default' },
value: s.local,
kind: 'init'
}))
},
init
}]
declarations: [
{
type: 'VariableDeclarator',
id:
node.specifiers[0].type === 'ImportNamespaceSpecifier'
? { type: 'Identifier', name: node.specifiers[0].local.name }
: {
type: 'ObjectPattern',
properties: node.specifiers.map((s) => ({
type: 'Property',
method: false,
shorthand: false,
computed: false,
key:
s.type === 'ImportSpecifier'
? s.imported
: { type: 'Identifier', name: 'default' },
value: s.local,
kind: 'init'
}))
},
init
}
]
};
});
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)}")`;
return node.specifiers.map(specifier => {
return node.specifiers.map((specifier) => {
return b`exports.${specifier.exported} = ${init}.${specifier.local};`;
});
});

@ -18,7 +18,7 @@ enum BlockAppliesToNode {
}
enum NodeExist {
Probably = 1,
Definitely = 2,
Definitely = 2
}
const whitelist_attribute_selector = new Map([
@ -57,7 +57,7 @@ export default class Selector {
}
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);
@ -85,7 +85,9 @@ export default class Selector {
}
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) {
const first = selector.children[0];
@ -125,7 +127,13 @@ export default class Selector {
if (block.global) {
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++) {
const block = this.blocks[i];
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) {
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 (let index = 0; index < block.selectors.length; index++) {
const selector = block.selectors[index];
if (selector.type === 'PseudoClassSelector' &&
if (
selector.type === 'PseudoClassSelector' &&
selector.name === 'global' &&
index !== 0 &&
selector.children &&
@ -201,21 +220,24 @@ export default class Selector {
let count = 0;
for (const block of this.blocks) {
if (block.should_encapsulate) {
count ++;
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();
if (!block) return false;
if (!node) {
return (
(block.global && blocks.every(block => block.global)) ||
(block.host && blocks.length === 0)
(block.global && blocks.every((block) => block.global)) || (block.host && blocks.length === 0)
);
}
@ -224,7 +246,7 @@ function apply_selector(blocks: Block[], node: Element, to_encapsulate: Array<{
return false;
case BlockAppliesToNode.UnknownSelectorType:
// bail. TODO figure out what these could be
// bail. TODO figure out what these could be
to_encapsulate.push({ node, block });
return true;
}
@ -242,8 +264,10 @@ function apply_selector(blocks: Block[], node: Element, to_encapsulate: Array<{
}
let parent = node;
while (parent = get_element_parent(parent)) {
if (block_might_apply_to_node(ancestor_block, parent) !== BlockAppliesToNode.NotPossible) {
while ((parent = get_element_parent(parent))) {
if (
block_might_apply_to_node(ancestor_block, parent) !== BlockAppliesToNode.NotPossible
) {
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 });
return true;
}
return false;
} 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)) {
to_encapsulate.push({ node, block });
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
// 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
const has_global = blocks.some(block => block.global);
const has_global = blocks.some((block) => block.global);
if (has_global) {
if (siblings.size === 0 && get_element_parent(node) !== null) {
return false;
@ -309,13 +333,19 @@ function block_might_apply_to_node(block: Block, node: Element): BlockAppliesToN
while (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')) {
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;
}
@ -324,17 +354,38 @@ function block_might_apply_to_node(block: Block, node: Element): BlockAppliesToN
}
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') {
if (!attribute_matches(node, 'id', name, '=', false)) return BlockAppliesToNode.NotPossible;
} else if (selector.type === 'AttributeSelector') {
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;
}
} 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 {
return BlockAppliesToNode.UnknownSelectorType;
}
@ -349,18 +400,31 @@ function test_attribute(operator, expected_value, case_insensitive, value) {
value = value.toLowerCase();
}
switch (operator) {
case '=': return value === expected_value;
case '~=': return value.split(/\s/).includes(expected_value);
case '|=': return `${value}-`.startsWith(`${expected_value}-`);
case '^=': return value.startsWith(expected_value);
case '$=': return value.endsWith(expected_value);
case '*=': return value.includes(expected_value);
default: throw new Error("this shouldn't happen");
case '=':
return value === expected_value;
case '~=':
return value.split(/\s/).includes(expected_value);
case '|=':
return `${value}-`.startsWith(`${expected_value}-`);
case '^=':
return value.startsWith(expected_value);
case '$=':
return value.endsWith(expected_value);
case '*=':
return value.includes(expected_value);
default:
throw new Error("this shouldn't happen");
}
}
function attribute_matches(node: CssNode, name: string, expected_value: string, operator: string, case_insensitive: boolean) {
const spread = node.attributes.find(attr => attr.type === 'Spread');
function attribute_matches(
node: CssNode,
name: string,
expected_value: string,
operator: string,
case_insensitive: boolean
) {
const spread = node.attributes.find((attr) => attr.type === 'Spread');
if (spread) return true;
if (node.bindings.some((binding: 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) {
const value = attr.chunks[0];
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();
@ -403,7 +468,7 @@ function attribute_matches(node: CssNode, name: string, expected_value: string,
if (remaining.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 = [];
@ -423,7 +488,7 @@ function attribute_matches(node: CssNode, name: string, expected_value: string,
});
continue;
} else {
prev_values.forEach(prev_value => possible_values.add(prev_value));
prev_values.forEach((prev_value) => possible_values.add(prev_value));
prev_values = [];
}
}
@ -444,7 +509,7 @@ function attribute_matches(node: CssNode, name: string, expected_value: string,
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;
@ -458,7 +523,7 @@ function attribute_matches(node: CssNode, name: string, expected_value: string,
function unquote(value: CssNode) {
if (value.type === 'Identifier') return value.name;
const str = value.value;
if (str[0] === str[str.length - 1] && str[0] === "'" || str[0] === '"') {
if ((str[0] === str[str.length - 1] && str[0] === "'") || str[0] === '"') {
return str.slice(1, str.length - 1);
}
return str;
@ -470,12 +535,55 @@ function get_element_parent(node: Element): 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();
let prev: INode = node;
while (prev = prev.prev) {
while ((prev = find_previous_sibling(prev))) {
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);
}
@ -495,7 +603,13 @@ function get_possible_element_siblings(node: INode, adjacent_only: boolean): Map
if (!prev || !adjacent_only) {
let parent: INode = node;
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);
add_to_map(possible_siblings, result);
@ -520,12 +634,17 @@ function get_possible_element_siblings(node: INode, adjacent_only: boolean): Map
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();
if (block.type === 'EachBlock') {
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);
@ -537,7 +656,9 @@ function get_possible_last_child(block: EachBlock | IfBlock | AwaitBlock, adjace
add_to_map(else_result, result);
} else if (block.type === 'IfBlock') {
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);
@ -549,11 +670,20 @@ function get_possible_last_child(block: EachBlock | IfBlock | AwaitBlock, adjace
add_to_map(if_result, result);
add_to_map(else_result, result);
} else if (block.type === 'AwaitBlock') {
const pending_result: Map<Element, NodeExist> = block.pending ? loop_child(block.pending.children, adjacent_only) : new Map();
const then_result: Map<Element, NodeExist> = block.then ? 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);
const pending_result: Map<Element, NodeExist> = block.pending
? loop_child(block.pending.children, adjacent_only)
: new Map();
const then_result: Map<Element, NodeExist> = block.then
? 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) {
mark_as_probably(pending_result);
@ -605,7 +735,11 @@ function loop_child(children: INode[], adjacent_only: boolean) {
if (adjacent_only) {
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);
add_to_map(child_result, result);
if (adjacent_only && has_definite_elements(child_result)) {
@ -642,7 +776,7 @@ class Block {
this.start = selector.start;
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.end = selector.end;
@ -653,7 +787,10 @@ class Block {
this.selectors.length >= 1 &&
this.selectors[0].type === 'PseudoClassSelector' &&
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, '');
}
const is_keyframes_node = (node: CssNode) =>
remove_css_prefix(node.name) === 'keyframes';
const is_keyframes_node = (node: CssNode) => remove_css_prefix(node.name) === 'keyframes';
const at_rule_has_declaration = ({ block }: CssNode): true =>
block &&
block.children &&
block.children.find((node: CssNode) => node.type === 'Declaration');
block && block.children && block.children.find((node: CssNode) => node.type === 'Declaration');
function minify_declarations(
code: MagicString,
@ -34,7 +31,7 @@ function minify_declarations(
declarations.forEach((declaration, i) => {
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);
}
declaration.minify(code);
@ -58,13 +55,14 @@ class Rule {
}
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) {
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;
return this.selectors.some(s => s.used);
return this.selectors.some((s) => s.used);
}
minify(code: MagicString, _dev: boolean) {
@ -74,7 +72,7 @@ class Rule {
this.selectors.forEach((selector) => {
if (selector.used) {
const separator = started ? ',' : '';
if ((selector.node.start - c) > separator.length) {
if (selector.node.start - c > separator.length) {
code.update(c, selector.node.start, separator);
}
@ -93,29 +91,39 @@ class Rule {
code.remove(c, this.node.block.end - 1);
}
transform(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;
transform(
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}`;
this.selectors.forEach(selector => selector.transform(code, attr, max_amount_class_specificity_increased));
this.declarations.forEach(declaration => declaration.transform(code, keyframes));
this.selectors.forEach((selector) =>
selector.transform(code, attr, max_amount_class_specificity_increased)
);
this.declarations.forEach((declaration) => declaration.transform(code, keyframes));
}
validate(component: Component) {
this.selectors.forEach(selector => {
this.selectors.forEach((selector) => {
selector.validate(component);
});
}
warn_on_unused_selector(handler: (selector: Selector) => void) {
this.selectors.forEach(selector => {
this.selectors.forEach((selector) => {
if (!selector.used) handler(selector);
});
}
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?
const c = this.node.start + this.node.property.length;
const first = this.node.value.children
? this.node.value.children[0]
: this.node.value;
const first = this.node.value.children ? this.node.value.children[0] : this.node.value;
// Don't minify whitespace in custom properties, since some browsers (Chromium < 99)
// treat --foo: ; and --foo:; differently
@ -173,13 +179,18 @@ class Atrule {
}
apply(node: Element) {
if (this.node.name === 'container' || this.node.name === 'media' || this.node.name === 'supports' || this.node.name === 'layer') {
this.children.forEach(child => {
if (
this.node.name === 'container' ||
this.node.name === 'media' ||
this.node.name === 'supports' ||
this.node.name === 'layer'
) {
this.children.forEach((child) => {
child.apply(node);
});
} else if (is_keyframes_node(this.node)) {
this.children.forEach((rule: Rule) => {
rule.selectors.forEach(selector => {
rule.selectors.forEach((selector) => {
selector.used = true;
});
});
@ -231,7 +242,7 @@ class Atrule {
if (this.children.length) c++;
}
this.children.forEach(child => {
this.children.forEach((child) => {
if (child.is_used(dev)) {
code.remove(c, child.node.start);
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)) {
this.node.prelude.children.forEach(({ type, name, start, end }: CssNode) => {
if (type === 'Identifier') {
if (name.startsWith('-global-')) {
code.remove(start, start + 8);
this.children.forEach((rule: Rule) => {
rule.selectors.forEach(selector => {
rule.selectors.forEach((selector) => {
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);
});
}
validate(component: Component) {
this.children.forEach(child => {
this.children.forEach((child) => {
child.validate(component);
});
}
@ -275,13 +291,15 @@ class Atrule {
warn_on_unused_selector(handler: (selector: Selector) => void) {
if (this.node.name !== 'media') return;
this.children.forEach(child => {
this.children.forEach((child) => {
child.warn_on_unused_selector(handler);
});
}
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)) {
const at_rule_declarations = node.block.children
.filter(node => node.type === 'Declaration')
.map(node => new Declaration(node));
.filter((node) => node.type === 'Declaration')
.map((node) => new Declaration(node));
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) {
return { code: null, map: null };
}
@ -421,15 +439,15 @@ export default class Stylesheet {
}
});
if (should_transform_selectors) {
const max = Math.max(...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);
});
}
const max = Math.max(
...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);
});
let c = 0;
this.children.forEach(child => {
this.children.forEach((child) => {
if (child.is_used(this.dev)) {
code.remove(c, child.node.start);
child.minify(code, this.dev);
@ -450,17 +468,24 @@ export default class Stylesheet {
}
validate(component: Component) {
this.children.forEach(child => {
this.children.forEach((child) => {
child.validate(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);
this.children.forEach(child => {
this.children.forEach((child) => {
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();

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

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

@ -27,6 +27,8 @@ export default class AwaitBlock extends Node {
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info);
this.cannot_use_innerhtml();
this.not_static_content();
this.expression = new Expression(component, this, scope, info.expression);
@ -35,12 +37,24 @@ export default class AwaitBlock extends Node {
if (this.then_node) {
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) {
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);

@ -37,7 +37,12 @@ export default class Binding extends Node {
is_contextual: 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);
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);
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);
// 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);
}
scope.dependencies_for_name.get(name).forEach(name => {
scope.dependencies_for_name.get(name).forEach((name) => {
const variable = component.var_lookup.get(name);
if (variable) {
variable.mutated = true;
@ -96,7 +103,7 @@ export default class Binding extends Node {
regex_box_size.test(this.name) ||
(isElement(parent) &&
((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() {
@ -104,12 +111,15 @@ export default class Binding extends Node {
}
validate_binding_rest_properties(scope: TemplateScope) {
this.expression.references.forEach(name => {
this.expression.references.forEach((name) => {
const each_block = scope.get_owner(name);
if (each_block && each_block.type === 'EachBlock') {
const rest_node = each_block.context_rest_properties.get(name);
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();
if (parent.catch_node) {
parent.catch_contexts.forEach(context => {
parent.catch_contexts.forEach((context) => {
if (context.type !== 'DestructuredVariable') return;
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) {
this.warn_if_empty_block();

@ -12,7 +12,15 @@ import get_object from '../utils/get_object';
import compiler_errors from '../compiler_errors';
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 {
type: 'ConstTag';
@ -23,9 +31,14 @@ export default class ConstTag extends Node {
context_rest_properties: Map<string, ESTreeNode> = new Map();
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);
if (!allowed_parents.has(parent.type)) {
@ -37,22 +50,24 @@ export default class ConstTag extends Node {
const { assignees, dependencies } = this;
extract_identifiers(info.expression.left).forEach(({ name }) => {
assignees.add(name);
assignees.add(name);
const owner = this.scope.get_owner(name);
if (owner === parent) {
component.error(info, compiler_errors.invalid_const_declaration(name));
}
});
});
walk(info.expression.right, {
enter(node, parent) {
if (is_reference(node as NodeWithPropertyDefinition, parent as NodeWithPropertyDefinition)) {
const identifier = get_object(node as any);
const { name } = identifier;
dependencies.add(name);
}
}
});
walk(info.expression.right, {
enter(node, parent) {
if (
is_reference(node as NodeWithPropertyDefinition, parent as NodeWithPropertyDefinition)
) {
const identifier = get_object(node as any);
const { name } = identifier;
dependencies.add(name);
}
}
});
}
parse_expression() {
@ -64,11 +79,14 @@ export default class ConstTag extends Node {
context_rest_properties: this.context_rest_properties
});
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;
const owner = this.scope.get_owner(context.key.name);
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);
});

@ -10,10 +10,7 @@ import { Element } from '../../interfaces';
import compiler_warnings from '../compiler_warnings';
import compiler_errors from '../compiler_errors';
const valid_bindings = [
'fullscreenElement',
'visibilityState'
];
const valid_bindings = ['fullscreenElement', 'visibilityState'];
export default class Document extends Node {
type: 'Document';
@ -31,9 +28,23 @@ export default class Document extends Node {
if (!~valid_bindings.indexOf(node.name)) {
const match = fuzzymatch(node.name, valid_bindings);
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 {
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() {
const handlers_map = new Set();
this.handlers.forEach(handler => (
handlers_map.add(handler.name)
));
this.handlers.forEach((handler) => handlers_map.add(handler.name));
if (handlers_map.has('mouseenter') || handlers_map.has('mouseleave')) {
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) {
super(component, parent, scope, info);
this.cannot_use_innerhtml();
this.not_static_content();
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
@ -42,9 +44,15 @@ export default class EachBlock extends AbstractBlock {
this.scope = scope.child();
this.context_rest_properties = new Map();
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;
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.key = info.key
? new Expression(component, this, this.scope, info.key)
: null;
this.key = info.key ? new Expression(component, this, this.scope, info.key) : null;
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) {
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) {
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);
return;
}
@ -75,9 +81,7 @@ export default class EachBlock extends AbstractBlock {
this.warn_if_empty_block();
this.else = info.else
? new ElseBlock(component, this, this.scope, info.else)
: null;
this.else = info.else ? 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);
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();
}

@ -17,7 +17,12 @@ export default class EventHandler extends Node {
uses_context = 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);
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.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
// `event` is passed to another function, we can make it passive
this.can_make_passive = true;
@ -37,11 +45,19 @@ export default class EventHandler extends Node {
if (node) {
if (node.type === 'VariableDeclaration') {
// for `const handleClick = () => {...}`, we want the [arrow] function expression node
const declarator = node.declarations.find(d => (d.id as Identifier).name === info.expression.name);
const declarator = node.declarations.find(
(d) => (d.id as Identifier).name === info.expression.name
);
node = declarator && declarator.init;
}
if (node && (node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration' || node.type === 'ArrowFunctionExpression') && node.params.length === 0) {
if (
node &&
(node.type === 'FunctionExpression' ||
node.type === 'FunctionDeclaration' ||
node.type === 'ArrowFunctionExpression') &&
node.params.length === 0
) {
this.can_make_passive = true;
}
}

@ -15,14 +15,21 @@ export default class Head extends Node {
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info);
this.cannot_use_innerhtml();
if (info.attributes.length) {
component.error(info.attributes[0], compiler_errors.invalid_attribute_head);
return;
}
this.children = map_children(component, parent, scope, info.children.filter(child => {
return (child.type !== 'Text' || regex_non_whitespace_character.test(child.data));
}));
this.children = map_children(
component,
parent,
scope,
info.children.filter((child) => {
return child.type !== 'Text' || regex_non_whitespace_character.test(child.data);
})
);
if (this.children.length > 0) {
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) {
super(component, parent, scope, info);
this.scope = scope.child();
this.cannot_use_innerhtml();
this.not_static_content();
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
? new ElseBlock(component, this, scope, info.else)
: null;
this.else = info.else ? new ElseBlock(component, this, scope, info.else) : null;
this.warn_if_empty_block();
}

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

@ -2,4 +2,9 @@ import Tag from './shared/Tag';
export default class RawMustacheTag extends Tag {
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';
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;
children: INode[];
slot_name: string;
@ -16,7 +17,7 @@ export default class Slot extends Element {
constructor(component: Component, parent: INode, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info);
info.attributes.forEach(attr => {
info.attributes.forEach((attr) => {
if (attr.type !== 'Attribute' && attr.type !== 'Spread') {
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);
this.cannot_use_innerhtml();
this.not_static_content();
}
}

@ -17,12 +17,7 @@ export default class SlotTemplate extends Node {
slot_attribute: Attribute;
slot_template_name: string = 'default';
constructor(
component: Component,
parent: INode,
scope: TemplateScope,
info: any
) {
constructor(component: Component, parent: INode, scope: TemplateScope, info: any) {
super(component, parent, scope, info);
this.validate_slot_template_placement();
@ -62,7 +57,7 @@ export default class SlotTemplate extends Node {
});
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() {

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

@ -8,16 +8,10 @@ import { regex_non_whitespace_character } from '../../utils/patterns';
// Whitespace inside one of these elements will not result in
// a whitespace node being created in any circumstances. (This
// list is almost certainly very incomplete)
const elements_without_text = new Set([
'audio',
'datalist',
'dl',
'optgroup',
'select',
'video'
]);
const elements_without_text = new Set(['audio', 'datalist', 'dl', 'optgroup', 'select', 'video']);
const regex_ends_with_svg = /svg$/;
const regex_non_whitespace_characters = /[\S\u00A0]/;
export default class Text extends Node {
type: 'Text';
@ -37,7 +31,8 @@ export default class Text extends Node {
if (!parent_element) return false;
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
if (regex_ends_with_svg.test(parent_element.namespace)) {
@ -63,4 +58,11 @@ export default class Text extends Node {
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();
if (parent.then_node) {
parent.then_contexts.forEach(context => {
parent.then_contexts.forEach((context) => {
if (context.type !== 'DestructuredVariable') return;
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) {
this.warn_if_empty_block();

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

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

@ -30,7 +30,7 @@ export default class Window extends Node {
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info);
info.attributes.forEach(node => {
info.attributes.forEach((node) => {
if (node.type === 'EventHandler') {
this.handlers.push(new EventHandler(component, this, scope, node));
} else if (node.type === 'Binding') {
@ -42,16 +42,31 @@ export default class Window extends Node {
}
if (!~valid_bindings.indexOf(node.name)) {
const match = (
node.name === 'width' ? 'innerWidth' :
node.name === 'height' ? 'innerHeight' :
fuzzymatch(node.name, valid_bindings)
);
const match =
node.name === 'width'
? 'innerWidth'
: node.name === 'height'
? 'innerHeight'
: fuzzymatch(node.name, valid_bindings);
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 {
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
// https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#discriminating-unions
export type INode = Action
| Animation
| Attribute
| AwaitBlock
| Binding
| Body
| CatchBlock
| Class
| Comment
| ConstTag
| DebugTag
| Document
| EachBlock
| Element
| ElseBlock
| EventHandler
| Fragment
| Head
| IfBlock
| InlineComponent
| KeyBlock
| Let
| MustacheTag
| Options
| PendingBlock
| RawMustacheTag
| Slot
| SlotTemplate
| StyleDirective
| Tag
| Text
| ThenBlock
| Title
| Transition
| Window;
export type INode =
| Action
| Animation
| Attribute
| AwaitBlock
| Binding
| Body
| CatchBlock
| Class
| Comment
| ConstTag
| DebugTag
| Document
| EachBlock
| Element
| ElseBlock
| EventHandler
| Fragment
| Head
| IfBlock
| InlineComponent
| KeyBlock
| Let
| MustacheTag
| Options
| PendingBlock
| RawMustacheTag
| Slot
| SlotTemplate
| StyleDirective
| Tag
| Text
| ThenBlock
| Title
| Transition
| Window;
export type INodeAllowConstTag =
| IfBlock
| ElseBlock
| EachBlock
| CatchBlock
| ThenBlock
| InlineComponent
| SlotTemplate;
| IfBlock
| ElseBlock
| EachBlock
| CatchBlock
| ThenBlock
| InlineComponent
| SlotTemplate;

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

@ -24,7 +24,7 @@ type Owner = INode;
const regex_contains_term_function_expression = /FunctionExpression/;
export default class Expression {
type: 'Expression' = 'Expression';
type: 'Expression' = 'Expression' as const;
component: Component;
owner: Owner;
node: Node;
@ -36,12 +36,18 @@ export default class Expression {
scope: Scope;
scope_map: WeakMap<Node, Scope>;
declarations: Array<(Node | Node[])> = [];
declarations: Array<Node | Node[]> = [];
uses_context = false;
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?
Object.defineProperties(this, {
component: {
@ -105,7 +111,9 @@ export default class Expression {
const is_index = owner.type === 'EachBlock' && owner.key && name === owner.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 {
if (!lazy) {
@ -128,19 +136,19 @@ export default class Expression {
deep = node.left.type === 'MemberExpression';
names = extract_names(deep ? get_object(node.left) : node.left);
} else if (node.type === 'UpdateExpression') {
deep = node.argument.type === 'MemberExpression';
deep = node.argument.type === 'MemberExpression';
names = extract_names(get_object(node.argument));
}
}
if (names) {
names.forEach(name => {
names.forEach((name) => {
if (template_scope.names.has(name)) {
if (template_scope.is_const(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);
if (variable) variable[deep ? 'mutated' : 'reassigned'] = true;
});
@ -188,7 +196,7 @@ export default class Expression {
}
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 (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?
manipulate(block?: Block, ctx?: string | void) {
// TODO ideally we wouldn't end up calling this method
// multiple times
if (this.manipulated) return this.manipulated;
const {
component,
declarations,
scope_map: map,
template_scope,
owner
} = this;
const { component, declarations, scope_map: map, template_scope, owner } = this;
let scope = this.scope;
let function_expression;
@ -237,7 +250,7 @@ export default class Expression {
if (template_scope.names.has(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);
});
} else {
@ -269,9 +282,7 @@ export default class Expression {
if (map.has(node)) scope = scope.parent;
if (node === function_expression) {
const id = component.get_unique_name(
sanitize(get_function_name(node, owner))
);
const id = component.get_unique_name(sanitize(get_function_name(node, owner)));
const declaration = b`const ${id} = ${node}`;
const extract_functions = () => {
@ -280,11 +291,11 @@ export default class Expression {
const has_args = function_expression.params.length > 0;
function_expression.params = [
...deps.map(name => ({ type: 'Identifier', name } as Identifier)),
...deps.map((name) => ({ type: 'Identifier', name } as Identifier)),
...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);
@ -346,7 +357,7 @@ export default class Expression {
const { deps, func_declaration } = extract_functions();
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
let node: INode = owner.parent;
while (node && !dep_scopes.has(node)) {
@ -376,7 +387,7 @@ export default class Expression {
type: 'DestructuredVariable',
key: func_id,
modifier: () => func_expression,
default_modifier: node => node
default_modifier: (node) => node
});
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 traced: Set<string> = new Set();
names.forEach(name => {
names.forEach((name) => {
const dependencies = template_scope.dependencies_for_name.get(name);
if (dependencies) {
dependencies.forEach(name => traced.add(name));
dependencies.forEach((name) => traced.add(name));
} else {
traced.add(name);
}
@ -441,7 +452,7 @@ export default class Expression {
if (declarations.length > 0) {
block.maintain_context = true;
declarations.forEach(declaration => {
declarations.forEach((declaration) => {
block.chunks.init.push(declaration);
});
}

@ -15,6 +15,7 @@ export default class Node {
next?: INode;
can_use_innerhtml: boolean;
is_static_content: boolean;
var: string;
attributes: Attribute[];
@ -33,6 +34,9 @@ export default class Node {
value: parent
}
});
this.can_use_innerhtml = true;
this.is_static_content = true;
}
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) {
if (selector.test(this.type)) return this;
if (this.parent) return this.parent.find_nearest(selector);
}
get_static_attribute_value(name: string) {
const attribute = this.attributes && this.attributes.find(
(attr: Attribute) => attr.type === 'Attribute' && attr.name.toLowerCase() === name
);
const attribute =
this.attributes &&
this.attributes.find(
(attr: Attribute) => attr.type === 'Attribute' && attr.name.toLowerCase() === name
);
if (!attribute) return null;
@ -65,8 +76,6 @@ export default class Node {
}
has_ancestor(type: string) {
return this.parent ?
this.parent.type === type || this.parent.has_ancestor(type) :
false;
return this.parent ? 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) {
super(component, parent, scope, info);
component.tags.push(this);
this.cannot_use_innerhtml();
this.expression = new Expression(component, this, scope, info.expression);
this.should_cache = (
this.should_cache =
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 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 {
names: Set<string>;
@ -33,7 +40,7 @@ export default class TemplateScope {
}
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 {
@ -42,7 +49,12 @@ export default class TemplateScope {
is_let(name: string) {
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) {

@ -6,86 +6,93 @@ import { INodeAllowConstTag, INode } from '../interfaces';
import check_graph_for_cycles from '../../utils/check_graph_for_cycles';
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>>] {
const const_tags: ConstTagType[] = [];
const others: Array<Exclude<TemplateNode, ConstTagType>> = [];
for (const child of children) {
if (child.type === 'ConstTag') {
const_tags.push(child as ConstTagType);
} else {
others.push(child);
}
}
const consts_nodes = const_tags.map(tag => new ConstTag(component, node, node.scope, tag));
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>>];
export default function get_const_tags(
children: TemplateNode[],
component: Component,
node: INodeAllowConstTag,
parent: INode
): [ConstTag[], Array<Exclude<INode, ConstTag>>] {
const const_tags: ConstTagType[] = [];
const others: Array<Exclude<TemplateNode, ConstTagType>> = [];
for (const child of children) {
if (child.type === 'ConstTag') {
const_tags.push(child as ConstTagType);
} else {
others.push(child);
}
}
const consts_nodes = const_tags.map((tag) => new ConstTag(component, node, node.scope, tag));
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) {
type ConstNode = {
assignees: Set<string>;
dependencies: Set<string>;
node: ConstTag;
};
const sorted_consts_nodes: ConstNode[] = [];
const unsorted_consts_nodes: ConstNode[] = consts_nodes.map(node => {
return {
assignees: node.assignees,
dependencies: node.dependencies,
node
};
});
const lookup = new Map();
unsorted_consts_nodes.forEach(node => {
node.assignees.forEach(name => {
if (!lookup.has(name)) {
lookup.set(name, []);
}
lookup.get(name).push(node);
});
});
const cycle = check_graph_for_cycles(unsorted_consts_nodes.reduce((acc, node) => {
node.assignees.forEach(v => {
node.dependencies.forEach(w => {
if (!node.assignees.has(w)) {
acc.push([v, w]);
}
});
});
return acc;
}, []));
if (cycle && cycle.length) {
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;
node.dependencies.forEach(name => {
if (node.assignees.has(name)) return;
const earlier_nodes = lookup.get(name);
if (earlier_nodes) {
earlier_nodes.forEach(add_node);
}
});
sorted_consts_nodes.push(node);
};
unsorted_consts_nodes.forEach(add_node);
return sorted_consts_nodes.map(node => node.node);
type ConstNode = {
assignees: Set<string>;
dependencies: Set<string>;
node: ConstTag;
};
const sorted_consts_nodes: ConstNode[] = [];
const unsorted_consts_nodes: ConstNode[] = consts_nodes.map((node) => {
return {
assignees: node.assignees,
dependencies: node.dependencies,
node
};
});
const lookup = new Map();
unsorted_consts_nodes.forEach((node) => {
node.assignees.forEach((name) => {
if (!lookup.has(name)) {
lookup.set(name, []);
}
lookup.get(name).push(node);
});
});
const cycle = check_graph_for_cycles(
unsorted_consts_nodes.reduce((acc, node) => {
node.assignees.forEach((v) => {
node.dependencies.forEach((w) => {
if (!node.assignees.has(w)) {
acc.push([v, w]);
}
});
});
return acc;
}, [])
);
if (cycle && cycle.length) {
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;
node.dependencies.forEach((name) => {
if (node.assignees.has(name)) return;
const earlier_nodes = lookup.get(name);
if (earlier_nodes) {
earlier_nodes.forEach(add_node);
}
});
sorted_consts_nodes.push(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) {
switch (type) {
case 'AwaitBlock': return AwaitBlock;
case 'Body': return Body;
case 'Comment': return Comment;
case 'ConstTag': return ConstTag;
case 'Document': return Document;
case 'EachBlock': return EachBlock;
case 'Element': return Element;
case 'Head': return Head;
case 'IfBlock': return IfBlock;
case 'InlineComponent': return InlineComponent;
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}`);
case 'AwaitBlock':
return AwaitBlock;
case 'Body':
return Body;
case 'Comment':
return Comment;
case 'ConstTag':
return ConstTag;
case 'Document':
return Document;
case 'EachBlock':
return EachBlock;
case 'Element':
return Element;
case 'Head':
return Head;
case 'IfBlock':
return IfBlock;
case 'InlineComponent':
return InlineComponent;
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 ignores = [];
return children.map(child => {
return children.map((child) => {
const constructor = get_constructor(child.type);
const use_ignores = child.type !== 'Text' && child.type !== 'Comment' && ignores.length;
if (use_ignores) component.push_ignores(ignores);
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) {
push_array(ignores, node.ignores);

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

@ -3,7 +3,16 @@ import { CompileOptions, Var } from '../../interfaces';
import Component from '../Component';
import FragmentWrapper from './wrappers/Fragment';
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 { reserved_keywords } from '../utils/reserved_keywords';
import { renderer_invalidate } from './invalidate';
@ -57,12 +66,14 @@ export default class Renderer {
this.file_var = options.dev && this.component.get_unique_name('file');
component.vars.filter(v => !v.hoistable || (v.export_name && !v.module)).forEach(v => this.add_to_context(v.name));
component.vars
.filter((v) => !v.hoistable || (v.export_name && !v.module))
.forEach((v) => this.add_to_context(v.name));
// ensure store values are included in context
component.vars.filter(v => v.subscribable).forEach(v => this.add_to_context(`$${v.name}`));
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)) {
this.add_to_context(keyword);
}
@ -97,7 +108,7 @@ export default class Renderer {
);
// TODO messy
this.blocks.forEach(block => {
this.blocks.forEach((block) => {
if (block instanceof Block) {
block.assign_variable_names();
}
@ -109,7 +120,7 @@ export default class Renderer {
this.context_overflow = this.context.length > 31;
this.context.forEach(member => {
this.context.forEach((member) => {
const { variable } = member;
if (variable) {
member.priority += 2;
@ -117,7 +128,8 @@ export default class Renderer {
// these determine whether variable is included in initial context
// 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.referenced) member.priority += 64;
} 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.forEach((member, i) => member.index.value = i);
this.context.sort(
(a, b) => b.priority - a.priority || (a.index.value as number) - (b.index.value as number)
);
this.context.forEach((member, i) => (member.index.value = i));
let i = this.context.length;
while (i--) {
const member = this.context[i];
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) {
break;
}
@ -180,9 +200,9 @@ export default class Renderer {
dirty(names: string[], is_reactive_declaration = false): Expression {
const renderer = this;
const dirty = (is_reactive_declaration
? x`$$self.$$.dirty`
: x`#dirty`) as Identifier | MemberExpression;
const dirty = (is_reactive_declaration ? x`$$self.$$.dirty` : x`#dirty`) as
| Identifier
| MemberExpression;
const get_bitmask = () => {
const bitmask: BitMasks = [];
@ -197,7 +217,7 @@ export default class Renderer {
const value = member.index.value as number;
const i = (value / 31) | 0;
const n = 1 << (value % 31);
const n = 1 << value % 31;
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 Component from '../Component';
import Renderer from './Renderer';
@ -6,13 +7,18 @@ import { walk } from 'estree-walker';
import { extract_names, Scope } from 'periscopic';
import { invalidate } from './invalidate';
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 { RawSourceMap, DecodedSourceMap } from '@ampproject/remapping/dist/types/types';
import { flatten } from '../../utils/flatten';
import check_enable_sourcemap from '../utils/check_enable_sourcemap';
import { push_array } from '../../utils/push_array';
import { regex_backslashes } from '../../utils/patterns';
export default function dom(
component: Component,
@ -25,9 +31,6 @@ export default function dom(
block.has_outro_method = true;
// prevent fragment being created twice (#1063)
if (options.customElement) block.chunks.create.push(b`this.c = @noop;`);
const body = [];
if (renderer.file_var) {
@ -35,27 +38,28 @@ export default function dom(
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');
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 {
css.map = null;
}
const styles = css_sourcemap_enabled && component.stylesheet.has_styles && options.dev
? `${css.code}\n/*# sourceMappingURL=${css.map.toUrl()} */`
: css.code;
const styles =
css_sourcemap_enabled && component.stylesheet.has_styles && options.dev
? `${css.code}\n/*# sourceMappingURL=${css.map.toUrl()} */`
: css.code;
const add_css = component.get_unique_name('add_css');
const should_add_css = (
!options.customElement &&
!!styles &&
options.css === 'injected'
);
const should_add_css = !!styles && (options.customElement || options.css === 'injected');
if (should_add_css) {
body.push(b`
@ -69,12 +73,15 @@ export default function dom(
// TODO the deconflicted names of blocks are reversed... should set them here
const blocks = renderer.blocks.slice().reverse();
push_array(body, blocks.map(block => {
// TODO this is a horrible mess — renderer.blocks
// contains a mixture of Blocks and Nodes
if ((block as Block).render) return (block as Block).render();
return block;
}));
push_array(
body,
blocks.map((block) => {
// TODO this is a horrible mess — renderer.blocks
// contains a mixture of Blocks and Nodes
if ((block as Block).render) return (block as Block).render();
return block;
})
);
if (options.dev && !options.hydratable) {
block.chunks.claim.push(
@ -90,34 +97,55 @@ export default function dom(
`;
}
const uses_props = component.var_lookup.has('$$props');
const uses_rest = component.var_lookup.has('$$restProps');
const $$props = uses_props || uses_rest ? '$$new_props' : '$$props';
const props = component.vars.filter(variable => !variable.module && variable.export_name);
const writable_props = props.filter(variable => variable.writable);
const props = component.vars.filter((variable) => !variable.module && variable.export_name);
const writable_props = props.filter((variable) => variable.writable);
const omit_props_names = component.get_unique_name('omit_props_names');
const compute_rest = x`@compute_rest_props($$props, ${omit_props_names.name})`;
const rest = uses_rest ? b`
const ${omit_props_names.name} = [${props.map(prop => `"${prop.export_name}"`).join(',')}];
const rest = uses_rest
? b`
const ${omit_props_names.name} = [${props.map((prop) => `"${prop.export_name}"`).join(',')}];
let $$restProps = ${compute_rest};
` : null;
`
: null;
const set = (uses_props || uses_rest || writable_props.length > 0 || component.slots.size > 0)
? x`
const set =
uses_props || uses_rest || writable_props.length > 0 || component.slots.size > 0
? x`
${$$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}`)}
${writable_props.map(prop =>
b`if ('${prop.export_name}' in ${$$props}) ${renderer.invalidate(prop.name, x`${prop.name} = ${$$props}.${prop.export_name}`)};`
${writable_props.map(
(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 = [];
@ -127,7 +155,7 @@ export default function dom(
let capture_state: Expression;
let props_inject: Node[] | Node;
props.forEach(prop => {
props.forEach((prop) => {
const variable = component.var_lookup.get(prop.name);
if (!variable.writable || component.component_options.accessors) {
@ -136,7 +164,11 @@ export default function dom(
kind: 'get',
key: { type: 'Identifier', name: prop.export_name },
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) {
@ -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 = {
...exports_from,
type: 'ImportDeclaration',
@ -192,7 +224,7 @@ export default function dom(
};
component.imports.push(import_declaration as ImportDeclaration);
exports_from.specifiers.forEach(specifier => {
exports_from.specifiers.forEach((specifier) => {
if (component.component_options.accessors) {
const name = component.get_unique_name(specifier.exported.name);
import_declaration.specifiers.push({
@ -225,33 +257,46 @@ export default function dom(
if (component.compile_options.dev) {
// 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) {
missing_props_check = b`
$$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}']])) {
@_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) {
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) {
inject_state = x`
${$$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(
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) {
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;
}
},
@ -310,9 +359,10 @@ export default function dom(
const value = `$${name}`;
const i = renderer.context_lookup.get(`$${name}`).index;
const insert = (reassigned || export_name)
? b`${`$$subscribe_${name}`}()`
: b`@component_subscribe($$self, ${name}, #value => $$invalidate(${i}, ${value} = #value))`;
const insert =
reassigned || export_name
? b`${`$$subscribe_${name}`}()`
: b`@component_subscribe($$self, ${name}, #value => $$invalidate(${i}, ${value} = #value))`;
if (component.compile_options.dev) {
return b`@validate_store(${name}, '${name}'); ${insert}`;
@ -323,7 +373,8 @@ export default function dom(
}
const args = [x`$$self`];
const has_invalidate = props.length > 0 ||
const has_invalidate =
props.length > 0 ||
component.has_reactive_assignments ||
component.slots.size > 0 ||
capture_state ||
@ -350,18 +401,20 @@ export default function dom(
${component.fully_hoisted}
`);
const filtered_props = props.filter(prop => {
const filtered_props = props.filter((prop) => {
const variable = component.var_lookup.get(prop.name);
if (variable.hoistable) return false;
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 has_definition = (
const has_definition =
component.compile_options.dev ||
(instance_javascript && instance_javascript.length > 0) ||
filtered_props.length > 0 ||
@ -370,44 +423,48 @@ export default function dom(
renderer.initial_context.length > 0 ||
component.reactive_declarations.length > 0 ||
capture_state ||
inject_state
);
inject_state;
const definition = has_definition
? component.alias('instance')
: { type: 'Literal', value: null };
const reactive_store_subscriptions = reactive_stores
.filter(store => {
.filter((store) => {
const variable = component.var_lookup.get(store.name.slice(1));
return !variable || variable.hoistable;
})
.map(({ name }) => b`
.map(
({ name }) => b`
${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
.filter(store => {
.filter((store) => {
const variable = component.var_lookup.get(store.name.slice(1));
return variable && (variable.reassigned || variable.export_name);
})
.map(({ name }) => b`$$self.$$.on_destroy.push(() => ${`$$unsubscribe_${name.slice(1)}`}());`);
if (has_definition) {
const reactive_declarations: (Node | Node[]) = [];
const reactive_declarations: Node | Node[] = [];
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 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);
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
@ -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);
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 = $name.slice(1);
@ -444,24 +501,29 @@ export default function dom(
let unknown_props_check: Node[] | undefined;
if (component.compile_options.dev && !(uses_props || uses_rest)) {
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 => {
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 = {
type: 'ArrayExpression',
elements: renderer.initial_context.map(member => ({
type: 'Identifier',
name: member.name
}) as Expression)
elements: renderer.initial_context.map(
(member) =>
({
type: 'Identifier',
name: member.name
} as Expression)
)
};
body.push(b`
function ${definition}(${args}) {
${injected.map(name => b`let ${name};`)}
${injected.map((name) => b`let ${name};`)}
${rest}
@ -471,8 +533,17 @@ export default function dom(
${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}
${instance_javascript}
@ -480,7 +551,10 @@ export default function dom(
${missing_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}
@ -492,11 +566,14 @@ export default function dom(
${/* before reactive declarations */ props_inject}
${reactive_declarations.length > 0 && b`
${
reactive_declarations.length > 0 &&
b`
$$self.$$.update = () => {
${reactive_declarations}
};
`}
`
}
${fixed_reactive_declarations}
@ -508,7 +585,9 @@ export default function dom(
}
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;
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)`;
if (uses_slots) {
init_props = x`{ ...${init_props}, $$slots: @get_custom_elements_slots(this) }`;
}
const declaration = b`
class ${name} extends @SvelteElement {
constructor(options) {
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);
}
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);
}
${(props.length > 0 || uses_props || uses_rest) && b`
if (options.props) {
this.$set(options.props);
@flush();
}`}
}
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;
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 (component.tag != null) {
body.push(b`
@_customElements.define("${component.tag}", ${name});
`);
}
} 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 });`}
}
if (options.customElement) {
const props_str = writable_props.reduce((def, prop) => {
def[prop.export_name] =
component.component_options.customElement?.props?.[prop.export_name] || {};
if (prop.is_boolean && !def[prop.export_name].type) {
def[prop.export_name].type = 'Boolean';
}
`[0] as ClassDeclaration;
push_array(declaration.body.body, accessors);
body.push(declaration);
return def;
}, {});
const slots_str = [...component.slots.keys()].map((key) => `"${key}"`).join(',');
const accessors_str = accessors
.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 };

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

@ -69,7 +69,7 @@ class AwaitBlockBranch extends Wrapper {
this.value = node.name;
this.renderer.add_to_context(this.value, true);
} else {
contexts.forEach(context => {
contexts.forEach((context) => {
if (context.type !== 'DestructuredVariable') return;
this.renderer.add_to_context(context.key.name, true);
});
@ -98,17 +98,28 @@ class AwaitBlockBranch extends Wrapper {
}
render_get_context() {
const props = this.is_destructured ? this.value_contexts.map(prop => {
if (prop.type === 'ComputedProperty') {
const expression = new Expression(this.renderer.component, this.node, this.has_consts(this.node) ? this.node.scope : null, prop.key);
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 props = this.is_destructured
? this.value_contexts.map((prop) => {
if (prop.type === 'ComputedProperty') {
const expression = new Expression(
this.renderer.component,
this.node,
this.has_consts(this.node) ? this.node.scope : null,
prop.key
);
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`);
this.block.renderer.blocks.push(b`
@ -143,9 +154,6 @@ export default class AwaitBlockWrapper extends Wrapper {
) {
super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
block.add_dependencies(this.node.expression.dependencies);
let is_dynamic = false;
@ -179,7 +187,7 @@ export default class AwaitBlockWrapper extends Wrapper {
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_intro_method = has_intros;
this[status].block.has_outro_method = has_outros;
@ -190,11 +198,7 @@ export default class AwaitBlockWrapper extends Wrapper {
}
}
render(
block: Block,
parent_node: Identifier,
parent_nodes: Identifier
) {
render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes);
const update_mount_node = this.get_update_mount_node(anchor);
@ -241,7 +245,8 @@ export default class AwaitBlockWrapper extends Wrapper {
const initial_mount_node = parent_node || '#target';
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`
${info}.block.m(${initial_mount_node}, ${info}.anchor = ${anchor_node});
@ -263,9 +268,7 @@ export default class AwaitBlockWrapper extends Wrapper {
${promise} !== (${promise} = ${snippet}) &&
@handle_promise(${promise}, ${info})`;
block.chunks.update.push(
b`${info}.ctx = #ctx;`
);
block.chunks.update.push(b`${info}.ctx = #ctx;`);
if (this.pending.block.has_update_method) {
block.chunks.update.push(b`
@ -303,7 +306,7 @@ export default class AwaitBlockWrapper extends Wrapper {
${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);
});
}

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

@ -9,18 +9,13 @@ export default class CommentWrapper extends Wrapper {
node: Comment;
var: Identifier;
constructor(
renderer: Renderer,
block: Block,
parent: Wrapper,
node: Comment
) {
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: Comment) {
super(renderer, block, parent, node);
this.var = x`c` as 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 = {
type: 'Literal',
@ -38,4 +33,10 @@ export default class CommentWrapper extends Wrapper {
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();
this.node.expressions.forEach(expression => {
this.node.expressions.forEach((expression) => {
add_to_set(dependencies, expression.dependencies);
});
const contextual_identifiers = this.node.expressions
.filter(e => {
.filter((e) => {
const variable = var_lookup.get((e.node as Identifier).name);
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`
${contextual_identifiers.map(name => b`const ${name} = ${renderer.reference(name)};`)}
${contextual_identifiers.map((name) => b`const ${name} = ${renderer.reference(name)};`)}
@_console.${log}({ ${logged_identifiers} });
debugger;`;

@ -14,10 +14,7 @@ const associated_events = {
visibilityState: ['visibilitychange']
};
const readonly = new Set([
'fullscreenElement',
'visibilityState'
]);
const readonly = new Set(['fullscreenElement', 'visibilityState']);
export default class DocumentWrapper extends Wrapper {
node: Document;
@ -25,7 +22,7 @@ export default class DocumentWrapper extends Wrapper {
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: TemplateNode) {
super(renderer, block, parent, node);
this.handlers = this.node.handlers.map(handler => new EventHandler(handler, this));
this.handlers = this.node.handlers.map((handler) => new EventHandler(handler, this));
}
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_actions(block, x`@_document`, this.node.actions);
this.node.bindings.forEach(binding => {
this.node.bindings.forEach((binding) => {
// TODO: what if it's a MemberExpression?
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 property = binding.name;
binding_events.forEach(associated_event => {
binding_events.forEach((associated_event) => {
if (!events[associated_event]) events[associated_event] = [];
events[associated_event].push({
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 props = events[event];
renderer.add_to_context(id.name);
const fn = renderer.reference(id.name);
props.forEach(prop => {
renderer.meta_bindings.push(
b`this._state.${prop.name} = @_document.${prop.value};`
);
props.forEach((prop) => {
renderer.meta_bindings.push(b`this._state.${prop.name} = @_document.${prop.value};`);
});
block.event_listeners.push(x`
@ -80,7 +75,7 @@ export default class DocumentWrapper extends Wrapper {
component.partly_hoisted.push(b`
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
) {
super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
const { dependencies } = node.expression;
block.add_dependencies(dependencies);
this.node.contexts.forEach(context => {
this.node.contexts.forEach((context) => {
if (context.type !== 'DestructuredVariable') return;
renderer.add_to_context(context.key.name, true);
});
@ -111,7 +109,7 @@ export default class EachBlockWrapper extends Wrapper {
const fixed_length =
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
: null;
@ -146,9 +144,10 @@ export default class EachBlockWrapper extends Wrapper {
};
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;
this.block.bindings.set(prop.key.name, {
object: this.vars.each_block_value,
@ -165,7 +164,14 @@ export default class EachBlockWrapper extends Wrapper {
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) {
this.else = new ElseBlockWrapper(
@ -198,8 +204,8 @@ export default class EachBlockWrapper extends Wrapper {
const { component } = renderer;
const needs_anchor = this.next
? !this.next.is_dom_node() :
!parent_node || !this.parent.is_dom_node();
? !this.next.is_dom_node()
: !parent_node || !this.parent.is_dom_node();
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});`);
}
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 update_anchor_node = needs_anchor
? block.get_unique_name(`${this.var.name}_anchor`)
: (this.next && this.next.var) || { type: 'Identifier', name: 'null' };
const update_mount_node: Identifier = this.get_update_mount_node((update_anchor_node as Identifier));
const update_mount_node: Identifier = this.get_update_mount_node(
update_anchor_node as Identifier
);
const args = {
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
? b`
@ -364,18 +377,34 @@ export default class EachBlockWrapper extends Wrapper {
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') {
const to_ctx = (name: string) => 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)};`;
const to_ctx = (name: string) =>
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 {
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')};`;
}
});
if (this.node.has_binding) this.context_props.push(b`child_ctx[${renderer.context_lookup.get(this.vars.each_block_value.name).index}] = list;`);
if (this.node.has_binding || this.node.has_index_binding || this.node.index) this.context_props.push(b`child_ctx[${renderer.context_lookup.get(this.index_name.name).index}] = i;`);
if (this.node.has_binding)
this.context_props.push(
b`child_ctx[${renderer.context_lookup.get(this.vars.each_block_value.name).index}] = list;`
);
if (this.node.has_binding || this.node.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()?
renderer.blocks.push(b`
@ -407,12 +436,7 @@ export default class EachBlockWrapper extends Wrapper {
update_anchor_node: Identifier;
update_mount_node: Identifier;
}) {
const {
create_each_block,
iterations,
data_length,
view_length
} = this.vars;
const { create_each_block, iterations, data_length, view_length } = this.vars;
const get_key = block.get_unique_name('get_key');
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;
} else {
this.block.first = this.block.get_unique_name('first');
this.block.add_element(
this.block.first,
x`@empty()`,
parent_nodes && x`@empty()`,
null
);
this.block.add_element(this.block.first, x`@empty()`, parent_nodes && x`@empty()`, null);
}
block.chunks.init.push(b`
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) {
let child_ctx = ${this.vars.get_each_context}(#ctx, ${this.vars.each_block_value}, #i);
let key = ${get_key}(child_ctx);
@ -468,12 +490,12 @@ export default class EachBlockWrapper extends Wrapper {
const dynamic = this.block.has_update_method;
const destroy = this.node.has_animation
? (this.block.has_outros
? this.block.has_outros
? '@fix_and_outro_and_destroy_block'
: '@fix_and_destroy_block')
: '@fix_and_destroy_block'
: this.block.has_outros
? '@outro_and_destroy_block'
: '@destroy_block';
? '@outro_and_destroy_block'
: '@destroy_block';
if (this.dependencies.size) {
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.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});`}
${iterations} = @update_keyed_each(${iterations}, #dirty, ${get_key}, ${dynamic ? 1 : 0}, #ctx, ${this.vars.each_block_value}, ${lookup}, ${update_mount_node}, ${destroy}, ${create_each_block}, ${update_anchor_node}, ${this.vars.get_each_context});
${this.node.has_animation && b`for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].a();`}
${
this.node.has_animation &&
b`for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].r();`
}
${
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();`}
`);
}
@ -523,13 +558,7 @@ export default class EachBlockWrapper extends Wrapper {
update_anchor_node: Identifier;
update_mount_node: Identifier;
}) {
const {
create_each_block,
iterations,
fixed_length,
data_length,
view_length
} = this.vars;
const { create_each_block, iterations, fixed_length, data_length, view_length } = this.vars;
block.chunks.init.push(b`
let ${iterations} = [];
@ -577,7 +606,7 @@ export default class EachBlockWrapper extends Wrapper {
}
`
: has_transitions
? b`
? b`
if (${iterations}[#i]) {
@transition_in(${this.vars.iterations}[#i], 1);
} else {
@ -587,7 +616,7 @@ export default class EachBlockWrapper extends Wrapper {
${iterations}[#i].m(${update_mount_node}, ${update_anchor_node});
}
`
: b`
: b`
if (!${iterations}[#i]) {
${iterations}[#i] = ${create_each_block}(child_ctx);
${iterations}[#i].c();
@ -616,7 +645,9 @@ export default class EachBlockWrapper extends Wrapper {
`;
} else {
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);
}
${!fixed_length && b`${view_length} = ${data_length};`}

@ -36,14 +36,11 @@ export class BaseAttributeWrapper {
this.parent = parent;
if (node.dependencies.size > 0) {
parent.cannot_use_innerhtml();
parent.not_static_content();
block.add_dependencies(node.dependencies);
}
}
render(_block: Block) { }
render(_block: Block) {}
}
const regex_minus_sign = /-/;
@ -75,7 +72,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
}
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.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
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);
}
@ -123,10 +122,13 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
const method = regex_minus_sign.test(element.node.name)
? '@set_custom_element_data'
: name.slice(0, 6) === 'xlink:'
? '@xlink_attr'
: '@attr';
? '@xlink_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 value = this.get_value(block);
@ -135,9 +137,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
const init = this.get_init(block, value);
if (is_legacy_input_type) {
block.chunks.hydrate.push(
b`@set_input_type(${element.var}, ${init});`
);
block.chunks.hydrate.push(b`@set_input_type(${element.var}, ${init});`);
updater = b`@set_input_type(${element.var}, ${should_cache ? this.last : value});`;
} else if (this.is_select_value_attribute) {
// annoying special case
@ -158,21 +158,17 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
);
updater = b`${method}(${element.var}, "${name}", ${should_cache ? this.last : value});`;
} else if (property_name) {
block.chunks.hydrate.push(
b`${element.var}.${property_name} = ${init};`
);
block.chunks.hydrate.push(b`${element.var}.${property_name} = ${init};`);
updater = block.renderer.options.dev
? b`@prop_dev(${element.var}, "${property_name}", ${should_cache ? this.last : value});`
: b`${element.var}.${property_name} = ${should_cache ? this.last : value};`;
} else {
block.chunks.hydrate.push(
b`${method}(${element.var}, "${name}", ${init});`
);
block.chunks.hydrate.push(b`${method}(${element.var}, "${name}", ${init});`);
updater = b`${method}(${element.var}, "${name}", ${should_cache ? this.last : 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);
updater = b`
@ -182,7 +178,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
}
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');
block.add_variable(this.parent.dynamic_value_condition, x`false`);
updater = b`
@ -211,9 +207,14 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
}
get_init(block: Block, value) {
this.last = this.should_cache && block.get_unique_name(
`${this.parent.var.name}_${this.name.replace(regex_invalid_variable_identifier_characters, '_')}_value`
);
this.last =
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);
@ -236,7 +237,9 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
if (this.is_input_value) {
const type = element.node.get_static_attribute_value('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) => {
const indirect_dependencies = this.parent.renderer.component.indirect_dependencies.get(prop);
if (indirect_dependencies) {
indirect_dependencies.forEach(indirect_dependency => {
indirect_dependencies.forEach((indirect_dependency) => {
dependencies.add(indirect_dependency);
});
}
@ -266,7 +269,8 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
get_metadata() {
if (this.parent.node.namespace) return null;
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;
}
@ -287,9 +291,10 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
: (this.node.chunks[0] as Expression).manipulate(block);
}
let value = this.node.name === 'class'
? this.get_class_name_text(block)
: this.render_chunks(block).reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
let value =
this.node.name === 'class'
? this.get_class_name_text(block)
: this.render_chunks(block).reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
// '{foo} {bar}' — treat as string concatenation
if (this.node.chunks[0].type !== 'Text') {
@ -327,17 +332,21 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
const value = this.node.chunks;
if (value.length === 0) return '=""';
return `="${value.map(chunk => {
return chunk.type === 'Text'
? chunk.data.replace(regex_double_quotes, '\\"')
: `\${${chunk.manipulate()}}`;
}).join('')}"`;
return `="${value
.map((chunk) => {
return chunk.type === 'Text'
? chunk.data.replace(regex_double_quotes, '\\"')
: `\${${chunk.manipulate()}}`;
})
.join('')}"`;
}
}
// source: https://html.spec.whatwg.org/multipage/indices.html
type AttributeMetadata = { property_name?: string, applies_to?: string[] };
const attribute_lookup: { [key in BooleanAttributes]: AttributeMetadata } & { [key in string]: AttributeMetadata } = {
type AttributeMetadata = { property_name?: string; applies_to?: string[] };
const attribute_lookup: { [key in BooleanAttributes]: AttributeMetadata } & {
[key in string]: AttributeMetadata;
} = {
allowfullscreen: { property_name: 'allowFullscreen', applies_to: ['iframe'] },
allowpaymentrequest: { property_name: 'allowPaymentRequest', applies_to: ['iframe'] },
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];
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) {
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 === 'input' &&
element.node.bindings.some(
(binding) => regex_contains_checked_or_group.test(binding.name)
)));
element.node.bindings.some((binding) =>
regex_contains_checked_or_group.test(binding.name)
)))
);
}

@ -20,7 +20,7 @@ export default class BindingWrapper {
object: string;
handler: {
uses_context: boolean;
mutation: (Node | Node[]);
mutation: Node | Node[];
contextual_dependencies: Set<string>;
lhs?: Node;
};
@ -52,13 +52,19 @@ export default class BindingWrapper {
}
// 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.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() {
@ -67,14 +73,14 @@ export default class BindingWrapper {
this.node.expression.dependencies.forEach((prop: string) => {
const indirect_dependencies = this.parent.renderer.component.indirect_dependencies.get(prop);
if (indirect_dependencies) {
indirect_dependencies.forEach(indirect_dependency => {
indirect_dependencies.forEach((indirect_dependency) => {
dependencies.add(indirect_dependency);
});
}
});
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;
@ -93,9 +99,10 @@ export default class BindingWrapper {
const result = new Set(dependencies);
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) {
indirect_dependencies.forEach(indirect_dependency => {
indirect_dependencies.forEach((indirect_dependency) => {
result.add(indirect_dependency);
});
}
@ -135,13 +142,9 @@ export default class BindingWrapper {
type === 'search' ||
type === 'url'
) {
update_conditions.push(
x`${parent.var}.${this.node.name} !== ${this.snippet}`
);
update_conditions.push(x`${parent.var}.${this.node.name} !== ${this.snippet}`);
} else if (type === 'number') {
update_conditions.push(
x`@to_number(${parent.var}.${this.node.name}) !== ${this.snippet}`
);
update_conditions.push(x`@to_number(${parent.var}.${this.node.name}) !== ${this.snippet}`);
}
}
@ -151,13 +154,12 @@ export default class BindingWrapper {
// special cases
switch (this.node.name) {
case 'group':
{
case 'group': {
block.renderer.add_to_context('$$binding_groups');
this.binding_group.add_element(block, this.parent.var);
if ((this.parent as ElementWrapper).has_dynamic_value) {
update_or_condition = (this.parent as ElementWrapper).dynamic_value_condition;
if ((this.parent as ElementWrapper).has_dynamic_value) {
update_or_condition = (this.parent as ElementWrapper).dynamic_value_condition;
}
break;
}
@ -166,7 +168,7 @@ export default class BindingWrapper {
update_conditions.push(x`${this.snippet} !== ${parent.var}.textContent`);
mount_conditions.push(x`${this.snippet} !== void 0`);
break;
case 'innerText':
update_conditions.push(x`${this.snippet} !== ${parent.var}.innerText`);
mount_conditions.push(x`${this.snippet} !== void 0`);
@ -188,8 +190,7 @@ export default class BindingWrapper {
mount_conditions.push(x`!@_isNaN(${this.snippet})`);
break;
case 'paused':
{
case 'paused': {
// this is necessary to prevent audio restarting by itself
const last = block.get_unique_name(`${parent.var.name}_is_paused`);
block.add_variable(last, x`true`);
@ -254,18 +255,20 @@ function get_dom_updater(
}
if (node.name === 'select') {
return node.get_static_attribute_value('multiple') === true ?
b`@select_options(${element.var}, ${binding.snippet})` :
mounting ? b`@select_option(${element.var}, ${binding.snippet}, true)` :
b`@select_option(${element.var}, ${binding.snippet})`;
return node.get_static_attribute_value('multiple') === true
? b`@select_options(${element.var}, ${binding.snippet})`
: mounting
? b`@select_option(${element.var}, ${binding.snippet}, true)`
: b`@select_option(${element.var}, ${binding.snippet})`;
}
if (binding.node.name === 'group') {
const type = node.get_static_attribute_value('type');
const condition = type === 'checkbox'
? x`~(${binding.snippet} || []).indexOf(${element.var}.__value)`
: x`${element.var}.__value === ${binding.snippet}`;
const condition =
type === 'checkbox'
? x`~(${binding.snippet} || []).indexOf(${element.var}.__value)`
: x`${element.var}.__value === ${binding.snippet}`;
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>();
contexts.forEach(context => {
contexts.forEach((context) => {
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}]`;
if (contexts.length > 0) {
contexts.forEach(secondary_index => {
contexts.forEach((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');
block.add_variable(local_name);
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(
b`${local_name} = @init_binding_group_dynamic(${binding_group}[${index}], ${indexes})`
);
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 {
block.chunks.init.push(
b`${local_name} = @init_binding_group(${binding_group}[${index}])`
);
}
block.chunks.hydrate.push(
b`${local_name}.p(${elements.get(block)})`
);
block.chunks.destroy.push(
b`${local_name}.r()`
);
block.chunks.hydrate.push(b`${local_name}.p(${elements.get(block)})`);
block.chunks.destroy.push(b`${local_name}.r()`);
}
});
}
@ -401,7 +405,7 @@ function get_event_handler(
lhs: Node
): {
uses_context: boolean;
mutation: (Node | Node[]);
mutation: Node | Node[];
contextual_dependencies: Set<string>;
lhs?: Node;
} {
@ -463,9 +467,9 @@ function get_value_from_dom(
// <select bind:value='selected>
if (node.name === 'select') {
return node.get_static_attribute_value('multiple') === true ?
x`@select_multiple_value(this)` :
x`@select_value(this)`;
return node.get_static_attribute_value('multiple') === true
? x`@select_multiple_value(this)`
: x`@select_value(this)`;
}
const type = node.get_static_attribute_value('type');
@ -486,7 +490,7 @@ function get_value_from_dom(
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})`;
}

@ -27,7 +27,9 @@ export default class EventHandlerWrapper {
}
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) {
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('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('trusted')) snippet = x`@trusted(${snippet})`;
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 === 1 && opts[0] === 'capture') {
args.push(TRUE);
} else {
args.push(x`{ ${ opts.map(opt =>
opt === 'nonpassive'
? p`passive: false`
: p`${opt}: true`
) } }`);
args.push(
x`{ ${opts.map((opt) => (opt === 'nonpassive' ? p`passive: false` : p`${opt}: true`))} }`
);
}
} else if (block.renderer.options.dev) {
args.push(FALSE);
@ -68,8 +71,6 @@ export default class EventHandlerWrapper {
args.push(this.node.modifiers.has('stopImmediatePropagation') ? TRUE : FALSE);
}
block.event_listeners.push(
x`@listen(${target}, "${this.node.name}", ${snippet}, ${args})`
);
block.event_listeners.push(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();
value = prop.value
.map(chunk => {
.map((chunk) => {
if (chunk.type === 'Text') {
return string_literal(chunk.data);
} 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();
svg_attributes.forEach(name => {
svg_attributes.forEach((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:
// 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
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 {
@ -95,9 +98,11 @@ export default class FragmentWrapper {
// We want to remove trailing whitespace inside an element/component/block,
// *unless* there is no whitespace between this node and its next sibling
if (this.nodes.length === 0) {
const should_trim = (
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')
);
const should_trim = 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');
if (should_trim && !child.keep_space()) {
data = trim_end(data);
@ -116,15 +121,22 @@ export default class FragmentWrapper {
this.nodes.unshift(wrapper);
link(last_child, last_child = wrapper);
link(last_child, (last_child = wrapper));
} else {
const Wrapper = wrappers[child.type];
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);
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);
this.can_use_innerhtml = false;
this.fragment = new FragmentWrapper(
renderer,
block,
@ -36,15 +34,15 @@ export default class HeadWrapper extends Wrapper {
let nodes: Identifier;
if (this.renderer.options.hydratable && this.fragment.nodes.length) {
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);
if (nodes && this.renderer.options.hydratable) {
block.chunks.claim.push(
b`${nodes}.forEach(@detach);`
);
block.chunks.claim.push(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;
function is_else_if(node: ElseBlock) {
return (
node && node.children.length === 1 && node.children[0].type === 'IfBlock'
);
return node && node.children.length === 1 && node.children[0].type === 'IfBlock';
}
class IfBlockBranch extends Wrapper {
@ -43,7 +41,7 @@ class IfBlockBranch extends Wrapper {
) {
super(renderer, block, parent, node);
const { expression } = (node as IfBlock);
const { expression } = node as IfBlock;
const is_else = !expression;
if (expression) {
@ -62,7 +60,7 @@ class IfBlockBranch extends Wrapper {
if (should_cache) {
this.condition = block.get_unique_name('show_if');
this.snippet = (expression.manipulate(block) as Node);
this.snippet = expression.manipulate(block) as Node;
} else {
this.condition = expression.manipulate(block);
}
@ -78,12 +76,21 @@ class IfBlockBranch extends Wrapper {
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;
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);
this.cannot_use_innerhtml();
this.not_static_content();
this.branches = [];
const blocks: Block[] = [];
@ -116,14 +120,7 @@ export default class IfBlockWrapper extends Wrapper {
let has_outros = false;
const create_branches = (node: IfBlock) => {
const branch = new IfBlockBranch(
renderer,
block,
this,
node,
strip_whitespace,
next_sibling
);
const branch = new IfBlockBranch(renderer, block, this, node, strip_whitespace, next_sibling);
this.branches.push(branch);
@ -172,7 +169,7 @@ export default class IfBlockWrapper extends Wrapper {
create_branches(this.node);
blocks.forEach(block => {
blocks.forEach((block) => {
block.has_update_method = is_dynamic;
block.has_intro_method = has_intros;
block.has_outro_method = has_outros;
@ -181,19 +178,17 @@ export default class IfBlockWrapper extends Wrapper {
push_array(renderer.blocks, blocks);
}
render(
block: Block,
parent_node: Identifier,
parent_nodes: Identifier
) {
render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
const name = this.var;
const needs_anchor = this.next ? !this.next.is_dom_node() : !parent_node || !this.parent.is_dom_node();
const needs_anchor = this.next
? !this.next.is_dom_node()
: !parent_node || !this.parent.is_dom_node();
const anchor = needs_anchor
? block.get_unique_name(`${this.var.name}_anchor`)
: (this.next && this.next.var) || 'null';
const has_else = !(this.branches[this.branches.length - 1].condition);
const has_else = !this.branches[this.branches.length - 1].condition;
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
@ -201,7 +196,7 @@ export default class IfBlockWrapper extends Wrapper {
const has_outros = this.branches[0].block.has_outro_method;
const has_transitions = has_intros || has_outros;
this.branches.forEach(branch => {
this.branches.forEach((branch) => {
if (branch.get_ctx_name) {
this.renderer.blocks.push(b`
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';
if (this.node.else) {
this.branches.forEach(branch => {
this.branches.forEach((branch) => {
if (branch.snippet) block.add_variable(branch.condition);
});
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});`);
} else {
@ -245,13 +247,9 @@ export default class IfBlockWrapper extends Wrapper {
if (parent_nodes && this.renderer.options.hydratable) {
if (if_exists_condition) {
block.chunks.claim.push(
b`if (${if_exists_condition}) ${name}.l(${parent_nodes});`
);
block.chunks.claim.push(b`if (${if_exists_condition}) ${name}.l(${parent_nodes});`);
} else {
block.chunks.claim.push(
b`${name}.l(${parent_nodes});`
);
block.chunks.claim.push(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);
});
}
@ -283,8 +281,10 @@ export default class IfBlockWrapper extends Wrapper {
) {
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 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 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 if_ctx = select_block_ctx ? x`${select_block_ctx}(#ctx, ${current_block_type})` : x`#ctx`;
const get_block = has_else
@ -295,36 +295,45 @@ export default class IfBlockWrapper extends Wrapper {
block.chunks.init.push(b`
function ${select_block_type}(#ctx, #dirty) {
${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
? b`
${this.branches.map(({ condition, snippet, block }) =>
condition
? b`
${snippet && b`if (${condition} == null) ${condition} = !!${snippet}`}
if (${condition}) return ${block.name};`
: b`return ${block.name};`
: b`return ${block.name};`
)}
}
`);
} else {
block.chunks.init.push(b`
function ${select_block_type}(#ctx, #dirty) {
${this.branches.map(({ condition, snippet, block }) => condition
? b`if (${snippet || condition}) return ${block.name};`
: b`return ${block.name};`)}
${this.branches.map(({ condition, snippet, block }) =>
condition
? b`if (${snippet || condition}) return ${block.name};`
: b`return ${block.name};`
)}
}
`);
}
if (need_select_block_ctx) {
// 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`
function ${select_block_ctx}(#ctx, #type) {
${this.branches.map(({ condition, get_ctx_name, block }) => {
return condition
? b`if (#type === ${block.name}) return ${get_ctx_name}(#ctx);`
: b`return ${get_ctx_name}(#ctx);`;
}).filter(Boolean)}
${this.branches
.map(({ condition, get_ctx_name, block }) => {
return condition
? b`if (#type === ${block.name}) return ${get_ctx_name}(#ctx);`
: b`return ${get_ctx_name}(#ctx);`;
})
.filter(Boolean)}
}
`);
} else {
@ -332,11 +341,13 @@ export default class IfBlockWrapper extends Wrapper {
// this code is simpler
block.chunks.init.push(b`
function ${select_block_ctx}(#ctx, #type) {
${this.branches.map(({ get_ctx_name, block }) => {
return get_ctx_name
? b`if (#type === ${block.name}) return ${get_ctx_name}(#ctx);`
: null;
}).filter(Boolean)}
${this.branches
.map(({ get_ctx_name, block }) => {
return get_ctx_name
? b`if (#type === ${block.name}) return ${get_ctx_name}(#ctx);`
: null;
})
.filter(Boolean)}
return #ctx;
}
`);
@ -356,9 +367,7 @@ export default class IfBlockWrapper extends Wrapper {
b`if (${if_exists_condition}) ${name}.m(${initial_mount_node}, ${anchor_node});`
);
} else {
block.chunks.mount.push(
b`${name}.m(${initial_mount_node}, ${anchor_node});`
);
block.chunks.mount.push(b`${name}.m(${initial_mount_node}, ${anchor_node});`);
}
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 if_block_creators = block.get_unique_name('if_block_creators');
const if_blocks = block.get_unique_name('if_blocks');
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 if_ctx = select_block_ctx ? x`${select_block_ctx}(#ctx, ${current_block_type_index})` : x`#ctx`;
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 if_ctx = select_block_ctx
? x`${select_block_ctx}(#ctx, ${current_block_type_index})`
: x`#ctx`;
const if_current_block_type_index = has_else
? (nodes: Node[]) => nodes
@ -438,45 +451,55 @@ export default class IfBlockWrapper extends Wrapper {
block.chunks.init.push(b`
const ${if_block_creators} = [
${this.branches.map(branch => branch.block.name)}
${this.branches.map((branch) => branch.block.name)}
];
const ${if_blocks} = [];
${this.needs_update
? b`
${
this.needs_update
? b`
function ${select_block_type}(#ctx, #dirty) {
${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
? b`
${this.branches.map(({ condition, snippet }, i) =>
condition
? b`
${snippet && b`if (${condition} == null) ${condition} = !!${snippet}`}
if (${condition}) return ${i};`
: b`return ${i};`)}
: b`return ${i};`
)}
${!has_else && b`return -1;`}
}
`
: b`
: b`
function ${select_block_type}(#ctx, #dirty) {
${this.branches.map(({ condition, snippet }, i) => condition
? b`if (${snippet || condition}) return ${i};`
: b`return ${i};`)}
${this.branches.map(({ condition, snippet }, i) =>
condition ? b`if (${snippet || condition}) return ${i};` : b`return ${i};`
)}
${!has_else && b`return -1;`}
}
`}
`
}
`);
if (need_select_block_ctx) {
// 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`
function ${select_block_ctx}(#ctx, #index) {
${this.branches.map(({ condition, get_ctx_name }, i) => {
return condition
? b`if (#index === ${i}) return ${get_ctx_name}(#ctx);`
: b`return ${get_ctx_name}(#ctx);`;
}).filter(Boolean)}
${this.branches
.map(({ condition, get_ctx_name }, i) => {
return condition
? b`if (#index === ${i}) return ${get_ctx_name}(#ctx);`
: b`return ${get_ctx_name}(#ctx);`;
})
.filter(Boolean)}
}
`);
} else {
@ -484,11 +507,11 @@ export default class IfBlockWrapper extends Wrapper {
// this code is simpler
block.chunks.init.push(b`
function ${select_block_ctx}(#ctx, #index) {
${this.branches.map(({ get_ctx_name }, i) => {
return get_ctx_name
? b`if (#index === ${i}) return ${get_ctx_name}(#ctx);`
: null;
}).filter(Boolean)}
${this.branches
.map(({ get_ctx_name }, i) => {
return get_ctx_name ? b`if (#index === ${i}) return ${get_ctx_name}(#ctx);` : null;
})
.filter(Boolean)}
return #ctx;
}
`);
@ -611,9 +634,7 @@ export default class IfBlockWrapper extends Wrapper {
const initial_mount_node = parent_node || '#target';
const anchor_node = parent_node ? 'null' : '#anchor';
block.chunks.mount.push(
b`if (${name}) ${name}.m(${initial_mount_node}, ${anchor_node});`
);
block.chunks.mount.push(b`if (${name}) ${name}.m(${initial_mount_node}, ${anchor_node});`);
if (branch.dependencies.length > 0) {
const update_mount_node = this.get_update_mount_node(anchor);
@ -621,11 +642,12 @@ export default class IfBlockWrapper extends Wrapper {
const enter = b`
if (${name}) {
${dynamic && b`${name}.p(${if_ctx}, #dirty);`}
${has_transitions &&
${
has_transitions &&
b`if (${block.renderer.dirty(branch.dependencies)}) {
@transition_in(${name}, 1);
}`
}
}
} else {
${name} = ${branch.block.name}(${if_ctx});
${name}.c();
@ -635,7 +657,11 @@ export default class IfBlockWrapper extends Wrapper {
`;
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,

@ -23,7 +23,12 @@ import compiler_warnings from '../../../compiler_warnings';
import { namespaces } from '../../../../utils/namespaces';
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;
@ -44,18 +49,15 @@ export default class InlineComponentWrapper extends Wrapper {
) {
super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
if (this.node.expression) {
block.add_dependencies(this.node.expression.dependencies);
}
this.node.attributes.forEach(attr => {
this.node.attributes.forEach((attr) => {
block.add_dependencies(attr.dependencies);
});
this.node.bindings.forEach(binding => {
this.node.bindings.forEach((binding) => {
if (binding.is_contextual) {
mark_each_block_bindings(this, binding);
}
@ -63,33 +65,44 @@ export default class InlineComponentWrapper extends Wrapper {
block.add_dependencies(binding.expression.dependencies);
});
this.node.handlers.forEach(handler => {
this.node.handlers.forEach((handler) => {
if (handler.expression) {
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);
});
this.var = {
type: 'Identifier',
name: (
this.node.name === 'svelte:self' ? renderer.component.name.name :
this.node.name === 'svelte:component' ? 'switch_instance' :
sanitize(this.node.name)
name: (this.node.name === 'svelte:self'
? renderer.component.name.name
: this.node.name === 'svelte:component'
? 'switch_instance'
: sanitize(this.node.name)
).toLowerCase()
};
if (this.node.children.length) {
this.node.lets.forEach(l => {
extract_names(l.value || l.name).forEach(name => {
this.node.lets.forEach((l) => {
extract_names(l.value || l.name).forEach((name) => {
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();
@ -112,19 +125,15 @@ export default class InlineComponentWrapper extends Wrapper {
return;
}
const ignores = extract_ignores_above_node(this.node);
this.renderer.component.push_ignores(ignores);
const ignores = extract_ignores_above_node(this.node);
this.renderer.component.push_ignores(ignores);
if (variable.reassigned || variable.export_name || variable.is_reactive_dependency) {
this.renderer.component.warn(this.node, compiler_warnings.reactive_component(name));
}
this.renderer.component.pop_ignores();
this.renderer.component.pop_ignores();
}
render(
block: Block,
parent_node: Identifier,
parent_nodes: Identifier
) {
render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
this.warn_if_reactive();
const { renderer } = this;
@ -146,7 +155,7 @@ export default class InlineComponentWrapper extends Wrapper {
let props: Identifier | undefined;
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
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 is_svg_namespace = this.node.namespace === namespaces.svg;
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) {
block.add_variable(css_custom_properties_wrapper);
}
const initial_props = this.slots.size > 0
? [
p`$$slots: {
const initial_props =
this.slots.size > 0
? [
p`$$slots: {
${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
}`
]
: [];
]
: [];
const attribute_object = uses_spread
? x`{ ${initial_props} }`
: 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}
}`;
@ -202,8 +216,8 @@ export default class InlineComponentWrapper extends Wrapper {
}
const fragment_dependencies = new Set(this.slots.size ? ['$$scope'] : []);
this.slots.forEach(slot => {
slot.block.dependencies.forEach(name => {
this.slots.forEach((slot) => {
slot.block.dependencies.forEach((name) => {
const is_let = slot.scope.is_let(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} = {};`);
}
@ -226,16 +245,17 @@ export default class InlineComponentWrapper extends Wrapper {
const all_dependencies: Set<string> = new Set();
this.node.attributes.forEach(attr => {
this.node.attributes.forEach((attr) => {
add_to_set(all_dependencies, attr.dependencies);
});
this.node.attributes.forEach((attr, i) => {
const { name, dependencies } = attr;
const condition = dependencies.size > 0 && (dependencies.size !== all_dependencies.size)
? renderer.dirty(Array.from(dependencies))
: null;
const condition =
dependencies.size > 0 && dependencies.size !== all_dependencies.size
? renderer.dirty(Array.from(dependencies))
: null;
const unchanged = dependencies.size === 0;
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;
if (binding.name === 'this') {
@ -328,8 +348,7 @@ export default class InlineComponentWrapper extends Wrapper {
statements.push(b`
if (${snippet} !== void 0) {
${props}.${binding.name} = ${snippet};
}`
);
}`);
updates.push(b`
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 args = [x`#value`];
if (contextual_dependencies.length > 0) {
contextual_dependencies.forEach(name => {
contextual_dependencies.forEach((name) => {
params.push({
type: 'Identifier',
name
@ -367,7 +385,6 @@ export default class InlineComponentWrapper extends Wrapper {
args.push(renderer.reference(name));
});
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}));`;
});
const munged_handlers = this.node.handlers.map(handler => {
const munged_handlers = this.node.handlers.map((handler) => {
const event_handler = new EventHandler(handler, this);
let snippet = event_handler.get_snippet(block);
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});`;
});
const mount_target = has_css_custom_properties ? css_custom_properties_wrapper : (parent_node || '#target');
const mount_anchor = has_css_custom_properties ? 'null' : (parent_node ? 'null' : '#anchor');
const mount_target = has_css_custom_properties
? 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;
let claim_nodes = parent_nodes;
@ -421,15 +440,23 @@ export default class InlineComponentWrapper extends Wrapper {
const dependencies = this.node.expression.dynamic_dependencies();
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`
var ${switch_value} = ${snippet};
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}
return ${component_opts};
}
@ -442,16 +469,30 @@ export default class InlineComponentWrapper extends Wrapper {
}
`);
block.chunks.create.push(
b`if (${name}) @create_component(${name}.$$.fragment);`
);
block.chunks.create.push(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);
block.chunks.mount.push(b`if (${name}) @mount_component(${name}, ${mount_target}, ${mount_anchor});`);
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`if (${name}) @mount_component(${name}, ${mount_target}, ${mount_anchor});`
);
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);
block.chunks.claim.push(b`if (${name}) @claim_component(${name}.$$.fragment, ${claim_nodes});`);
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`if (${name}) @claim_component(${name}.$$.fragment, ${claim_nodes});`
);
}
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 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 =
css_custom_properties_wrapper &&
(tmp_anchor.name !== 'null'
@ -508,19 +551,23 @@ export default class InlineComponentWrapper extends Wrapper {
if (${name}) @transition_in(${name}.$$.fragment, #local);
`);
block.chunks.outro.push(
b`if (${name}) @transition_out(${name}.$$.fragment, #local);`
);
block.chunks.outro.push(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 {
const expression = this.node.name === 'svelte:self'
? component.name
: this.renderer.reference(string_to_member_expression(this.node.name));
const expression =
this.node.name === 'svelte:self'
? component.name
: this.renderer.reference(string_to_member_expression(this.node.name));
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}
${name} = new ${expression}(${component_opts});
@ -529,15 +576,32 @@ export default class InlineComponentWrapper extends Wrapper {
`);
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);`);
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});`);
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});`);
}
@ -556,9 +620,7 @@ export default class InlineComponentWrapper extends Wrapper {
@destroy_component(${name}, ${parent_node ? null : 'detaching'});
`);
block.chunks.outro.push(
b`@transition_out(${name}.$$.fragment, #local);`
);
block.chunks.outro.push(b`@transition_out(${name}.$$.fragment, #local);`);
}
}
@ -567,17 +629,19 @@ export default class InlineComponentWrapper extends Wrapper {
parent_node: Identifier,
css_custom_properties_wrapper: Identifier | null
) {
if (parent_node) {
block.chunks.mount.push(b`@append(${parent_node}, ${css_custom_properties_wrapper})`);
if (is_head(parent_node)) {
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});`);
if (parent_node) {
block.chunks.mount.push(b`@append(${parent_node}, ${css_custom_properties_wrapper})`);
if (is_head(parent_node)) {
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});`
);
}
}
private create_css_custom_properties_wrapper_claim_chunk(
@ -603,12 +667,21 @@ export default class InlineComponentWrapper extends Wrapper {
is_svg_namespace: boolean
) {
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}");`);
if (!is_svg_namespace) block.chunks.hydrate.push(b`@set_style(${css_custom_properties_wrapper}, "display", "contents");`);
block.chunks.create.push(
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) => {
const dependencies = attr.get_dependencies();
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);
const value = attr.get_value(block);
const init = should_cache ? x`${last} = ${value}` : value;

@ -24,9 +24,6 @@ export default class KeyBlockWrapper extends Wrapper {
) {
super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
this.dependencies = node.expression.dynamic_dependencies();
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) {
this.fragment.render(
this.block,
null,
(x`#nodes` as unknown) as Identifier
);
this.fragment.render(this.block, null, x`#nodes` as unknown as Identifier);
const has_transitions = !!(
this.block.has_intro_method || this.block.has_outro_method
);
const has_transitions = !!(this.block.has_intro_method || this.block.has_outro_method);
const dynamic = this.block.has_update_method;
const previous_key = block.get_unique_name('previous_key');
const snippet = this.node.expression.manipulate(block);
block.add_variable(previous_key, snippet);
const not_equal = this.renderer.component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`;
const condition = x`${this.renderer.dirty(this.dependencies)} && ${not_equal}(${previous_key}, ${previous_key} = ${snippet})`;
const not_equal = this.renderer.component.component_options.immutable
? 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`
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.mount.push(
b`${this.var}.m(${parent_node || '#target'}, ${
parent_node ? 'null' : '#anchor'
});`
b`${this.var}.m(${parent_node || '#target'}, ${parent_node ? 'null' : '#anchor'});`
);
const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes);
const body = b`

@ -12,11 +12,21 @@ import AttributeWrapper from './Element/Attribute';
export default class MustacheTagWrapper extends Tag {
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);
}
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 =
this.parent instanceof ElementWrapper &&
this.parent.attributes.filter((a) => a.node.name === 'contenteditable');
@ -33,24 +43,21 @@ export default class MustacheTagWrapper extends Tag {
} else {
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']`;
}
const { init } = this.rename_this_method(
block,
value => {
if (contenteditable_attr_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})`;
}
const { init } = this.rename_this_method(block, (value) => {
if (contenteditable_attr_value) {
if (contenteditable_attr_value === true) {
return x`@set_data_contenteditable(${this.var}, ${value})`;
} 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(
this.var,

@ -20,8 +20,6 @@ export default class RawMustacheTagWrapper extends Tag {
node: MustacheTag | RawMustacheTag
) {
super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
}
render(block: Block, parent_node: Identifier, _parent_nodes: Identifier) {
@ -32,24 +30,20 @@ export default class RawMustacheTagWrapper extends Tag {
if (can_use_innerhtml) {
const insert = (content: Node) => b`${parent_node}.innerHTML = ${content};`[0];
const { init } = this.rename_this_method(
block,
content => insert(content)
);
const { init } = this.rename_this_method(block, (content) => insert(content));
block.chunks.mount.push(insert(init));
} 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_anchor = needs_anchor && block.get_unique_name('html_anchor');
block.add_variable(html_tag);
const { init } = this.rename_this_method(
block,
content => x`${html_tag}.p(${content})`
);
const { init } = this.rename_this_method(block, (content) => x`${html_tag}.p(${content})`);
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'});`);
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.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) {
block.add_element(html_anchor, x`@empty()`, x`@empty()`, parent_node);

@ -30,8 +30,6 @@ export default class SlotWrapper extends Wrapper {
next_sibling: Wrapper
) {
super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
if (this.node.children.length) {
this.fallback = block.child({
@ -51,7 +49,7 @@ export default class SlotWrapper extends Wrapper {
next_sibling
);
this.node.values.forEach(attribute => {
this.node.values.forEach((attribute) => {
add_to_set(this.dependencies, attribute.dependencies);
});
@ -62,11 +60,7 @@ export default class SlotWrapper extends Wrapper {
block.add_outro();
}
render(
block: Block,
parent_node: Identifier,
parent_nodes: Identifier
) {
render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
const { renderer } = this;
const { slot_name } = this.node;
@ -80,18 +74,27 @@ export default class SlotWrapper extends Wrapper {
let get_slot_context_fn: Identifier | 'null';
if (this.node.values.size > 0) {
get_slot_changes_fn = renderer.component.get_unique_name(`get_${sanitize(slot_name)}_slot_changes`);
get_slot_context_fn = renderer.component.get_unique_name(`get_${sanitize(slot_name)}_slot_context`);
get_slot_changes_fn = renderer.component.get_unique_name(
`get_${sanitize(slot_name)}_slot_changes`
);
get_slot_context_fn = renderer.component.get_unique_name(
`get_${sanitize(slot_name)}_slot_context`
);
const changes = x`{}` as ObjectExpression;
const spread_dynamic_dependencies = new Set<string>();
this.node.values.forEach(attribute => {
this.node.values.forEach((attribute) => {
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 {
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) {
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) {
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`
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 {
@ -126,22 +133,22 @@ export default class SlotWrapper extends Wrapper {
const slot = block.get_unique_name(`${sanitize(slot_name)}_slot`);
const slot_definition = block.get_unique_name(`${sanitize(slot_name)}_slot_template`);
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`
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}
`);
block.chunks.create.push(
b`if (${slot_or_fallback}) ${slot_or_fallback}.c();`
);
block.chunks.create.push(b`if (${slot_or_fallback}) ${slot_or_fallback}.c();`);
if (renderer.options.hydratable) {
block.chunks.claim.push(
b`if (${slot_or_fallback}) ${slot_or_fallback}.l(${parent_nodes});`
);
block.chunks.claim.push(b`if (${slot_or_fallback}) ${slot_or_fallback}.l(${parent_nodes});`);
}
block.chunks.mount.push(b`
@ -150,15 +157,13 @@ export default class SlotWrapper extends Wrapper {
}
`);
block.chunks.intro.push(
b`@transition_in(${slot_or_fallback}, #local);`
);
block.chunks.intro.push(b`@transition_in(${slot_or_fallback}, #local);`);
block.chunks.outro.push(
b`@transition_out(${slot_or_fallback}, #local);`
);
block.chunks.outro.push(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
? 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,
block.has_outros ? x`!#current` : null
].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[];
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`
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 {
slot_update = b`
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}`;
}
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}) {
${slot_or_fallback}.p(#ctx, ${fallback_dirty});
}
@ -222,9 +240,7 @@ export default class SlotWrapper extends Wrapper {
`);
}
block.chunks.destroy.push(
b`if (${slot_or_fallback}) ${slot_or_fallback}.d(detaching);`
);
block.chunks.destroy.push(b`if (${slot_or_fallback}) ${slot_or_fallback}.d(detaching);`);
}
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;
lets.forEach(l => {
extract_names(l.value || l.name).forEach(name => {
lets.forEach((l) => {
extract_names(l.value || l.name).forEach((name) => {
renderer.add_to_context(name, true);
});
});
@ -40,22 +40,17 @@ export default class SlotTemplateWrapper extends Wrapper {
this.block = block.child({
comment: create_debugging_comment(this.node, this.renderer.component),
name: this.renderer.component.get_unique_name(
`create_${sanitize(slot_template_name)}_slot`
),
name: this.renderer.component.get_unique_name(`create_${sanitize(slot_template_name)}_slot`),
type: 'slot'
});
this.renderer.blocks.push(this.block);
const seen = new Set(lets.map(l => l.name.name));
this.parent.node.lets.forEach(l => {
const seen = new Set(lets.map((l) => l.name.name));
this.parent.node.lets.forEach((l) => {
if (!seen.has(l.name.name)) lets.push(l);
});
this.parent.set_slot(
slot_template_name,
get_slot_definition(this.block, scope, lets)
);
this.parent.set_slot(slot_template_name, get_slot_definition(this.block, scope, lets));
this.fragment = new FragmentWrapper(
renderer,

@ -5,33 +5,32 @@ import Wrapper from './shared/Wrapper';
import { x } from 'code-red';
import { Identifier } from 'estree';
const regex_non_whitespace_characters = /[\S\u00A0]/;
export default class TextWrapper extends Wrapper {
node: Text;
data: string;
_data: string;
skip: boolean;
var: Identifier;
constructor(
renderer: Renderer,
block: Block,
parent: Wrapper,
node: Text,
data: string
) {
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: Text, data: string) {
super(renderer, block, parent, node);
this.skip = this.node.should_skip();
this.data = data;
this._data = data;
this.var = (this.skip ? null : x`t`) as unknown as Identifier;
}
use_space() {
if (this.renderer.component.component_options.preserveWhitespace) return false;
if (regex_non_whitespace_characters.test(this.data)) return false;
return this.node.use_space();
}
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) {
@ -50,7 +49,10 @@ export default class TextWrapper extends Wrapper {
block.add_element(
this.var,
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
);
}

@ -24,7 +24,7 @@ export default class TitleWrapper extends Wrapper {
}
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) {
let value;
@ -42,10 +42,10 @@ export default class TitleWrapper extends Wrapper {
} else {
// '{foo} {bar}' — treat as string concatenation
value = this.node.children
.map(chunk => {
.map((chunk) => {
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);
});
@ -58,17 +58,13 @@ export default class TitleWrapper extends Wrapper {
}
}
const last = this.node.should_cache && block.get_unique_name(
'title_value'
);
const last = this.node.should_cache && block.get_unique_name('title_value');
if (this.node.should_cache) block.add_variable(last);
const init = this.node.should_cache ? x`${last} = ${value}` : value;
block.chunks.init.push(
b`@_document.title = ${init};`
);
block.chunks.init.push(b`@_document.title = ${init};`);
const updater = b`@_document.title = ${this.node.should_cache ? last : value};`;
@ -91,9 +87,10 @@ export default class TitleWrapper extends Wrapper {
}`);
}
} else {
const value = this.node.children.length > 0
? string_literal((this.node.children[0] as Text).data)
: x`""`;
const value =
this.node.children.length > 0
? string_literal((this.node.children[0] as Text).data)
: x`""`;
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) {
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) {
@ -53,7 +53,7 @@ export default class WindowWrapper extends Wrapper {
add_actions(block, '@_window', this.node.actions);
add_event_handlers(block, '@_window', this.handlers);
this.node.bindings.forEach(binding => {
this.node.bindings.forEach((binding) => {
// TODO: what if it's a MemberExpression?
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 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 props = events[event];
@ -94,9 +94,10 @@ export default class WindowWrapper extends Wrapper {
block.add_variable(clear_scrolling, x`() => { ${scrolling} = false }`);
block.add_variable(scrolling_timeout);
const condition = bindings.scrollX && bindings.scrollY
? x`"${bindings.scrollX}" in this._state || "${bindings.scrollY}" in this._state`
: x`"${bindings.scrollX || bindings.scrollY}" in this._state`;
const condition =
bindings.scrollX && bindings.scrollY
? x`"${bindings.scrollX}" in this._state || "${bindings.scrollY}" in this._state`
: x`"${bindings.scrollX || bindings.scrollY}" in this._state`;
const scrollX = bindings.scrollX && x`this._state.${bindings.scrollX}`;
const scrollY = bindings.scrollY && x`this._state.${bindings.scrollY}`;
@ -118,10 +119,8 @@ export default class WindowWrapper extends Wrapper {
})
`);
} else {
props.forEach(prop => {
renderer.meta_bindings.push(
b`this._state.${prop.name} = @_window.${prop.value};`
);
props.forEach((prop) => {
renderer.meta_bindings.push(b`this._state.${prop.name} = @_window.${prop.value};`);
});
block.event_listeners.push(x`
@ -131,7 +130,7 @@ export default class WindowWrapper extends Wrapper {
component.partly_hoisted.push(b`
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) {
const condition = renderer.dirty([bindings.scrollX, bindings.scrollY].filter(Boolean));
const scrollX = bindings.scrollX ? renderer.reference(bindings.scrollX) : x`@_window.pageXOffset`;
const scrollY = bindings.scrollY ? renderer.reference(bindings.scrollY) : x`@_window.pageYOffset`;
const scrollX = bindings.scrollX
? renderer.reference(bindings.scrollX)
: x`@_window.pageXOffset`;
const scrollY = bindings.scrollY
? renderer.reference(bindings.scrollY)
: x`@_window.pageYOffset`;
block.chunks.update.push(b`
if (${condition} && !${scrolling}) {

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

@ -13,15 +13,8 @@ export default class Wrapper {
next: Wrapper | null;
var: Identifier;
can_use_innerhtml: boolean;
is_static_content: boolean;
constructor(
renderer: Renderer,
block: Block,
parent: Wrapper,
node: TemplateNode
) {
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: TemplateNode) {
this.node = node;
// 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);
}
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) {
// TODO use this in EachBlock and IfBlock — tricky because
// 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
? block.get_unique_name(`${this.var.name}_anchor`)
: (this.next && this.next.var) || { type: 'Identifier', name: 'null' };
@ -72,20 +54,23 @@ export default class Wrapper {
}
get_update_mount_node(anchor: Identifier): Identifier {
return ((this.parent && this.parent.is_dom_node())
? this.parent.var
: x`${anchor}.parentNode`) as Identifier;
return (
this.parent && this.parent.is_dom_node() ? this.parent.var : x`${anchor}.parentNode`
) as Identifier;
}
is_dom_node() {
return (
this.node.type === 'Element' ||
this.node.type === 'Text' ||
this.node.type === 'MustacheTag'
this.node.type === 'Element' || 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');
}
}

@ -4,12 +4,8 @@ import Action from '../../../nodes/Action';
import { Expression, Node } from 'estree';
import is_contextual from '../../../nodes/shared/is_contextual';
export default function add_actions(
block: Block,
target: string | Expression,
actions: Action[]
) {
actions.forEach(action => add_action(block, target, action));
export default function add_actions(block: Block, target: string | Expression, actions: Action[]) {
actions.forEach((action) => add_action(block, target, action));
}
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)}`;
}
block.chunks.update.push(
b`if (${condition}) ${id}.update.call(null, ${snippet});`
);
block.chunks.update.push(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 name = `#constants_${i}`;
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') {
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 {
const expression = new Expression(block.renderer.component, const_tag, const_tag.scope, context.key);
const_tags_props.push(b`const ${context.property_name} = ${expression.manipulate(block, ctx)}`);
const expression = new Expression(
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[]) {
const_tags.forEach(const_tag => {
const_tag.contexts.forEach(context => {
const_tags.forEach((const_tag) => {
const_tag.contexts.forEach((context) => {
if (context.type !== 'DestructuredVariable') return;
renderer.add_to_context(context.key.name, true);
});

@ -7,7 +7,7 @@ export default function add_event_handlers(
target: string | Expression,
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(

@ -5,7 +5,12 @@ import BindingWrapper from '../Element/Binding';
import { Identifier } from 'estree';
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`);
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`
${mutation}
${Array.from(dependencies)
.filter(dep => dep[0] !== '$')
.filter(dep => !contextual_dependencies.has(dep))
.map(dep => b`${block.renderer.invalidate(dep)};`)}
.filter((dep) => dep[0] !== '$')
.filter((dep) => !contextual_dependencies.has(dep))
.map((dep) => b`${block.renderer.invalidate(dep)};`)}
`;
if (contextual_dependencies.size) {
const params: Identifier[] = Array.from(contextual_dependencies).map(name => ({
const params: Identifier[] = Array.from(contextual_dependencies).map((name) => ({
type: 'Identifier',
name
}));
@ -66,7 +71,9 @@ export default function bind_this(component: Component, block: Block, binding: B
`);
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}`);
// 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`
if (${condition}) {
${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}();
}`
);
}`);
block.chunks.destroy.push(b`${unassign}();`);
return b`${assign}();`;

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

Loading…
Cancel
Save