From 46e9e94a99f8281eaed0f62b626e8f4dc30f0b85 Mon Sep 17 00:00:00 2001 From: Leonardo Marquine Date: Wed, 30 Oct 2019 09:34:26 -0300 Subject: [PATCH 01/24] Add easing param to fade transition --- src/runtime/transition/index.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/runtime/transition/index.ts b/src/runtime/transition/index.ts index ec19ca1b68..0a20c81b1f 100644 --- a/src/runtime/transition/index.ts +++ b/src/runtime/transition/index.ts @@ -1,4 +1,4 @@ -import { cubicOut, cubicInOut } from 'svelte/easing'; +import { cubicOut, cubicInOut, linear } from 'svelte/easing'; import { assign, is_function } from 'svelte/internal'; type EasingFunction = (t: number) => number; @@ -43,17 +43,20 @@ export function blur(node: Element, { interface FadeParams { delay: number; duration: number; + easing: EasingFunction; } export function fade(node: Element, { delay = 0, - duration = 400 + duration = 400, + easing = linear }: FadeParams): TransitionConfig { const o = +getComputedStyle(node).opacity; return { delay, duration, + easing, css: t => `opacity: ${t * o}` }; } From 798a47b1da69b00980239dee071385ac18f957b8 Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Thu, 31 Oct 2019 01:02:03 +0800 Subject: [PATCH 02/24] feat unused css selector understands string concat --- src/compiler/compile/css/Selector.ts | 87 +++++++++-- .../unused-selector-string-concat/_config.js | 143 ++++++++++++++++++ .../expected.css | 1 + .../input.svelte | 29 ++++ .../unused-selector-ternary-bailed/_config.js | 3 + .../expected.css | 1 + .../input.svelte | 18 +++ .../unused-selector-ternary-concat/_config.js | 25 +++ .../expected.css | 1 + .../input.svelte | 15 ++ .../unused-selector-ternary-nested/_config.js | 31 ++++ .../expected.css | 1 + .../input.svelte | 18 +++ 13 files changed, 364 insertions(+), 9 deletions(-) create mode 100644 test/css/samples/unused-selector-string-concat/_config.js create mode 100644 test/css/samples/unused-selector-string-concat/expected.css create mode 100644 test/css/samples/unused-selector-string-concat/input.svelte create mode 100644 test/css/samples/unused-selector-ternary-bailed/_config.js create mode 100644 test/css/samples/unused-selector-ternary-bailed/expected.css create mode 100644 test/css/samples/unused-selector-ternary-bailed/input.svelte create mode 100644 test/css/samples/unused-selector-ternary-concat/_config.js create mode 100644 test/css/samples/unused-selector-ternary-concat/expected.css create mode 100644 test/css/samples/unused-selector-ternary-concat/input.svelte create mode 100644 test/css/samples/unused-selector-ternary-nested/_config.js create mode 100644 test/css/samples/unused-selector-ternary-nested/expected.css create mode 100644 test/css/samples/unused-selector-ternary-nested/input.svelte diff --git a/src/compiler/compile/css/Selector.ts b/src/compiler/compile/css/Selector.ts index ab19ebd1e1..d99af7a110 100644 --- a/src/compiler/compile/css/Selector.ts +++ b/src/compiler/compile/css/Selector.ts @@ -3,6 +3,7 @@ import Stylesheet from './Stylesheet'; import { gather_possible_values, UNKNOWN } from './gather_possible_values'; import { CssNode } from './interfaces'; import Component from '../Component'; +import Element from '../nodes/Element'; enum BlockAppliesToNode { NotPossible, @@ -34,8 +35,8 @@ export default class Selector { this.used = this.blocks[0].global; } - apply(node: CssNode, stack: CssNode[]) { - const to_encapsulate: CssNode[] = []; + apply(node: Element, stack: Element[]) { + const to_encapsulate: any[] = []; apply_selector(this.local_blocks.slice(), node, stack.slice(), to_encapsulate); @@ -132,7 +133,7 @@ export default class Selector { } } -function apply_selector(blocks: Block[], node: CssNode, stack: CssNode[], to_encapsulate: any[]): boolean { +function apply_selector(blocks: Block[], node: Element, stack: Element[], to_encapsulate: any[]): boolean { const block = blocks.pop(); if (!block) return false; @@ -259,16 +260,84 @@ function attribute_matches(node: CssNode, name: string, expected_value: string, const attr = node.attributes.find((attr: CssNode) => attr.name === name); if (!attr) return false; if (attr.is_true) return operator === null; - if (attr.chunks.length > 1) return true; if (!expected_value) return true; - const value = attr.chunks[0]; - - if (!value) return false; - if (value.type === 'Text') return test_attribute(operator, expected_value, case_insensitive, value.data); + if (attr.chunks.length === 1) { + const value = attr.chunks[0]; + if (!value) return false; + if (value.type === 'Text') return test_attribute(operator, expected_value, case_insensitive, value.data); + } const possible_values = new Set(); - gather_possible_values(value.node, possible_values); + + let prev_values = []; + for (const chunk of attr.chunks) { + const current_possible_values = new Set(); + if (chunk.type === 'Text') { + current_possible_values.add(chunk.data); + } else { + gather_possible_values(chunk.node, current_possible_values); + } + + // impossible to find out all combinations + if (current_possible_values.has(UNKNOWN)) return true; + + if (prev_values.length > 0) { + const start_with_space = []; + const remaining = []; + current_possible_values.forEach((current_possible_value: string) => { + if (/^\s/.test(current_possible_value)) { + start_with_space.push(current_possible_value); + } else { + remaining.push(current_possible_value); + } + }); + + if (remaining.length > 0) { + if (start_with_space.length > 0) { + prev_values.forEach(prev_value => possible_values.add(prev_value)); + } + + const combined = []; + prev_values.forEach((prev_value: string) => { + remaining.forEach((value: string) => { + combined.push(prev_value + value); + }); + }); + prev_values = combined; + + start_with_space.forEach((value: string) => { + if (/\s$/.test(value)) { + possible_values.add(value); + } else { + prev_values.push(value); + } + }); + continue; + } else { + prev_values.forEach(prev_value => possible_values.add(prev_value)); + prev_values = []; + } + } + + current_possible_values.forEach((current_possible_value: string) => { + if (/\s$/.test(current_possible_value)) { + possible_values.add(current_possible_value); + } else { + prev_values.push(current_possible_value); + } + }); + if (prev_values.length < current_possible_values.size) { + prev_values.push(' '); + } + + if (prev_values.length > 20) { + // might grow exponentially, bail out + return true; + } + } + prev_values.forEach(prev_value => possible_values.add(prev_value)); + if (possible_values.has(UNKNOWN)) return true; for (const value of possible_values) { diff --git a/test/css/samples/unused-selector-string-concat/_config.js b/test/css/samples/unused-selector-string-concat/_config.js new file mode 100644 index 0000000000..81318fd3ac --- /dev/null +++ b/test/css/samples/unused-selector-string-concat/_config.js @@ -0,0 +1,143 @@ +export default { + warnings: [ + { + code: 'css-unused-selector', + message: 'Unused CSS selector', + frame: + ` 9: `, + start: { line: 28, column: 2, character: 595 }, + end: { line: 28, column: 9, character: 602 }, + pos: 595, + }, + ], +}; diff --git a/test/css/samples/unused-selector-string-concat/expected.css b/test/css/samples/unused-selector-string-concat/expected.css new file mode 100644 index 0000000000..756c2eecae --- /dev/null +++ b/test/css/samples/unused-selector-string-concat/expected.css @@ -0,0 +1 @@ +.foo.svelte-xyz{color:red}.foocc.svelte-xyz{color:red}.aa.svelte-xyz{color:red}.bb.svelte-xyz{color:red}.cc.svelte-xyz{color:red}.dd.svelte-xyz{color:red}.aabar.svelte-xyz{color:red}.fooddbar.svelte-xyz{color:red}.baz.svelte-xyz{color:red} \ No newline at end of file diff --git a/test/css/samples/unused-selector-string-concat/input.svelte b/test/css/samples/unused-selector-string-concat/input.svelte new file mode 100644 index 0000000000..0f69463e78 --- /dev/null +++ b/test/css/samples/unused-selector-string-concat/input.svelte @@ -0,0 +1,29 @@ + + +
+ some stuff +
+ + \ No newline at end of file diff --git a/test/css/samples/unused-selector-ternary-bailed/_config.js b/test/css/samples/unused-selector-ternary-bailed/_config.js new file mode 100644 index 0000000000..e5f82e4a85 --- /dev/null +++ b/test/css/samples/unused-selector-ternary-bailed/_config.js @@ -0,0 +1,3 @@ +export default { + warnings: [], +}; diff --git a/test/css/samples/unused-selector-ternary-bailed/expected.css b/test/css/samples/unused-selector-ternary-bailed/expected.css new file mode 100644 index 0000000000..042d33f3cc --- /dev/null +++ b/test/css/samples/unused-selector-ternary-bailed/expected.css @@ -0,0 +1 @@ +.thing.svelte-xyz{color:blue}.active.svelte-xyz{color:blue}.thing.active.svelte-xyz{color:blue}.hover.svelte-xyz{color:blue}.hover.unused.svelte-xyz{color:blue}.unused.svelte-xyz{color:blue} \ No newline at end of file diff --git a/test/css/samples/unused-selector-ternary-bailed/input.svelte b/test/css/samples/unused-selector-ternary-bailed/input.svelte new file mode 100644 index 0000000000..f9af44ec8b --- /dev/null +++ b/test/css/samples/unused-selector-ternary-bailed/input.svelte @@ -0,0 +1,18 @@ + + +
+ some stuff +
+ + \ No newline at end of file diff --git a/test/css/samples/unused-selector-ternary-concat/_config.js b/test/css/samples/unused-selector-ternary-concat/_config.js new file mode 100644 index 0000000000..5015fccb25 --- /dev/null +++ b/test/css/samples/unused-selector-ternary-concat/_config.js @@ -0,0 +1,25 @@ +export default { + warnings: [ + { + code: 'css-unused-selector', + end: { + character: 205, + column: 9, + line: 14, + }, + frame: ` + 12: .thing.active {color: blue;} + 13: + 14: .unused {color: blue;} + ^ + 15: `, + message: 'Unused CSS selector', + pos: 198, + start: { + character: 198, + column: 2, + line: 14, + }, + }, + ], +}; diff --git a/test/css/samples/unused-selector-ternary-concat/expected.css b/test/css/samples/unused-selector-ternary-concat/expected.css new file mode 100644 index 0000000000..8142a20e83 --- /dev/null +++ b/test/css/samples/unused-selector-ternary-concat/expected.css @@ -0,0 +1 @@ +.thing.svelte-xyz{color:blue}.active.svelte-xyz{color:blue}.thing.active.svelte-xyz{color:blue} \ No newline at end of file diff --git a/test/css/samples/unused-selector-ternary-concat/input.svelte b/test/css/samples/unused-selector-ternary-concat/input.svelte new file mode 100644 index 0000000000..8db417c928 --- /dev/null +++ b/test/css/samples/unused-selector-ternary-concat/input.svelte @@ -0,0 +1,15 @@ + + +
+ some stuff +
+ + \ No newline at end of file diff --git a/test/css/samples/unused-selector-ternary-nested/_config.js b/test/css/samples/unused-selector-ternary-nested/_config.js new file mode 100644 index 0000000000..afee5ac822 --- /dev/null +++ b/test/css/samples/unused-selector-ternary-nested/_config.js @@ -0,0 +1,31 @@ +export default { + warnings: [ + { + code: 'css-unused-selector', + message: 'Unused CSS selector', + frame: ` + 13: .thing.active {color: blue;} + 14: .hover { color: blue; } + 15: .hover.unused { color: blue; } + ^ + 16: + 17: .unused {color: blue;}`, + start: { line: 15, column: 2, character: 261 }, + end: { line: 15, column: 15, character: 274 }, + pos: 261, + }, + { + code: 'css-unused-selector', + message: 'Unused CSS selector', + frame: ` + 15: .hover.unused { color: blue; } + 16: + 17: .unused {color: blue;} + ^ + 18: `, + start: { line: 17, column: 2, character: 295 }, + end: { line: 17, column: 9, character: 302 }, + pos: 295, + }, + ], +}; diff --git a/test/css/samples/unused-selector-ternary-nested/expected.css b/test/css/samples/unused-selector-ternary-nested/expected.css new file mode 100644 index 0000000000..85a95ad23e --- /dev/null +++ b/test/css/samples/unused-selector-ternary-nested/expected.css @@ -0,0 +1 @@ +.thing.svelte-xyz{color:blue}.active.svelte-xyz{color:blue}.thing.active.svelte-xyz{color:blue}.hover.svelte-xyz{color:blue} \ No newline at end of file diff --git a/test/css/samples/unused-selector-ternary-nested/input.svelte b/test/css/samples/unused-selector-ternary-nested/input.svelte new file mode 100644 index 0000000000..a7a8f0e02a --- /dev/null +++ b/test/css/samples/unused-selector-ternary-nested/input.svelte @@ -0,0 +1,18 @@ + + +
+ some stuff +
+ + \ No newline at end of file From aa0341c9cadaf4927db8bec1ef296160fad0c7be Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Thu, 31 Oct 2019 22:15:12 +0800 Subject: [PATCH 03/24] Don't create a fragment at all for DOM-less components --- src/compiler/compile/render_dom/Block.ts | 15 ++++++ src/compiler/compile/render_dom/index.ts | 17 ++++--- .../wrappers/InlineComponent/index.ts | 10 ++-- src/runtime/internal/Component.ts | 46 +++++++++++++++---- src/runtime/internal/scheduler.ts | 4 +- .../component-static-array/expected.js | 3 +- .../component-static-immutable/expected.js | 3 +- .../component-static-immutable2/expected.js | 3 +- .../samples/component-static-var/expected.js | 5 +- test/js/samples/component-static/expected.js | 3 +- .../expected.js | 14 +----- .../samples/computed-collapsed-if/expected.js | 15 +----- .../js/samples/deconflict-globals/expected.js | 15 +----- test/js/samples/dynamic-import/expected.js | 3 +- test/js/samples/empty-dom/expected.js | 15 ++++++ test/js/samples/empty-dom/input.svelte | 3 ++ .../non-imported-component/expected.js | 5 +- .../expected.js | 15 +----- .../expected.js | 15 +----- test/js/samples/setup-method/expected.js | 16 +------ test/runtime/samples/empty-dom/_config.js | 3 ++ test/runtime/samples/empty-dom/main.svelte | 3 ++ 22 files changed, 120 insertions(+), 111 deletions(-) create mode 100644 test/js/samples/empty-dom/expected.js create mode 100644 test/js/samples/empty-dom/input.svelte create mode 100644 test/runtime/samples/empty-dom/_config.js create mode 100644 test/runtime/samples/empty-dom/main.svelte diff --git a/src/compiler/compile/render_dom/Block.ts b/src/compiler/compile/render_dom/Block.ts index b268954dd0..faa7c43271 100644 --- a/src/compiler/compile/render_dom/Block.ts +++ b/src/compiler/compile/render_dom/Block.ts @@ -398,6 +398,21 @@ export default class Block { return body; } + has_content() { + return this.renderer.options.dev || + this.first || + this.event_listeners.length > 0 || + this.chunks.intro.length > 0 || + this.chunks.outro.length > 0 || + this.chunks.create.length > 0 || + this.chunks.hydrate.length > 0 || + this.chunks.claim.length > 0 || + this.chunks.mount.length > 0 || + this.chunks.update.length > 0 || + this.chunks.destroy.length > 0 || + this.has_animation; + } + render() { const key = this.key && this.get_unique_name('key'); diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index a50a86e2f3..49ce17236a 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -241,11 +241,16 @@ export default function dom( args.push(x`$$props`, x`$$invalidate`); } - body.push(b` - function create_fragment(#ctx) { - ${block.get_contents()} - } + const has_create_fragment = block.has_content(); + if (has_create_fragment) { + body.push(b` + function create_fragment(#ctx) { + ${block.get_contents()} + } + `); + } + body.push(b` ${component.extract_javascript(component.ast.module)} ${component.fully_hoisted} @@ -437,7 +442,7 @@ export default function dom( ${css.code && b`this.shadowRoot.innerHTML = \`\`;`} - @init(this, { target: this.shadowRoot }, ${definition}, create_fragment, ${not_equal}, ${prop_names}); + @init(this, { target: this.shadowRoot }, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_names}); ${dev_props_check} @@ -489,7 +494,7 @@ export default function dom( constructor(options) { super(${options.dev && `options`}); ${should_add_css && b`if (!@_document.getElementById("${component.stylesheet.id}-style")) ${add_css}();`} - @init(this, options, ${definition}, create_fragment, ${not_equal}, ${prop_names}); + @init(this, options, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_names}); ${options.dev && b`@dispatch_dev("SvelteRegisterComponent", { component: this, tagName: "${name.name}", options, id: create_fragment.name });`} ${dev_props_check} diff --git a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts index c4a848b456..a9d989c92a 100644 --- a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts +++ b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts @@ -396,12 +396,12 @@ export default class InlineComponentWrapper extends Wrapper { `); block.chunks.create.push( - b`if (${name}) ${name}.$$.fragment.c();` + b`if (${name}) @create_component(${name}.$$.fragment);` ); if (parent_nodes && this.renderer.options.hydratable) { block.chunks.claim.push( - b`if (${name}) ${name}.$$.fragment.l(${parent_nodes});` + b`if (${name}) @claim_component(${name}.$$.fragment, ${parent_nodes});` ); } @@ -437,7 +437,7 @@ export default class InlineComponentWrapper extends Wrapper { ${munged_bindings} ${munged_handlers} - ${name}.$$.fragment.c(); + @create_component(${name}.$$.fragment); @transition_in(${name}.$$.fragment, 1); @mount_component(${name}, ${update_mount_node}, ${anchor}); } else { @@ -472,11 +472,11 @@ export default class InlineComponentWrapper extends Wrapper { ${munged_handlers} `); - block.chunks.create.push(b`${name}.$$.fragment.c();`); + block.chunks.create.push(b`@create_component(${name}.$$.fragment);`); if (parent_nodes && this.renderer.options.hydratable) { block.chunks.claim.push( - b`${name}.$$.fragment.l(${parent_nodes});` + b`@claim_component(${name}.$$.fragment, ${parent_nodes});` ); } diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index 3855d3c361..0ce3b3209b 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -4,6 +4,21 @@ import { blank_object, is_function, run, run_all, noop, has_prop } from './utils import { children } from './dom'; import { transition_in } from './transitions'; +interface Fragment { + key: string|null; + first: null; + /* create */ c: () => void; + /* claim */ l: (nodes: any) => void; + /* hydrate */ h: () => void; + /* mount */ m: (target: HTMLElement, anchor: any) => void; + /* update */ p: (changed: any, ctx: any) => void; + /* measure */ r: () => void; + /* fix */ f: () => void; + /* animate */ a: () => void; + /* intro */ i: (local: any) => void; + /* outro */ o: (local: any) => void; + /* destroy */ d: (detaching: 0|1) => void; +} // eslint-disable-next-line @typescript-eslint/class-name-casing interface T$$ { dirty: null; @@ -13,7 +28,7 @@ interface T$$ { callbacks: any; after_update: any[]; props: Record; - fragment: null|any; + fragment: null|false|Fragment; not_equal: any; before_update: any[]; context: Map; @@ -29,10 +44,18 @@ export function bind(component, name, callback) { } } +export function create_component(block) { + block && block.c(); +} + +export function claim_component(block, parent_nodes) { + block && block.l(parent_nodes); +} + export function mount_component(component, target, anchor) { const { fragment, on_mount, on_destroy, after_update } = component.$$; - fragment.m(target, anchor); + fragment && fragment.m(target, anchor); // onMount happens before the initial afterUpdate add_render_callback(() => { @@ -51,15 +74,16 @@ export function mount_component(component, target, anchor) { } export function destroy_component(component, detaching) { - if (component.$$.fragment) { - run_all(component.$$.on_destroy); + const $$ = component.$$; + if ($$.fragment !== null) { + run_all($$.on_destroy); - component.$$.fragment.d(detaching); + $$.fragment && $$.fragment.d(detaching); // TODO null out other refs, including component.$$ (but need to // preserve final state?) - component.$$.on_destroy = component.$$.fragment = null; - component.$$.ctx = {}; + $$.on_destroy = $$.fragment = null; + $$.ctx = {}; } } @@ -115,15 +139,17 @@ export function init(component, options, instance, create_fragment, not_equal, p $$.update(); ready = true; run_all($$.before_update); - $$.fragment = create_fragment($$.ctx); + + // `false` as a special case of no DOM component + $$.fragment = create_fragment ? create_fragment($$.ctx) : false; if (options.target) { if (options.hydrate) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - $$.fragment!.l(children(options.target)); + $$.fragment && $$.fragment!.l(children(options.target)); } else { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - $$.fragment!.c(); + $$.fragment && $$.fragment!.c(); } if (options.intro) transition_in(component.$$.fragment); diff --git a/src/runtime/internal/scheduler.ts b/src/runtime/internal/scheduler.ts index e3d7181fcb..7cb00c085b 100644 --- a/src/runtime/internal/scheduler.ts +++ b/src/runtime/internal/scheduler.ts @@ -70,10 +70,10 @@ export function flush() { } function update($$) { - if ($$.fragment) { + if ($$.fragment !== null) { $$.update($$.dirty); run_all($$.before_update); - $$.fragment.p($$.dirty, $$.ctx); + $$.fragment && $$.fragment.p($$.dirty, $$.ctx); $$.dirty = null; $$.after_update.forEach(add_render_callback); diff --git a/test/js/samples/component-static-array/expected.js b/test/js/samples/component-static-array/expected.js index 2c38d80f30..0f32421e91 100644 --- a/test/js/samples/component-static-array/expected.js +++ b/test/js/samples/component-static-array/expected.js @@ -1,5 +1,6 @@ import { SvelteComponent, + create_component, destroy_component, init, mount_component, @@ -15,7 +16,7 @@ function create_fragment(ctx) { return { c() { - nested.$$.fragment.c(); + create_component(nested.$$.fragment); }, m(target, anchor) { mount_component(nested, target, anchor); diff --git a/test/js/samples/component-static-immutable/expected.js b/test/js/samples/component-static-immutable/expected.js index d60aeb3939..817db4e494 100644 --- a/test/js/samples/component-static-immutable/expected.js +++ b/test/js/samples/component-static-immutable/expected.js @@ -1,5 +1,6 @@ import { SvelteComponent, + create_component, destroy_component, init, mount_component, @@ -15,7 +16,7 @@ function create_fragment(ctx) { return { c() { - nested.$$.fragment.c(); + create_component(nested.$$.fragment); }, m(target, anchor) { mount_component(nested, target, anchor); diff --git a/test/js/samples/component-static-immutable2/expected.js b/test/js/samples/component-static-immutable2/expected.js index d60aeb3939..817db4e494 100644 --- a/test/js/samples/component-static-immutable2/expected.js +++ b/test/js/samples/component-static-immutable2/expected.js @@ -1,5 +1,6 @@ import { SvelteComponent, + create_component, destroy_component, init, mount_component, @@ -15,7 +16,7 @@ function create_fragment(ctx) { return { c() { - nested.$$.fragment.c(); + create_component(nested.$$.fragment); }, m(target, anchor) { mount_component(nested, target, anchor); diff --git a/test/js/samples/component-static-var/expected.js b/test/js/samples/component-static-var/expected.js index e1372a7b6d..0120b2c8cf 100644 --- a/test/js/samples/component-static-var/expected.js +++ b/test/js/samples/component-static-var/expected.js @@ -1,5 +1,6 @@ import { SvelteComponent, + create_component, destroy_component, detach, element, @@ -28,9 +29,9 @@ function create_fragment(ctx) { return { c() { - foo.$$.fragment.c(); + create_component(foo.$$.fragment); t0 = space(); - bar.$$.fragment.c(); + create_component(bar.$$.fragment); t1 = space(); input = element("input"); dispose = listen(input, "input", ctx.input_input_handler); diff --git a/test/js/samples/component-static/expected.js b/test/js/samples/component-static/expected.js index 14b85a917a..76321cfd82 100644 --- a/test/js/samples/component-static/expected.js +++ b/test/js/samples/component-static/expected.js @@ -1,5 +1,6 @@ import { SvelteComponent, + create_component, destroy_component, init, mount_component, @@ -15,7 +16,7 @@ function create_fragment(ctx) { return { c() { - nested.$$.fragment.c(); + create_component(nested.$$.fragment); }, m(target, anchor) { mount_component(nested, target, anchor); diff --git a/test/js/samples/component-store-file-invalidate/expected.js b/test/js/samples/component-store-file-invalidate/expected.js index 987f3971a1..5a466d7786 100644 --- a/test/js/samples/component-store-file-invalidate/expected.js +++ b/test/js/samples/component-store-file-invalidate/expected.js @@ -2,24 +2,12 @@ import { SvelteComponent, component_subscribe, init, - noop, safe_not_equal, set_store_value } from "svelte/internal"; import { count } from "./store.js"; -function create_fragment(ctx) { - return { - c: noop, - m: noop, - p: noop, - i: noop, - o: noop, - d: noop - }; -} - function instance($$self, $$props, $$invalidate) { let $count; component_subscribe($$self, count, $$value => $$invalidate("$count", $count = $$value)); @@ -34,7 +22,7 @@ function instance($$self, $$props, $$invalidate) { class Component extends SvelteComponent { constructor(options) { super(); - init(this, options, instance, create_fragment, safe_not_equal, { increment: 0 }); + init(this, options, instance, null, safe_not_equal, { increment: 0 }); } get increment() { diff --git a/test/js/samples/computed-collapsed-if/expected.js b/test/js/samples/computed-collapsed-if/expected.js index a63ee30ca0..d4b0289212 100644 --- a/test/js/samples/computed-collapsed-if/expected.js +++ b/test/js/samples/computed-collapsed-if/expected.js @@ -1,15 +1,4 @@ -import { SvelteComponent, init, noop, safe_not_equal } from "svelte/internal"; - -function create_fragment(ctx) { - return { - c: noop, - m: noop, - p: noop, - i: noop, - o: noop, - d: noop - }; -} +import { SvelteComponent, init, safe_not_equal } from "svelte/internal"; function instance($$self, $$props, $$invalidate) { let { x } = $$props; @@ -32,7 +21,7 @@ function instance($$self, $$props, $$invalidate) { class Component extends SvelteComponent { constructor(options) { super(); - init(this, options, instance, create_fragment, safe_not_equal, { x: 0, a: 0, b: 0 }); + init(this, options, instance, null, safe_not_equal, { x: 0, a: 0, b: 0 }); } get a() { diff --git a/test/js/samples/deconflict-globals/expected.js b/test/js/samples/deconflict-globals/expected.js index bf67b6aae4..92492dcd05 100644 --- a/test/js/samples/deconflict-globals/expected.js +++ b/test/js/samples/deconflict-globals/expected.js @@ -1,17 +1,6 @@ -import { SvelteComponent, init, noop, safe_not_equal } from "svelte/internal"; +import { SvelteComponent, init, safe_not_equal } from "svelte/internal"; import { onMount } from "svelte"; -function create_fragment(ctx) { - return { - c: noop, - m: noop, - p: noop, - i: noop, - o: noop, - d: noop - }; -} - function instance($$self, $$props, $$invalidate) { let { foo = "bar" } = $$props; @@ -29,7 +18,7 @@ function instance($$self, $$props, $$invalidate) { class Component extends SvelteComponent { constructor(options) { super(); - init(this, options, instance, create_fragment, safe_not_equal, { foo: 0 }); + init(this, options, instance, null, safe_not_equal, { foo: 0 }); } } diff --git a/test/js/samples/dynamic-import/expected.js b/test/js/samples/dynamic-import/expected.js index 95a50f4a4d..605f19983f 100644 --- a/test/js/samples/dynamic-import/expected.js +++ b/test/js/samples/dynamic-import/expected.js @@ -1,5 +1,6 @@ import { SvelteComponent, + create_component, destroy_component, init, mount_component, @@ -17,7 +18,7 @@ function create_fragment(ctx) { return { c() { - lazyload.$$.fragment.c(); + create_component(lazyload.$$.fragment); }, m(target, anchor) { mount_component(lazyload, target, anchor); diff --git a/test/js/samples/empty-dom/expected.js b/test/js/samples/empty-dom/expected.js new file mode 100644 index 0000000000..6ae0bc2999 --- /dev/null +++ b/test/js/samples/empty-dom/expected.js @@ -0,0 +1,15 @@ +import { SvelteComponent, init, safe_not_equal } from "svelte/internal"; + +function instance($$self) { + const a = 1 + 2; + return {}; +} + +class Component extends SvelteComponent { + constructor(options) { + super(); + init(this, options, instance, null, safe_not_equal, {}); + } +} + +export default Component; \ No newline at end of file diff --git a/test/js/samples/empty-dom/input.svelte b/test/js/samples/empty-dom/input.svelte new file mode 100644 index 0000000000..3098443ea3 --- /dev/null +++ b/test/js/samples/empty-dom/input.svelte @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/test/js/samples/non-imported-component/expected.js b/test/js/samples/non-imported-component/expected.js index d9176f35f7..be1ff282c3 100644 --- a/test/js/samples/non-imported-component/expected.js +++ b/test/js/samples/non-imported-component/expected.js @@ -1,5 +1,6 @@ import { SvelteComponent, + create_component, destroy_component, detach, init, @@ -22,9 +23,9 @@ function create_fragment(ctx) { return { c() { - imported.$$.fragment.c(); + create_component(imported.$$.fragment); t = space(); - nonimported.$$.fragment.c(); + create_component(nonimported.$$.fragment); }, m(target, anchor) { mount_component(imported, target, anchor); diff --git a/test/js/samples/reactive-values-non-topologically-ordered/expected.js b/test/js/samples/reactive-values-non-topologically-ordered/expected.js index abd22c4da0..69fd368ee8 100644 --- a/test/js/samples/reactive-values-non-topologically-ordered/expected.js +++ b/test/js/samples/reactive-values-non-topologically-ordered/expected.js @@ -1,15 +1,4 @@ -import { SvelteComponent, init, noop, safe_not_equal } from "svelte/internal"; - -function create_fragment(ctx) { - return { - c: noop, - m: noop, - p: noop, - i: noop, - o: noop, - d: noop - }; -} +import { SvelteComponent, init, safe_not_equal } from "svelte/internal"; function instance($$self, $$props, $$invalidate) { let { x } = $$props; @@ -36,7 +25,7 @@ function instance($$self, $$props, $$invalidate) { class Component extends SvelteComponent { constructor(options) { super(); - init(this, options, instance, create_fragment, safe_not_equal, { x: 0 }); + init(this, options, instance, null, safe_not_equal, { x: 0 }); } } diff --git a/test/js/samples/reactive-values-non-writable-dependencies/expected.js b/test/js/samples/reactive-values-non-writable-dependencies/expected.js index 6b68368894..233e9ef835 100644 --- a/test/js/samples/reactive-values-non-writable-dependencies/expected.js +++ b/test/js/samples/reactive-values-non-writable-dependencies/expected.js @@ -1,15 +1,4 @@ -import { SvelteComponent, init, noop, safe_not_equal } from "svelte/internal"; - -function create_fragment(ctx) { - return { - c: noop, - m: noop, - p: noop, - i: noop, - o: noop, - d: noop - }; -} +import { SvelteComponent, init, safe_not_equal } from "svelte/internal"; function instance($$self, $$props, $$invalidate) { let { a = 1 } = $$props; @@ -32,7 +21,7 @@ function instance($$self, $$props, $$invalidate) { class Component extends SvelteComponent { constructor(options) { super(); - init(this, options, instance, create_fragment, safe_not_equal, { a: 0, b: 0 }); + init(this, options, instance, null, safe_not_equal, { a: 0, b: 0 }); } } diff --git a/test/js/samples/setup-method/expected.js b/test/js/samples/setup-method/expected.js index 40fc049383..e22398748e 100644 --- a/test/js/samples/setup-method/expected.js +++ b/test/js/samples/setup-method/expected.js @@ -1,16 +1,4 @@ -import { SvelteComponent, init, noop, safe_not_equal } from "svelte/internal"; - -function create_fragment(ctx) { - return { - c: noop, - m: noop, - p: noop, - i: noop, - o: noop, - d: noop - }; -} - +import { SvelteComponent, init, safe_not_equal } from "svelte/internal"; const SOME_CONSTANT = 42; function foo(bar) { @@ -20,7 +8,7 @@ function foo(bar) { class Component extends SvelteComponent { constructor(options) { super(); - init(this, options, null, create_fragment, safe_not_equal, { foo: 0 }); + init(this, options, null, null, safe_not_equal, { foo: 0 }); } get foo() { diff --git a/test/runtime/samples/empty-dom/_config.js b/test/runtime/samples/empty-dom/_config.js new file mode 100644 index 0000000000..e3e3d0ecd5 --- /dev/null +++ b/test/runtime/samples/empty-dom/_config.js @@ -0,0 +1,3 @@ +export default { + html: '', +}; \ No newline at end of file diff --git a/test/runtime/samples/empty-dom/main.svelte b/test/runtime/samples/empty-dom/main.svelte new file mode 100644 index 0000000000..3098443ea3 --- /dev/null +++ b/test/runtime/samples/empty-dom/main.svelte @@ -0,0 +1,3 @@ + \ No newline at end of file From b4c22264384b947d48ccfb7db38aecb70cf16a6d Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Wed, 30 Oct 2019 22:33:53 +0800 Subject: [PATCH 04/24] swapped out expected and actual for asserts in error --- test/runtime/index.js | 2 +- test/server-side-rendering/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/runtime/index.js b/test/runtime/index.js index 14c2143e09..fc989d352f 100644 --- a/test/runtime/index.js +++ b/test/runtime/index.js @@ -190,7 +190,7 @@ describe("runtime", () => { if (typeof config.error === 'function') { config.error(assert, err); } else { - assert.equal(config.error, err.message); + assert.equal(err.message, config.error); } } else { throw err; diff --git a/test/server-side-rendering/index.js b/test/server-side-rendering/index.js index d89b47191a..73b286044f 100644 --- a/test/server-side-rendering/index.js +++ b/test/server-side-rendering/index.js @@ -138,7 +138,7 @@ describe("ssr", () => { if (typeof config.error === 'function') { config.error(assert, err); } else { - assert.equal(config.error, err.message); + assert.equal(err.message, config.error); } } else { showOutput(cwd, compileOptions); From f7c557f5ce81b548504fbd2a3bfbd615847b2cec Mon Sep 17 00:00:00 2001 From: Matt Pilott Date: Sat, 2 Nov 2019 22:22:53 +0000 Subject: [PATCH 05/24] Switched to @rollup/plugin-replace --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a061cdf742..72c8fac2f2 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "@types/node": "^8.10.53", "@typescript-eslint/eslint-plugin": "^1.13.0", "@typescript-eslint/parser": "^2.1.0", + "@rollup/plugin-replace": "^2.2.0", "acorn": "^7.1.0", "agadoo": "^1.1.0", "c8": "^5.0.1", @@ -81,7 +82,6 @@ "rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-json": "^4.0.0", "rollup-plugin-node-resolve": "^5.2.0", - "rollup-plugin-replace": "^2.2.0", "rollup-plugin-sucrase": "^2.1.0", "rollup-plugin-typescript": "^1.0.1", "rollup-plugin-virtual": "^1.0.1", From 8998da9bc34a342c8e7b6a34dc9fc8dd22b2201c Mon Sep 17 00:00:00 2001 From: Matt Pilott Date: Sat, 2 Nov 2019 22:23:18 +0000 Subject: [PATCH 06/24] Switched to @rollup/plugin-replace --- rollup.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rollup.config.js b/rollup.config.js index 4444494a5f..c55cbf2426 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,5 +1,5 @@ import fs from 'fs'; -import replace from 'rollup-plugin-replace'; +import replace from '@rollup/plugin-replace'; import resolve from 'rollup-plugin-node-resolve'; import commonjs from 'rollup-plugin-commonjs'; import json from 'rollup-plugin-json'; From 1cd893ad970d8d939946ef42af4dd65b33a6de04 Mon Sep 17 00:00:00 2001 From: "Fernando G. Vilar" Date: Sun, 3 Nov 2019 16:43:22 +0700 Subject: [PATCH 07/24] feat: support unicode filenames --- src/compiler/compile/utils/get_name_from_filename.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/compiler/compile/utils/get_name_from_filename.ts b/src/compiler/compile/utils/get_name_from_filename.ts index 19c781825c..04479ce6bd 100644 --- a/src/compiler/compile/utils/get_name_from_filename.ts +++ b/src/compiler/compile/utils/get_name_from_filename.ts @@ -1,7 +1,7 @@ export default function get_name_from_filename(filename: string) { if (!filename) return null; // eslint-disable-next-line no-useless-escape - const parts = filename.split(/[\/\\]/); + const parts = encodeURI(filename).split(/[\/\\]/); if (parts.length > 1) { const index_match = parts[parts.length - 1].match(/^index(\.\w+)/); @@ -12,6 +12,7 @@ export default function get_name_from_filename(filename: string) { } const base = parts.pop() + .replace(/%/g, 'u') .replace(/\.[^.]+$/, "") .replace(/[^a-zA-Z_$0-9]+/g, '_') .replace(/^_/, '') From 0c8cb44d01d3908e4471ceb94dd9c79dccf8f725 Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Sun, 3 Nov 2019 20:50:57 +0800 Subject: [PATCH 08/24] fix window bindings to store (#3835) Fixes #3832 --- .../compile/render_dom/wrappers/Window.ts | 4 +-- test/helpers.js | 1 + .../samples/window-binding-scroll/expected.js | 2 +- .../window-binding-scroll-store/_config.js | 31 +++++++++++++++++++ .../window-binding-scroll-store/main.svelte | 15 +++++++++ 5 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 test/runtime/samples/window-binding-scroll-store/_config.js create mode 100644 test/runtime/samples/window-binding-scroll-store/main.svelte diff --git a/src/compiler/compile/render_dom/wrappers/Window.ts b/src/compiler/compile/render_dom/wrappers/Window.ts index 90846f8a7c..b9a32c03fd 100644 --- a/src/compiler/compile/render_dom/wrappers/Window.ts +++ b/src/compiler/compile/render_dom/wrappers/Window.ts @@ -127,7 +127,7 @@ export default class WindowWrapper extends Wrapper { component.partly_hoisted.push(b` function ${id}() { - ${props.map(prop => b`$$invalidate('${prop.name}', ${prop.name} = @_window.${prop.value});`)} + ${props.map(prop => component.invalidate(prop.name, x`${prop.name} = @_window.${prop.value}`))} } `); @@ -167,7 +167,7 @@ export default class WindowWrapper extends Wrapper { component.partly_hoisted.push(b` function ${id}() { - $$invalidate('${name}', ${name} = @_navigator.onLine); + ${component.invalidate(name, x`${name} = @_navigator.onLine`)} } `); diff --git a/test/helpers.js b/test/helpers.js index 6ac20eed8b..5a2d1403f0 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -52,6 +52,7 @@ global.document = window.document; global.navigator = window.navigator; global.getComputedStyle = window.getComputedStyle; global.requestAnimationFrame = null; // placeholder, filled in using set_raf +global.window = window; // add missing ecmascript globals to window for (const key of Object.getOwnPropertyNames(global)) { diff --git a/test/js/samples/window-binding-scroll/expected.js b/test/js/samples/window-binding-scroll/expected.js index 87f31505d7..4cd784e2bf 100644 --- a/test/js/samples/window-binding-scroll/expected.js +++ b/test/js/samples/window-binding-scroll/expected.js @@ -68,7 +68,7 @@ function instance($$self, $$props, $$invalidate) { let { y } = $$props; function onwindowscroll() { - $$invalidate("y", y = window.pageYOffset); + $$invalidate("y", y = window.pageYOffset) } $$self.$set = $$props => { diff --git a/test/runtime/samples/window-binding-scroll-store/_config.js b/test/runtime/samples/window-binding-scroll-store/_config.js new file mode 100644 index 0000000000..abddb86765 --- /dev/null +++ b/test/runtime/samples/window-binding-scroll-store/_config.js @@ -0,0 +1,31 @@ +export default { + skip_if_ssr: true, + + async test({ assert, component, target, window }) { + assert.equal(window.pageYOffset, 0); + + const event = new window.Event('scroll'); + Object.defineProperties(window, { + pageYOffset: { + value: 234, + configurable: true, + }, + }); + + await window.dispatchEvent(event); + + assert.htmlEqual( + target.innerHTML, + `

scroll\ny\nis\n234.\n234\n*\n234\n=\n54756

` + ); + }, + + after_test() { + Object.defineProperties(window, { + pageYOffset: { + value: 0, + configurable: true, + }, + }); + }, +}; diff --git a/test/runtime/samples/window-binding-scroll-store/main.svelte b/test/runtime/samples/window-binding-scroll-store/main.svelte new file mode 100644 index 0000000000..fe225520d7 --- /dev/null +++ b/test/runtime/samples/window-binding-scroll-store/main.svelte @@ -0,0 +1,15 @@ + + + + +

+ scroll y is {$y}. {$y} * {$y} = {$y_squared} +

+ +
+ +
\ No newline at end of file From 16ccc7493db0f85713043f24d866e3ff9a6e548d Mon Sep 17 00:00:00 2001 From: Conduitry Date: Sun, 3 Nov 2019 07:51:45 -0500 Subject: [PATCH 09/24] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41d70eedcc..ccdf043a72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ Also: * Flush changes in newly attached block when using `{#await}` ([#3660](https://github.com/sveltejs/svelte/issues/3660)) * Throw exception immediately when calling `createEventDispatcher()` after component instantiation ([#3667](https://github.com/sveltejs/svelte/pull/3667)) * Fix globals shadowing contextual template scope ([#3674](https://github.com/sveltejs/svelte/issues/3674)) +* Fix `` bindings to stores ([#3832](https://github.com/sveltejs/svelte/issues/3832)) ## 3.12.1 From 3174d3cc596df5d87173990a2e938221bd9d9c61 Mon Sep 17 00:00:00 2001 From: red-meadow <38740713+red-meadow@users.noreply.github.com> Date: Sun, 3 Nov 2019 16:25:46 +0200 Subject: [PATCH 10/24] Minor tutorial fix (#3843) --- .../16-special-elements/07-svelte-options/app-a/Todo.svelte | 1 - .../16-special-elements/07-svelte-options/app-b/Todo.svelte | 1 - 2 files changed, 2 deletions(-) diff --git a/site/content/tutorial/16-special-elements/07-svelte-options/app-a/Todo.svelte b/site/content/tutorial/16-special-elements/07-svelte-options/app-a/Todo.svelte index dae595e7d0..5e0dbca300 100644 --- a/site/content/tutorial/16-special-elements/07-svelte-options/app-a/Todo.svelte +++ b/site/content/tutorial/16-special-elements/07-svelte-options/app-a/Todo.svelte @@ -3,7 +3,6 @@ import flash from './flash.js'; export let todo; - export let toggle; let div; diff --git a/site/content/tutorial/16-special-elements/07-svelte-options/app-b/Todo.svelte b/site/content/tutorial/16-special-elements/07-svelte-options/app-b/Todo.svelte index 447ddc601c..e7ddcedc47 100644 --- a/site/content/tutorial/16-special-elements/07-svelte-options/app-b/Todo.svelte +++ b/site/content/tutorial/16-special-elements/07-svelte-options/app-b/Todo.svelte @@ -5,7 +5,6 @@ import flash from './flash.js'; export let todo; - export let toggle; let div; From a3cc276a902ae1f08c5dfdf4d9bae1ab23710104 Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Sat, 2 Nov 2019 02:48:36 +0800 Subject: [PATCH 11/24] use changed(dependencies) --- src/compiler/compile/render_dom/wrappers/shared/Tag.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/compiler/compile/render_dom/wrappers/shared/Tag.ts b/src/compiler/compile/render_dom/wrappers/shared/Tag.ts index 20bfd3597b..ccb7c6ea40 100644 --- a/src/compiler/compile/render_dom/wrappers/shared/Tag.ts +++ b/src/compiler/compile/render_dom/wrappers/shared/Tag.ts @@ -5,6 +5,7 @@ import Block from '../../Block'; import MustacheTag from '../../../nodes/MustacheTag'; import RawMustacheTag from '../../../nodes/RawMustacheTag'; import { Node } from 'estree'; +import { changed } from './changed'; export default class Tag extends Wrapper { node: MustacheTag | RawMustacheTag; @@ -39,10 +40,7 @@ export default class Tag extends Wrapper { if (this.node.should_cache) block.add_variable(value, snippet); // TODO may need to coerce snippet to string if (dependencies.length > 0) { - let condition = x`#changed.${dependencies[0]}`; - for (let i = 1; i < dependencies.length; i += 1) { - condition = x`${condition} || #changed.${dependencies[i]}`; - } + let condition = changed(dependencies); if (block.has_outros) { condition = x`!#current || ${condition}`; From 601ec45780aeec8d4f62e6438ff1af974ce25c39 Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Sun, 3 Nov 2019 15:37:01 +0800 Subject: [PATCH 12/24] reenable skipped test due to jsdom --- test/helpers.js | 28 +++++++++++++ .../binding-select-in-each-block/_config.js | 13 +++++- .../binding-select-in-each-block/main.svelte | 3 ++ .../binding-select-multiple/_config.js | 41 +++++++++++-------- .../binding-select-optgroup/_config.js | 18 ++++++-- .../window-bind-scroll-update/_config.js | 35 ++++++++++++++-- .../samples/window-binding-resize/_config.js | 19 ++++++--- .../window-binding-scroll-store/_config.js | 18 ++++---- test/runtime/samples/window-event/_config.js | 6 +-- test/runtime/samples/window-event/main.svelte | 2 +- 10 files changed, 137 insertions(+), 46 deletions(-) diff --git a/test/helpers.js b/test/helpers.js index 5a2d1403f0..d4f507fc87 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -59,6 +59,12 @@ for (const key of Object.getOwnPropertyNames(global)) { window[key] = window[key] || global[key]; } +// implement mock scroll +window.scrollTo = function(pageXOffset, pageYOffset) { + window.pageXOffset = pageXOffset; + window.pageYOffset = pageYOffset; +}; + export function env() { window.document.title = ''; window.document.body.innerHTML = '
'; @@ -205,3 +211,25 @@ export function spaces(i) { while (i--) result += ' '; return result; } + +// fake timers +const original_set_timeout = global.setTimeout; + +export function useFakeTimers() { + const callbacks = []; + + global.setTimeout = function(fn) { + callbacks.push(fn); + }; + + return { + flush() { + callbacks.forEach(fn => fn()); + callbacks.splice(0, callbacks.length); + }, + removeFakeTimers() { + callbacks.splice(0, callbacks.length); + global.setTimeout = original_set_timeout; + } + }; +} diff --git a/test/runtime/samples/binding-select-in-each-block/_config.js b/test/runtime/samples/binding-select-in-each-block/_config.js index e8ff7f9f07..89f40f8d48 100644 --- a/test/runtime/samples/binding-select-in-each-block/_config.js +++ b/test/runtime/samples/binding-select-in-each-block/_config.js @@ -1,5 +1,16 @@ export default { - skip: true, // JSDOM... + + ssrHtml: ` + + + + `, html: ` diff --git a/test/runtime/samples/binding-select-multiple/_config.js b/test/runtime/samples/binding-select-multiple/_config.js index e1d21c0ea8..b7d6c3589f 100644 --- a/test/runtime/samples/binding-select-multiple/_config.js +++ b/test/runtime/samples/binding-select-multiple/_config.js @@ -1,49 +1,58 @@ export default { - skip: true, // JSDOM props: { selected: [ 'two', 'three' ] }, + ssrHtml: ` + + +

selected: two, three

+ `, + html: `

selected: two, three

`, - test({ assert, component, target, window }) { + async test({ assert, component, target, window }) { const select = target.querySelector( 'select' ); const options = [ ...target.querySelectorAll( 'option' ) ]; const change = new window.Event( 'change' ); options[1].selected = false; - select.dispatchEvent( change ); + await select.dispatchEvent( change ); assert.deepEqual( component.selected, [ 'three' ] ); assert.htmlEqual( target.innerHTML, `

selected: three

` ); options[0].selected = true; - select.dispatchEvent( change ); + await select.dispatchEvent( change ); assert.deepEqual( component.selected, [ 'one', 'three' ] ); assert.htmlEqual( target.innerHTML, `

selected: one, three

@@ -57,9 +66,9 @@ export default { assert.htmlEqual( target.innerHTML, `

selected: one, two

diff --git a/test/runtime/samples/binding-select-optgroup/_config.js b/test/runtime/samples/binding-select-optgroup/_config.js index 5927bce901..e8a1d40797 100644 --- a/test/runtime/samples/binding-select-optgroup/_config.js +++ b/test/runtime/samples/binding-select-optgroup/_config.js @@ -1,5 +1,15 @@ export default { - skip: true, // JSDOM + + ssrHtml: ` +

Hello undefined!

+ + + `, html: `

Hello Harry!

@@ -12,17 +22,17 @@ export default { `, - test({ assert, component, target, window }) { + async test({ assert, component, target, window }) { const select = target.querySelector('select'); const options = [...target.querySelectorAll('option')]; - assert.deepEqual(options, select.options); + assert.deepEqual(options, [...select.options]); assert.equal(component.name, 'Harry'); const change = new window.Event('change'); options[1].selected = true; - select.dispatchEvent(change); + await select.dispatchEvent(change); assert.equal(component.name, 'World'); assert.htmlEqual(target.innerHTML, ` diff --git a/test/runtime/samples/window-bind-scroll-update/_config.js b/test/runtime/samples/window-bind-scroll-update/_config.js index b9b620d1cd..cb13ea11c2 100644 --- a/test/runtime/samples/window-bind-scroll-update/_config.js +++ b/test/runtime/samples/window-bind-scroll-update/_config.js @@ -1,10 +1,37 @@ +import { env, useFakeTimers } from "../../../helpers"; + +let clock; + export default { - skip: true, // JSDOM + before_test() { + clock = useFakeTimers(); + + const window = env(); + Object.defineProperties(window, { + pageYOffset: { + value: 0, + configurable: true + }, + pageXOffset: { + value: 0, + configurable: true + } + }); + }, - test({ assert, component, target, window }) { + after_test() { + clock.removeFakeTimers(); + clock = null; + }, + + async test({ assert, component, target, window }) { assert.equal(window.pageYOffset, 0); + // clear the previous 'scrolling' state + clock.flush(); component.scrollY = 100; + + clock.flush(); assert.equal(window.pageYOffset, 100); - } -}; \ No newline at end of file + }, +}; diff --git a/test/runtime/samples/window-binding-resize/_config.js b/test/runtime/samples/window-binding-resize/_config.js index d8fbe69f75..1429f67db8 100644 --- a/test/runtime/samples/window-binding-resize/_config.js +++ b/test/runtime/samples/window-binding-resize/_config.js @@ -1,16 +1,25 @@ export default { html: `
1024x768
`, - skip: true, // some weird stuff happening with JSDOM 11 - // skip: /^v4/.test(process.version), // node 4 apparently does some dumb stuff + before_test() { + Object.defineProperties(window, { + innerWidth: { + value: 1024, + configurable: true + }, + innerHeight: { + value: 768, + configurable: true + } + }); + }, + skip_if_ssr: true, // there's some kind of weird bug with this test... it compiles with the wrong require.extensions hook for some bizarre reason async test({ assert, component, target, window }) { const event = new window.Event('resize'); - // JSDOM executes window event listeners with `global` rather than - // `window` (bug?) so we need to do this - Object.defineProperties(global, { + Object.defineProperties(window, { innerWidth: { value: 567, configurable: true diff --git a/test/runtime/samples/window-binding-scroll-store/_config.js b/test/runtime/samples/window-binding-scroll-store/_config.js index abddb86765..dbab4b36ac 100644 --- a/test/runtime/samples/window-binding-scroll-store/_config.js +++ b/test/runtime/samples/window-binding-scroll-store/_config.js @@ -1,6 +1,13 @@ export default { skip_if_ssr: true, - + before_test() { + Object.defineProperties(window, { + pageYOffset: { + value: 0, + configurable: true, + }, + }); + }, async test({ assert, component, target, window }) { assert.equal(window.pageYOffset, 0); @@ -19,13 +26,4 @@ export default { `

scroll\ny\nis\n234.\n234\n*\n234\n=\n54756

` ); }, - - after_test() { - Object.defineProperties(window, { - pageYOffset: { - value: 0, - configurable: true, - }, - }); - }, }; diff --git a/test/runtime/samples/window-event/_config.js b/test/runtime/samples/window-event/_config.js index 16e0f870ef..bd09487f5e 100644 --- a/test/runtime/samples/window-event/_config.js +++ b/test/runtime/samples/window-event/_config.js @@ -1,16 +1,12 @@ export default { html: `
undefinedxundefined
`, - skip: true, // some weird stuff happening with JSDOM 11 - // skip: /^v4/.test(process.version), // node 4 apparently does some dumb stuff skip_if_ssr: true, // there's some kind of weird bug with this test... it compiles with the wrong require.extensions hook for some bizarre reason async test({ assert, component, target, window }) { const event = new window.Event('resize'); - // JSDOM executes window event listeners with `global` rather than - // `window` (bug?) so we need to do this - Object.defineProperties(global, { + Object.defineProperties(window, { innerWidth: { value: 567, configurable: true diff --git a/test/runtime/samples/window-event/main.svelte b/test/runtime/samples/window-event/main.svelte index 1aa84a0890..e246902063 100644 --- a/test/runtime/samples/window-event/main.svelte +++ b/test/runtime/samples/window-event/main.svelte @@ -3,6 +3,6 @@ export let height; - +
{width}x{height}
\ No newline at end of file From fb30ed079e2add84883d4224797b5f228573633a Mon Sep 17 00:00:00 2001 From: AlbertLucianto Date: Tue, 5 Nov 2019 00:48:16 +0800 Subject: [PATCH 13/24] fix input binding fails if type declared last --- src/compiler/compile/nodes/Element.ts | 10 +++ .../bindings-readonly-order/expected.js | 82 +++++++++++++++++++ .../bindings-readonly-order/input.svelte | 6 ++ 3 files changed, 98 insertions(+) create mode 100644 test/js/samples/bindings-readonly-order/expected.js create mode 100644 test/js/samples/bindings-readonly-order/input.svelte diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index d353d20158..82dfd3a4c4 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -151,6 +151,16 @@ export default class Element extends Node { } } + // Binding relies on Attribute, defer its evaluation + const deferreds = ['Binding']; + + info.attributes.sort((node1, node2) => { + const deferIndex1 = deferreds.indexOf(node1.type); + const deferIndex2 = deferreds.indexOf(node2.type); + + return deferIndex1 - deferIndex2; + }); + info.attributes.forEach(node => { switch (node.type) { case 'Action': diff --git a/test/js/samples/bindings-readonly-order/expected.js b/test/js/samples/bindings-readonly-order/expected.js new file mode 100644 index 0000000000..b7e004ec23 --- /dev/null +++ b/test/js/samples/bindings-readonly-order/expected.js @@ -0,0 +1,82 @@ +import { + SvelteComponent, + attr, + detach, + element, + init, + insert, + listen, + noop, + run_all, + safe_not_equal, + space +} from "svelte/internal"; + +function create_fragment(ctx) { + let input0; + let t; + let input1; + let dispose; + + return { + c() { + input0 = element("input"); + t = space(); + input1 = element("input"); + attr(input0, "type", "file"); + attr(input1, "type", "file"); + + dispose = [ + listen(input0, "change", ctx.input0_change_handler), + listen(input1, "change", ctx.input1_change_handler) + ]; + }, + m(target, anchor) { + insert(target, input0, anchor); + insert(target, t, anchor); + insert(target, input1, anchor); + }, + p: noop, + i: noop, + o: noop, + d(detaching) { + if (detaching) detach(input0); + if (detaching) detach(t); + if (detaching) detach(input1); + run_all(dispose); + } + }; +} + +function instance($$self, $$props, $$invalidate) { + let { files } = $$props; + + function input0_change_handler() { + files = this.files; + $$invalidate("files", files); + } + + function input1_change_handler() { + files = this.files; + $$invalidate("files", files); + } + + $$self.$set = $$props => { + if ("files" in $$props) $$invalidate("files", files = $$props.files); + }; + + return { + files, + input0_change_handler, + input1_change_handler + }; +} + +class Component extends SvelteComponent { + constructor(options) { + super(); + init(this, options, instance, create_fragment, safe_not_equal, { files: 0 }); + } +} + +export default Component; \ No newline at end of file diff --git a/test/js/samples/bindings-readonly-order/input.svelte b/test/js/samples/bindings-readonly-order/input.svelte new file mode 100644 index 0000000000..9a133b90f2 --- /dev/null +++ b/test/js/samples/bindings-readonly-order/input.svelte @@ -0,0 +1,6 @@ + + + + From 6d8fc8646f724426e1a26d1af6280880b1200963 Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Tue, 5 Nov 2019 08:44:13 +0800 Subject: [PATCH 14/24] escape html quotes --- src/compiler/compile/render_dom/wrappers/Element/index.ts | 2 +- src/compiler/compile/utils/stringify.ts | 4 +++- .../runtime/samples/attribute-static-quotemarks/_config.js | 7 ++++++- .../samples/attribute-static-quotemarks/main.svelte | 5 ++++- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index a80db84169..033ef85ef4 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -894,7 +894,7 @@ function to_html(wrappers: Array, blo attr.node.chunks.forEach(chunk => { if (chunk.type === 'Text') { - state.quasi.value.raw += chunk.data; + state.quasi.value.raw += escape_html(chunk.data); } else { literal.quasis.push(state.quasi); literal.expressions.push(chunk.manipulate(block)); diff --git a/src/compiler/compile/utils/stringify.ts b/src/compiler/compile/utils/stringify.ts index d042cd43ca..19f6d99673 100644 --- a/src/compiler/compile/utils/stringify.ts +++ b/src/compiler/compile/utils/stringify.ts @@ -12,13 +12,15 @@ export function escape(data: string, { only_escape_at_symbol = false } = {}) { } const escaped = { + '"': '"', + "'": ''', '&': '&', '<': '<', '>': '>', }; export function escape_html(html) { - return String(html).replace(/[&<>]/g, match => escaped[match]); + return String(html).replace(/["'&<>]/g, match => escaped[match]); } export function escape_template(str) { diff --git a/test/runtime/samples/attribute-static-quotemarks/_config.js b/test/runtime/samples/attribute-static-quotemarks/_config.js index 794d01c29f..3d389c2273 100644 --- a/test/runtime/samples/attribute-static-quotemarks/_config.js +++ b/test/runtime/samples/attribute-static-quotemarks/_config.js @@ -1,3 +1,8 @@ export default { - html: `foo` + html: ` + + foo + bar + + ` }; \ No newline at end of file diff --git a/test/runtime/samples/attribute-static-quotemarks/main.svelte b/test/runtime/samples/attribute-static-quotemarks/main.svelte index 5f9c68c53c..e01af9f098 100644 --- a/test/runtime/samples/attribute-static-quotemarks/main.svelte +++ b/test/runtime/samples/attribute-static-quotemarks/main.svelte @@ -1 +1,4 @@ -foo \ No newline at end of file + + foo + bar + \ No newline at end of file From 153debb6fa18969febf8dfdd6d93fc9b0a4fc551 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Tue, 5 Nov 2019 12:18:59 -0500 Subject: [PATCH 15/24] deconflict `block` var used in dev mode (#3854) --- src/compiler/compile/render_dom/Block.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/compiler/compile/render_dom/Block.ts b/src/compiler/compile/render_dom/Block.ts index b268954dd0..c1f67c0a8a 100644 --- a/src/compiler/compile/render_dom/Block.ts +++ b/src/compiler/compile/render_dom/Block.ts @@ -376,6 +376,8 @@ export default class Block { d: ${properties.destroy} }`; + const block = dev && this.get_unique_name('block'); + const body = b` ${Array.from(this.variables.values()).map(({ id, init }) => { return init @@ -387,9 +389,15 @@ export default class Block { ${dev ? b` - const block = ${return_value}; - @dispatch_dev("SvelteRegisterBlock", { block, id: ${this.name || 'create_fragment'}.name, type: "${this.type}", source: "${this.comment ? this.comment.replace(/"/g, '\\"') : ''}", ctx: #ctx }); - return block;` + const ${block} = ${return_value}; + @dispatch_dev("SvelteRegisterBlock", { + block: ${block}, + id: ${this.name || 'create_fragment'}.name, + type: "${this.type}", + source: "${this.comment ? this.comment.replace(/"/g, '\\"') : ''}", + ctx: #ctx + }); + return ${block};` : b` return ${return_value};` } From 15cc6f729a071f6287e223f86e59ca1f829c9098 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 6 Nov 2019 07:03:32 -0500 Subject: [PATCH 16/24] compactify code --- src/compiler/compile/nodes/Element.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 82dfd3a4c4..555c772f23 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -152,14 +152,8 @@ export default class Element extends Node { } // Binding relies on Attribute, defer its evaluation - const deferreds = ['Binding']; - - info.attributes.sort((node1, node2) => { - const deferIndex1 = deferreds.indexOf(node1.type); - const deferIndex2 = deferreds.indexOf(node2.type); - - return deferIndex1 - deferIndex2; - }); + const order = ['Binding']; // everything else is -1 + info.attributes.sort((a, b) => order.indexOf(a.type) - order.indexOf(b.type)); info.attributes.forEach(node => { switch (node.type) { From 1b8a1b22a694b244379ac1b47bd1ec3f7a71456d Mon Sep 17 00:00:00 2001 From: Leonardo Di Vittorio Date: Wed, 6 Nov 2019 15:20:51 +0100 Subject: [PATCH 17/24] fix: svelte-logo-mask correct file name --- .../tutorial/06-bindings/12-bind-this/app-a/App.svelte | 4 ++-- .../tutorial/06-bindings/12-bind-this/app-b/App.svelte | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/site/content/tutorial/06-bindings/12-bind-this/app-a/App.svelte b/site/content/tutorial/06-bindings/12-bind-this/app-a/App.svelte index c85cab9225..d40c45ef6c 100644 --- a/site/content/tutorial/06-bindings/12-bind-this/app-a/App.svelte +++ b/site/content/tutorial/06-bindings/12-bind-this/app-a/App.svelte @@ -43,8 +43,8 @@ width: 100%; height: 100%; background-color: #666; - -webkit-mask: url(logo-mask.svg) 50% 50% no-repeat; - mask: url(logo-mask.svg) 50% 50% no-repeat; + -webkit-mask: url(svelte-logo-mask.svg) 50% 50% no-repeat; + mask: url(svelte-logo-mask.svg) 50% 50% no-repeat; } diff --git a/site/content/tutorial/06-bindings/12-bind-this/app-b/App.svelte b/site/content/tutorial/06-bindings/12-bind-this/app-b/App.svelte index 9a8fabc265..8e4b3c5bef 100644 --- a/site/content/tutorial/06-bindings/12-bind-this/app-b/App.svelte +++ b/site/content/tutorial/06-bindings/12-bind-this/app-b/App.svelte @@ -43,8 +43,8 @@ width: 100%; height: 100%; background-color: #666; - -webkit-mask: url(logo-mask.svg) 50% 50% no-repeat; - mask: url(logo-mask.svg) 50% 50% no-repeat; + -webkit-mask: url(svelte-logo-mask.svg) 50% 50% no-repeat; + mask: url(svelte-logo-mask.svg) 50% 50% no-repeat; } From 105919e7bae5a65071e7d4180a4c27e3d6a9b60e Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Thu, 7 Nov 2019 00:39:03 +0800 Subject: [PATCH 18/24] feat dynamic event handler --- src/compiler/compile/nodes/EventHandler.ts | 33 +----- src/compiler/compile/render_dom/Block.ts | 14 +-- .../compile/render_dom/wrappers/Body.ts | 21 ++-- .../wrappers/Element/EventHandler.ts | 69 ++++++++++++ .../render_dom/wrappers/Element/index.ts | 6 +- .../wrappers/InlineComponent/index.ts | 4 +- .../compile/render_dom/wrappers/Window.ts | 5 +- .../wrappers/shared/add_event_handlers.ts | 33 +----- .../samples/event-handler-dynamic/expected.js | 106 ++++++++++++++++++ .../event-handler-dynamic/input.svelte | 23 ++++ .../Button.svelte | 1 + .../_config.js | 18 +++ .../main.svelte | 11 ++ .../Button.svelte | 1 + .../_config.js | 18 +++ .../main.svelte | 11 ++ .../samples/event-handler-dynamic/_config.js | 50 +++++++++ .../samples/event-handler-dynamic/main.svelte | 23 ++++ 18 files changed, 368 insertions(+), 79 deletions(-) create mode 100644 src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts create mode 100644 test/js/samples/event-handler-dynamic/expected.js create mode 100644 test/js/samples/event-handler-dynamic/input.svelte create mode 100644 test/runtime/samples/component-event-handler-dynamic/Button.svelte create mode 100644 test/runtime/samples/component-event-handler-dynamic/_config.js create mode 100644 test/runtime/samples/component-event-handler-dynamic/main.svelte create mode 100644 test/runtime/samples/component-event-handler-modifier-once-dynamic/Button.svelte create mode 100644 test/runtime/samples/component-event-handler-modifier-once-dynamic/_config.js create mode 100644 test/runtime/samples/component-event-handler-modifier-once-dynamic/main.svelte create mode 100644 test/runtime/samples/event-handler-dynamic/_config.js create mode 100644 test/runtime/samples/event-handler-dynamic/main.svelte diff --git a/src/compiler/compile/nodes/EventHandler.ts b/src/compiler/compile/nodes/EventHandler.ts index 7faf5d15dd..19da3d9dd7 100644 --- a/src/compiler/compile/nodes/EventHandler.ts +++ b/src/compiler/compile/nodes/EventHandler.ts @@ -1,8 +1,6 @@ import Node from './shared/Node'; import Expression from './shared/Expression'; import Component from '../Component'; -import { b, x } from 'code-red'; -import Block from '../render_dom/Block'; import { sanitize } from '../../utils/names'; import { Identifier } from 'estree'; @@ -14,6 +12,7 @@ export default class EventHandler extends Node { handler_name: Identifier; uses_context = false; can_make_passive = false; + reassigned?: boolean; constructor(component: Component, parent, template_scope, info) { super(component, parent, template_scope, info); @@ -22,7 +21,7 @@ export default class EventHandler extends Node { this.modifiers = new Set(info.modifiers); if (info.expression) { - this.expression = new Expression(component, this, template_scope, info.expression, true); + this.expression = new Expression(component, this, template_scope, info.expression); this.uses_context = this.expression.uses_context; if (/FunctionExpression/.test(info.expression.type) && info.expression.params.length === 0) { @@ -42,34 +41,12 @@ export default class EventHandler extends Node { if (node && (node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration' || node.type === 'ArrowFunctionExpression') && node.params.length === 0) { this.can_make_passive = true; } + + this.reassigned = component.var_lookup.get(info.expression.name).reassigned; } } } else { - const id = component.get_unique_name(`${sanitize(this.name)}_handler`); - - component.add_var({ - name: id.name, - internal: true, - referenced: true - }); - - component.partly_hoisted.push(b` - function ${id}(event) { - @bubble($$self, event); - } - `); - - this.handler_name = id; + this.handler_name = component.get_unique_name(`${sanitize(this.name)}_handler`); } } - - // TODO move this? it is specific to render-dom - render(block: Block) { - if (this.expression) { - return this.expression.manipulate(block); - } - - // this.component.add_reference(this.handler_name); - return x`#ctx.${this.handler_name}`; - } } diff --git a/src/compiler/compile/render_dom/Block.ts b/src/compiler/compile/render_dom/Block.ts index 8f5740ecb4..49e40c3a4f 100644 --- a/src/compiler/compile/render_dom/Block.ts +++ b/src/compiler/compile/render_dom/Block.ts @@ -60,7 +60,7 @@ export default class Block { destroy: Array; }; - event_listeners: Node[] = []; + event_listeners: Array = []; maintain_context: boolean; has_animation: boolean; @@ -203,13 +203,11 @@ export default class Block { } add_variable(id: Identifier, init?: Node) { - this.variables.forEach(v => { - if (v.id.name === id.name) { - throw new Error( - `Variable '${id.name}' already initialised with a different value` - ); - } - }); + if (this.variables.has(id.name)) { + throw new Error( + `Variable '${id.name}' already initialised with a different value` + ); + } this.variables.set(id.name, { id, init }); } diff --git a/src/compiler/compile/render_dom/wrappers/Body.ts b/src/compiler/compile/render_dom/wrappers/Body.ts index dc1f561b1f..e16ebc25bd 100644 --- a/src/compiler/compile/render_dom/wrappers/Body.ts +++ b/src/compiler/compile/render_dom/wrappers/Body.ts @@ -3,21 +3,24 @@ import Wrapper from './shared/Wrapper'; import { b } from 'code-red'; import Body from '../../nodes/Body'; import { Identifier } from 'estree'; +import EventHandler from './Element/EventHandler'; export default class BodyWrapper extends Wrapper { node: Body; render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) { - this.node.handlers.forEach(handler => { - const snippet = handler.render(block); + this.node.handlers + .map(handler => new EventHandler(handler, this)) + .forEach(handler => { + const snippet = handler.get_snippet(block); - block.chunks.init.push(b` - @_document.body.addEventListener("${handler.name}", ${snippet}); - `); + block.chunks.init.push(b` + @_document.body.addEventListener("${handler.node.name}", ${snippet}); + `); - block.chunks.destroy.push(b` - @_document.body.removeEventListener("${handler.name}", ${snippet}); - `); - }); + block.chunks.destroy.push(b` + @_document.body.removeEventListener("${handler.node.name}", ${snippet}); + `); + }); } } diff --git a/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts b/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts new file mode 100644 index 0000000000..37089e7493 --- /dev/null +++ b/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts @@ -0,0 +1,69 @@ +import EventHandler from '../../../nodes/EventHandler'; +import Wrapper from '../shared/Wrapper'; +import Block from '../../Block'; +import { b, x, p } from 'code-red'; + +const TRUE = x`true`; +const FALSE = x`false`; + +export default class EventHandlerWrapper { + node: EventHandler; + parent: Wrapper; + + constructor(node: EventHandler, parent: Wrapper) { + this.node = node; + this.parent = parent; + + if (!node.expression) { + this.parent.renderer.component.add_var({ + name: node.handler_name.name, + internal: true, + referenced: true, + }); + + this.parent.renderer.component.partly_hoisted.push(b` + function ${node.handler_name.name}(event) { + @bubble($$self, event); + } + `); + } + } + + get_snippet(block) { + const snippet = this.node.expression ? this.node.expression.manipulate(block) : x`#ctx.${this.node.handler_name}`; + + if (this.node.reassigned) { + block.maintain_context = true; + return x`function () { ${snippet}.apply(this, arguments); }`; + } + return snippet; + } + + render(block: Block, target: string) { + let snippet = this.get_snippet(block); + + if (this.node.modifiers.has('preventDefault')) snippet = x`@prevent_default(${snippet})`; + if (this.node.modifiers.has('stopPropagation')) snippet = x`@stop_propagation(${snippet})`; + if (this.node.modifiers.has('self')) snippet = x`@self(${snippet})`; + + const args = []; + + const opts = ['passive', 'once', 'capture'].filter(mod => this.node.modifiers.has(mod)); + if (opts.length) { + args.push((opts.length === 1 && opts[0] === 'capture') + ? TRUE + : x`{ ${opts.map(opt => p`${opt}: true`)} }`); + } else if (block.renderer.options.dev) { + args.push(FALSE); + } + + if (block.renderer.options.dev) { + args.push(this.node.modifiers.has('stopPropagation') ? TRUE : FALSE); + args.push(this.node.modifiers.has('preventDefault') ? TRUE : FALSE); + } + + block.event_listeners.push( + x`@listen(${target}, "${this.node.name}", ${snippet}, ${args})` + ); + } +} diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 033ef85ef4..9b7bbfef74 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -24,6 +24,7 @@ import bind_this from '../shared/bind_this'; import { changed } from '../shared/changed'; import { is_head } from '../shared/is_head'; import { Identifier } from 'estree'; +import EventHandler from './EventHandler'; const events = [ { @@ -113,6 +114,7 @@ export default class ElementWrapper extends Wrapper { fragment: FragmentWrapper; attributes: AttributeWrapper[]; bindings: Binding[]; + event_handlers: EventHandler[]; class_dependencies: string[]; slot_block: Block; @@ -194,6 +196,8 @@ export default class ElementWrapper extends Wrapper { // e.g.