chore: add prettier (#8543)

pull/8572/head
Yuichiro Yamashita 2 years ago committed by GitHub
parent 573acf9073
commit b6a400a549
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,6 +1,6 @@
module.exports = {
root: true,
extends: '@sveltejs',
extends: ['@sveltejs', 'prettier'],
settings: {
'import/core-modules': [
'svelte',

@ -44,7 +44,7 @@ jobs:
with:
node-version: 16
cache: pnpm
- run: 'pnpm i && pnpm lint'
- run: 'pnpm i && pnpm format:check && pnpm lint'
Unit:
runs-on: ${{ matrix.os }}
timeout-minutes: 10

@ -0,0 +1,13 @@
/*
!/elements
!/scripts
# TODO: after launch new site, format site dir.
/site
!/src
src/compiler/compile/internal_exports.ts
!/test
/test/**/*.svelte
/test/**/_expected*
/test/**/_actual*
/test/**/expected*
/types

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

116
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>;
@ -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
@ -554,7 +586,16 @@ export interface HTMLAttributes<T extends EventTarget> extends AriaAttributes, D
// 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;
@ -563,12 +604,7 @@ export interface HTMLAttributes<T extends EventTarget> extends AriaAttributes, D
[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;
@ -844,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;
@ -1155,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;
@ -1604,15 +1660,27 @@ export interface SvelteHTMLElements {
'svelte:body': HTMLAttributes<HTMLElement>;
'svelte:fragment': { slot?: string };
'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;
};
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
[name: string]: any;
};
'svelte:head': { [name: string]: any };

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

@ -81,6 +81,8 @@
},
"types": "types/runtime/index.d.ts",
"scripts": {
"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",
@ -133,6 +135,7 @@
"code-red": "^1.0.0",
"css-tree": "^2.3.1",
"eslint": "^8.35.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-svelte3": "^4.0.0",
"estree-walker": "^3.0.3",
@ -143,6 +146,8 @@
"magic-string": "^0.30.0",
"mocha": "^10.2.0",
"periscopic": "^3.1.0",
"prettier": "^2.8.8",
"prettier-plugin-svelte": "^2.10.0",
"puppeteer": "^19.8.5",
"rollup": "^3.20.2",
"source-map": "^0.7.4",

@ -70,6 +70,9 @@ devDependencies:
eslint:
specifier: ^8.35.0
version: 8.35.0
eslint-config-prettier:
specifier: ^8.8.0
version: 8.8.0(eslint@8.35.0)
eslint-plugin-import:
specifier: ^2.27.5
version: 2.27.5(@typescript-eslint/parser@5.58.0)(eslint@8.35.0)
@ -100,6 +103,12 @@ devDependencies:
periscopic:
specifier: ^3.1.0
version: 3.1.0
prettier:
specifier: ^2.8.8
version: 2.8.8
prettier-plugin-svelte:
specifier: ^2.10.0
version: 2.10.0(prettier@2.8.8)(svelte@3.58.0)
puppeteer:
specifier: ^19.8.5
version: 19.8.5(typescript@5.0.4)
@ -1238,6 +1247,15 @@ packages:
source-map: 0.6.1
dev: true
/eslint-config-prettier@8.8.0(eslint@8.35.0):
resolution: {integrity: sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==}
hasBin: true
peerDependencies:
eslint: '>=7.0.0'
dependencies:
eslint: 8.35.0
dev: true
/eslint-import-resolver-node@0.3.7:
resolution: {integrity: sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==}
dependencies:
@ -2552,6 +2570,22 @@ packages:
engines: {node: '>= 0.8.0'}
dev: true
/prettier-plugin-svelte@2.10.0(prettier@2.8.8)(svelte@3.58.0):
resolution: {integrity: sha512-GXMY6t86thctyCvQq+jqElO+MKdB09BkL3hexyGP3Oi8XLKRFaJP1ud/xlWCZ9ZIa2BxHka32zhHfcuU+XsRQg==}
peerDependencies:
prettier: ^1.16.4 || ^2.0.0
svelte: ^3.2.0
dependencies:
prettier: 2.8.8
svelte: 3.58.0
dev: true
/prettier@2.8.8:
resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==}
engines: {node: '>=10.13.0'}
hasBin: true
dev: true
/progress@2.0.3:
resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
engines: {node: '>=0.4.0'}

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

@ -6,12 +6,7 @@ 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';
@ -26,7 +21,25 @@ 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, VariableDeclarator, ObjectExpression, Pattern, Expression } 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';
@ -36,7 +49,10 @@ import Element from './nodes/Element';
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';
@ -48,7 +64,14 @@ interface ComponentOptions {
customElement?: {
tag: string | null;
shadow?: 'open' | 'none';
props?: Record<string, { attribute?: string; reflect?: boolean; type?: 'String' | 'Boolean' | 'Number' | 'Array' | 'Object' }>;
props?: Record<
string,
{
attribute?: string;
reflect?: boolean;
type?: 'String' | 'Boolean' | 'Number' | 'Array' | 'Object';
}
>;
};
}
@ -86,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>;
@ -146,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 });
@ -162,13 +185,9 @@ 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) {
this.tag = this.component_options.customElement?.tag || compile_options.tag || this.name.name;
@ -178,18 +197,26 @@ export default class Component {
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));
this.elements.forEach((element) => this.stylesheet.apply(element));
this.stylesheet.reify();
this.stylesheet.warn_on_unused_selectors(this);
}
@ -341,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');
@ -365,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
);
}
}
@ -435,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,
@ -505,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}`
});
}
@ -522,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);
}
@ -538,15 +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 (declarator.init?.type === 'Literal' && typeof declarator.init.value === 'boolean') {
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));
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)
);
}
});
});
@ -559,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)
);
}
}
});
@ -579,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;
@ -610,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, {
@ -659,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;
@ -667,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;
@ -685,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, {
@ -702,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);
@ -786,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)) {
@ -801,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';
@ -820,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;
@ -863,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;
@ -922,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
@ -952,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);
}
@ -970,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);
}
}
@ -980,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);
@ -1000,10 +1065,7 @@ export default class Component {
return {
type: 'BlockStatement',
body: [
before[0],
node
]
body: [before[0], node]
};
}
return null;
@ -1036,7 +1098,11 @@ export default class Component {
const inserts = [];
const props = [];
function add_new_props(exported: Identifier, local: Pattern, default_value: Expression) {
function add_new_props(
exported: Identifier,
local: Pattern,
default_value: Expression
) {
props.push({
type: 'Property',
method: false,
@ -1046,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
});
}
@ -1074,7 +1140,11 @@ 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;
@ -1097,7 +1167,11 @@ export default class Component {
break;
}
case 'ArrayPattern': {
const handle_element = (element: Pattern | null, index: number, array: Array<Pattern | null>) => {
const handle_element = (
element: Pattern | null,
index: number,
array: Array<Pattern | null>
) => {
if (element) {
if (element.type === 'Identifier') {
array[index] = get_new_name(element);
@ -1135,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)) {
@ -1144,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();
}
}
@ -1168,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();
@ -1183,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;
@ -1198,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;
}
@ -1210,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;
});
@ -1238,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;
}
@ -1260,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);
@ -1278,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;
@ -1347,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);
@ -1384,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);
});
@ -1395,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;
@ -1410,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) &&
@ -1433,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;
@ -1452,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, []);
}
@ -1464,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]);
@ -1481,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) {
@ -1499,14 +1578,14 @@ export default class Component {
}
check_if_tags_content_dynamic() {
this.tags.forEach(tag => {
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));
}
@ -1549,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;
@ -1571,7 +1650,7 @@ function process_component_options(component: Component, nodes) {
}
if (node) {
node.attributes.forEach(attribute => {
node.attributes.forEach((attribute) => {
if (attribute.type === 'Attribute') {
const { name } = attribute;
@ -1588,7 +1667,7 @@ function process_component_options(component: Component, nodes) {
component.warn(attribute, compiler_warnings.missing_custom_element_compile_options);
}
component_options.customElement = component_options.customElement || {} as any;
component_options.customElement = component_options.customElement || ({} as any);
component_options.customElement.tag = tag;
}
@ -1600,7 +1679,7 @@ function process_component_options(component: Component, nodes) {
}
case 'customElement': {
component_options.customElement = component_options.customElement || {} as any;
component_options.customElement = component_options.customElement || ({} as any);
const { value } = attribute;
@ -1614,9 +1693,7 @@ function process_component_options(component: Component, nodes) {
return component.error(attribute, compiler_errors.invalid_customElement_attribute);
}
const tag = value[0].expression.properties.find(
(prop: any) => prop.key.name === 'tag'
);
const tag = value[0].expression.properties.find((prop: any) => prop.key.name === 'tag');
if (tag) {
parse_tag(tag, tag.value?.value);
} else {
@ -1627,7 +1704,8 @@ function process_component_options(component: Component, nodes) {
(prop: any) => prop.key.name === 'props'
);
if (props) {
const error = () => component.error(attribute, compiler_errors.invalid_props_attribute);
const error = () =>
component.error(attribute, compiler_errors.invalid_props_attribute);
if (props.value?.type !== 'ObjectExpression') {
return error();
}
@ -1635,22 +1713,37 @@ function process_component_options(component: Component, nodes) {
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') {
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') {
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'
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;
component_options.customElement.props[property.key.name][prop.key.name] =
prop.value.value;
}
}
}
@ -1680,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;
@ -1701,7 +1797,10 @@ function process_component_options(component: Component, nodes) {
}
default:
return component.error(attribute, compiler_errors.invalid_options_attribute_unknown(name));
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',
@ -204,8 +213,9 @@ export default {
},
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: .. } } }"
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',
@ -217,12 +227,14 @@ export default {
},
invalid_props_attribute: {
code: 'invalid-props-attribute',
message: "'props' must be a statically analyzable object literal of the form " +
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',
@ -238,7 +250,8 @@ export default {
}),
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',
@ -250,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',
@ -262,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',
@ -278,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',

@ -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 'customElement' 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',
@ -117,9 +130,14 @@ export default {
}),
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`
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) => ({
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}'`
}),
@ -127,25 +145,35 @@ export default {
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) => ({
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'
@ -172,7 +200,9 @@ export default {
}),
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 || '...'}">`
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',
@ -208,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',
@ -224,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',
@ -236,6 +267,7 @@ export default {
}),
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;
@ -488,7 +553,7 @@ function get_element_parent(node: Element): Element | null {
*/
function find_previous_sibling(node: INode): INode {
let current_node: INode = node;
do {
do {
if (current_node.type === 'Slot') {
const slot_children = current_node.children;
if (slot_children.length > 0) {
@ -506,12 +571,19 @@ function find_previous_sibling(node: INode): INode {
return current_node;
}
function get_possible_element_siblings(node: INode, adjacent_only: boolean): Map<Element, NodeExist> {
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 = find_previous_sibling(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);
}
@ -531,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);
@ -556,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);
@ -573,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);
@ -585,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);
@ -641,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)) {
@ -678,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;
@ -689,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);
}
@ -421,13 +439,15 @@ export default class Stylesheet {
}
});
const max = Math.max(...this.children.map(rule => rule.get_max_amount_class_specificity_increased()));
this.children.forEach((child: (Atrule | Rule)) => {
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);
@ -448,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,16 @@ 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) {
@ -70,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());
}
@ -90,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') {
@ -106,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;
}
}

@ -37,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);

@ -44,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);
});
@ -57,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;
}
@ -77,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;
}
}

@ -22,9 +22,14 @@ export default class Head extends Node {
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))}`;

@ -22,11 +22,9 @@ export default class IfBlock extends AbstractBlock {
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();
}

@ -40,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':
@ -55,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;
@ -90,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);
});
});
@ -101,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);
}
@ -115,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,
@ -151,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,
@ -166,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;
}
}

@ -7,7 +7,7 @@ import { TemplateNode } from '../../interfaces';
import compiler_errors from '../compiler_errors';
export default class Slot extends Element {
// @ts-ignore Slot elements have the 'Slot' type, but TypeScript doesn't allow us to have 'Slot' when it extends 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[];
@ -17,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);
}

@ -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,14 +8,7 @@ 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]/;
@ -38,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)) {

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

@ -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;
@ -112,9 +112,7 @@ 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,
@ -125,7 +123,7 @@ export function unpack_destructuring({
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]: ... }
@ -150,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;
@ -200,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`);
@ -217,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();
}
@ -232,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)) {

@ -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;
@ -198,11 +206,13 @@ 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);
});
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);
}
);
});
}
@ -212,13 +222,7 @@ export default class Expression {
// 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;
@ -246,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 {
@ -278,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 = () => {
@ -289,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);
@ -355,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)) {
@ -385,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));
}
@ -417,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);
}
@ -450,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);
});
}

@ -57,9 +57,11 @@ export default class Node {
}
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;
@ -74,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;
}
}

@ -13,13 +13,15 @@ export default class Tag extends Node {
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;
return (
this.expression.dynamic_contextual_dependencies().length === 0 &&
this.expression.dynamic_dependencies().length === 0
);
}
check_if_content_dynamic() {
if (!this.is_dependencies_static()) {

@ -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: [] };

@ -7,7 +7,14 @@ import { walk } from 'estree-walker';
import { extract_names, Scope } from 'periscopic';
import { invalidate } from './invalidate';
import Block from './Block';
import { ImportDeclaration, ClassDeclaration, Node, Statement, ObjectExpression, Expression } from 'estree';
import {
ImportDeclaration,
ClassDeclaration,
Node,
Statement,
ObjectExpression,
Expression
} from 'estree';
import { apply_preprocessor_sourcemap } from '../../utils/mapped_code';
import { flatten } from '../../utils/flatten';
import check_enable_sourcemap from '../utils/check_enable_sourcemap';
@ -36,21 +43,23 @@ export default function dom(
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 = (
!!styles &&
(options.customElement || options.css === 'injected')
);
const should_add_css = !!styles && (options.customElement || options.css === 'injected');
if (should_add_css) {
body.push(b`
@ -64,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(
@ -85,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 = [];
@ -122,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) {
@ -131,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) {
@ -178,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',
@ -187,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({
@ -220,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}`
)};`
)}
}
`;
@ -273,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;
}
},
@ -305,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}`;
@ -318,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 ||
@ -345,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 ||
@ -365,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
@ -415,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);
@ -439,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}
@ -466,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}
@ -475,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}
@ -487,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}
@ -503,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;
@ -533,8 +617,13 @@ export default function dom(
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 });`}
@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;
@ -544,25 +633,35 @@ export default function dom(
if (options.customElement) {
const props_str = writable_props.reduce((def, prop) => {
def[prop.export_name] = component.component_options.customElement?.props?.[prop.export_name] || {};
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';
}
return def;
}, {});
const slots_str = [...component.slots.keys()].map(key => `"${key}"`).join(',');
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}"`)
.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';
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}));`
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});`);
body.push(
b`@create_custom_element(${name}, ${JSON.stringify(
props_str
)}, [${slots_str}], [${accessors_str}], ${use_shadow_dom});`
);
}
}

@ -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`
@ -176,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;
@ -187,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);
@ -238,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});
@ -260,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`
@ -300,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',
@ -40,7 +35,7 @@ export default class CommentWrapper extends Wrapper {
}
text() {
if (!this.renderer.options.preserveComments) return '';
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}`))}
}
`);

@ -84,7 +84,7 @@ export default class EachBlockWrapper extends Wrapper {
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);
});
@ -109,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;
@ -144,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,
@ -163,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(
@ -196,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);
@ -206,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,
@ -302,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`
@ -362,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`
@ -405,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`);
@ -422,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);
@ -466,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;
@ -481,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();`}
`);
}
@ -521,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} = [];
@ -575,7 +606,7 @@ export default class EachBlockWrapper extends Wrapper {
}
`
: has_transitions
? b`
? b`
if (${iterations}[#i]) {
@transition_in(${this.vars.iterations}[#i], 1);
} else {
@ -585,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();
@ -614,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};`}

@ -40,7 +40,7 @@ export class BaseAttributeWrapper {
}
}
render(_block: Block) { }
render(_block: Block) {}
}
const regex_minus_sign = /-/;
@ -72,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);
});
@ -106,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);
}
@ -120,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);
@ -132,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
@ -155,16 +158,12 @@ 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});`;
}
@ -179,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`
@ -208,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);
@ -233,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
}`;
}
}
@ -251,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);
});
}
@ -263,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;
}
@ -284,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') {
@ -324,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'] },
@ -387,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;
});
@ -400,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);
});

@ -12,7 +12,15 @@ import { namespaces } from '../../../../utils/namespaces';
import AttributeWrapper from './Attribute';
import StyleAttributeWrapper from './StyleAttribute';
import SpreadAttributeWrapper from './SpreadAttribute';
import { regex_dimensions, regex_starts_with_newline, regex_backslashes, regex_border_box_size, regex_content_box_size, regex_device_pixel_content_box_size, regex_content_rect } from '../../../../utils/patterns';
import {
regex_dimensions,
regex_starts_with_newline,
regex_backslashes,
regex_border_box_size,
regex_content_box_size,
regex_device_pixel_content_box_size,
regex_content_rect
} from '../../../../utils/patterns';
import Binding from './Binding';
import add_to_set from '../../../utils/add_to_set';
import { add_event_handler } from '../shared/add_event_handlers';
@ -44,21 +52,24 @@ const events = [
event_names: ['input'],
filter: (node: Element, _name: string) =>
node.name === 'textarea' ||
node.name === 'input' &&
!regex_contains_radio_or_checkbox_or_range_or_file.test(node.get_static_attribute_value('type') as string)
(node.name === 'input' &&
!regex_contains_radio_or_checkbox_or_range_or_file.test(
node.get_static_attribute_value('type') as string
))
},
{
event_names: ['input'],
filter: (node: Element, name: string) =>
is_name_contenteditable(name) &&
has_contenteditable_attr(node)
is_name_contenteditable(name) && has_contenteditable_attr(node)
},
{
event_names: ['change'],
filter: (node: Element, _name: string) =>
node.name === 'select' ||
node.name === 'input' &&
regex_contains_radio_or_checkbox_or_file.test(node.get_static_attribute_value('type') as string)
(node.name === 'input' &&
regex_contains_radio_or_checkbox_or_file.test(
node.get_static_attribute_value('type') as string
))
},
{
event_names: ['change', 'input'],
@ -68,8 +79,7 @@ const events = [
// resize events
{
event_names: ['elementresize'],
filter: (_node: Element, name: string) =>
regex_dimensions.test(name)
filter: (_node: Element, name: string) => regex_dimensions.test(name)
},
{
event_names: ['elementresizecontentbox'],
@ -79,89 +89,76 @@ const events = [
{
event_names: ['elementresizeborderbox'],
filter: (_node: Element, name: string) =>
regex_border_box_size.test(name)
filter: (_node: Element, name: string) => regex_border_box_size.test(name)
},
{
event_names: ['elementresizedevicepixelcontentbox'],
filter: (_node: Element, name: string) =>
regex_device_pixel_content_box_size.test(name)
filter: (_node: Element, name: string) => regex_device_pixel_content_box_size.test(name)
},
// media events
{
event_names: ['timeupdate'],
filter: (node: Element, name: string) =>
node.is_media_node() &&
(name === 'currentTime' || name === 'played' || name === 'ended')
node.is_media_node() && (name === 'currentTime' || name === 'played' || name === 'ended')
},
{
event_names: ['durationchange'],
filter: (node: Element, name: string) =>
node.is_media_node() &&
name === 'duration'
filter: (node: Element, name: string) => node.is_media_node() && name === 'duration'
},
{
event_names: ['play', 'pause'],
filter: (node: Element, name: string) =>
node.is_media_node() &&
name === 'paused'
filter: (node: Element, name: string) => node.is_media_node() && name === 'paused'
},
{
event_names: ['progress'],
filter: (node: Element, name: string) =>
node.is_media_node() &&
name === 'buffered'
filter: (node: Element, name: string) => node.is_media_node() && name === 'buffered'
},
{
event_names: ['loadedmetadata'],
filter: (node: Element, name: string) =>
node.is_media_node() &&
(name === 'buffered' || name === 'seekable')
node.is_media_node() && (name === 'buffered' || name === 'seekable')
},
{
event_names: ['volumechange'],
filter: (node: Element, name: string) =>
node.is_media_node() &&
(name === 'volume' || name === 'muted')
node.is_media_node() && (name === 'volume' || name === 'muted')
},
{
event_names: ['ratechange'],
filter: (node: Element, name: string) =>
node.is_media_node() &&
name === 'playbackRate'
filter: (node: Element, name: string) => node.is_media_node() && name === 'playbackRate'
},
{
event_names: ['seeking', 'seeked'],
filter: (node: Element, name: string) =>
node.is_media_node() &&
(name === 'seeking')
filter: (node: Element, name: string) => node.is_media_node() && name === 'seeking'
},
{
event_names: ['ended'],
filter: (node: Element, name: string) =>
node.is_media_node() &&
name === 'ended'
filter: (node: Element, name: string) => node.is_media_node() && name === 'ended'
},
{
event_names: ['resize'],
filter: (node: Element, name: string) =>
node.is_media_node() &&
(name === 'videoHeight' || name === 'videoWidth')
node.is_media_node() && (name === 'videoHeight' || name === 'videoWidth')
},
{
// from https://html.spec.whatwg.org/multipage/media.html#ready-states
// and https://html.spec.whatwg.org/multipage/media.html#loading-the-media-resource
event_names: ['loadedmetadata', 'loadeddata', 'canplay', 'canplaythrough', 'playing', 'waiting', 'emptied'],
filter: (node: Element, name: string) =>
node.is_media_node() &&
name === 'readyState'
event_names: [
'loadedmetadata',
'loadeddata',
'canplay',
'canplaythrough',
'playing',
'waiting',
'emptied'
],
filter: (node: Element, name: string) => node.is_media_node() && name === 'readyState'
},
// details event
{
event_names: ['toggle'],
filter: (node: Element, _name: string) =>
node.name === 'details'
filter: (node: Element, _name: string) => node.name === 'details'
},
{
event_names: ['load'],
@ -240,14 +237,14 @@ export default class ElementWrapper extends Wrapper {
this.dynamic_style_dependencies = new Set();
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.attributes = this.node.attributes.map(attribute => {
this.attributes = this.node.attributes.map((attribute) => {
if (attribute.name === 'style') {
return new StyleAttributeWrapper(this, block, attribute);
}
@ -256,14 +253,18 @@ export default class ElementWrapper extends Wrapper {
}
return new AttributeWrapper(this, block, attribute);
});
this.has_dynamic_attribute = !!this.attributes.find(attr => attr.node.get_dependencies().length > 0);
this.has_dynamic_attribute = !!this.attributes.find(
(attr) => attr.node.get_dependencies().length > 0
);
// ordinarily, there'll only be one... but we need to handle
// the rare case where an element can have multiple bindings,
// e.g. <audio bind:paused bind:currentTime>
this.bindings = this.node.bindings.map(binding => new Binding(block, binding, this));
this.bindings = this.node.bindings.map((binding) => new Binding(block, binding, this));
this.event_handlers = this.node.handlers.map(event_handler => new EventHandler(event_handler, this));
this.event_handlers = this.node.handlers.map(
(event_handler) => new EventHandler(event_handler, this)
);
if (node.intro || node.outro) {
if (node.intro) block.add_intro(node.intro.is_local);
@ -277,19 +278,28 @@ export default class ElementWrapper extends Wrapper {
block.add_dependencies(node.tag_expr.dependencies);
// add directive and handler dependencies
[node.animation, node.outro, ...node.actions, ...node.classes, ...node.styles].forEach(directive => {
if (directive && directive.expression) {
block.add_dependencies(directive.expression.dependencies);
[node.animation, node.outro, ...node.actions, ...node.classes, ...node.styles].forEach(
(directive) => {
if (directive && directive.expression) {
block.add_dependencies(directive.expression.dependencies);
}
}
});
);
node.handlers.forEach(handler => {
node.handlers.forEach((handler) => {
if (handler.expression) {
block.add_dependencies(handler.expression.dependencies);
}
});
this.fragment = new FragmentWrapper(renderer, block, node.children, this, strip_whitespace, next_sibling);
this.fragment = new FragmentWrapper(
renderer,
block,
node.children,
this,
strip_whitespace,
next_sibling
);
this.element_data_name = block.get_unique_name(`${this.var.name}_data`);
}
@ -306,7 +316,7 @@ export default class ElementWrapper extends Wrapper {
this.child_dynamic_element.render(
this.child_dynamic_element_block,
null,
(x`#nodes` as unknown) as Identifier
x`#nodes` as unknown as Identifier
);
const is_tag_dynamic = this.node.tag_expr.dynamic_dependencies().length > 0;
@ -314,7 +324,11 @@ export default class ElementWrapper extends Wrapper {
block.chunks.init.push(b`
${this.renderer.options.dev && b`@validate_dynamic_element(${tag});`}
${this.renderer.options.dev && this.node.children.length > 0 && b`@validate_void_dynamic_element(${tag});`}
${
this.renderer.options.dev &&
this.node.children.length > 0 &&
b`@validate_void_dynamic_element(${tag});`
}
let ${this.var} = ${tag} && ${this.child_dynamic_element_block.name}(#ctx);
`);
@ -337,7 +351,9 @@ export default class ElementWrapper extends Wrapper {
block.add_variable(previous_tag, tag);
const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes);
const has_transitions = !!(this.node.intro || this.node.outro);
const not_equal = this.renderer.component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`;
const not_equal = this.renderer.component.component_options.immutable
? x`@not_equal`
: x`@safe_not_equal`;
const tag_will_be_removed = block.get_unique_name('tag_will_be_removed');
if (has_transitions) {
@ -355,25 +371,36 @@ export default class ElementWrapper extends Wrapper {
} else if (${not_equal}(${previous_tag}, ${tag})) {
${this.var}.d(1);
${this.renderer.options.dev && b`@validate_dynamic_element(${tag});`}
${this.renderer.options.dev && this.node.children.length > 0 && b`@validate_void_dynamic_element(${tag});`}
${
this.renderer.options.dev &&
this.node.children.length > 0 &&
b`@validate_void_dynamic_element(${tag});`
}
${this.var} = ${this.child_dynamic_element_block.name}(#ctx);
${previous_tag} = ${tag};
${this.var}.c();
${has_transitions && b`if (${tag_will_be_removed}) {
${
has_transitions &&
b`if (${tag_will_be_removed}) {
${tag_will_be_removed} = false;
@transition_in(${this.var})
}`}
}`
}
${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor});
} else {
${has_transitions && b`if (${tag_will_be_removed}) {
${
has_transitions &&
b`if (${tag_will_be_removed}) {
${tag_will_be_removed} = false;
@transition_in(${this.var})
}`}
}`
}
${this.var}.p(#ctx, #dirty);
}
} else if (${previous_tag}) {
${has_transitions
? b`
${
has_transitions
? b`
${tag_will_be_removed} = true;
@group_outros();
@transition_out(${this.var}, 1, 1, () => {
@ -383,12 +410,12 @@ export default class ElementWrapper extends Wrapper {
});
@check_outros();
`
: b`
: b`
${this.var}.d(1);
${this.var} = null;
${previous_tag} = ${tag};
`
}
}
}
`);
} else {
@ -426,7 +453,6 @@ export default class ElementWrapper extends Wrapper {
}
render_element(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
const { renderer } = this;
const hydratable = renderer.options.hydratable;
@ -438,9 +464,7 @@ export default class ElementWrapper extends Wrapper {
block.add_variable(node);
const render_statement = this.get_render_statement(block);
block.chunks.create.push(
b`${node} = ${render_statement};`
);
block.chunks.create.push(b`${node} = ${render_statement};`);
const { can_use_textcontent, can_optimise_to_html_string } = this.node;
@ -456,9 +480,7 @@ export default class ElementWrapper extends Wrapper {
`);
}
} else {
block.chunks.claim.push(
b`${node} = ${render_statement};`
);
block.chunks.claim.push(b`${node} = ${render_statement};`);
}
}
@ -499,7 +521,9 @@ export default class ElementWrapper extends Wrapper {
block.chunks.create.push(b`${node}.textContent = ${text};`);
if (hydratable) {
block.chunks.claim.push(b`if (@get_svelte_dataset(${node}) !== "${this.node.hash()}") ${node}.textContent = ${text};`);
block.chunks.claim.push(
b`if (@get_svelte_dataset(${node}) !== "${this.node.hash()}") ${node}.textContent = ${text};`
);
}
} else {
const state = {
@ -516,7 +540,13 @@ export default class ElementWrapper extends Wrapper {
};
const can_use_raw_text = !this.node.can_use_innerhtml && can_use_textcontent;
to_html((this.fragment.nodes as unknown as Array<ElementWrapper | CommentWrapper | TextWrapper>), block, literal, state, can_use_raw_text);
to_html(
this.fragment.nodes as unknown as Array<ElementWrapper | CommentWrapper | TextWrapper>,
block,
literal,
state,
can_use_raw_text
);
literal.quasis.push(state.quasi as any);
if (hydratable) {
@ -528,25 +558,23 @@ export default class ElementWrapper extends Wrapper {
block.chunks.create.push(b`${node}.${property} = ${literal};`);
if (hydratable) {
block.chunks.claim.push(b`if (@get_svelte_dataset(${node}) !== "${this.node.hash()}") ${node}.${property} = ${literal};`);
block.chunks.claim.push(
b`if (@get_svelte_dataset(${node}) !== "${this.node.hash()}") ${node}.${property} = ${literal};`
);
}
}
} else {
this.fragment.nodes.forEach((child: Wrapper) => {
child.render(
block,
this.node.name === 'template' ? x`${node}.content` : node,
nodes,
{ element_data_name: this.element_data_name }
);
child.render(block, this.node.name === 'template' ? x`${node}.content` : node, nodes, {
element_data_name: this.element_data_name
});
});
}
const event_handler_or_binding_uses_context = (
this.bindings.some(binding => binding.handler.uses_context) ||
this.node.handlers.some(handler => handler.uses_context) ||
this.node.actions.some(action => action.uses_context)
);
const event_handler_or_binding_uses_context =
this.bindings.some((binding) => binding.handler.uses_context) ||
this.node.handlers.some((handler) => handler.uses_context) ||
this.node.actions.some((action) => action.uses_context);
if (event_handler_or_binding_uses_context) {
block.maintain_context = true;
@ -573,7 +601,9 @@ export default class ElementWrapper extends Wrapper {
if (renderer.options.dev) {
const loc = renderer.locate(this.node.start);
block.chunks.hydrate.push(
b`@add_location(${this.var}, ${renderer.file_var}, ${loc.line - 1}, ${loc.column}, ${this.node.start});`
b`@add_location(${this.var}, ${renderer.file_var}, ${loc.line - 1}, ${loc.column}, ${
this.node.start
});`
);
}
@ -592,9 +622,11 @@ export default class ElementWrapper extends Wrapper {
return x`@_document.createElementNS("${namespace}", "${name}")`;
}
const is: AttributeWrapper = this.attributes.find(attr => attr.node.name === 'is') as any;
const is: AttributeWrapper = this.attributes.find((attr) => attr.node.name === 'is') as any;
if (is) {
return x`@element_is("${name}", ${is.render_chunks(block).reduce((lhs, rhs) => x`${lhs} + ${rhs}`)})`;
return x`@element_is("${name}", ${is
.render_chunks(block)
.reduce((lhs, rhs) => x`${lhs} + ${rhs}`)})`;
}
return x`@element(${reference})`;
@ -610,7 +642,7 @@ export default class ElementWrapper extends Wrapper {
if (this.node.namespace) {
reference = `"${this.node.tag_expr.node.value}"`;
} else {
reference = `"${(this.node.tag_expr.node.value as String || '').toUpperCase()}"`;
reference = `"${((this.node.tag_expr.node.value as String) || '').toUpperCase()}"`;
}
} else if (this.node.namespace) {
reference = x`${this.node.tag_expr.manipulate(block)}`;
@ -633,15 +665,15 @@ export default class ElementWrapper extends Wrapper {
type OrderedAttribute = EventHandler | BindingGroup | Binding | Action;
const binding_groups = events
.map(event => ({
.map((event) => ({
events: event.event_names,
bindings: this.bindings
.filter(binding => binding.node.name !== 'this')
.filter(binding => event.filter(this.node, binding.node.name))
.filter((binding) => binding.node.name !== 'this')
.filter((binding) => event.filter(this.node, binding.node.name))
}))
.filter(group => group.bindings.length);
.filter((group) => group.bindings.length);
const this_binding = this.bindings.find(b => b.node.name === 'this');
const this_binding = this.bindings.find((b) => b.node.name === 'this');
function getOrder(item: OrderedAttribute) {
if (item instanceof EventHandler) {
@ -655,15 +687,17 @@ export default class ElementWrapper extends Wrapper {
}
}
([
...binding_groups,
...this.event_handlers,
this_binding,
...this.node.actions
] as OrderedAttribute[])
(
[
...binding_groups,
...this.event_handlers,
this_binding,
...this.node.actions
] as OrderedAttribute[]
)
.filter(Boolean)
.sort((a, b) => getOrder(a) - getOrder(b))
.forEach(item => {
.forEach((item) => {
if (item instanceof EventHandler) {
add_event_handler(block, this.var, item);
} else if (item instanceof Binding) {
@ -683,22 +717,24 @@ export default class ElementWrapper extends Wrapper {
renderer.component.has_reactive_assignments = true;
const lock = binding_group.bindings.some(binding => binding.needs_lock) ?
block.get_unique_name(`${this.var.name}_updating`) :
null;
const lock = binding_group.bindings.some((binding) => binding.needs_lock)
? block.get_unique_name(`${this.var.name}_updating`)
: null;
if (lock) block.add_variable(lock, x`false`);
const handler = renderer.component.get_unique_name(`${this.var.name}_${binding_group.events.join('_')}_handler`);
const handler = renderer.component.get_unique_name(
`${this.var.name}_${binding_group.events.join('_')}_handler`
);
renderer.add_to_context(handler.name);
// TODO figure out how to handle locks
const needs_lock = binding_group.bindings.some(binding => binding.needs_lock);
const needs_lock = binding_group.bindings.some((binding) => binding.needs_lock);
const dependencies: Set<string> = new Set();
const contextual_dependencies: Set<string> = new Set();
binding_group.bindings.forEach(binding => {
binding_group.bindings.forEach((binding) => {
// TODO this is a mess
add_to_set(dependencies, binding.get_update_dependencies());
add_to_set(contextual_dependencies, binding.handler.contextual_dependencies);
@ -721,7 +757,7 @@ export default class ElementWrapper extends Wrapper {
// TODO dry this out — similar code for event handlers and component bindings
if (has_local_function) {
const args = Array.from(contextual_dependencies).map(name => renderer.reference(name));
const args = Array.from(contextual_dependencies).map((name) => renderer.reference(name));
// need to create a block-local function that calls an instance-level function
if (animation_frame) {
@ -747,23 +783,30 @@ export default class ElementWrapper extends Wrapper {
callee = handler;
}
const params = Array.from(contextual_dependencies).map(name => ({
const params = Array.from(contextual_dependencies).map((name) => ({
type: 'Identifier',
name
}));
this.renderer.component.partly_hoisted.push(b`
function ${handler}(${params}) {
${binding_group.bindings.map(b => b.handler.mutation)}
${binding_group.bindings.map((b) => b.handler.mutation)}
${Array.from(dependencies)
.filter(dep => dep[0] !== '$')
.filter(dep => !contextual_dependencies.has(dep))
.map(dep => b`${this.renderer.invalidate(dep)};`)}
.filter((dep) => dep[0] !== '$')
.filter((dep) => !contextual_dependencies.has(dep))
.map((dep) => b`${this.renderer.invalidate(dep)};`)}
}
`);
binding_group.events.forEach(name => {
if (['elementresize', 'elementresizecontentbox', 'elementresizeborderbox', 'elementresizedevicepixelcontentbox'].indexOf(name) !== -1) {
binding_group.events.forEach((name) => {
if (
[
'elementresize',
'elementresizecontentbox',
'elementresizeborderbox',
'elementresizedevicepixelcontentbox'
].indexOf(name) !== -1
) {
const resize_listener = block.get_unique_name(`${this.var.name}_resize_listener`);
block.add_variable(resize_listener);
@ -791,28 +834,24 @@ export default class ElementWrapper extends Wrapper {
break;
}
block.chunks.destroy.push(
b`${resize_listener}();`
);
block.chunks.destroy.push(b`${resize_listener}();`);
} else {
block.event_listeners.push(
x`@listen(${this.var}, "${name}", ${callee})`
);
block.event_listeners.push(x`@listen(${this.var}, "${name}", ${callee})`);
}
});
const some_initial_state_is_undefined = binding_group.bindings
.map(binding => x`${binding.snippet} === void 0`)
.map((binding) => x`${binding.snippet} === void 0`)
.reduce((lhs, rhs) => x`${lhs} || ${rhs}`);
const should_initialise = (
const should_initialise =
this.node.name === 'select' ||
binding_group.bindings.find(binding => (
binding.node.name === 'indeterminate' ||
is_name_contenteditable(binding.node.name) ||
binding.is_readonly_media_attribute()
))
);
binding_group.bindings.find(
(binding) =>
binding.node.name === 'indeterminate' ||
is_name_contenteditable(binding.node.name) ||
binding.is_readonly_media_attribute()
);
if (should_initialise) {
const callback = has_local_function ? handler : x`() => ${callee}.call(${this.var})`;
@ -822,9 +861,7 @@ export default class ElementWrapper extends Wrapper {
}
if (binding_group.events[0] === 'elementresize') {
block.chunks.hydrate.push(
b`@add_render_callback(() => ${callee}.call(${this.var}));`
);
block.chunks.hydrate.push(b`@add_render_callback(() => ${callee}.call(${this.var}));`);
}
if (lock) {
@ -852,7 +889,7 @@ export default class ElementWrapper extends Wrapper {
}
});
if (this.node.attributes.some(attr => attr.is_spread)) {
if (this.node.attributes.some((attr) => attr.is_spread)) {
this.add_spread_attributes(block);
return;
}
@ -868,28 +905,27 @@ export default class ElementWrapper extends Wrapper {
const initial_props = [];
const updates = [];
this.attributes
.forEach(attr => {
const dependencies = attr.node.get_dependencies();
this.attributes.forEach((attr) => {
const dependencies = attr.node.get_dependencies();
const condition = dependencies.length > 0
? block.renderer.dirty(dependencies)
: null;
const condition = dependencies.length > 0 ? block.renderer.dirty(dependencies) : null;
if (attr instanceof SpreadAttributeWrapper) {
const snippet = attr.node.expression.manipulate(block);
if (attr instanceof SpreadAttributeWrapper) {
const snippet = attr.node.expression.manipulate(block);
initial_props.push(snippet);
initial_props.push(snippet);
updates.push(condition ? x`${condition} && ${snippet}` : snippet);
} else {
const name = attr.property_name || attr.name;
initial_props.push(x`{ ${name}: ${attr.get_init(block, attr.get_value(block))} }`);
const snippet = x`{ ${name}: ${attr.should_cache ? attr.last : attr.get_value(block)} }`;
updates.push(condition ? x`${condition} && ${snippet}` : snippet);
} else {
const name = attr.property_name || attr.name;
initial_props.push(x`{ ${name}: ${attr.get_init(block, attr.get_value(block))} }`);
const snippet = x`{ ${name}: ${attr.should_cache ? attr.last : attr.get_value(block)} }`;
updates.push(condition ? x`${attr.get_dom_update_conditions(block, condition)} && ${snippet}` : snippet);
}
});
updates.push(
condition ? x`${attr.get_dom_update_conditions(block, condition)} && ${snippet}` : snippet
);
}
});
block.chunks.init.push(b`
let ${levels} = [${initial_props}];
@ -904,12 +940,10 @@ export default class ElementWrapper extends Wrapper {
this.node.namespace === namespaces.svg
? x`@set_svg_attributes`
: this.node.is_dynamic_element
? x`@set_dynamic_element_data(${this.node.tag_expr.manipulate(block)})`
: x`@set_attributes`;
? x`@set_dynamic_element_data(${this.node.tag_expr.manipulate(block)})`
: x`@set_attributes`;
block.chunks.hydrate.push(
b`${fn}(${this.var}, ${this.element_data_name});`
);
block.chunks.hydrate.push(b`${fn}(${this.var}, ${this.element_data_name});`);
if (this.has_dynamic_attribute) {
block.chunks.update.push(b`
@ -933,11 +967,22 @@ export default class ElementWrapper extends Wrapper {
`);
block.chunks.update.push(b`
if (${block.renderer.dirty(Array.from(dependencies))} && 'value' in ${this.element_data_name}) (${this.element_data_name}.multiple ? @select_options : @select_option)(${this.var}, ${this.element_data_name}.value);
if (${block.renderer.dirty(Array.from(dependencies))} && 'value' in ${this.element_data_name}) (${
this.element_data_name
}.multiple ? @select_options : @select_option)(${this.var}, ${this.element_data_name}.value);
`);
} else if (this.node.name === 'input' && this.attributes.find(attr => attr.node.name === 'value')) {
} else if (
this.node.name === 'input' &&
this.attributes.find((attr) => attr.node.name === 'value')
) {
const type = this.node.get_static_attribute_value('type');
if (type === null || type === '' || type === 'text' || type === 'email' || type === 'password') {
if (
type === null ||
type === '' ||
type === 'text' ||
type === 'email' ||
type === 'password'
) {
block.chunks.mount.push(b`
if ('value' in ${this.element_data_name}) {
${this.var}.value = ${this.element_data_name}.value;
@ -979,12 +1024,10 @@ export default class ElementWrapper extends Wrapper {
this.node.namespace === namespaces.svg
? x`@set_svg_attributes`
: this.node.is_dynamic_element
? x`@set_dynamic_element_data(${this.node.tag_expr.manipulate(block)})`
: x`@set_attributes`;
? x`@set_dynamic_element_data(${this.node.tag_expr.manipulate(block)})`
: x`@set_attributes`;
block.chunks.hydrate.push(
b`${fn}(${this.var}, {${static_attributes}});`
);
block.chunks.hydrate.push(b`${fn}(${this.var}, {${static_attributes}});`);
}
add_transitions(block: Block) {
@ -994,9 +1037,7 @@ export default class ElementWrapper extends Wrapper {
if (intro === outro) {
// bidirectional transition
const name = block.get_unique_name(`${this.var.name}_transition`);
const snippet = intro.expression
? intro.expression.manipulate(block)
: x`{}`;
const snippet = intro.expression ? intro.expression.manipulate(block) : x`{}`;
block.add_variable(name);
@ -1039,9 +1080,7 @@ export default class ElementWrapper extends Wrapper {
if (intro) {
block.add_variable(intro_name);
const snippet = intro.expression
? intro.expression.manipulate(block)
: x`{}`;
const snippet = intro.expression ? intro.expression.manipulate(block) : x`{}`;
const fn = this.renderer.reference(intro.name);
@ -1082,9 +1121,7 @@ export default class ElementWrapper extends Wrapper {
if (outro) {
block.add_variable(outro_name);
const snippet = outro.expression
? outro.expression.manipulate(block)
: x`{}`;
const snippet = outro.expression ? outro.expression.manipulate(block) : x`{}`;
const fn = this.renderer.reference(outro.name);
@ -1114,7 +1151,10 @@ export default class ElementWrapper extends Wrapper {
}
}
if ((intro && intro.expression && intro.expression.dependencies.size) || (outro && outro.expression && outro.expression.dependencies.size)) {
if (
(intro && intro.expression && intro.expression.dependencies.size) ||
(outro && outro.expression && outro.expression.dependencies.size)
) {
block.maintain_context = true;
}
}
@ -1170,8 +1210,8 @@ export default class ElementWrapper extends Wrapper {
}
add_classes(block: Block) {
const has_spread = this.node.attributes.some(attr => attr.is_spread);
this.node.classes.forEach(class_directive => {
const has_spread = this.node.attributes.some((attr) => attr.is_spread);
this.node.classes.forEach((class_directive) => {
const { expression, name } = class_directive;
let snippet: Node | string;
let dependencies: Set<string>;
@ -1213,13 +1253,13 @@ export default class ElementWrapper extends Wrapper {
}
add_styles(block: Block) {
const has_spread = this.node.attributes.some(attr => attr.is_spread);
const has_spread = this.node.attributes.some((attr) => attr.is_spread);
let style_changed_var: Identifier | undefined;
const maybe_create_style_changed_var = () => {
if (!style_changed_var && this.dynamic_style_dependencies.size) {
style_changed_var = block.get_unique_name('style_changed');
const style_attr_dirty = block.renderer.dirty([...this.dynamic_style_dependencies]);
const style_attr_dirty = block.renderer.dirty([...this.dynamic_style_dependencies]);
block.chunks.update.push(b`const ${style_changed_var} = ${style_attr_dirty};`);
}
};
@ -1234,15 +1274,14 @@ export default class ElementWrapper extends Wrapper {
block.add_variable(cached_snippet, snippet);
}
const updater = b`@set_style(${this.var}, "${name}", ${should_cache ? cached_snippet : snippet}, ${important ? 1 : null})`;
const updater = b`@set_style(${this.var}, "${name}", ${
should_cache ? cached_snippet : snippet
}, ${important ? 1 : null})`;
block.chunks.hydrate.push(updater);
const self_deps = expression.dynamic_dependencies();
const all_deps = new Set([
...self_deps,
...this.dynamic_style_dependencies
]);
const all_deps = new Set([...self_deps, ...this.dynamic_style_dependencies]);
let condition = block.renderer.dirty([...all_deps]);
@ -1253,12 +1292,11 @@ export default class ElementWrapper extends Wrapper {
block.chunks.update.push(b`
if (${condition}) {
${cached_snippet} = ${snippet};
}`
);
}`);
}
block.chunks.update.push(updater);
} else {
if (all_deps.size === 0) return;
if (all_deps.size === 0) return;
if (should_cache) {
condition = x`${condition} && ${cached_snippet} !== (${cached_snippet} = ${snippet})`;
@ -1294,8 +1332,16 @@ export default class ElementWrapper extends Wrapper {
const regex_backticks = /`/g;
const regex_dollar_signs = /\$/g;
function to_html(wrappers: Array<CommentWrapper | ElementWrapper | TextWrapper | MustacheTagWrapper | RawMustacheTagWrapper>, block: Block, literal: any, state: any, can_use_raw_text?: boolean) {
wrappers.forEach(wrapper => {
function to_html(
wrappers: Array<
CommentWrapper | ElementWrapper | TextWrapper | MustacheTagWrapper | RawMustacheTagWrapper
>,
block: Block,
literal: any,
state: any,
can_use_raw_text?: boolean
) {
wrappers.forEach((wrapper) => {
if (wrapper instanceof CommentWrapper) {
state.quasi.value.raw += wrapper.text();
} else if (wrapper instanceof TextWrapper) {
@ -1310,11 +1356,8 @@ function to_html(wrappers: Array<CommentWrapper | ElementWrapper | TextWrapper |
const parent = wrapper.node.parent as Element;
const raw = parent && (
parent.name === 'script' ||
parent.name === 'style' ||
can_use_raw_text
);
const raw =
parent && (parent.name === 'script' || parent.name === 'style' || can_use_raw_text);
state.quasi.value.raw += (raw ? wrapper.data : escape_html(wrapper.data))
.replace(regex_backslashes, '\\\\')
@ -1356,14 +1399,18 @@ function to_html(wrappers: Array<CommentWrapper | ElementWrapper | TextWrapper |
// Two or more leading newlines are required to restore the leading newline immediately after `<pre>`.
// see https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element
const first = wrapper.fragment.nodes[0];
if (first && first.node.type === 'Text' && regex_starts_with_newline.test(first.node.data)) {
if (
first &&
first.node.type === 'Text' &&
regex_starts_with_newline.test(first.node.data)
) {
state.quasi.value.raw += '\n';
}
}
if (is_empty_textarea) {
// The <textarea> renders the value attribute as content because the content is stored in the value attribute.
const value_attribute = wrapper.attributes.find(attr => attr.node.name === 'value');
const value_attribute = wrapper.attributes.find((attr) => attr.node.name === 'value');
if (value_attribute) {
// Two or more leading newlines are required to restore the leading newline immediately after `<textarea>`.
// see https://html.spec.whatwg.org/multipage/syntax.html#element-restrictions
@ -1375,7 +1422,12 @@ function to_html(wrappers: Array<CommentWrapper | ElementWrapper | TextWrapper |
}
}
to_html(wrapper.fragment.nodes as Array<ElementWrapper | TextWrapper>, block, literal, state);
to_html(
wrapper.fragment.nodes as Array<ElementWrapper | TextWrapper>,
block,
literal,
state
);
state.quasi.value.raw += `</${nodeName}>`;
} else {
@ -1385,8 +1437,13 @@ function to_html(wrappers: Array<CommentWrapper | ElementWrapper | TextWrapper |
});
}
function to_html_for_attr_value(attr: AttributeWrapper | StyleAttributeWrapper | SpreadAttributeWrapper, block: Block, literal: any, state: any) {
attr.node.chunks.forEach(chunk => {
function to_html_for_attr_value(
attr: AttributeWrapper | StyleAttributeWrapper | SpreadAttributeWrapper,
block: Block,
literal: any,
state: any
) {
attr.node.chunks.forEach((chunk) => {
if (chunk.type === 'Text') {
state.quasi.value.raw += escape_html(chunk.data);
} else {

@ -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));
}
}

@ -34,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'
);
}
}
}
@ -113,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);
@ -169,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;
@ -178,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
@ -198,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) {
@ -215,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 {
@ -242,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});`);
}
}
@ -265,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);
});
}
@ -280,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
@ -292,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 {
@ -329,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;
}
`);
@ -353,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) {
@ -422,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
@ -435,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 {
@ -481,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;
}
`);
@ -608,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);
@ -618,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();
@ -632,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;
@ -48,11 +53,11 @@ export default class InlineComponentWrapper extends Wrapper {
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);
}
@ -60,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();
@ -109,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;
@ -143,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()) {
@ -156,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}
}`;
@ -199,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);
@ -208,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} = {};`);
}
@ -223,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>;
@ -306,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') {
@ -325,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))}) {
@ -353,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
@ -364,7 +385,6 @@ export default class InlineComponentWrapper extends Wrapper {
args.push(renderer.reference(name));
});
block.maintain_context = true; // TODO put this somewhere more logical
}
@ -397,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})`;
@ -405,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;
@ -418,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};
}
@ -439,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) {
@ -459,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'
@ -505,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});
@ -526,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});`);
}
@ -553,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);`);
}
}
@ -564,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(
@ -600,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;

@ -60,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);
@ -86,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,

@ -30,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';
@ -56,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);

@ -49,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);
});
@ -60,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;
@ -78,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)}`);
@ -103,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 {
@ -124,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`
@ -148,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))
@ -172,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});
}
`;
}
@ -198,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});
}
@ -220,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,

@ -11,13 +11,7 @@ export default class TextWrapper extends Wrapper {
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();
@ -55,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,16 +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);
block.add_dependencies(node.expression.dependencies);
}
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);

@ -14,12 +14,7 @@ export default class Wrapper {
var: Identifier;
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
@ -39,7 +34,9 @@ export default class Wrapper {
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' };
@ -57,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}();`;

@ -2,11 +2,7 @@ import Component from '../../../Component';
import { INode } from '../../../nodes/interfaces';
import { regex_whitespace_characters } from '../../../../utils/patterns';
export default function create_debugging_comment(
node: INode,
component: Component
) {
export default function create_debugging_comment(node: INode, component: Component) {
const { locate, source } = component;
let c = node.start;

@ -9,7 +9,7 @@ export function get_slot_definition(block: Block, scope: TemplateScope, lets: Le
const context_input = {
type: 'ObjectPattern',
properties: lets.map(l => ({
properties: lets.map((l) => ({
type: 'Property',
kind: 'init',
key: l.name,
@ -20,7 +20,7 @@ export function get_slot_definition(block: Block, scope: TemplateScope, lets: Le
const properties = [];
const value_map = new Map();
lets.forEach(l => {
lets.forEach((l) => {
let value: Identifier;
if (l.names.length > 1) {
// more than one, probably destructuring
@ -46,8 +46,8 @@ export function get_slot_definition(block: Block, scope: TemplateScope, lets: Le
const names: Set<string> = new Set();
const names_lookup: Map<string, string> = new Map();
lets.forEach(l => {
l.names.forEach(name => {
lets.forEach((l) => {
l.names.forEach((name) => {
names.add(name);
if (value_map.has(l.value)) {
names_lookup.set(name, value_map.get(l.value));
@ -57,7 +57,9 @@ export function get_slot_definition(block: Block, scope: TemplateScope, lets: Le
const context = {
type: 'ObjectExpression',
properties: Array.from(names).map(name => p`${block.renderer.context_lookup.get(name).index}: ${name}`)
properties: Array.from(names).map(
(name) => p`${block.renderer.context_lookup.get(name).index}: ${name}`
)
};
const { context_lookup } = block.renderer;
@ -70,7 +72,7 @@ export function get_slot_definition(block: Block, scope: TemplateScope, lets: Le
if (block.renderer.context_overflow) {
const grouped = [];
Array.from(names).forEach(name => {
Array.from(names).forEach((name) => {
const i = context_lookup.get(name).index.value as number;
const g = Math.floor(i / 31);
@ -85,8 +87,10 @@ export function get_slot_definition(block: Block, scope: TemplateScope, lets: Le
for (let g = 0; g < grouped.length; g += 1) {
elements[g] = grouped[g]
? grouped[g]
.map(({ name, n }) => x`${name} ? ${1 << n} : 0`)
.reduce((lhs: ReturnType<typeof x>, rhs: ReturnType<typeof x>) => x`${lhs} | ${rhs}`)
.map(({ name, n }) => x`${name} ? ${1 << n} : 0`)
.reduce(
(lhs: ReturnType<typeof x>, rhs: ReturnType<typeof x>) => x`${lhs} | ${rhs}`
)
: x`0`;
}
@ -97,7 +101,7 @@ export function get_slot_definition(block: Block, scope: TemplateScope, lets: Le
}
return Array.from(names)
.map(name => {
.map((name) => {
const lookup_name = names_lookup.has(name) ? names_lookup.get(name) : name;
const i = context_lookup.get(name).index.value as number;
return x`${lookup_name} ? ${1 << i} : 0`;

@ -1,5 +1,10 @@
import { Node } from 'estree';
export function is_head(node: Node) {
return node && node.type === 'MemberExpression' && node.object['name'] === '@_document' && node.property['name'] === 'head';
return (
node &&
node.type === 'MemberExpression' &&
node.object['name'] === '@_document' &&
node.property['name'] === 'head'
);
}

@ -9,7 +9,7 @@ export default function mark_each_block_bindings(
) {
// we need to ensure that the each block creates a context including
// the list and the index, if they're not otherwise referenced
binding.expression.references.forEach(name => {
binding.expression.references.forEach((name) => {
const each_block = parent.node.scope.get_owner(name);
if (each_block) {
(each_block as EachBlock).has_binding = true;

@ -45,7 +45,7 @@ const handlers: Record<string, Handler> = {
Window: noop
};
export interface RenderOptions extends CompileOptions{
export interface RenderOptions extends CompileOptions {
locate: (c: number) => { line: number; column: number };
head_id?: string;
has_added_svelte_hash?: boolean;
@ -83,13 +83,13 @@ export default class Renderer {
}
push() {
const current = this.current = { value: '' };
const current = (this.current = { value: '' });
const literal = this.literal = {
const literal = (this.literal = {
type: 'TemplateLiteral',
expressions: [],
quasis: []
};
});
this.stack.push({ current, literal });
}
@ -116,7 +116,7 @@ export default class Renderer {
}
render(nodes: INode[], options: RenderOptions) {
nodes.forEach(node => {
nodes.forEach((node) => {
const handler = handlers[node.type];
if (!handler) {

@ -3,7 +3,7 @@ import AwaitBlock from '../../nodes/AwaitBlock';
import { x } from 'code-red';
import { get_const_tags } from './shared/get_const_tags';
export default function(node: AwaitBlock, renderer: Renderer, options: RenderOptions) {
export default function (node: AwaitBlock, renderer: Renderer, options: RenderOptions) {
renderer.push();
renderer.render(node.pending.children, options);
const pending = renderer.pop();
@ -18,7 +18,9 @@ export default function(node: AwaitBlock, renderer: Renderer, options: RenderOpt
__value.then(null, @noop);
return ${pending};
}
return (function(${node.then_node ? node.then_node : ''}) { ${get_const_tags(node.then.const_tags)}; return ${then}; }(__value));
return (function(${node.then_node ? node.then_node : ''}) { ${get_const_tags(
node.then.const_tags
)}; return ${then}; }(__value));
}(${node.expression.node})
`);
}

@ -1,7 +1,7 @@
import Renderer, { RenderOptions } from '../Renderer';
import Comment from '../../nodes/Comment';
export default function(node: Comment, renderer: Renderer, options: RenderOptions) {
export default function (node: Comment, renderer: Renderer, options: RenderOptions) {
if (options.preserveComments) {
renderer.add_string(`<!--${node.data}-->`);
}

@ -3,15 +3,17 @@ import Renderer, { RenderOptions } from '../Renderer';
import { x, p } from 'code-red';
import { Identifier } from 'estree';
export default function(node: DebugTag, renderer: Renderer, options: RenderOptions) {
export default function (node: DebugTag, renderer: Renderer, options: RenderOptions) {
if (!options.dev) return;
const filename = options.filename || null;
const { line, column } = options.locate(node.start + 1);
const obj = x`{
${node.expressions.map(e => p`${(e.node as Identifier).name}`)}
${node.expressions.map((e) => p`${(e.node as Identifier).name}`)}
}`;
renderer.add_expression(x`@debug(${filename ? x`"${filename}"` : x`null`}, ${line - 1}, ${column}, ${obj})`);
renderer.add_expression(
x`@debug(${filename ? x`"${filename}"` : x`null`}, ${line - 1}, ${column}, ${obj})`
);
}

@ -4,7 +4,7 @@ import { x } from 'code-red';
import { get_const_tags } from './shared/get_const_tags';
import { Node } from 'estree';
export default function(node: EachBlock, renderer: Renderer, options: RenderOptions) {
export default function (node: EachBlock, renderer: Renderer, options: RenderOptions) {
const args = [node.context_node];
if (node.index) args.push({ type: 'Identifier', name: node.index });
@ -12,13 +12,16 @@ export default function(node: EachBlock, renderer: Renderer, options: RenderOpti
renderer.render(node.children, options);
const result = renderer.pop();
const consequent = x`@each(${node.expression.node}, (${args}) => { ${get_const_tags(node.const_tags)}; return ${result} })`;
const consequent = x`@each(${node.expression.node}, (${args}) => { ${get_const_tags(
node.const_tags
)}; return ${result} })`;
if (node.else) {
renderer.push();
renderer.render(node.else.children, options);
let alternate: Node = renderer.pop();
if (node.else.const_tags.length > 0) alternate = x`(() => { ${get_const_tags(node.else.const_tags)}; return ${alternate} })()`;
if (node.else.const_tags.length > 0)
alternate = x`(() => { ${get_const_tags(node.else.const_tags)}; return ${alternate} })()`;
renderer.add_expression(x`${node.expression.node}.length ? ${consequent} : ${alternate}`);
} else {

@ -1,5 +1,9 @@
import { is_void } from '../../../../shared/utils/names';
import { get_attribute_expression, get_attribute_value, get_class_attribute_value } from './shared/get_attribute_value';
import {
get_attribute_expression,
get_attribute_value,
get_class_attribute_value
} from './shared/get_attribute_value';
import { boolean_attributes } from '../../../../shared/boolean_attributes';
import { is_name_contenteditable, is_contenteditable } from '../../utils/contenteditable';
import Renderer, { RenderOptions } from '../Renderer';
@ -14,7 +18,6 @@ import { regex_starts_with_newline } from '../../../utils/patterns';
import { Node, Expression as ESExpression } from 'estree';
export default function (node: Element, renderer: Renderer, options: RenderOptions) {
const children = remove_whitespace_children(node.children, node.next);
// awkward special case
@ -29,7 +32,7 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio
renderer.add_string('<');
add_tag_name();
const class_expression_list = node.classes.map(class_directive => {
const class_expression_list = node.classes.map((class_directive) => {
const { expression, name } = class_directive;
const snippet = expression ? expression.node : x`#ctx.${name}`; // TODO is this right?
return x`${snippet} ? "${name}" : ""`;
@ -41,26 +44,31 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio
class_expression_list.length > 0 &&
class_expression_list.reduce((lhs, rhs) => x`${lhs} + ' ' + ${rhs}`);
const style_expression_list = node.styles.map(style_directive => {
let { name, important, expression: { node: expression } } = style_directive;
const style_expression_list = node.styles.map((style_directive) => {
let {
name,
important,
expression: { node: expression }
} = style_directive;
if (important) {
expression = x`${expression} + ' !important'`;
}
return p`"${name}": ${expression}`;
});
const style_expression =
style_expression_list.length > 0 &&
x`{ ${style_expression_list} }`;
const style_expression = style_expression_list.length > 0 && x`{ ${style_expression_list} }`;
if (node.attributes.some(attr => attr.is_spread)) {
if (node.attributes.some((attr) => attr.is_spread)) {
// TODO dry this out
const args = [];
node.attributes.forEach(attribute => {
node.attributes.forEach((attribute) => {
if (attribute.is_spread) {
args.push(x`@escape_object(${attribute.expression.node})`);
} else {
const attr_name = node.namespace === namespaces.foreign ? attribute.name : fix_attribute_casing(attribute.name);
const attr_name =
node.namespace === namespaces.foreign
? attribute.name
: fix_attribute_casing(attribute.name);
const name = attribute.name.toLowerCase();
if (name === 'value' && node.name.toLowerCase() === 'textarea') {
node_contents = get_attribute_value(attribute);
@ -82,13 +90,18 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio
}
});
renderer.add_expression(x`@spread([${args}], { classes: ${class_expression}, styles: ${style_expression} })`);
renderer.add_expression(
x`@spread([${args}], { classes: ${class_expression}, styles: ${style_expression} })`
);
} else {
let add_class_attribute = !!class_expression;
let add_style_attribute = !!style_expression;
node.attributes.forEach(attribute => {
node.attributes.forEach((attribute) => {
const name = attribute.name.toLowerCase();
const attr_name = node.namespace === namespaces.foreign ? attribute.name : fix_attribute_casing(attribute.name);
const attr_name =
node.namespace === namespaces.foreign
? attribute.name
: fix_attribute_casing(attribute.name);
if (name === 'value' && node.name.toLowerCase() === 'textarea') {
node_contents = get_attribute_value(attribute);
} else if (attribute.is_true) {
@ -100,21 +113,31 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio
) {
// a boolean attribute with one non-Text chunk
renderer.add_string(' ');
renderer.add_expression(x`${(attribute.chunks[0] as Expression).node} ? "${attr_name}" : ""`);
renderer.add_expression(
x`${(attribute.chunks[0] as Expression).node} ? "${attr_name}" : ""`
);
} else if (name === 'class' && class_expression) {
add_class_attribute = false;
renderer.add_string(` ${attr_name}="`);
renderer.add_expression(x`[${get_class_attribute_value(attribute)}, ${class_expression}].join(' ').trim()`);
renderer.add_expression(
x`[${get_class_attribute_value(attribute)}, ${class_expression}].join(' ').trim()`
);
renderer.add_string('"');
} else if (name === 'style' && style_expression) {
add_style_attribute = false;
renderer.add_expression(x`@add_styles(@merge_ssr_styles(${get_attribute_value(attribute)}, ${style_expression}))`);
renderer.add_expression(
x`@add_styles(@merge_ssr_styles(${get_attribute_value(attribute)}, ${style_expression}))`
);
} else if (attribute.chunks.length === 1 && attribute.chunks[0].type !== 'Text') {
const snippet = (attribute.chunks[0] as Expression).node;
renderer.add_expression(x`@add_attribute("${attr_name}", ${snippet}, ${boolean_attributes.has(name) ? 1 : 0})`);
renderer.add_expression(
x`@add_attribute("${attr_name}", ${snippet}, ${boolean_attributes.has(name) ? 1 : 0})`
);
} else {
renderer.add_string(` ${attr_name}="`);
renderer.add_expression((name === 'class' ? get_class_attribute_value : get_attribute_value)(attribute));
renderer.add_expression(
(name === 'class' ? get_class_attribute_value : get_attribute_value)(attribute)
);
renderer.add_string('"');
}
});
@ -139,7 +162,8 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio
const value = get_attribute_expression(value_attribute);
const type = node.get_static_attribute_value('type');
const bound = expression.node;
const condition = type === 'checkbox' ? x`~${bound}.indexOf(${value})` : x`${value} === ${bound}`;
const condition =
type === 'checkbox' ? x`~${bound}.indexOf(${value})` : x`${value} === ${bound}`;
renderer.add_expression(x`${condition} ? @add_attribute("checked", true, 1) : ""`);
}
} else if (contenteditable && is_name_contenteditable(name)) {
@ -154,7 +178,9 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio
// NOTE: do not add "value" attribute on <select />
} else {
const snippet = expression.node;
renderer.add_expression(x`@add_attribute("${name}", ${snippet}, ${boolean_attributes.has(name) ? 1 : 0})`);
renderer.add_expression(
x`@add_attribute("${name}", ${snippet}, ${boolean_attributes.has(name) ? 1 : 0})`
);
}
});
@ -173,7 +199,9 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio
renderer.render(children, options);
const result = renderer.pop();
renderer.add_expression(x`($$value => $$value === void 0 ? ${result} : $$value)(${node_contents})`);
renderer.add_expression(
x`($$value => $$value === void 0 ? ${result} : $$value)(${node_contents})`
);
} else {
if (node.name === 'textarea') {
// Two or more leading newlines are required to restore the leading newline immediately after `<textarea>`.
@ -211,7 +239,8 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio
if (node.is_dynamic_element) {
let content: Node = renderer.pop();
if (options.dev && node.children.length > 0) content = x`(() => { @validate_void_dynamic_element(#tag); return ${content}; })()`;
if (options.dev && node.children.length > 0)
content = x`(() => { @validate_void_dynamic_element(#tag); return ${content}; })()`;
renderer.add_expression(x`((#tag) => {
${options.dev && x`@validate_dynamic_element(#tag)`}
return #tag ? ${content} : '';
@ -238,4 +267,3 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio
}
}
}

@ -3,7 +3,7 @@ import Head from '../../nodes/Head';
import { x } from 'code-red';
import { Node } from 'estree';
export default function(node: Head, renderer: Renderer, options: RenderOptions) {
export default function (node: Head, renderer: Renderer, options: RenderOptions) {
const head_options = {
...options,
head_id: node.id

@ -2,7 +2,7 @@ import Renderer, { RenderOptions } from '../Renderer';
import RawMustacheTag from '../../nodes/RawMustacheTag';
import { Expression } from 'estree';
export default function(node: RawMustacheTag, renderer: Renderer, options: RenderOptions) {
export default function (node: RawMustacheTag, renderer: Renderer, options: RenderOptions) {
if (options.hydratable) renderer.add_string('<!-- HTML_TAG_START -->');
renderer.add_expression(node.expression.node as Expression);
if (options.hydratable) renderer.add_string('<!-- HTML_TAG_END -->');

@ -10,12 +10,14 @@ export default function (node: IfBlock, renderer: Renderer, options: RenderOptio
renderer.push();
renderer.render(node.children, options);
let consequent: Node = renderer.pop();
if (node.const_tags.length > 0) consequent = x`(() => { ${get_const_tags(node.const_tags)}; return ${consequent} })()`;
if (node.const_tags.length > 0)
consequent = x`(() => { ${get_const_tags(node.const_tags)}; return ${consequent} })()`;
renderer.push();
if (node.else) renderer.render(node.else.children, options);
let alternate: Node = renderer.pop();
if (node.else && node.else.const_tags.length > 0) alternate = x`(() => { ${get_const_tags(node.else.const_tags)}; return ${alternate} })()`;
if (node.else && node.else.const_tags.length > 0)
alternate = x`(() => { ${get_const_tags(node.else.const_tags)}; return ${alternate} })()`;
renderer.add_expression(x`${condition} ? ${consequent} : ${alternate}`);
}

@ -10,18 +10,18 @@ function get_prop_value(attribute) {
if (attribute.chunks.length === 0) return x`''`;
return attribute.chunks
.map(chunk => {
.map((chunk) => {
if (chunk.type === 'Text') return string_literal(chunk.data);
return chunk.node;
})
.reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
}
export default function(node: InlineComponent, renderer: Renderer, options: RenderOptions) {
export default function (node: InlineComponent, renderer: Renderer, options: RenderOptions) {
const binding_props = [];
const binding_fns = [];
node.bindings.forEach(binding => {
node.bindings.forEach((binding) => {
renderer.has_bindings = true;
// TODO this probably won't work for contextual bindings
@ -31,25 +31,23 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend
binding_fns.push(p`${binding.name}: $$value => { ${snippet} = $$value; $$settled = false }`);
});
const uses_spread = node.attributes.find(attr => attr.is_spread);
const uses_spread = node.attributes.find((attr) => attr.is_spread);
let props;
if (uses_spread) {
props = x`@_Object.assign({}, ${
node.attributes
.map(attribute => {
if (attribute.is_spread) {
return attribute.expression.node;
} else {
return x`{ ${attribute.name}: ${get_prop_value(attribute)} }`;
}
})
.concat(binding_props.map(p => x`{ ${p} }`))
})`;
props = x`@_Object.assign({}, ${node.attributes
.map((attribute) => {
if (attribute.is_spread) {
return attribute.expression.node;
} else {
return x`{ ${attribute.name}: ${get_prop_value(attribute)} }`;
}
})
.concat(binding_props.map((p) => x`{ ${p} }`))})`;
} else {
props = x`{
${node.attributes.map(attribute => p`${attribute.name}: ${get_prop_value(attribute)}`)},
${node.attributes.map((attribute) => p`${attribute.name}: ${get_prop_value(attribute)}`)},
${binding_props}
}`;
}
@ -58,13 +56,12 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend
${binding_fns}
}`;
const expression = (
const expression =
node.name === 'svelte:self'
? renderer.name
: node.name === 'svelte:component'
? x`(${node.expression.node}) || @missing_component`
: node.name.split('.').reduce(((lhs, rhs) => x`${lhs}.${rhs}`) as any)
);
? x`(${node.expression.node}) || @missing_component`
: node.name.split('.').reduce(((lhs, rhs) => x`${lhs}.${rhs}`) as any);
const slot_fns = [];
@ -73,14 +70,15 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend
if (children.length) {
const slot_scopes = new Map();
renderer.render(children, Object.assign({}, options, {
slot_scopes
}));
renderer.render(
children,
Object.assign({}, options, {
slot_scopes
})
);
slot_scopes.forEach(({ input, output, statements }, name) => {
slot_fns.push(
p`${name}: (${input}) => { ${statements}; return ${output}; }`
);
slot_fns.push(p`${name}: (${input}) => { ${statements}; return ${output}; }`);
});
}
@ -103,7 +101,9 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend
renderer.add_string('">');
}
renderer.add_expression(x`@validate_component(${expression}, "${node.name}").$$render($$result, ${props}, ${bindings}, ${slots})`);
renderer.add_expression(
x`@validate_component(${expression}, "${node.name}").$$render($$result, ${props}, ${bindings}, ${slots})`
);
if (node.css_custom_properties.length > 0) {
if (node.namespace === namespaces.svg) {

@ -1,6 +1,6 @@
import KeyBlock from '../../nodes/KeyBlock';
import Renderer, { RenderOptions } from '../Renderer';
export default function(node: KeyBlock, renderer: Renderer, options: RenderOptions) {
export default function (node: KeyBlock, renderer: Renderer, options: RenderOptions) {
renderer.render(node.children, options);
}

@ -4,9 +4,13 @@ import { x } from 'code-red';
import get_slot_data from '../../utils/get_slot_data';
import { get_slot_scope } from './shared/get_slot_scope';
export default function(node: Slot, renderer: Renderer, options: RenderOptions & {
slot_scopes: Map<any, any>;
}) {
export default function (
node: Slot,
renderer: Renderer,
options: RenderOptions & {
slot_scopes: Map<any, any>;
}
) {
const slot_data = get_slot_data(node.values);
const slot = node.get_static_attribute_value('slot');
const nearest_inline_component = node.find_nearest(/InlineComponent/);
@ -27,9 +31,9 @@ export default function(node: Slot, renderer: Renderer, options: RenderOptions &
if (slot && nearest_inline_component) {
const lets = node.lets;
const seen = new Set(lets.map(l => l.name.name));
const seen = new Set(lets.map((l) => l.name.name));
nearest_inline_component.lets.forEach(l => {
nearest_inline_component.lets.forEach((l) => {
if (!seen.has(l.name.name)) lets.push(l);
});
options.slot_scopes.set(slot, {

@ -5,18 +5,25 @@ import { get_slot_scope } from './shared/get_slot_scope';
import InlineComponent from '../../nodes/InlineComponent';
import { get_const_tags } from './shared/get_const_tags';
export default function(node: SlotTemplate, renderer: Renderer, options: RenderOptions & {
slot_scopes: Map<any, any>;
}) {
export default function (
node: SlotTemplate,
renderer: Renderer,
options: RenderOptions & {
slot_scopes: Map<any, any>;
}
) {
const parent_inline_component = node.parent as InlineComponent;
const children = remove_whitespace_children(node instanceof SlotTemplate ? node.children : [node], node.next);
const children = remove_whitespace_children(
node instanceof SlotTemplate ? node.children : [node],
node.next
);
renderer.push();
renderer.render(children, options);
const lets = node.lets;
const seen = new Set(lets.map(l => l.name.name));
parent_inline_component.lets.forEach(l => {
const seen = new Set(lets.map((l) => l.name.name));
parent_inline_component.lets.forEach((l) => {
if (!seen.has(l.name.name)) lets.push(l);
});
@ -26,7 +33,9 @@ export default function(node: SlotTemplate, renderer: Renderer, options: RenderO
if (node.slot_template_name === 'default') {
throw new Error('Found elements without slot attribute when using slot="default"');
}
throw new Error(`Duplicate slot name "${node.slot_template_name}" in <${parent_inline_component.name}>`);
throw new Error(
`Duplicate slot name "${node.slot_template_name}" in <${parent_inline_component.name}>`
);
}
options.slot_scopes.set(node.slot_template_name, {

@ -1,14 +1,11 @@
import Renderer, { RenderOptions } from '../Renderer';
import { x } from 'code-red';
export default function(node, renderer: Renderer, _options: RenderOptions) {
export default function (node, renderer: Renderer, _options: RenderOptions) {
const snippet = node.expression.node;
renderer.add_expression(
node.parent &&
node.parent.type === 'Element' &&
node.parent.name === 'style'
node.parent && node.parent.type === 'Element' && node.parent.name === 'style'
? snippet
: x`@escape(${snippet})`
);

@ -3,7 +3,7 @@ import Renderer, { RenderOptions } from '../Renderer';
import Text from '../../nodes/Text';
import Element from '../../nodes/Element';
export default function(node: Text, renderer: Renderer, _options: RenderOptions) {
export default function (node: Text, renderer: Renderer, _options: RenderOptions) {
let text = node.data;
if (node.use_space()) {
text = ' ';

@ -2,7 +2,7 @@ import Renderer, { RenderOptions } from '../Renderer';
import Title from '../../nodes/Title';
import { x } from 'code-red';
export default function(node: Title, renderer: Renderer, options: RenderOptions) {
export default function (node: Title, renderer: Renderer, options: RenderOptions) {
renderer.push();
renderer.add_string('<title>');

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

Loading…
Cancel
Save