From a422d2aba5661d9f9ca54db8a1e7b11692d4cde4 Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Sat, 11 Jan 2020 22:01:40 +0800 Subject: [PATCH 01/82] fix actions having no access to parent nodes (#4252) --- CHANGELOG.md | 1 + src/compiler/compile/render_dom/Block.ts | 4 ++-- .../action-custom-event-handler/expected.js | 4 ++-- test/js/samples/action/expected.js | 2 +- test/js/samples/bind-online/expected.js | 4 ++-- test/js/samples/bind-open/expected.js | 3 +-- .../bindings-readonly-order/expected.js | 10 ++++----- .../capture-inject-dev-only/expected.js | 2 +- .../samples/component-static-var/expected.js | 2 +- .../expected.js | 2 +- .../samples/dont-invalidate-this/expected.js | 2 +- .../samples/event-handler-dynamic/expected.js | 16 +++++++------- .../event-handler-no-passive/expected.js | 2 +- test/js/samples/event-modifiers/expected.js | 14 ++++++------ test/js/samples/input-files/expected.js | 2 +- .../input-no-initial-value/expected.js | 10 ++++----- test/js/samples/input-range/expected.js | 8 +++---- test/js/samples/input-value/expected.js | 2 +- .../input-without-blowback-guard/expected.js | 2 +- .../expected.js | 2 +- .../expected.js | 2 +- .../expected.js | 2 +- .../expected.js | 2 +- test/js/samples/media-bindings/expected.js | 22 +++++++++---------- test/js/samples/video-bindings/expected.js | 8 +++---- .../samples/window-binding-online/expected.js | 4 ++-- .../samples/window-binding-scroll/expected.js | 10 ++++----- .../_config.js | 8 +++++++ .../main.svelte | 8 +++++++ 29 files changed, 88 insertions(+), 72 deletions(-) create mode 100644 test/runtime/samples/action-receives-element-mounted/_config.js create mode 100644 test/runtime/samples/action-receives-element-mounted/main.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e5d5df95f..f6af419913 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Prevent text input cursor jumping in Safari with one-way binding ([#3449](https://github.com/sveltejs/svelte/issues/3449)) * Expose compiler version in dev events ([#4047](https://github.com/sveltejs/svelte/issues/4047)) +* Don't run actions before their element is in the document ([#4166](https://github.com/sveltejs/svelte/issues/4166)) * Fix reactive assignments with destructuring and stores where the destructured value should be undefined ([#4170](https://github.com/sveltejs/svelte/issues/4170)) * Do not automatically declare variables in reactive declarations when assigning to a member expression ([#4212](https://github.com/sveltejs/svelte/issues/4212)) diff --git a/src/compiler/compile/render_dom/Block.ts b/src/compiler/compile/render_dom/Block.ts index c8fa884721..68d28024fe 100644 --- a/src/compiler/compile/render_dom/Block.ts +++ b/src/compiler/compile/render_dom/Block.ts @@ -450,7 +450,7 @@ export default class Block { this.add_variable(dispose); if (this.event_listeners.length === 1) { - this.chunks.hydrate.push( + this.chunks.mount.push( b`${dispose} = ${this.event_listeners[0]};` ); @@ -458,7 +458,7 @@ export default class Block { b`${dispose}();` ); } else { - this.chunks.hydrate.push(b` + this.chunks.mount.push(b` ${dispose} = [ ${this.event_listeners} ]; diff --git a/test/js/samples/action-custom-event-handler/expected.js b/test/js/samples/action-custom-event-handler/expected.js index da42603895..968b5965d5 100644 --- a/test/js/samples/action-custom-event-handler/expected.js +++ b/test/js/samples/action-custom-event-handler/expected.js @@ -20,10 +20,10 @@ function create_fragment(ctx) { c() { button = element("button"); button.textContent = "foo"; - dispose = action_destroyer(foo_action = foo.call(null, button, /*foo_function*/ ctx[1])); }, m(target, anchor) { insert(target, button, anchor); + dispose = action_destroyer(foo_action = foo.call(null, button, /*foo_function*/ ctx[1])); }, p(ctx, [dirty]) { if (foo_action && is_function(foo_action.update) && dirty & /*bar*/ 1) foo_action.update.call(null, /*foo_function*/ ctx[1]); @@ -42,7 +42,7 @@ function handleFoo(bar) { } function foo(node, callback) { - + } function instance($$self, $$props, $$invalidate) { diff --git a/test/js/samples/action/expected.js b/test/js/samples/action/expected.js index dc3ebb5cf8..22d9cd939c 100644 --- a/test/js/samples/action/expected.js +++ b/test/js/samples/action/expected.js @@ -21,10 +21,10 @@ function create_fragment(ctx) { a = element("a"); a.textContent = "Test"; attr(a, "href", "#"); - dispose = action_destroyer(link_action = link.call(null, a)); }, m(target, anchor) { insert(target, a, anchor); + dispose = action_destroyer(link_action = link.call(null, a)); }, p: noop, i: noop, diff --git a/test/js/samples/bind-online/expected.js b/test/js/samples/bind-online/expected.js index 8285646481..e129e66d71 100644 --- a/test/js/samples/bind-online/expected.js +++ b/test/js/samples/bind-online/expected.js @@ -14,13 +14,13 @@ function create_fragment(ctx) { add_render_callback(/*onlinestatuschanged*/ ctx[1]); return { - c() { + c: noop, + m(target, anchor) { dispose = [ listen(window, "online", /*onlinestatuschanged*/ ctx[1]), listen(window, "offline", /*onlinestatuschanged*/ ctx[1]) ]; }, - m: noop, p: noop, i: noop, o: noop, diff --git a/test/js/samples/bind-open/expected.js b/test/js/samples/bind-open/expected.js index d4f148cac9..7d66145f0a 100644 --- a/test/js/samples/bind-open/expected.js +++ b/test/js/samples/bind-open/expected.js @@ -20,12 +20,11 @@ function create_fragment(ctx) { details.innerHTML = `summarycontent `; - - dispose = listen(details, "toggle", /*details_toggle_handler*/ ctx[1]); }, m(target, anchor) { insert(target, details, anchor); details.open = /*open*/ ctx[0]; + dispose = listen(details, "toggle", /*details_toggle_handler*/ ctx[1]); }, p(ctx, [dirty]) { if (dirty & /*open*/ 1) { diff --git a/test/js/samples/bindings-readonly-order/expected.js b/test/js/samples/bindings-readonly-order/expected.js index cf30686662..db0e7cb007 100644 --- a/test/js/samples/bindings-readonly-order/expected.js +++ b/test/js/samples/bindings-readonly-order/expected.js @@ -26,16 +26,16 @@ function create_fragment(ctx) { input1 = element("input"); attr(input0, "type", "file"); attr(input1, "type", "file"); - - dispose = [ - listen(input0, "change", /*input0_change_handler*/ ctx[1]), - listen(input1, "change", /*input1_change_handler*/ ctx[2]) - ]; }, m(target, anchor) { insert(target, input0, anchor); insert(target, t, anchor); insert(target, input1, anchor); + + dispose = [ + listen(input0, "change", /*input0_change_handler*/ ctx[1]), + listen(input1, "change", /*input1_change_handler*/ ctx[2]) + ]; }, p: noop, i: noop, diff --git a/test/js/samples/capture-inject-dev-only/expected.js b/test/js/samples/capture-inject-dev-only/expected.js index 6c639d9207..a314b0cff3 100644 --- a/test/js/samples/capture-inject-dev-only/expected.js +++ b/test/js/samples/capture-inject-dev-only/expected.js @@ -28,7 +28,6 @@ function create_fragment(ctx) { t0 = text(/*foo*/ ctx[0]); t1 = space(); input = element("input"); - dispose = listen(input, "input", /*input_input_handler*/ ctx[1]); }, m(target, anchor) { insert(target, p, anchor); @@ -36,6 +35,7 @@ function create_fragment(ctx) { insert(target, t1, anchor); insert(target, input, anchor); set_input_value(input, /*foo*/ ctx[0]); + dispose = listen(input, "input", /*input_input_handler*/ ctx[1]); }, p(ctx, [dirty]) { if (dirty & /*foo*/ 1) set_data(t0, /*foo*/ ctx[0]); diff --git a/test/js/samples/component-static-var/expected.js b/test/js/samples/component-static-var/expected.js index e01402b6d4..a65d9186a7 100644 --- a/test/js/samples/component-static-var/expected.js +++ b/test/js/samples/component-static-var/expected.js @@ -35,7 +35,6 @@ function create_fragment(ctx) { create_component(bar.$$.fragment); t1 = space(); input = element("input"); - dispose = listen(input, "input", /*input_input_handler*/ ctx[1]); }, m(target, anchor) { mount_component(foo, target, anchor); @@ -45,6 +44,7 @@ function create_fragment(ctx) { insert(target, input, anchor); set_input_value(input, /*z*/ ctx[0]); current = true; + dispose = listen(input, "input", /*input_input_handler*/ ctx[1]); }, p(ctx, [dirty]) { const bar_changes = {}; diff --git a/test/js/samples/component-store-reassign-invalidate/expected.js b/test/js/samples/component-store-reassign-invalidate/expected.js index 02a74cf22e..771b20dec4 100644 --- a/test/js/samples/component-store-reassign-invalidate/expected.js +++ b/test/js/samples/component-store-reassign-invalidate/expected.js @@ -31,13 +31,13 @@ function create_fragment(ctx) { t1 = space(); button = element("button"); button.textContent = "reset"; - dispose = listen(button, "click", /*click_handler*/ ctx[2]); }, m(target, anchor) { insert(target, h1, anchor); append(h1, t0); insert(target, t1, anchor); insert(target, button, anchor); + dispose = listen(button, "click", /*click_handler*/ ctx[2]); }, p(ctx, [dirty]) { if (dirty & /*$foo*/ 2) set_data(t0, /*$foo*/ ctx[1]); diff --git a/test/js/samples/dont-invalidate-this/expected.js b/test/js/samples/dont-invalidate-this/expected.js index 98f638dfcf..f5f6d07812 100644 --- a/test/js/samples/dont-invalidate-this/expected.js +++ b/test/js/samples/dont-invalidate-this/expected.js @@ -17,10 +17,10 @@ function create_fragment(ctx) { return { c() { input = element("input"); - dispose = listen(input, "input", make_uppercase); }, m(target, anchor) { insert(target, input, anchor); + dispose = listen(input, "input", make_uppercase); }, p: noop, i: noop, diff --git a/test/js/samples/event-handler-dynamic/expected.js b/test/js/samples/event-handler-dynamic/expected.js index 42c6b2951a..16b4a3f626 100644 --- a/test/js/samples/event-handler-dynamic/expected.js +++ b/test/js/samples/event-handler-dynamic/expected.js @@ -42,14 +42,6 @@ function create_fragment(ctx) { t5 = space(); button2 = element("button"); button2.textContent = "click"; - - dispose = [ - listen(button0, "click", /*updateHandler1*/ ctx[2]), - listen(button1, "click", /*updateHandler2*/ ctx[3]), - listen(button2, "click", function () { - if (is_function(/*clickHandler*/ ctx[0])) /*clickHandler*/ ctx[0].apply(this, arguments); - }) - ]; }, m(target, anchor) { insert(target, p0, anchor); @@ -61,6 +53,14 @@ function create_fragment(ctx) { append(p1, t4); insert(target, t5, anchor); insert(target, button2, anchor); + + dispose = [ + listen(button0, "click", /*updateHandler1*/ ctx[2]), + listen(button1, "click", /*updateHandler2*/ ctx[3]), + listen(button2, "click", function () { + if (is_function(/*clickHandler*/ ctx[0])) /*clickHandler*/ ctx[0].apply(this, arguments); + }) + ]; }, p(new_ctx, [dirty]) { ctx = new_ctx; diff --git a/test/js/samples/event-handler-no-passive/expected.js b/test/js/samples/event-handler-no-passive/expected.js index 6f04e67808..c519fac668 100644 --- a/test/js/samples/event-handler-no-passive/expected.js +++ b/test/js/samples/event-handler-no-passive/expected.js @@ -20,10 +20,10 @@ function create_fragment(ctx) { a = element("a"); a.textContent = "this should not navigate to example.com"; attr(a, "href", "https://example.com"); - dispose = listen(a, "touchstart", touchstart_handler); }, m(target, anchor) { insert(target, a, anchor); + dispose = listen(a, "touchstart", touchstart_handler); }, p: noop, i: noop, diff --git a/test/js/samples/event-modifiers/expected.js b/test/js/samples/event-modifiers/expected.js index 3f324bb76d..252034a431 100644 --- a/test/js/samples/event-modifiers/expected.js +++ b/test/js/samples/event-modifiers/expected.js @@ -35,13 +35,6 @@ function create_fragment(ctx) { t3 = space(); button2 = element("button"); button2.textContent = "or me!"; - - dispose = [ - listen(button0, "click", stop_propagation(prevent_default(handleClick))), - listen(button1, "click", handleClick, { once: true, capture: true }), - listen(button2, "click", handleClick, true), - listen(div, "touchstart", handleTouchstart, { passive: true }) - ]; }, m(target, anchor) { insert(target, div, anchor); @@ -50,6 +43,13 @@ function create_fragment(ctx) { append(div, button1); append(div, t3); append(div, button2); + + dispose = [ + listen(button0, "click", stop_propagation(prevent_default(handleClick))), + listen(button1, "click", handleClick, { once: true, capture: true }), + listen(button2, "click", handleClick, true), + listen(div, "touchstart", handleTouchstart, { passive: true }) + ]; }, p: noop, i: noop, diff --git a/test/js/samples/input-files/expected.js b/test/js/samples/input-files/expected.js index c3e46f0c79..2a2254fbd7 100644 --- a/test/js/samples/input-files/expected.js +++ b/test/js/samples/input-files/expected.js @@ -20,10 +20,10 @@ function create_fragment(ctx) { input = element("input"); attr(input, "type", "file"); input.multiple = true; - dispose = listen(input, "change", /*input_change_handler*/ ctx[1]); }, m(target, anchor) { insert(target, input, anchor); + dispose = listen(input, "change", /*input_change_handler*/ ctx[1]); }, p: noop, i: noop, diff --git a/test/js/samples/input-no-initial-value/expected.js b/test/js/samples/input-no-initial-value/expected.js index 8ff2b2798b..d588f0bf73 100644 --- a/test/js/samples/input-no-initial-value/expected.js +++ b/test/js/samples/input-no-initial-value/expected.js @@ -31,11 +31,6 @@ function create_fragment(ctx) { button.textContent = "Store"; attr(input, "type", "text"); input.required = true; - - dispose = [ - listen(input, "input", /*input_input_handler*/ ctx[2]), - listen(form, "submit", /*handleSubmit*/ ctx[1]) - ]; }, m(target, anchor) { insert(target, form, anchor); @@ -43,6 +38,11 @@ function create_fragment(ctx) { set_input_value(input, /*test*/ ctx[0]); append(form, t0); append(form, button); + + dispose = [ + listen(input, "input", /*input_input_handler*/ ctx[2]), + listen(form, "submit", /*handleSubmit*/ ctx[1]) + ]; }, p(ctx, [dirty]) { if (dirty & /*test*/ 1 && input.value !== /*test*/ ctx[0]) { diff --git a/test/js/samples/input-range/expected.js b/test/js/samples/input-range/expected.js index 5a074d9754..12dfd3e90e 100644 --- a/test/js/samples/input-range/expected.js +++ b/test/js/samples/input-range/expected.js @@ -22,16 +22,16 @@ function create_fragment(ctx) { c() { input = element("input"); attr(input, "type", "range"); + }, + m(target, anchor) { + insert(target, input, anchor); + set_input_value(input, /*value*/ ctx[0]); dispose = [ listen(input, "change", /*input_change_input_handler*/ ctx[1]), listen(input, "input", /*input_change_input_handler*/ ctx[1]) ]; }, - m(target, anchor) { - insert(target, input, anchor); - set_input_value(input, /*value*/ ctx[0]); - }, p(ctx, [dirty]) { if (dirty & /*value*/ 1) { set_input_value(input, /*value*/ ctx[0]); diff --git a/test/js/samples/input-value/expected.js b/test/js/samples/input-value/expected.js index 81753441e4..21c7bfc83b 100644 --- a/test/js/samples/input-value/expected.js +++ b/test/js/samples/input-value/expected.js @@ -30,7 +30,6 @@ function create_fragment(ctx) { t1 = text(/*name*/ ctx[0]); t2 = text("!"); input.value = /*name*/ ctx[0]; - dispose = listen(input, "input", /*onInput*/ ctx[1]); }, m(target, anchor) { insert(target, input, anchor); @@ -38,6 +37,7 @@ function create_fragment(ctx) { insert(target, h1, anchor); append(h1, t1); append(h1, t2); + dispose = listen(input, "input", /*onInput*/ ctx[1]); }, p(ctx, [dirty]) { if (dirty & /*name*/ 1 && input.value !== /*name*/ ctx[0]) { diff --git a/test/js/samples/input-without-blowback-guard/expected.js b/test/js/samples/input-without-blowback-guard/expected.js index 344976ade6..fefe867e14 100644 --- a/test/js/samples/input-without-blowback-guard/expected.js +++ b/test/js/samples/input-without-blowback-guard/expected.js @@ -19,11 +19,11 @@ function create_fragment(ctx) { c() { input = element("input"); attr(input, "type", "checkbox"); - dispose = listen(input, "change", /*input_change_handler*/ ctx[1]); }, m(target, anchor) { insert(target, input, anchor); input.checked = /*foo*/ ctx[0]; + dispose = listen(input, "change", /*input_change_handler*/ ctx[1]); }, p(ctx, [dirty]) { if (dirty & /*foo*/ 1) { diff --git a/test/js/samples/instrumentation-script-if-no-block/expected.js b/test/js/samples/instrumentation-script-if-no-block/expected.js index 4127a6d7d6..7634481a2d 100644 --- a/test/js/samples/instrumentation-script-if-no-block/expected.js +++ b/test/js/samples/instrumentation-script-if-no-block/expected.js @@ -30,7 +30,6 @@ function create_fragment(ctx) { p = element("p"); t2 = text("x: "); t3 = text(/*x*/ ctx[0]); - dispose = listen(button, "click", /*foo*/ ctx[1]); }, m(target, anchor) { insert(target, button, anchor); @@ -38,6 +37,7 @@ function create_fragment(ctx) { insert(target, p, anchor); append(p, t2); append(p, t3); + dispose = listen(button, "click", /*foo*/ ctx[1]); }, p(ctx, [dirty]) { if (dirty & /*x*/ 1) set_data(t3, /*x*/ ctx[0]); diff --git a/test/js/samples/instrumentation-script-x-equals-x/expected.js b/test/js/samples/instrumentation-script-x-equals-x/expected.js index 0d4493baf3..c154608cd5 100644 --- a/test/js/samples/instrumentation-script-x-equals-x/expected.js +++ b/test/js/samples/instrumentation-script-x-equals-x/expected.js @@ -31,7 +31,6 @@ function create_fragment(ctx) { p = element("p"); t2 = text("number of things: "); t3 = text(t3_value); - dispose = listen(button, "click", /*foo*/ ctx[1]); }, m(target, anchor) { insert(target, button, anchor); @@ -39,6 +38,7 @@ function create_fragment(ctx) { insert(target, p, anchor); append(p, t2); append(p, t3); + dispose = listen(button, "click", /*foo*/ ctx[1]); }, p(ctx, [dirty]) { if (dirty & /*things*/ 1 && t3_value !== (t3_value = /*things*/ ctx[0].length + "")) set_data(t3, t3_value); diff --git a/test/js/samples/instrumentation-template-if-no-block/expected.js b/test/js/samples/instrumentation-template-if-no-block/expected.js index 0bd627eb87..77780baa99 100644 --- a/test/js/samples/instrumentation-template-if-no-block/expected.js +++ b/test/js/samples/instrumentation-template-if-no-block/expected.js @@ -30,7 +30,6 @@ function create_fragment(ctx) { p = element("p"); t2 = text("x: "); t3 = text(/*x*/ ctx[0]); - dispose = listen(button, "click", /*click_handler*/ ctx[1]); }, m(target, anchor) { insert(target, button, anchor); @@ -38,6 +37,7 @@ function create_fragment(ctx) { insert(target, p, anchor); append(p, t2); append(p, t3); + dispose = listen(button, "click", /*click_handler*/ ctx[1]); }, p(ctx, [dirty]) { if (dirty & /*x*/ 1) set_data(t3, /*x*/ ctx[0]); diff --git a/test/js/samples/instrumentation-template-x-equals-x/expected.js b/test/js/samples/instrumentation-template-x-equals-x/expected.js index e049a6d39b..4fe45616c7 100644 --- a/test/js/samples/instrumentation-template-x-equals-x/expected.js +++ b/test/js/samples/instrumentation-template-x-equals-x/expected.js @@ -31,7 +31,6 @@ function create_fragment(ctx) { p = element("p"); t2 = text("number of things: "); t3 = text(t3_value); - dispose = listen(button, "click", /*click_handler*/ ctx[1]); }, m(target, anchor) { insert(target, button, anchor); @@ -39,6 +38,7 @@ function create_fragment(ctx) { insert(target, p, anchor); append(p, t2); append(p, t3); + dispose = listen(button, "click", /*click_handler*/ ctx[1]); }, p(ctx, [dirty]) { if (dirty & /*things*/ 1 && t3_value !== (t3_value = /*things*/ ctx[0].length + "")) set_data(t3, t3_value); diff --git a/test/js/samples/media-bindings/expected.js b/test/js/samples/media-bindings/expected.js index b5548a3efe..52fef36792 100644 --- a/test/js/samples/media-bindings/expected.js +++ b/test/js/samples/media-bindings/expected.js @@ -41,6 +41,17 @@ function create_fragment(ctx) { if (/*duration*/ ctx[4] === void 0) add_render_callback(() => /*audio_durationchange_handler*/ ctx[13].call(audio)); if (/*seeking*/ ctx[8] === void 0) add_render_callback(() => /*audio_seeking_seeked_handler*/ ctx[17].call(audio)); if (/*ended*/ ctx[9] === void 0) add_render_callback(() => /*audio_ended_handler*/ ctx[18].call(audio)); + }, + m(target, anchor) { + insert(target, audio, anchor); + + if (!isNaN(/*volume*/ ctx[6])) { + audio.volume = /*volume*/ ctx[6]; + } + + if (!isNaN(/*playbackRate*/ ctx[7])) { + audio.playbackRate = /*playbackRate*/ ctx[7]; + } dispose = [ listen(audio, "progress", /*audio_progress_handler*/ ctx[10]), @@ -56,17 +67,6 @@ function create_fragment(ctx) { listen(audio, "ended", /*audio_ended_handler*/ ctx[18]) ]; }, - m(target, anchor) { - insert(target, audio, anchor); - - if (!isNaN(/*volume*/ ctx[6])) { - audio.volume = /*volume*/ ctx[6]; - } - - if (!isNaN(/*playbackRate*/ ctx[7])) { - audio.playbackRate = /*playbackRate*/ ctx[7]; - } - }, p(ctx, [dirty]) { if (!audio_updating && dirty & /*currentTime*/ 8 && !isNaN(/*currentTime*/ ctx[3])) { audio.currentTime = /*currentTime*/ ctx[3]; diff --git a/test/js/samples/video-bindings/expected.js b/test/js/samples/video-bindings/expected.js index 5b734a70a6..c8cd1d84ce 100644 --- a/test/js/samples/video-bindings/expected.js +++ b/test/js/samples/video-bindings/expected.js @@ -37,16 +37,16 @@ function create_fragment(ctx) { video = element("video"); if (/*videoHeight*/ ctx[1] === void 0 || /*videoWidth*/ ctx[2] === void 0) add_render_callback(() => /*video_resize_handler*/ ctx[5].call(video)); add_render_callback(() => /*video_elementresize_handler*/ ctx[6].call(video)); + }, + m(target, anchor) { + insert(target, video, anchor); + video_resize_listener = add_resize_listener(video, /*video_elementresize_handler*/ ctx[6].bind(video)); dispose = [ listen(video, "timeupdate", video_timeupdate_handler), listen(video, "resize", /*video_resize_handler*/ ctx[5]) ]; }, - m(target, anchor) { - insert(target, video, anchor); - video_resize_listener = add_resize_listener(video, /*video_elementresize_handler*/ ctx[6].bind(video)); - }, p(ctx, [dirty]) { if (!video_updating && dirty & /*currentTime*/ 1 && !isNaN(/*currentTime*/ ctx[0])) { video.currentTime = /*currentTime*/ ctx[0]; diff --git a/test/js/samples/window-binding-online/expected.js b/test/js/samples/window-binding-online/expected.js index 8285646481..e129e66d71 100644 --- a/test/js/samples/window-binding-online/expected.js +++ b/test/js/samples/window-binding-online/expected.js @@ -14,13 +14,13 @@ function create_fragment(ctx) { add_render_callback(/*onlinestatuschanged*/ ctx[1]); return { - c() { + c: noop, + m(target, anchor) { dispose = [ listen(window, "online", /*onlinestatuschanged*/ ctx[1]), listen(window, "offline", /*onlinestatuschanged*/ ctx[1]) ]; }, - m: noop, p: noop, i: noop, o: noop, diff --git a/test/js/samples/window-binding-scroll/expected.js b/test/js/samples/window-binding-scroll/expected.js index f79212e25e..70c39eedd2 100644 --- a/test/js/samples/window-binding-scroll/expected.js +++ b/test/js/samples/window-binding-scroll/expected.js @@ -33,6 +33,11 @@ function create_fragment(ctx) { p = element("p"); t0 = text("scrolled to "); t1 = text(/*y*/ ctx[0]); + }, + m(target, anchor) { + insert(target, p, anchor); + append(p, t0); + append(p, t1); dispose = listen(window, "scroll", () => { scrolling = true; @@ -41,11 +46,6 @@ function create_fragment(ctx) { /*onwindowscroll*/ ctx[1](); }); }, - m(target, anchor) { - insert(target, p, anchor); - append(p, t0); - append(p, t1); - }, p(ctx, [dirty]) { if (dirty & /*y*/ 1 && !scrolling) { scrolling = true; diff --git a/test/runtime/samples/action-receives-element-mounted/_config.js b/test/runtime/samples/action-receives-element-mounted/_config.js new file mode 100644 index 0000000000..0806d0fa90 --- /dev/null +++ b/test/runtime/samples/action-receives-element-mounted/_config.js @@ -0,0 +1,8 @@ +const result = {}; + +export default { + props: { result }, + async test({ assert, component, target, window }) { + assert.notEqual(result.parentElement, null); + } +}; diff --git a/test/runtime/samples/action-receives-element-mounted/main.svelte b/test/runtime/samples/action-receives-element-mounted/main.svelte new file mode 100644 index 0000000000..a53ce81de0 --- /dev/null +++ b/test/runtime/samples/action-receives-element-mounted/main.svelte @@ -0,0 +1,8 @@ + + +

Hello!

\ No newline at end of file From 7c3e34c00bb585b05104c13a03d3606427cb0457 Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Mon, 13 Jan 2020 04:42:45 +0800 Subject: [PATCH 02/82] fix hydrating each else (#4253) --- CHANGELOG.md | 1 + .../compile/render_dom/wrappers/EachBlock.ts | 13 +++++++++++++ test/hydration/samples/each-else/_after.html | 4 ++++ test/hydration/samples/each-else/_before.html | 4 ++++ test/hydration/samples/each-else/main.svelte | 15 +++++++++++++++ 5 files changed, 37 insertions(+) create mode 100644 test/hydration/samples/each-else/_after.html create mode 100644 test/hydration/samples/each-else/_before.html create mode 100644 test/hydration/samples/each-else/main.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index f6af419913..25c0aa2093 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * Expose compiler version in dev events ([#4047](https://github.com/sveltejs/svelte/issues/4047)) * Don't run actions before their element is in the document ([#4166](https://github.com/sveltejs/svelte/issues/4166)) * Fix reactive assignments with destructuring and stores where the destructured value should be undefined ([#4170](https://github.com/sveltejs/svelte/issues/4170)) +* Fix hydrating `{:else}` in `{#each}` ([#4202](https://github.com/sveltejs/svelte/issues/4202)) * Do not automatically declare variables in reactive declarations when assigning to a member expression ([#4212](https://github.com/sveltejs/svelte/issues/4212)) ## 3.16.7 diff --git a/src/compiler/compile/render_dom/wrappers/EachBlock.ts b/src/compiler/compile/render_dom/wrappers/EachBlock.ts index 5b13b486e6..4928b5a38c 100644 --- a/src/compiler/compile/render_dom/wrappers/EachBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/EachBlock.ts @@ -264,10 +264,23 @@ export default class EachBlockWrapper extends Wrapper { block.chunks.init.push(b` if (!${this.vars.data_length}) { ${each_block_else} = ${this.else.block.name}(#ctx); + } + `); + + block.chunks.create.push(b` + if (${each_block_else}) { ${each_block_else}.c(); } `); + if (this.renderer.options.hydratable) { + block.chunks.claim.push(b` + if (${each_block_else}) { + ${each_block_else}.l(${parent_nodes}); + } + `); + } + block.chunks.mount.push(b` if (${each_block_else}) { ${each_block_else}.m(${initial_mount_node}, ${initial_anchor_node}); diff --git a/test/hydration/samples/each-else/_after.html b/test/hydration/samples/each-else/_after.html new file mode 100644 index 0000000000..7920500ec3 --- /dev/null +++ b/test/hydration/samples/each-else/_after.html @@ -0,0 +1,4 @@ +

Hello, world

+

+ weird +

\ No newline at end of file diff --git a/test/hydration/samples/each-else/_before.html b/test/hydration/samples/each-else/_before.html new file mode 100644 index 0000000000..7920500ec3 --- /dev/null +++ b/test/hydration/samples/each-else/_before.html @@ -0,0 +1,4 @@ +

Hello, world

+

+ weird +

\ No newline at end of file diff --git a/test/hydration/samples/each-else/main.svelte b/test/hydration/samples/each-else/main.svelte new file mode 100644 index 0000000000..64d3a37c58 --- /dev/null +++ b/test/hydration/samples/each-else/main.svelte @@ -0,0 +1,15 @@ + + +

Hello, {name}

+{#each array as elem} +

+ item +

+{:else} +

+ weird +

+{/each} From ef56a70acbe484bf1be9787ae63ae5102ee275ea Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Tue, 14 Jan 2020 00:13:54 +0800 Subject: [PATCH 03/82] SSR should only render one (#4250) --- CHANGELOG.md | 1 + src/compiler/compile/render_ssr/handlers/Title.ts | 6 ++++++ src/runtime/internal/ssr.ts | 5 +++-- .../samples/head-multiple-title/A.svelte | 3 +++ .../samples/head-multiple-title/B.svelte | 3 +++ .../samples/head-multiple-title/_expected-head.html | 1 + .../samples/head-multiple-title/_expected.html | 0 .../samples/head-multiple-title/data.json | 3 +++ .../samples/head-multiple-title/main.svelte | 10 ++++++++++ 9 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 test/server-side-rendering/samples/head-multiple-title/A.svelte create mode 100644 test/server-side-rendering/samples/head-multiple-title/B.svelte create mode 100644 test/server-side-rendering/samples/head-multiple-title/_expected-head.html create mode 100644 test/server-side-rendering/samples/head-multiple-title/_expected.html create mode 100644 test/server-side-rendering/samples/head-multiple-title/data.json create mode 100644 test/server-side-rendering/samples/head-multiple-title/main.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index 25c0aa2093..8d69c6194b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * Fix reactive assignments with destructuring and stores where the destructured value should be undefined ([#4170](https://github.com/sveltejs/svelte/issues/4170)) * Fix hydrating `{:else}` in `{#each}` ([#4202](https://github.com/sveltejs/svelte/issues/4202)) * Do not automatically declare variables in reactive declarations when assigning to a member expression ([#4212](https://github.com/sveltejs/svelte/issues/4212)) +* Only render one `<title>` in SSR mode when multiple components provide one ([#4250](https://github.com/sveltejs/svelte/pull/4250)) ## 3.16.7 diff --git a/src/compiler/compile/render_ssr/handlers/Title.ts b/src/compiler/compile/render_ssr/handlers/Title.ts index 62d49d461a..f1f458ed5b 100644 --- a/src/compiler/compile/render_ssr/handlers/Title.ts +++ b/src/compiler/compile/render_ssr/handlers/Title.ts @@ -1,10 +1,16 @@ import Renderer, { RenderOptions } from '../Renderer'; import Title from '../../nodes/Title'; +import { x } from 'code-red'; export default function(node: Title, renderer: Renderer, options: RenderOptions) { + renderer.push(); + renderer.add_string(`<title>`); renderer.render(node.children, options); renderer.add_string(``); + const result = renderer.pop(); + + renderer.add_expression(x`($$result.title = ${result}, "")`); } diff --git a/src/runtime/internal/ssr.ts b/src/runtime/internal/ssr.ts index 274006f243..b032284d52 100644 --- a/src/runtime/internal/ssr.ts +++ b/src/runtime/internal/ssr.ts @@ -103,12 +103,13 @@ export function create_ssr_component(fn) { on_destroy = []; const result: { + title: string; head: string; css: Set<{ map: null; code: string; }>; - } = { head: '', css: new Set() }; + } = { title: '', head: '', css: new Set() }; const html = $$render(result, props, {}, options); @@ -120,7 +121,7 @@ export function create_ssr_component(fn) { code: Array.from(result.css).map(css => css.code).join('\n'), map: null // TODO }, - head: result.head + head: result.title + result.head }; }, diff --git a/test/server-side-rendering/samples/head-multiple-title/A.svelte b/test/server-side-rendering/samples/head-multiple-title/A.svelte new file mode 100644 index 0000000000..b139b4ff77 --- /dev/null +++ b/test/server-side-rendering/samples/head-multiple-title/A.svelte @@ -0,0 +1,3 @@ + + A + diff --git a/test/server-side-rendering/samples/head-multiple-title/B.svelte b/test/server-side-rendering/samples/head-multiple-title/B.svelte new file mode 100644 index 0000000000..4a29ecb04c --- /dev/null +++ b/test/server-side-rendering/samples/head-multiple-title/B.svelte @@ -0,0 +1,3 @@ + + B + diff --git a/test/server-side-rendering/samples/head-multiple-title/_expected-head.html b/test/server-side-rendering/samples/head-multiple-title/_expected-head.html new file mode 100644 index 0000000000..af5c5feba4 --- /dev/null +++ b/test/server-side-rendering/samples/head-multiple-title/_expected-head.html @@ -0,0 +1 @@ +B \ No newline at end of file diff --git a/test/server-side-rendering/samples/head-multiple-title/_expected.html b/test/server-side-rendering/samples/head-multiple-title/_expected.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/server-side-rendering/samples/head-multiple-title/data.json b/test/server-side-rendering/samples/head-multiple-title/data.json new file mode 100644 index 0000000000..eab238e816 --- /dev/null +++ b/test/server-side-rendering/samples/head-multiple-title/data.json @@ -0,0 +1,3 @@ +{ + "adjective": "custom" +} \ No newline at end of file diff --git a/test/server-side-rendering/samples/head-multiple-title/main.svelte b/test/server-side-rendering/samples/head-multiple-title/main.svelte new file mode 100644 index 0000000000..fb9a70b923 --- /dev/null +++ b/test/server-side-rendering/samples/head-multiple-title/main.svelte @@ -0,0 +1,10 @@ + + + + Main + + + From e3d66869df5b75b81da6183c5d68cde53b2439aa Mon Sep 17 00:00:00 2001 From: Jesse Skinner Date: Mon, 13 Jan 2020 11:24:34 -0500 Subject: [PATCH 04/82] fix stringifying of attributes in presence of spread in SSR (#4247) --- src/runtime/internal/ssr.ts | 4 +--- .../spread-attributes-white-space/_expected.html | 8 ++++++++ .../spread-attributes-white-space/main.svelte | 12 ++++++++++++ 3 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 test/server-side-rendering/samples/spread-attributes-white-space/_expected.html create mode 100644 test/server-side-rendering/samples/spread-attributes-white-space/main.svelte diff --git a/src/runtime/internal/ssr.ts b/src/runtime/internal/ssr.ts index b032284d52..646a81d817 100644 --- a/src/runtime/internal/ssr.ts +++ b/src/runtime/internal/ssr.ts @@ -25,9 +25,7 @@ export function spread(args, classes_to_add) { else if (boolean_attributes.has(name.toLowerCase())) { if (value) str += " " + name; } else if (value != null) { - str += " " + name + "=" + JSON.stringify(String(value) - .replace(/"/g, '"') - .replace(/'/g, ''')); + str += ` ${name}="${String(value).replace(/"/g, '"').replace(/'/g, ''')}"`; } }); diff --git a/test/server-side-rendering/samples/spread-attributes-white-space/_expected.html b/test/server-side-rendering/samples/spread-attributes-white-space/_expected.html new file mode 100644 index 0000000000..a73bb17e8c --- /dev/null +++ b/test/server-side-rendering/samples/spread-attributes-white-space/_expected.html @@ -0,0 +1,8 @@ + + + \ No newline at end of file diff --git a/test/server-side-rendering/samples/spread-attributes-white-space/main.svelte b/test/server-side-rendering/samples/spread-attributes-white-space/main.svelte new file mode 100644 index 0000000000..6919d9ea54 --- /dev/null +++ b/test/server-side-rendering/samples/spread-attributes-white-space/main.svelte @@ -0,0 +1,12 @@ + + + + + From d7d7ce1e6c4ab0166eb5f40e5f2dad6a49dd0fe3 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Mon, 13 Jan 2020 11:25:48 -0500 Subject: [PATCH 05/82] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d69c6194b..a2db4470d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * Fix reactive assignments with destructuring and stores where the destructured value should be undefined ([#4170](https://github.com/sveltejs/svelte/issues/4170)) * Fix hydrating `{:else}` in `{#each}` ([#4202](https://github.com/sveltejs/svelte/issues/4202)) * Do not automatically declare variables in reactive declarations when assigning to a member expression ([#4212](https://github.com/sveltejs/svelte/issues/4212)) +* Fix stringifying of attributes in SSR mode when there are spread attributes ([#4240](https://github.com/sveltejs/svelte/issues/4240)) * Only render one `` in SSR mode when multiple components provide one ([#4250](https://github.com/sveltejs/svelte/pull/4250)) ## 3.16.7 From b3582c7fb24572a6dfd58e2009925158a9a37233 Mon Sep 17 00:00:00 2001 From: Tan Li Hau <tanhauhau@users.noreply.github.com> Date: Tue, 14 Jan 2020 00:55:55 +0800 Subject: [PATCH 06/82] fix hydrating <head> (#4082) --- CHANGELOG.md | 1 + src/compiler/compile/css/Stylesheet.ts | 10 +-------- src/compiler/compile/nodes/Head.ts | 6 ++++++ .../compile/render_dom/wrappers/Head.ts | 17 +++++++++++++-- src/compiler/compile/render_ssr/Renderer.ts | 1 + .../compile/render_ssr/handlers/Element.ts | 4 ++++ .../compile/render_ssr/handlers/Head.ts | 7 ++++++- .../compile/render_ssr/handlers/Title.ts | 2 +- src/compiler/compile/utils/hash.ts | 8 +++++++ src/runtime/internal/dom.ts | 4 ++++ test/helpers.js | 1 + test/hydration/index.js | 21 +++++++++++++++++++ .../head-meta-hydrate-duplicate/_after.html | 1 + .../_after_head.html | 4 ++++ .../head-meta-hydrate-duplicate/_before.html | 1 + .../_before_head.html | 4 ++++ .../head-meta-hydrate-duplicate/_config.js | 5 +++++ .../head-meta-hydrate-duplicate/main.svelte | 8 +++++++ .../_expected-head.html | 4 ++++ .../_expected.html | 3 +++ .../head-meta-hydrate-duplicate/main.svelte | 8 +++++++ .../head-multiple-title/_expected-head.html | 2 +- .../samples/head-title/_expected-head.html | 2 +- 23 files changed, 109 insertions(+), 15 deletions(-) create mode 100644 src/compiler/compile/utils/hash.ts create mode 100644 test/hydration/samples/head-meta-hydrate-duplicate/_after.html create mode 100644 test/hydration/samples/head-meta-hydrate-duplicate/_after_head.html create mode 100644 test/hydration/samples/head-meta-hydrate-duplicate/_before.html create mode 100644 test/hydration/samples/head-meta-hydrate-duplicate/_before_head.html create mode 100644 test/hydration/samples/head-meta-hydrate-duplicate/_config.js create mode 100644 test/hydration/samples/head-meta-hydrate-duplicate/main.svelte create mode 100644 test/server-side-rendering/samples/head-meta-hydrate-duplicate/_expected-head.html create mode 100644 test/server-side-rendering/samples/head-meta-hydrate-duplicate/_expected.html create mode 100644 test/server-side-rendering/samples/head-meta-hydrate-duplicate/main.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index a2db4470d1..4bacc5c26b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +* Remove old `<head>` elements during hydration so they aren't duplicated ([#1607](https://github.com/sveltejs/svelte/issues/1607)) * Prevent text input cursor jumping in Safari with one-way binding ([#3449](https://github.com/sveltejs/svelte/issues/3449)) * Expose compiler version in dev events ([#4047](https://github.com/sveltejs/svelte/issues/4047)) * Don't run actions before their element is in the document ([#4166](https://github.com/sveltejs/svelte/issues/4166)) diff --git a/src/compiler/compile/css/Stylesheet.ts b/src/compiler/compile/css/Stylesheet.ts index 998a879687..246dab0f12 100644 --- a/src/compiler/compile/css/Stylesheet.ts +++ b/src/compiler/compile/css/Stylesheet.ts @@ -5,6 +5,7 @@ import Element from '../nodes/Element'; import { Ast, TemplateNode } from '../../interfaces'; import Component from '../Component'; import { CssNode } from './interfaces'; +import hash from "../utils/hash"; function remove_css_prefix(name: string): string { return name.replace(/^-((webkit)|(moz)|(o)|(ms))-/, ''); @@ -37,15 +38,6 @@ function minify_declarations( return c; } -// https://github.com/darkskyapp/string-hash/blob/master/index.js -function hash(str: string): string { - let hash = 5381; - let i = str.length; - - while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i); - return (hash >>> 0).toString(36); -} - class Rule { selectors: Selector[]; declarations: Declaration[]; diff --git a/src/compiler/compile/nodes/Head.ts b/src/compiler/compile/nodes/Head.ts index 2c08dcd595..53e76d7a4d 100644 --- a/src/compiler/compile/nodes/Head.ts +++ b/src/compiler/compile/nodes/Head.ts @@ -1,9 +1,11 @@ import Node from './shared/Node'; import map_children from './shared/map_children'; +import hash from '../utils/hash'; export default class Head extends Node { type: 'Head'; children: any[]; // TODO + id: string; constructor(component, parent, scope, info) { super(component, parent, scope, info); @@ -18,5 +20,9 @@ export default class Head extends Node { this.children = map_children(component, parent, scope, info.children.filter(child => { return (child.type !== 'Text' || /\S/.test(child.data)); })); + + if (this.children.length > 0) { + this.id = `svelte-${hash(this.component.source.slice(this.start, this.end))}`; + } } } diff --git a/src/compiler/compile/render_dom/wrappers/Head.ts b/src/compiler/compile/render_dom/wrappers/Head.ts index 188c26931a..e0b723d6dd 100644 --- a/src/compiler/compile/render_dom/wrappers/Head.ts +++ b/src/compiler/compile/render_dom/wrappers/Head.ts @@ -3,11 +3,12 @@ import Renderer from '../Renderer'; import Block from '../Block'; import Head from '../../nodes/Head'; import FragmentWrapper from './Fragment'; -import { x } from 'code-red'; +import { x, b } from 'code-red'; import { Identifier } from 'estree'; export default class HeadWrapper extends Wrapper { fragment: FragmentWrapper; + node: Head; constructor( renderer: Renderer, @@ -32,6 +33,18 @@ export default class HeadWrapper extends Wrapper { } render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) { - this.fragment.render(block, x`@_document.head` as unknown as Identifier, x`#nodes` as unknown as Identifier); + let nodes; + if (this.renderer.options.hydratable && this.fragment.nodes.length) { + nodes = block.get_unique_name('head_nodes'); + block.chunks.claim.push(b`const ${nodes} = @query_selector_all('[data-svelte="${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);` + ); + } } } diff --git a/src/compiler/compile/render_ssr/Renderer.ts b/src/compiler/compile/render_ssr/Renderer.ts index 00a7ee2fb5..fb9216327c 100644 --- a/src/compiler/compile/render_ssr/Renderer.ts +++ b/src/compiler/compile/render_ssr/Renderer.ts @@ -41,6 +41,7 @@ const handlers: Record<string, Handler> = { export interface RenderOptions extends CompileOptions{ locate: (c: number) => { line: number; column: number }; + head_id?: string; } export default class Renderer { diff --git a/src/compiler/compile/render_ssr/handlers/Element.ts b/src/compiler/compile/render_ssr/handlers/Element.ts index 81b8801686..4c1eca8a9d 100644 --- a/src/compiler/compile/render_ssr/handlers/Element.ts +++ b/src/compiler/compile/render_ssr/handlers/Element.ts @@ -124,6 +124,10 @@ export default function(node: Element, renderer: Renderer, options: RenderOption } }); + if (options.head_id) { + renderer.add_string(` data-svelte="${options.head_id}"`); + } + renderer.add_string('>'); if (node_contents !== undefined) { diff --git a/src/compiler/compile/render_ssr/handlers/Head.ts b/src/compiler/compile/render_ssr/handlers/Head.ts index d457942922..456e5c279b 100644 --- a/src/compiler/compile/render_ssr/handlers/Head.ts +++ b/src/compiler/compile/render_ssr/handlers/Head.ts @@ -3,8 +3,13 @@ import Head from '../../nodes/Head'; import { x } from 'code-red'; export default function(node: Head, renderer: Renderer, options: RenderOptions) { + const head_options = { + ...options, + head_id: node.id + }; + renderer.push(); - renderer.render(node.children, options); + renderer.render(node.children, head_options); const result = renderer.pop(); renderer.add_expression(x`($$result.head += ${result}, "")`); diff --git a/src/compiler/compile/render_ssr/handlers/Title.ts b/src/compiler/compile/render_ssr/handlers/Title.ts index f1f458ed5b..e93ae13d66 100644 --- a/src/compiler/compile/render_ssr/handlers/Title.ts +++ b/src/compiler/compile/render_ssr/handlers/Title.ts @@ -5,7 +5,7 @@ import { x } from 'code-red'; export default function(node: Title, renderer: Renderer, options: RenderOptions) { renderer.push(); - renderer.add_string(`<title>`); + renderer.add_string(`<title data-svelte="${options.head_id}">`); renderer.render(node.children, options); diff --git a/src/compiler/compile/utils/hash.ts b/src/compiler/compile/utils/hash.ts new file mode 100644 index 0000000000..7ac892611b --- /dev/null +++ b/src/compiler/compile/utils/hash.ts @@ -0,0 +1,8 @@ +// https://github.com/darkskyapp/string-hash/blob/master/index.js +export default function hash(str: string): string { + let hash = 5381; + let i = str.length; + + while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i); + return (hash >>> 0).toString(36); +} \ No newline at end of file diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index c641315bc3..f9e89f41b9 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -273,6 +273,10 @@ export function custom_event<T=any>(type: string, detail?: T) { return e; } +export function query_selector_all(selector: string, parent: HTMLElement = document.body) { + return Array.from(parent.querySelectorAll(selector)); +} + export class HtmlTag { e: HTMLElement; n: ChildNode[]; diff --git a/test/helpers.js b/test/helpers.js index 2a03e0f436..5e9428243b 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -68,6 +68,7 @@ window.scrollTo = function(pageXOffset, pageYOffset) { export function env() { window.document.title = ''; + window.document.head.innerHTML = ''; window.document.body.innerHTML = '<main></main>'; return window; diff --git a/test/hydration/index.js b/test/hydration/index.js index a0bfd6de4b..f57a0cdc1a 100644 --- a/test/hydration/index.js +++ b/test/hydration/index.js @@ -67,8 +67,16 @@ describe('hydration', () => { } const target = window.document.body; + const head = window.document.head; + target.innerHTML = fs.readFileSync(`${cwd}/_before.html`, 'utf-8'); + let before_head; + try { + before_head = fs.readFileSync(`${cwd}/_before_head.html`, 'utf-8'); + head.innerHTML = before_head; + } catch (err) {} + const snapshot = config.snapshot ? config.snapshot(target) : {}; const component = new SvelteComponent({ @@ -88,6 +96,19 @@ describe('hydration', () => { } } + if (before_head) { + try { + assert.htmlEqual(head.innerHTML, fs.readFileSync(`${cwd}/_after_head.html`, 'utf-8')); + } catch (error) { + if (shouldUpdateExpected()) { + fs.writeFileSync(`${cwd}/_after_head.html`, head.innerHTML); + console.log(`Updated ${cwd}/_after_head.html.`); + } else { + throw error; + } + } + } + if (config.test) { config.test(assert, target, snapshot, component, window); } else { diff --git a/test/hydration/samples/head-meta-hydrate-duplicate/_after.html b/test/hydration/samples/head-meta-hydrate-duplicate/_after.html new file mode 100644 index 0000000000..3e5b375f0a --- /dev/null +++ b/test/hydration/samples/head-meta-hydrate-duplicate/_after.html @@ -0,0 +1 @@ +<div>Just a dummy page.</div> \ No newline at end of file diff --git a/test/hydration/samples/head-meta-hydrate-duplicate/_after_head.html b/test/hydration/samples/head-meta-hydrate-duplicate/_after_head.html new file mode 100644 index 0000000000..10cf2c8b9a --- /dev/null +++ b/test/hydration/samples/head-meta-hydrate-duplicate/_after_head.html @@ -0,0 +1,4 @@ +<title>Some Title + + + \ No newline at end of file diff --git a/test/hydration/samples/head-meta-hydrate-duplicate/_before.html b/test/hydration/samples/head-meta-hydrate-duplicate/_before.html new file mode 100644 index 0000000000..3e5b375f0a --- /dev/null +++ b/test/hydration/samples/head-meta-hydrate-duplicate/_before.html @@ -0,0 +1 @@ +
Just a dummy page.
\ No newline at end of file diff --git a/test/hydration/samples/head-meta-hydrate-duplicate/_before_head.html b/test/hydration/samples/head-meta-hydrate-duplicate/_before_head.html new file mode 100644 index 0000000000..d2f218fb8d --- /dev/null +++ b/test/hydration/samples/head-meta-hydrate-duplicate/_before_head.html @@ -0,0 +1,4 @@ +Some Title + + + \ No newline at end of file diff --git a/test/hydration/samples/head-meta-hydrate-duplicate/_config.js b/test/hydration/samples/head-meta-hydrate-duplicate/_config.js new file mode 100644 index 0000000000..482efd564d --- /dev/null +++ b/test/hydration/samples/head-meta-hydrate-duplicate/_config.js @@ -0,0 +1,5 @@ +export default { + test(assert, target, snapshot, component, window) { + assert.equal(window.document.querySelectorAll('meta').length, 2); + } +}; diff --git a/test/hydration/samples/head-meta-hydrate-duplicate/main.svelte b/test/hydration/samples/head-meta-hydrate-duplicate/main.svelte new file mode 100644 index 0000000000..1a8b125dd2 --- /dev/null +++ b/test/hydration/samples/head-meta-hydrate-duplicate/main.svelte @@ -0,0 +1,8 @@ + + Some Title + + + + + +
Just a dummy page.
\ No newline at end of file diff --git a/test/server-side-rendering/samples/head-meta-hydrate-duplicate/_expected-head.html b/test/server-side-rendering/samples/head-meta-hydrate-duplicate/_expected-head.html new file mode 100644 index 0000000000..d2f218fb8d --- /dev/null +++ b/test/server-side-rendering/samples/head-meta-hydrate-duplicate/_expected-head.html @@ -0,0 +1,4 @@ +Some Title + + + \ No newline at end of file diff --git a/test/server-side-rendering/samples/head-meta-hydrate-duplicate/_expected.html b/test/server-side-rendering/samples/head-meta-hydrate-duplicate/_expected.html new file mode 100644 index 0000000000..a469e618fa --- /dev/null +++ b/test/server-side-rendering/samples/head-meta-hydrate-duplicate/_expected.html @@ -0,0 +1,3 @@ + + +
Just a dummy page.
\ No newline at end of file diff --git a/test/server-side-rendering/samples/head-meta-hydrate-duplicate/main.svelte b/test/server-side-rendering/samples/head-meta-hydrate-duplicate/main.svelte new file mode 100644 index 0000000000..1a8b125dd2 --- /dev/null +++ b/test/server-side-rendering/samples/head-meta-hydrate-duplicate/main.svelte @@ -0,0 +1,8 @@ + + Some Title + + + + + +
Just a dummy page.
\ No newline at end of file diff --git a/test/server-side-rendering/samples/head-multiple-title/_expected-head.html b/test/server-side-rendering/samples/head-multiple-title/_expected-head.html index af5c5feba4..7147550839 100644 --- a/test/server-side-rendering/samples/head-multiple-title/_expected-head.html +++ b/test/server-side-rendering/samples/head-multiple-title/_expected-head.html @@ -1 +1 @@ -B \ No newline at end of file +B \ No newline at end of file diff --git a/test/server-side-rendering/samples/head-title/_expected-head.html b/test/server-side-rendering/samples/head-title/_expected-head.html index 7d696352f9..6e73e671e6 100644 --- a/test/server-side-rendering/samples/head-title/_expected-head.html +++ b/test/server-side-rendering/samples/head-title/_expected-head.html @@ -1 +1 @@ -a custom title \ No newline at end of file +a custom title \ No newline at end of file From c97e8f81db1f77a623716c9bedaa382def1d653c Mon Sep 17 00:00:00 2001 From: Conduitry Date: Mon, 13 Jan 2020 11:59:06 -0500 Subject: [PATCH 07/82] -> v3.17.0 --- CHANGELOG.md | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bacc5c26b..32012876a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Svelte changelog -## Unreleased +## 3.17.0 * Remove old `` elements during hydration so they aren't duplicated ([#1607](https://github.com/sveltejs/svelte/issues/1607)) * Prevent text input cursor jumping in Safari with one-way binding ([#3449](https://github.com/sveltejs/svelte/issues/3449)) diff --git a/package-lock.json b/package-lock.json index 6dc707c8d5..9ee0b53af6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.16.7", + "version": "3.17.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 9c249f499f..2c5edf9437 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.16.7", + "version": "3.17.0", "description": "Cybernetically enhanced web apps", "module": "index.mjs", "main": "index", From 7494509dfd67011f0cb73aa8b77c729b3dab46f5 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Tue, 14 Jan 2020 12:11:52 -0500 Subject: [PATCH 08/82] only attach SSR markers when hydratable: true (#4260) --- CHANGELOG.md | 4 +++ site/content/docs/03-run-time.md | 2 +- site/content/docs/04-compile-time.md | 2 +- .../compile/render_ssr/handlers/Element.ts | 2 +- .../compile/render_ssr/handlers/Title.ts | 6 +++- test/helpers.js | 6 ++++ test/runtime/index.js | 7 ++-- test/server-side-rendering/index.js | 34 ++++++++++++------- .../head-meta-hydrate-duplicate/_config.js | 5 +++ .../head-multiple-title/_expected-head.html | 2 +- .../samples/head-title/_expected-head.html | 2 +- 11 files changed, 48 insertions(+), 24 deletions(-) create mode 100644 test/server-side-rendering/samples/head-meta-hydrate-duplicate/_config.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 32012876a3..480c145a95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Svelte changelog +## Unreleased + +* Only attach SSR mode markers to a component's `` elements when compiling with `hydratable: true` ([#4258](https://github.com/sveltejs/svelte/issues/4258)) + ## 3.17.0 * Remove old `` elements during hydration so they aren't duplicated ([#1607](https://github.com/sveltejs/svelte/issues/1607)) diff --git a/site/content/docs/03-run-time.md b/site/content/docs/03-run-time.md index e1509cf4d1..c75ec694d9 100644 --- a/site/content/docs/03-run-time.md +++ b/site/content/docs/03-run-time.md @@ -887,7 +887,7 @@ Existing children of `target` are left where they are. --- -The `hydrate` option instructs Svelte to upgrade existing DOM (usually from server-side rendering) rather than creating new elements. It will only work if the component was compiled with the [`hydratable: true` option](docs#svelte_compile). +The `hydrate` option instructs Svelte to upgrade existing DOM (usually from server-side rendering) rather than creating new elements. It will only work if the component was compiled with the [`hydratable: true` option](docs#svelte_compile). Hydration of `` elements only works properly if the server-side rendering code was also compiled with `hydratable: true`, which adds a marker to each element in the `` so that the component knows which elements it's responsible for removing during hydration. Whereas children of `target` are normally left alone, `hydrate: true` will cause any children to be removed. For that reason, the `anchor` option cannot be used alongside `hydrate: true`. diff --git a/site/content/docs/04-compile-time.md b/site/content/docs/04-compile-time.md index e9126ccf13..f753b2fcfe 100644 --- a/site/content/docs/04-compile-time.md +++ b/site/content/docs/04-compile-time.md @@ -68,7 +68,7 @@ The following options can be passed to the compiler. None are required: | `generate` | `"dom"` | If `"dom"`, Svelte emits a JavaScript class for mounting to the DOM. If `"ssr"`, Svelte emits an object with a `render` method suitable for server-side rendering. If `false`, no JavaScript or CSS is returned; just metadata. | `dev` | `false` | If `true`, causes extra code to be added to components that will perform runtime checks and provide debugging information during development. | `immutable` | `false` | If `true`, tells the compiler that you promise not to mutate any objects. This allows it to be less conservative about checking whether values have changed. -| `hydratable` | `false` | If `true`, enables the `hydrate: true` runtime option, which allows a component to upgrade existing DOM rather than creating new DOM from scratch. +| `hydratable` | `false` | If `true` when generating DOM code, enables the `hydrate: true` runtime option, which allows a component to upgrade existing DOM rather than creating new DOM from scratch. When generating SSR code, this adds markers to `` elements so that hydration knows which to replace. | `legacy` | `false` | If `true`, generates code that will work in IE9 and IE10, which don't support things like `element.dataset`. | `accessors` | `false` | If `true`, getters and setters will be created for the component's props. If `false`, they will only be created for readonly exported values (i.e. those declared with `const`, `class` and `function`). If compiling with `customElement: true` this option defaults to `true`. | `customElement` | `false` | If `true`, tells the compiler to generate a custom element constructor instead of a regular Svelte component. diff --git a/src/compiler/compile/render_ssr/handlers/Element.ts b/src/compiler/compile/render_ssr/handlers/Element.ts index 4c1eca8a9d..e0982a0415 100644 --- a/src/compiler/compile/render_ssr/handlers/Element.ts +++ b/src/compiler/compile/render_ssr/handlers/Element.ts @@ -124,7 +124,7 @@ export default function(node: Element, renderer: Renderer, options: RenderOption } }); - if (options.head_id) { + if (options.hydratable && options.head_id) { renderer.add_string(` data-svelte="${options.head_id}"`); } diff --git a/src/compiler/compile/render_ssr/handlers/Title.ts b/src/compiler/compile/render_ssr/handlers/Title.ts index e93ae13d66..a3f271ab1b 100644 --- a/src/compiler/compile/render_ssr/handlers/Title.ts +++ b/src/compiler/compile/render_ssr/handlers/Title.ts @@ -5,7 +5,11 @@ import { x } from 'code-red'; export default function(node: Title, renderer: Renderer, options: RenderOptions) { renderer.push(); - renderer.add_string(``); + renderer.add_string('<title'); + if (options.hydratable && options.head_id) { + renderer.add_string(` data-svelte="${options.head_id}"`); + } + renderer.add_string('>'); renderer.render(node.children, options); diff --git a/test/helpers.js b/test/helpers.js index 5e9428243b..a764d43f96 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -45,6 +45,12 @@ export function tryToReadFile(file) { } } +export function cleanRequireCache() { + Object.keys(require.cache) + .filter(x => x.endsWith('.svelte')) + .forEach(file => delete require.cache[file]); +} + const virtualConsole = new jsdom.VirtualConsole(); virtualConsole.sendTo(console); diff --git a/test/runtime/index.js b/test/runtime/index.js index df02cabcb4..f070eb8185 100644 --- a/test/runtime/index.js +++ b/test/runtime/index.js @@ -10,6 +10,7 @@ import { showOutput, loadConfig, loadSvelte, + cleanRequireCache, env, setupHtmlEqual, mkdirp @@ -79,11 +80,7 @@ describe("runtime", () => { compileOptions.immutable = config.immutable; compileOptions.accessors = 'accessors' in config ? config.accessors : true; - Object.keys(require.cache) - .filter(x => x.endsWith('.svelte')) - .forEach(file => { - delete require.cache[file]; - }); + cleanRequireCache(); let mod; let SvelteComponent; diff --git a/test/server-side-rendering/index.js b/test/server-side-rendering/index.js index a56a4ddaed..ee1319845d 100644 --- a/test/server-side-rendering/index.js +++ b/test/server-side-rendering/index.js @@ -9,6 +9,7 @@ import { loadSvelte, setupHtmlEqual, tryToLoadJson, + cleanRequireCache, shouldUpdateExpected, mkdirp } from "../helpers.js"; @@ -27,11 +28,6 @@ let compile = null; describe("ssr", () => { before(() => { - require("../../register")({ - extensions: ['.svelte', '.html'], - sveltePath - }); - compile = loadSvelte(true).compile; return setupHtmlEqual(); @@ -40,9 +36,11 @@ describe("ssr", () => { fs.readdirSync(`${__dirname}/samples`).forEach(dir => { if (dir[0] === ".") return; + const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`); + // add .solo to a sample directory name to only run that test, or // .show to always show the output. or both - const solo = /\.solo/.test(dir); + const solo = config.solo || /\.solo/.test(dir); const show = /\.show/.test(dir); if (solo && process.env.CI) { @@ -51,6 +49,18 @@ describe("ssr", () => { (solo ? it.only : it)(dir, () => { dir = path.resolve(`${__dirname}/samples`, dir); + + cleanRequireCache(); + + const compileOptions = { + sveltePath, + ...config.compileOptions, + generate: 'ssr', + format: 'cjs' + }; + + require("../../register")(compileOptions); + try { const Component = require(`${dir}/main.svelte`).default; @@ -133,18 +143,16 @@ describe("ssr", () => { (config.skip ? it.skip : solo ? it.only : it)(dir, () => { const cwd = path.resolve("test/runtime/samples", dir); - Object.keys(require.cache) - .filter(x => x.endsWith('.svelte')) - .forEach(file => { - delete require.cache[file]; - }); + cleanRequireCache(); delete global.window; - const compileOptions = Object.assign({ sveltePath }, config.compileOptions, { + const compileOptions = { + sveltePath, + ...config.compileOptions, generate: 'ssr', format: 'cjs' - }); + }; require("../../register")(compileOptions); diff --git a/test/server-side-rendering/samples/head-meta-hydrate-duplicate/_config.js b/test/server-side-rendering/samples/head-meta-hydrate-duplicate/_config.js new file mode 100644 index 0000000000..ae9b250f86 --- /dev/null +++ b/test/server-side-rendering/samples/head-meta-hydrate-duplicate/_config.js @@ -0,0 +1,5 @@ +export default { + compileOptions: { + hydratable: true + } +}; diff --git a/test/server-side-rendering/samples/head-multiple-title/_expected-head.html b/test/server-side-rendering/samples/head-multiple-title/_expected-head.html index 7147550839..af5c5feba4 100644 --- a/test/server-side-rendering/samples/head-multiple-title/_expected-head.html +++ b/test/server-side-rendering/samples/head-multiple-title/_expected-head.html @@ -1 +1 @@ -<title data-svelte="svelte-1csszk6">B \ No newline at end of file +B \ No newline at end of file diff --git a/test/server-side-rendering/samples/head-title/_expected-head.html b/test/server-side-rendering/samples/head-title/_expected-head.html index 6e73e671e6..7d696352f9 100644 --- a/test/server-side-rendering/samples/head-title/_expected-head.html +++ b/test/server-side-rendering/samples/head-title/_expected-head.html @@ -1 +1 @@ -a custom title \ No newline at end of file +a custom title \ No newline at end of file From 4e812a95e9abce3d68c53b33ad86fb91bbbe4773 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Tue, 14 Jan 2020 12:16:52 -0500 Subject: [PATCH 09/82] -> v3.17.1 --- .vscode/settings.json | 1 + CHANGELOG.md | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..1fe67481c7 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1 @@ +{"files.insertFinalNewline":false} diff --git a/CHANGELOG.md b/CHANGELOG.md index 480c145a95..dce6d47858 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Svelte changelog -## Unreleased +## 3.17.1 * Only attach SSR mode markers to a component's `` elements when compiling with `hydratable: true` ([#4258](https://github.com/sveltejs/svelte/issues/4258)) diff --git a/package.json b/package.json index 2c5edf9437..c7ac32f3ae 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.17.0", + "version": "3.17.1", "description": "Cybernetically enhanced web apps", "module": "index.mjs", "main": "index", From bfff7a9d0e67804e7a0db04ed4fb4b9a6078d0ec Mon Sep 17 00:00:00 2001 From: Conduitry Date: Tue, 14 Jan 2020 16:03:20 -0500 Subject: [PATCH 10/82] oops --- .vscode/settings.json | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 1fe67481c7..0000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1 +0,0 @@ -{"files.insertFinalNewline":false} From 8b9b2c266e55c7a3e589a32cecf8f208e430c10a Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Thu, 16 Jan 2020 22:25:12 +0800 Subject: [PATCH 11/82] fix allow let scoped to root element (#4266) --- CHANGELOG.md | 4 +++ src/compiler/compile/nodes/Element.ts | 31 +++++++++---------- .../samples/component-slot-let-g/A.svelte | 5 +++ .../samples/component-slot-let-g/_config.js | 22 +++++++++++++ .../samples/component-slot-let-g/main.svelte | 17 ++++++++++ 5 files changed, 63 insertions(+), 16 deletions(-) create mode 100644 test/runtime/samples/component-slot-let-g/A.svelte create mode 100644 test/runtime/samples/component-slot-let-g/_config.js create mode 100644 test/runtime/samples/component-slot-let-g/main.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index dce6d47858..1f93815185 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Svelte changelog +## Unreleased + +* Allow access to `let:` variables in sibling attributes on slot root ([#4173](https://github.com/sveltejs/svelte/issues/4173)) + ## 3.17.1 * Only attach SSR mode markers to a component's `` elements when compiling with `hydratable: true` ([#4258](https://github.com/sveltejs/svelte/issues/4258)) diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index a3b8dc7286..e8108858c5 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -151,6 +151,11 @@ export default class Element extends Node { } } + const has_let = info.attributes.some(node => node.type === 'Let'); + if (has_let) { + scope = scope.child(); + } + // Binding relies on Attribute, defer its evaluation const order = ['Binding']; // everything else is -1 info.attributes.sort((a, b) => order.indexOf(a.type) - order.indexOf(b.type)); @@ -181,9 +186,16 @@ export default class Element extends Node { this.handlers.push(new EventHandler(component, this, scope, node)); break; - case 'Let': - this.lets.push(new Let(component, this, scope, node)); + case 'Let': { + const l = new Let(component, this, scope, node); + this.lets.push(l); + const dependencies = new Set([l.name.name]); + + l.names.forEach(name => { + scope.add(name, dependencies, this); + }); break; + } case 'Transition': { @@ -202,20 +214,7 @@ export default class Element extends Node { } }); - if (this.lets.length > 0) { - this.scope = scope.child(); - - this.lets.forEach(l => { - const dependencies = new Set([l.name.name]); - - l.names.forEach(name => { - this.scope.add(name, dependencies, this); - }); - }); - } else { - this.scope = scope; - } - + this.scope = scope; this.children = map_children(component, this, this.scope, info.children); this.validate(); diff --git a/test/runtime/samples/component-slot-let-g/A.svelte b/test/runtime/samples/component-slot-let-g/A.svelte new file mode 100644 index 0000000000..4f4ac95014 --- /dev/null +++ b/test/runtime/samples/component-slot-let-g/A.svelte @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/test/runtime/samples/component-slot-let-g/_config.js b/test/runtime/samples/component-slot-let-g/_config.js new file mode 100644 index 0000000000..aaa9895ea8 --- /dev/null +++ b/test/runtime/samples/component-slot-let-g/_config.js @@ -0,0 +1,22 @@ +export default { + html: ` + 1 + 0 + `, + async test({ assert, target, component, window }) { + component.x = 2; + + assert.htmlEqual(target.innerHTML, ` + 2 + 0 + `); + + const span = target.querySelector('span'); + await span.dispatchEvent(new window.MouseEvent('click')); + + assert.htmlEqual(target.innerHTML, ` + 2 + 2 + `); + } +}; diff --git a/test/runtime/samples/component-slot-let-g/main.svelte b/test/runtime/samples/component-slot-let-g/main.svelte new file mode 100644 index 0000000000..e7d4890e6b --- /dev/null +++ b/test/runtime/samples/component-slot-let-g/main.svelte @@ -0,0 +1,17 @@ + + +
+ y = reflected} + slot="foo" + let:reflected + class={reflected} + > + {reflected} + + +{ y } \ No newline at end of file From 2fd593f9d55d242cfcd4a6aafa09ce919d05ba7c Mon Sep 17 00:00:00 2001 From: John Muhl Date: Thu, 16 Jan 2020 20:27:43 -0600 Subject: [PATCH 12/82] Add more globals --- src/compiler/utils/names.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/compiler/utils/names.ts b/src/compiler/utils/names.ts index 88d4d4a319..02cc12e087 100644 --- a/src/compiler/utils/names.ts +++ b/src/compiler/utils/names.ts @@ -5,6 +5,8 @@ export const globals = new Set([ 'alert', 'Array', 'Boolean', + 'clearInterval', + 'clearTimeout', 'confirm', 'console', 'Date', @@ -16,6 +18,9 @@ export const globals = new Set([ 'Error', 'EvalError', 'Event', + 'fetch', + 'global', + 'globalThis', 'history', 'Infinity', 'InternalError', @@ -41,11 +46,14 @@ export const globals = new Set([ 'RegExp', 'sessionStorage', 'Set', + 'setInterval', + 'setTimeout', 'String', 'SyntaxError', 'TypeError', 'undefined', 'URIError', + 'URL', 'window' ]); From 1438994bd4f51633ba5c869c26d16be2e8224213 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Thu, 16 Jan 2020 22:13:41 -0500 Subject: [PATCH 13/82] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f93815185..f833dfae2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased * Allow access to `let:` variables in sibling attributes on slot root ([#4173](https://github.com/sveltejs/svelte/issues/4173)) +* Add some more known globals ([#4276](https://github.com/sveltejs/svelte/pull/4276)) ## 3.17.1 From 527ddea289196f3dc4c8ca5e0d001b9c7e5ce2a0 Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Fri, 17 Jan 2020 08:07:48 +0800 Subject: [PATCH 14/82] disallow binding variables declared in await and catch --- src/compiler/compile/nodes/Binding.ts | 7 +++++++ src/compiler/compile/nodes/shared/TemplateScope.ts | 5 +++++ test/validator/samples/binding-await-catch/errors.json | 9 +++++++++ test/validator/samples/binding-await-catch/input.svelte | 7 +++++++ test/validator/samples/binding-await-then-2/errors.json | 9 +++++++++ test/validator/samples/binding-await-then-2/input.svelte | 7 +++++++ test/validator/samples/binding-await-then/errors.json | 9 +++++++++ test/validator/samples/binding-await-then/input.svelte | 6 ++++++ 8 files changed, 59 insertions(+) create mode 100644 test/validator/samples/binding-await-catch/errors.json create mode 100644 test/validator/samples/binding-await-catch/input.svelte create mode 100644 test/validator/samples/binding-await-then-2/errors.json create mode 100644 test/validator/samples/binding-await-then-2/input.svelte create mode 100644 test/validator/samples/binding-await-then/errors.json create mode 100644 test/validator/samples/binding-await-then/input.svelte diff --git a/src/compiler/compile/nodes/Binding.ts b/src/compiler/compile/nodes/Binding.ts index 28e6af5aa1..7d6fad0a81 100644 --- a/src/compiler/compile/nodes/Binding.ts +++ b/src/compiler/compile/nodes/Binding.ts @@ -50,6 +50,13 @@ export default class Binding extends Node { message: 'Cannot bind to a variable declared with the let: directive' }); } else if (this.is_contextual) { + if (scope.is_await(name)) { + component.error(this, { + code: 'invalid-binding', + message: 'Cannot bind to a variable declared with {#await ... then} or {:catch} blocks' + }); + } + scope.dependencies_for_name.get(name).forEach(name => { const variable = component.var_lookup.get(name); if (variable) { diff --git a/src/compiler/compile/nodes/shared/TemplateScope.ts b/src/compiler/compile/nodes/shared/TemplateScope.ts index 5f30d0c883..4e087eedf5 100644 --- a/src/compiler/compile/nodes/shared/TemplateScope.ts +++ b/src/compiler/compile/nodes/shared/TemplateScope.ts @@ -42,4 +42,9 @@ export default class TemplateScope { const owner = this.get_owner(name); return owner && (owner.type === 'Element' || owner.type === 'InlineComponent'); } + + is_await(name: string) { + const owner = this.get_owner(name); + return owner && (owner.type === 'ThenBlock' || owner.type === 'CatchBlock'); + } } diff --git a/test/validator/samples/binding-await-catch/errors.json b/test/validator/samples/binding-await-catch/errors.json new file mode 100644 index 0000000000..00141686f3 --- /dev/null +++ b/test/validator/samples/binding-await-catch/errors.json @@ -0,0 +1,9 @@ +[ + { + "code": "invalid-binding", + "message": "Cannot bind to a variable declared with {#await ... then} or {:catch} blocks", + "pos": 79, + "start": { "line": 6, "column": 9, "character": 79 }, + "end": { "line": 6, "column": 27, "character": 97 } + } +] diff --git a/test/validator/samples/binding-await-catch/input.svelte b/test/validator/samples/binding-await-catch/input.svelte new file mode 100644 index 0000000000..b640f6305b --- /dev/null +++ b/test/validator/samples/binding-await-catch/input.svelte @@ -0,0 +1,7 @@ + +{#await promise} +{:catch error} + +{/await} diff --git a/test/validator/samples/binding-await-then-2/errors.json b/test/validator/samples/binding-await-then-2/errors.json new file mode 100644 index 0000000000..e734ed4717 --- /dev/null +++ b/test/validator/samples/binding-await-then-2/errors.json @@ -0,0 +1,9 @@ +[ + { + "code": "invalid-binding", + "message": "Cannot bind to a variable declared with {#await ... then} or {:catch} blocks", + "pos": 78, + "start": { "line": 6, "column": 9, "character": 78 }, + "end": { "line": 6, "column": 19, "character": 88 } + } +] diff --git a/test/validator/samples/binding-await-then-2/input.svelte b/test/validator/samples/binding-await-then-2/input.svelte new file mode 100644 index 0000000000..e8c56c8e0b --- /dev/null +++ b/test/validator/samples/binding-await-then-2/input.svelte @@ -0,0 +1,7 @@ + +{#await promise} +{:then value} + +{/await} diff --git a/test/validator/samples/binding-await-then/errors.json b/test/validator/samples/binding-await-then/errors.json new file mode 100644 index 0000000000..a611e7731f --- /dev/null +++ b/test/validator/samples/binding-await-then/errors.json @@ -0,0 +1,9 @@ +[ + { + "code": "invalid-binding", + "message": "Cannot bind to a variable declared with {#await ... then} or {:catch} blocks", + "pos": 75, + "start": { "line": 5, "column": 9, "character": 75 }, + "end": { "line": 5, "column": 19, "character": 85 } + } +] diff --git a/test/validator/samples/binding-await-then/input.svelte b/test/validator/samples/binding-await-then/input.svelte new file mode 100644 index 0000000000..bc55ef97e4 --- /dev/null +++ b/test/validator/samples/binding-await-then/input.svelte @@ -0,0 +1,6 @@ + +{#await promise then value} + +{/await} From bda254e250e737a1dd7acf6c732d656ce52034a1 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Thu, 16 Jan 2020 22:17:25 -0500 Subject: [PATCH 15/82] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f833dfae2a..bc6cf36109 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +* Disallow two-way binding to a variable declared by an `{#await}` block ([#4012](https://github.com/sveltejs/svelte/issues/4012)) * Allow access to `let:` variables in sibling attributes on slot root ([#4173](https://github.com/sveltejs/svelte/issues/4173)) * Add some more known globals ([#4276](https://github.com/sveltejs/svelte/pull/4276)) From b39282a9188027903fdb99cdf762b640f11c6cc1 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Fri, 17 Jan 2020 07:22:40 -0500 Subject: [PATCH 16/82] apply event modifiers to events (#4279) --- CHANGELOG.md | 1 + .../compile/render_dom/wrappers/Body.ts | 25 ++++++++----------- .../wrappers/Element/EventHandler.ts | 3 ++- .../wrappers/shared/add_event_handlers.ts | 5 ++-- .../_config.js | 11 ++++++++ .../main.svelte | 5 ++++ 6 files changed, 33 insertions(+), 17 deletions(-) create mode 100644 test/runtime/samples/event-handler-modifier-body-once/_config.js create mode 100644 test/runtime/samples/event-handler-modifier-body-once/main.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index bc6cf36109..9b2c8f5b07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * Disallow two-way binding to a variable declared by an `{#await}` block ([#4012](https://github.com/sveltejs/svelte/issues/4012)) * Allow access to `let:` variables in sibling attributes on slot root ([#4173](https://github.com/sveltejs/svelte/issues/4173)) * Add some more known globals ([#4276](https://github.com/sveltejs/svelte/pull/4276)) +* Correctly apply event modifiers to `` events ([#4278](https://github.com/sveltejs/svelte/issues/4278)) ## 3.17.1 diff --git a/src/compiler/compile/render_dom/wrappers/Body.ts b/src/compiler/compile/render_dom/wrappers/Body.ts index e16ebc25bd..d80ef6c4a0 100644 --- a/src/compiler/compile/render_dom/wrappers/Body.ts +++ b/src/compiler/compile/render_dom/wrappers/Body.ts @@ -1,26 +1,23 @@ import Block from '../Block'; import Wrapper from './shared/Wrapper'; -import { b } from 'code-red'; +import { x } from 'code-red'; import Body from '../../nodes/Body'; import { Identifier } from 'estree'; import EventHandler from './Element/EventHandler'; +import add_event_handlers from './shared/add_event_handlers'; +import { TemplateNode } from '../../../interfaces'; +import Renderer from '../Renderer'; export default class BodyWrapper extends Wrapper { node: Body; + handlers: EventHandler[]; - render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) { - this.node.handlers - .map(handler => new EventHandler(handler, this)) - .forEach(handler => { - const snippet = handler.get_snippet(block); - - block.chunks.init.push(b` - @_document.body.addEventListener("${handler.node.name}", ${snippet}); - `); + 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)); + } - block.chunks.destroy.push(b` - @_document.body.removeEventListener("${handler.node.name}", ${snippet}); - `); - }); + render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) { + add_event_handlers(block, x`@_document.body`, this.handlers); } } diff --git a/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts b/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts index 03183ee576..157e186ea6 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts @@ -2,6 +2,7 @@ import EventHandler from '../../../nodes/EventHandler'; import Wrapper from '../shared/Wrapper'; import Block from '../../Block'; import { b, x, p } from 'code-red'; +import { Expression } from 'estree'; const TRUE = x`true`; const FALSE = x`false`; @@ -35,7 +36,7 @@ export default class EventHandlerWrapper { return snippet; } - render(block: Block, target: string) { + render(block: Block, target: string | Expression) { let snippet = this.get_snippet(block); if (this.node.modifiers.has('preventDefault')) snippet = x`@prevent_default(${snippet})`; diff --git a/src/compiler/compile/render_dom/wrappers/shared/add_event_handlers.ts b/src/compiler/compile/render_dom/wrappers/shared/add_event_handlers.ts index 23a37715cc..99b8080b17 100644 --- a/src/compiler/compile/render_dom/wrappers/shared/add_event_handlers.ts +++ b/src/compiler/compile/render_dom/wrappers/shared/add_event_handlers.ts @@ -1,9 +1,10 @@ import Block from '../../Block'; import EventHandler from '../Element/EventHandler'; +import { Expression } from 'estree'; export default function add_event_handlers( block: Block, - target: string, + target: string | Expression, handlers: EventHandler[] ) { handlers.forEach(handler => add_event_handler(block, target, handler)); @@ -11,7 +12,7 @@ export default function add_event_handlers( export function add_event_handler( block: Block, - target: string, + target: string | Expression, handler: EventHandler ) { handler.render(block, target); diff --git a/test/runtime/samples/event-handler-modifier-body-once/_config.js b/test/runtime/samples/event-handler-modifier-body-once/_config.js new file mode 100644 index 0000000000..4127034010 --- /dev/null +++ b/test/runtime/samples/event-handler-modifier-body-once/_config.js @@ -0,0 +1,11 @@ +export default { + async test({ assert, component, window }) { + const event = new window.MouseEvent('click'); + + await window.document.body.dispatchEvent(event); + assert.equal(component.count, 1); + + await window.document.body.dispatchEvent(event); + assert.equal(component.count, 1); + } +}; diff --git a/test/runtime/samples/event-handler-modifier-body-once/main.svelte b/test/runtime/samples/event-handler-modifier-body-once/main.svelte new file mode 100644 index 0000000000..423a75d1f0 --- /dev/null +++ b/test/runtime/samples/event-handler-modifier-body-once/main.svelte @@ -0,0 +1,5 @@ + + + \ No newline at end of file From 2f81365e44f585c28295cb3937910eef00a35b0e Mon Sep 17 00:00:00 2001 From: Conduitry Date: Sat, 18 Jan 2020 07:10:13 -0500 Subject: [PATCH 17/82] fix awaited expressions that need parentheses (#4283) --- CHANGELOG.md | 1 + package-lock.json | 8 ++++---- package.json | 2 +- test/js/samples/debug-empty/expected.js | 2 +- test/js/samples/debug-foo-bar-baz-things/expected.js | 2 +- test/js/samples/debug-foo/expected.js | 2 +- .../samples/dev-warning-missing-data-computed/expected.js | 2 +- 7 files changed, 10 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b2c8f5b07..5264f35504 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Disallow two-way binding to a variable declared by an `{#await}` block ([#4012](https://github.com/sveltejs/svelte/issues/4012)) * Allow access to `let:` variables in sibling attributes on slot root ([#4173](https://github.com/sveltejs/svelte/issues/4173)) +* Fix code generation for `await`ed expressions that need parentheses ([#4267](https://github.com/sveltejs/svelte/issues/4267)) * Add some more known globals ([#4276](https://github.com/sveltejs/svelte/pull/4276)) * Correctly apply event modifiers to `` events ([#4278](https://github.com/sveltejs/svelte/issues/4278)) diff --git a/package-lock.json b/package-lock.json index 9ee0b53af6..4d902d5c48 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.17.0", + "version": "3.17.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -597,9 +597,9 @@ "dev": true }, "code-red": { - "version": "0.0.28", - "resolved": "https://registry.npmjs.org/code-red/-/code-red-0.0.28.tgz", - "integrity": "sha512-k9L7Sp85HNt3f/CvfrZKXoZOaO0tWOJCw2kU5GKc/c5pDj52OgBa0J+krMRmYtnGzS2dk4Xrn0EjjsJaM51hWQ==", + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/code-red/-/code-red-0.0.30.tgz", + "integrity": "sha512-nsScy3A59tbV5uzndcedIEGHmdWNEByJrC7DUyb0Wh7qZcoPfAlcYsr0ZINkAEhZofSI26F1mi+lRO0/6EV2/g==", "dev": true, "requires": { "acorn": "^7.1.0", diff --git a/package.json b/package.json index c7ac32f3ae..671a7e5025 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "acorn": "^7.1.0", "agadoo": "^1.1.0", "c8": "^5.0.1", - "code-red": "0.0.28", + "code-red": "0.0.30", "codecov": "^3.5.0", "css-tree": "1.0.0-alpha22", "eslint": "^6.3.0", diff --git a/test/js/samples/debug-empty/expected.js b/test/js/samples/debug-empty/expected.js index 5821faadf1..358363c661 100644 --- a/test/js/samples/debug-empty/expected.js +++ b/test/js/samples/debug-empty/expected.js @@ -103,7 +103,7 @@ class Component extends SvelteComponentDev { }); const { ctx } = this.$$; - const props = options.props || ({}); + const props = options.props || {}; if (/*name*/ ctx[0] === undefined && !("name" in props)) { console.warn(" was created without expected prop 'name'"); diff --git a/test/js/samples/debug-foo-bar-baz-things/expected.js b/test/js/samples/debug-foo-bar-baz-things/expected.js index a1d6dba3b3..fc65c59eda 100644 --- a/test/js/samples/debug-foo-bar-baz-things/expected.js +++ b/test/js/samples/debug-foo-bar-baz-things/expected.js @@ -210,7 +210,7 @@ class Component extends SvelteComponentDev { }); const { ctx } = this.$$; - const props = options.props || ({}); + const props = options.props || {}; if (/*things*/ ctx[0] === undefined && !("things" in props)) { console.warn(" was created without expected prop 'things'"); diff --git a/test/js/samples/debug-foo/expected.js b/test/js/samples/debug-foo/expected.js index af79667cc2..710d6b2232 100644 --- a/test/js/samples/debug-foo/expected.js +++ b/test/js/samples/debug-foo/expected.js @@ -198,7 +198,7 @@ class Component extends SvelteComponentDev { }); const { ctx } = this.$$; - const props = options.props || ({}); + const props = options.props || {}; if (/*things*/ ctx[0] === undefined && !("things" in props)) { console.warn(" was created without expected prop 'things'"); diff --git a/test/js/samples/dev-warning-missing-data-computed/expected.js b/test/js/samples/dev-warning-missing-data-computed/expected.js index beb794a50c..cc16de67e6 100644 --- a/test/js/samples/dev-warning-missing-data-computed/expected.js +++ b/test/js/samples/dev-warning-missing-data-computed/expected.js @@ -107,7 +107,7 @@ class Component extends SvelteComponentDev { }); const { ctx } = this.$$; - const props = options.props || ({}); + const props = options.props || {}; if (/*foo*/ ctx[0] === undefined && !("foo" in props)) { console.warn(" was created without expected prop 'foo'"); From e4460e38ba58d5209514f0fa93dc61b6bb1ebb54 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Sun, 19 Jan 2020 13:36:22 -0500 Subject: [PATCH 18/82] fix '~=' and class selectors with arbitrary whitespace (#4286) --- CHANGELOG.md | 1 + src/compiler/compile/css/Selector.ts | 6 +++--- .../expected.css | 1 + .../input.svelte | 13 +++++++++++++ 4 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 test/css/samples/attribute-selector-word-arbitrary-whitespace/expected.css create mode 100644 test/css/samples/attribute-selector-word-arbitrary-whitespace/input.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index 5264f35504..5b0bb4751d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Disallow two-way binding to a variable declared by an `{#await}` block ([#4012](https://github.com/sveltejs/svelte/issues/4012)) * Allow access to `let:` variables in sibling attributes on slot root ([#4173](https://github.com/sveltejs/svelte/issues/4173)) +* Fix `~=` and class selector matching against values separated by any whitespace characters ([#4242](https://github.com/sveltejs/svelte/issues/4242)) * Fix code generation for `await`ed expressions that need parentheses ([#4267](https://github.com/sveltejs/svelte/issues/4267)) * Add some more known globals ([#4276](https://github.com/sveltejs/svelte/pull/4276)) * Correctly apply event modifiers to `` events ([#4278](https://github.com/sveltejs/svelte/issues/4278)) diff --git a/src/compiler/compile/css/Selector.ts b/src/compiler/compile/css/Selector.ts index eecb0cd975..39bbc2e8d1 100644 --- a/src/compiler/compile/css/Selector.ts +++ b/src/compiler/compile/css/Selector.ts @@ -171,7 +171,7 @@ function apply_selector(blocks: Block[], node: Element, stack: Element[], to_enc if (ancestor_block.global) { continue; } - + for (const stack_node of stack) { if (block_might_apply_to_node(ancestor_block, stack_node) !== BlockAppliesToNode.NotPossible) { to_encapsulate.push({ node: stack_node, block: ancestor_block }); @@ -256,7 +256,7 @@ function test_attribute(operator, expected_value, case_insensitive, value) { } switch (operator) { case '=': return value === expected_value; - case '~=': return ` ${value} `.includes(` ${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); @@ -295,7 +295,7 @@ function attribute_matches(node: CssNode, name: string, expected_value: string, // 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 = []; diff --git a/test/css/samples/attribute-selector-word-arbitrary-whitespace/expected.css b/test/css/samples/attribute-selector-word-arbitrary-whitespace/expected.css new file mode 100644 index 0000000000..c1c0f3fdf6 --- /dev/null +++ b/test/css/samples/attribute-selector-word-arbitrary-whitespace/expected.css @@ -0,0 +1 @@ +.foo.svelte-xyz{color:red}[class~="bar"].svelte-xyz{background:blue} \ No newline at end of file diff --git a/test/css/samples/attribute-selector-word-arbitrary-whitespace/input.svelte b/test/css/samples/attribute-selector-word-arbitrary-whitespace/input.svelte new file mode 100644 index 0000000000..758aa27e0b --- /dev/null +++ b/test/css/samples/attribute-selector-word-arbitrary-whitespace/input.svelte @@ -0,0 +1,13 @@ +
+ + From 33c3a02ee4b9ac3ee9d9dbec1c7328db0d49d6c0 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Mon, 20 Jan 2020 10:31:31 -0500 Subject: [PATCH 19/82] failing test --- test/hydration/samples/element-attribute-removed/_before.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/hydration/samples/element-attribute-removed/_before.html b/test/hydration/samples/element-attribute-removed/_before.html index c6a8a8c95d..80c0591a4d 100644 --- a/test/hydration/samples/element-attribute-removed/_before.html +++ b/test/hydration/samples/element-attribute-removed/_before.html @@ -1 +1 @@ -
\ No newline at end of file +
\ No newline at end of file From 91e0d1b720e209833f11e894b956ea361ef8878a Mon Sep 17 00:00:00 2001 From: Conduitry Date: Mon, 20 Jan 2020 10:34:29 -0500 Subject: [PATCH 20/82] fix removing attributes during hydration (#1733) --- src/runtime/internal/dom.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index f9e89f41b9..5a165136ce 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -152,11 +152,16 @@ export function claim_element(nodes, name, attributes, svg) { for (let i = 0; i < nodes.length; i += 1) { const node = nodes[i]; if (node.nodeName === name) { - for (let j = 0; j < node.attributes.length; j += 1) { + let j = 0; + while (j < node.attributes.length) { const attribute = node.attributes[j]; - if (!attributes[attribute.name]) node.removeAttribute(attribute.name); + if (attributes[attribute.name]) { + j++; + } else { + node.removeAttribute(attribute.name); + } } - return nodes.splice(i, 1)[0]; // TODO strip unwanted attributes + return nodes.splice(i, 1)[0]; } } From b0415a269bb4918048508b97399f97a71f6499fe Mon Sep 17 00:00:00 2001 From: Conduitry Date: Mon, 20 Jan 2020 10:35:13 -0500 Subject: [PATCH 21/82] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b0bb4751d..d9ec5900b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +* Fix removing attributes during hydration ([#1733](https://github.com/sveltejs/svelte/issues/1733)) * Disallow two-way binding to a variable declared by an `{#await}` block ([#4012](https://github.com/sveltejs/svelte/issues/4012)) * Allow access to `let:` variables in sibling attributes on slot root ([#4173](https://github.com/sveltejs/svelte/issues/4173)) * Fix `~=` and class selector matching against values separated by any whitespace characters ([#4242](https://github.com/sveltejs/svelte/issues/4242)) From f12340acf02e6c8e7dc23cafe0469eb72d278fc8 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Mon, 20 Jan 2020 21:17:24 -0500 Subject: [PATCH 22/82] preserve js comments where possible (#4293) --- CHANGELOG.md | 1 + package-lock.json | 6 +++--- package.json | 2 +- src/compiler/parse/acorn.ts | 9 ++++----- .../js/samples/action-custom-event-handler/expected.js | 2 +- .../samples/collapses-text-around-comments/expected.js | 2 +- test/js/samples/css-media-query/expected.js | 2 +- test/js/samples/event-modifiers/expected.js | 4 ++-- test/js/samples/ssr-no-oncreate-etc/expected.js | 2 +- test/parser/samples/script-comment-only/output.json | 10 +++++++++- .../script-comment-trailing-multiline/output.json | 10 +++++++++- .../parser/samples/script-comment-trailing/output.json | 10 +++++++++- 12 files changed, 42 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9ec5900b4..59fa25682e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * Allow access to `let:` variables in sibling attributes on slot root ([#4173](https://github.com/sveltejs/svelte/issues/4173)) * Fix `~=` and class selector matching against values separated by any whitespace characters ([#4242](https://github.com/sveltejs/svelte/issues/4242)) * Fix code generation for `await`ed expressions that need parentheses ([#4267](https://github.com/sveltejs/svelte/issues/4267)) +* Preserve JavaScript comments from the original component source where possible ([#4268](https://github.com/sveltejs/svelte/issues/4268)) * Add some more known globals ([#4276](https://github.com/sveltejs/svelte/pull/4276)) * Correctly apply event modifiers to `` events ([#4278](https://github.com/sveltejs/svelte/issues/4278)) diff --git a/package-lock.json b/package-lock.json index 4d902d5c48..90f5a331ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -597,9 +597,9 @@ "dev": true }, "code-red": { - "version": "0.0.30", - "resolved": "https://registry.npmjs.org/code-red/-/code-red-0.0.30.tgz", - "integrity": "sha512-nsScy3A59tbV5uzndcedIEGHmdWNEByJrC7DUyb0Wh7qZcoPfAlcYsr0ZINkAEhZofSI26F1mi+lRO0/6EV2/g==", + "version": "0.0.31", + "resolved": "https://registry.npmjs.org/code-red/-/code-red-0.0.31.tgz", + "integrity": "sha512-7Gf3vm8pDbs+H/hKsaqOZe0xKlE9Neah12GCfs7qun3fBUaOXwexAMjn0Eo9cvJJvhRMaL0jgPiY9ZGLTWoe8A==", "dev": true, "requires": { "acorn": "^7.1.0", diff --git a/package.json b/package.json index 671a7e5025..6a66276c2d 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "acorn": "^7.1.0", "agadoo": "^1.1.0", "c8": "^5.0.1", - "code-red": "0.0.30", + "code-red": "0.0.31", "codecov": "^3.5.0", "css-tree": "1.0.0-alpha22", "eslint": "^6.3.0", diff --git a/src/compiler/parse/acorn.ts b/src/compiler/parse/acorn.ts index 30ab3c398c..d14721cdab 100644 --- a/src/compiler/parse/acorn.ts +++ b/src/compiler/parse/acorn.ts @@ -1,14 +1,13 @@ -import * as acorn from 'acorn'; +import { Node } from 'acorn'; +import * as code_red from 'code-red'; -const Parser = acorn.Parser; - -export const parse = (source: string) => Parser.parse(source, { +export const parse = (source: string): Node => code_red.parse(source, { sourceType: 'module', ecmaVersion: 11, locations: true }); -export const parse_expression_at = (source: string, index: number) => Parser.parseExpressionAt(source, index, { +export const parse_expression_at = (source: string, index: number): Node => code_red.parseExpressionAt(source, index, { ecmaVersion: 11, locations: true }); \ No newline at end of file diff --git a/test/js/samples/action-custom-event-handler/expected.js b/test/js/samples/action-custom-event-handler/expected.js index 968b5965d5..ead6d90e06 100644 --- a/test/js/samples/action-custom-event-handler/expected.js +++ b/test/js/samples/action-custom-event-handler/expected.js @@ -43,7 +43,7 @@ function handleFoo(bar) { function foo(node, callback) { -} +} // code goes here function instance($$self, $$props, $$invalidate) { let { bar } = $$props; diff --git a/test/js/samples/collapses-text-around-comments/expected.js b/test/js/samples/collapses-text-around-comments/expected.js index 8c0e9d5373..6fef0f9490 100644 --- a/test/js/samples/collapses-text-around-comments/expected.js +++ b/test/js/samples/collapses-text-around-comments/expected.js @@ -63,4 +63,4 @@ class Component extends SvelteComponent { } } -export default Component; +export default Component; \ No newline at end of file diff --git a/test/js/samples/css-media-query/expected.js b/test/js/samples/css-media-query/expected.js index 0566c22ddd..f477670059 100644 --- a/test/js/samples/css-media-query/expected.js +++ b/test/js/samples/css-media-query/expected.js @@ -46,4 +46,4 @@ class Component extends SvelteComponent { } } -export default Component; +export default Component; \ No newline at end of file diff --git a/test/js/samples/event-modifiers/expected.js b/test/js/samples/event-modifiers/expected.js index 252034a431..c12c3523a0 100644 --- a/test/js/samples/event-modifiers/expected.js +++ b/test/js/samples/event-modifiers/expected.js @@ -63,11 +63,11 @@ function create_fragment(ctx) { function handleTouchstart() { -} +} // ... function handleClick() { -} +} // ... class Component extends SvelteComponent { constructor(options) { diff --git a/test/js/samples/ssr-no-oncreate-etc/expected.js b/test/js/samples/ssr-no-oncreate-etc/expected.js index 276587eeca..803f06a882 100644 --- a/test/js/samples/ssr-no-oncreate-etc/expected.js +++ b/test/js/samples/ssr-no-oncreate-etc/expected.js @@ -13,7 +13,7 @@ function foo() { function swipe(node, callback) { -} +} // TODO implement const Component = create_ssr_component(($$result, $$props, $$bindings, $$slots) => { onMount(() => { diff --git a/test/parser/samples/script-comment-only/output.json b/test/parser/samples/script-comment-only/output.json index 3441d8a7bb..e090b30ae6 100644 --- a/test/parser/samples/script-comment-only/output.json +++ b/test/parser/samples/script-comment-only/output.json @@ -41,7 +41,15 @@ } }, "body": [], - "sourceType": "module" + "sourceType": "module", + "trailingComments": [ + { + "type": "Line", + "value": " TODO write some code", + "start": 10, + "end": 33 + } + ] } } } \ No newline at end of file diff --git a/test/parser/samples/script-comment-trailing-multiline/output.json b/test/parser/samples/script-comment-trailing-multiline/output.json index 3c02b1fbde..be83eeea8b 100644 --- a/test/parser/samples/script-comment-trailing-multiline/output.json +++ b/test/parser/samples/script-comment-trailing-multiline/output.json @@ -134,7 +134,15 @@ "kind": "let" } ], - "sourceType": "module" + "sourceType": "module", + "trailingComments": [ + { + "type": "Block", + "value": "\n\ttrailing multiline comment\n", + "start": 32, + "end": 67 + } + ] } } } \ No newline at end of file diff --git a/test/parser/samples/script-comment-trailing/output.json b/test/parser/samples/script-comment-trailing/output.json index beca001042..a2bc00bc00 100644 --- a/test/parser/samples/script-comment-trailing/output.json +++ b/test/parser/samples/script-comment-trailing/output.json @@ -134,7 +134,15 @@ "kind": "let" } ], - "sourceType": "module" + "sourceType": "module", + "trailingComments": [ + { + "type": "Line", + "value": " trailing line comment", + "start": 32, + "end": 56 + } + ] } } } \ No newline at end of file From e93c9913623ba43d8424191fe33f8bebb36e7dea Mon Sep 17 00:00:00 2001 From: Conduitry Date: Mon, 20 Jan 2020 21:18:15 -0500 Subject: [PATCH 23/82] -> v3.17.2 --- CHANGELOG.md | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59fa25682e..92e61719d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Svelte changelog -## Unreleased +## 3.17.2 * Fix removing attributes during hydration ([#1733](https://github.com/sveltejs/svelte/issues/1733)) * Disallow two-way binding to a variable declared by an `{#await}` block ([#4012](https://github.com/sveltejs/svelte/issues/4012)) diff --git a/package-lock.json b/package-lock.json index 90f5a331ed..ca225baa1b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.17.1", + "version": "3.17.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 6a66276c2d..0910dca8c0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.17.1", + "version": "3.17.2", "description": "Cybernetically enhanced web apps", "module": "index.mjs", "main": "index", From e4daaccd06cfe8fe1f4885fcb10942132153b521 Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Wed, 22 Jan 2020 19:32:24 +0800 Subject: [PATCH 24/82] fix nested block not reactive (#4294) --- CHANGELOG.md | 4 +++ src/compiler/compile/render_dom/Block.ts | 3 ++ .../component-slot-nested-if/Display.svelte | 2 ++ .../component-slot-nested-if/Input.svelte | 6 ++++ .../component-slot-nested-if/_config.js | 30 +++++++++++++++++++ .../component-slot-nested-if/main.svelte | 10 +++++++ 6 files changed, 55 insertions(+) create mode 100644 test/runtime/samples/component-slot-nested-if/Display.svelte create mode 100644 test/runtime/samples/component-slot-nested-if/Input.svelte create mode 100644 test/runtime/samples/component-slot-nested-if/_config.js create mode 100644 test/runtime/samples/component-slot-nested-if/main.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index 92e61719d0..60153dcaba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Svelte changelog +## Unreleased + +* Fix updating a `` inside an `{#if}` or other block ([#4292](https://github.com/sveltejs/svelte/issues/4292)) + ## 3.17.2 * Fix removing attributes during hydration ([#1733](https://github.com/sveltejs/svelte/issues/1733)) diff --git a/src/compiler/compile/render_dom/Block.ts b/src/compiler/compile/render_dom/Block.ts index 68d28024fe..62bdc5bdd9 100644 --- a/src/compiler/compile/render_dom/Block.ts +++ b/src/compiler/compile/render_dom/Block.ts @@ -160,6 +160,9 @@ export default class Block { }); this.has_update_method = true; + if (this.parent) { + this.parent.add_dependencies(dependencies); + } } add_element( diff --git a/test/runtime/samples/component-slot-nested-if/Display.svelte b/test/runtime/samples/component-slot-nested-if/Display.svelte new file mode 100644 index 0000000000..d9034e4be2 --- /dev/null +++ b/test/runtime/samples/component-slot-nested-if/Display.svelte @@ -0,0 +1,2 @@ +Display: + \ No newline at end of file diff --git a/test/runtime/samples/component-slot-nested-if/Input.svelte b/test/runtime/samples/component-slot-nested-if/Input.svelte new file mode 100644 index 0000000000..fd8f22db00 --- /dev/null +++ b/test/runtime/samples/component-slot-nested-if/Input.svelte @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/test/runtime/samples/component-slot-nested-if/_config.js b/test/runtime/samples/component-slot-nested-if/_config.js new file mode 100644 index 0000000000..89dfd006cc --- /dev/null +++ b/test/runtime/samples/component-slot-nested-if/_config.js @@ -0,0 +1,30 @@ +export default { + html: ` + + `, + async test({ assert, target, snapshot, component, window }) { + const input = target.querySelector('input'); + + input.value = 'a'; + await input.dispatchEvent(new window.Event('input')); + + assert.htmlEqual( + target.innerHTML, + ` + + Display: a + ` + ); + + input.value = 'abc'; + await input.dispatchEvent(new window.Event('input')); + + assert.htmlEqual( + target.innerHTML, + ` + + Display: abc + ` + ); + }, +}; diff --git a/test/runtime/samples/component-slot-nested-if/main.svelte b/test/runtime/samples/component-slot-nested-if/main.svelte new file mode 100644 index 0000000000..727927b157 --- /dev/null +++ b/test/runtime/samples/component-slot-nested-if/main.svelte @@ -0,0 +1,10 @@ + + + + {#if foo} + {foo} + {/if} + From 5107ad38b6ce0e388cb2eb99361c5000c4b6de91 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Wed, 22 Jan 2020 09:19:32 -0500 Subject: [PATCH 25/82] fix deriving from RxJS observables (#4300) --- CHANGELOG.md | 1 + src/runtime/internal/utils.ts | 4 ++-- src/runtime/store/index.ts | 5 +++-- test/store/index.js | 25 +++++++++++++++---------- 4 files changed, 21 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60153dcaba..388fe61397 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased * Fix updating a `` inside an `{#if}` or other block ([#4292](https://github.com/sveltejs/svelte/issues/4292)) +* Fix using RxJS observables in `derived` stores ([#4298](https://github.com/sveltejs/svelte/issues/4298)) ## 3.17.2 diff --git a/src/runtime/internal/utils.ts b/src/runtime/internal/utils.ts index c94a135869..c7722e1a07 100644 --- a/src/runtime/internal/utils.ts +++ b/src/runtime/internal/utils.ts @@ -48,8 +48,8 @@ export function validate_store(store, name) { } } -export function subscribe(store, callback) { - const unsub = store.subscribe(callback); +export function subscribe(store, ...callbacks) { + const unsub = store.subscribe(...callbacks); return unsub.unsubscribe ? () => unsub.unsubscribe() : unsub; } diff --git a/src/runtime/store/index.ts b/src/runtime/store/index.ts index 64a63d4179..2bbfdfcd60 100644 --- a/src/runtime/store/index.ts +++ b/src/runtime/store/index.ts @@ -1,4 +1,4 @@ -import { run_all, noop, safe_not_equal, is_function, get_store_value } from 'svelte/internal'; +import { run_all, subscribe, noop, safe_not_equal, is_function, get_store_value } from 'svelte/internal'; /** Callback to inform of a value updates. */ type Subscriber = (value: T) => void; @@ -173,7 +173,8 @@ export function derived(stores: Stores, fn: Function, initial_value?: T): Rea } }; - const unsubscribers = stores_array.map((store, i) => store.subscribe( + const unsubscribers = stores_array.map((store, i) => subscribe( + store, (value) => { values[i] = value; pending &= ~(1 << i); diff --git a/test/store/index.js b/test/store/index.js index a39fab86e6..2af4a6f35d 100644 --- a/test/store/index.js +++ b/test/store/index.js @@ -116,6 +116,15 @@ describe('store', () => { }); }); + const fake_observable = { + subscribe(fn) { + fn(42); + return { + unsubscribe: () => {} + }; + } + }; + describe('derived', () => { it('maps a single store', () => { const a = writable(1); @@ -346,6 +355,11 @@ describe('store', () => { b.set(2); assert.deepEqual(get(c), 'two 2'); }); + + it('works with RxJS-style observables', () => { + const d = derived(fake_observable, _ => _); + assert.equal(get(d), 42); + }); }); describe('get', () => { @@ -355,16 +369,7 @@ describe('store', () => { }); it('works with RxJS-style observables', () => { - const observable = { - subscribe(fn) { - fn(42); - return { - unsubscribe: () => {} - }; - } - }; - - assert.equal(get(observable), 42); + assert.equal(get(fake_observable), 42); }); }); }); From 1a343b165c577429e968cea48607cccabf714b9b Mon Sep 17 00:00:00 2001 From: Conduitry Date: Wed, 22 Jan 2020 11:05:31 -0500 Subject: [PATCH 26/82] disallow duplicate each keys in dev mode (#4303) --- CHANGELOG.md | 1 + .../compile/render_dom/wrappers/EachBlock.ts | 2 ++ src/runtime/internal/keyed_each.ts | 14 +++++++++----- .../samples/keyed-each-dev-unique/_config.js | 7 +++++++ .../samples/keyed-each-dev-unique/main.svelte | 7 +++++++ 5 files changed, 26 insertions(+), 5 deletions(-) create mode 100644 test/runtime/samples/keyed-each-dev-unique/_config.js create mode 100644 test/runtime/samples/keyed-each-dev-unique/main.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index 388fe61397..5838dcac80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Fix updating a `` inside an `{#if}` or other block ([#4292](https://github.com/sveltejs/svelte/issues/4292)) * Fix using RxJS observables in `derived` stores ([#4298](https://github.com/sveltejs/svelte/issues/4298)) +* Add dev mode check to disallow duplicate keys in a keyed `{#each}` ([#4301](https://github.com/sveltejs/svelte/issues/4301)) ## 3.17.2 diff --git a/src/compiler/compile/render_dom/wrappers/EachBlock.ts b/src/compiler/compile/render_dom/wrappers/EachBlock.ts index 4928b5a38c..639a8831bd 100644 --- a/src/compiler/compile/render_dom/wrappers/EachBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/EachBlock.ts @@ -374,6 +374,7 @@ export default class EachBlockWrapper extends Wrapper { 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});`} 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); @@ -416,6 +417,7 @@ export default class EachBlockWrapper extends Wrapper { ${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.block.has_outros && b`@check_outros();`} diff --git a/src/runtime/internal/keyed_each.ts b/src/runtime/internal/keyed_each.ts index 8382a2f76a..b397335c87 100644 --- a/src/runtime/internal/keyed_each.ts +++ b/src/runtime/internal/keyed_each.ts @@ -108,9 +108,13 @@ export function update_keyed_each(old_blocks, dirty, get_key, dynamic, ctx, list return new_blocks; } -export function measure(blocks) { - const rects = {}; - let i = blocks.length; - while (i--) rects[blocks[i].key] = blocks[i].node.getBoundingClientRect(); - return rects; +export function validate_each_keys(ctx, list, get_context, get_key) { + const keys = new Set(); + for (let i = 0; i < list.length; i++) { + const key = get_key(get_context(ctx, list, i)); + if (keys.has(key)) { + throw new Error(`Cannot have duplicate keys in a keyed each`); + } + keys.add(key); + } } diff --git a/test/runtime/samples/keyed-each-dev-unique/_config.js b/test/runtime/samples/keyed-each-dev-unique/_config.js new file mode 100644 index 0000000000..8f46af9d52 --- /dev/null +++ b/test/runtime/samples/keyed-each-dev-unique/_config.js @@ -0,0 +1,7 @@ +export default { + compileOptions: { + dev: true + }, + + error: `Cannot have duplicate keys in a keyed each` +}; diff --git a/test/runtime/samples/keyed-each-dev-unique/main.svelte b/test/runtime/samples/keyed-each-dev-unique/main.svelte new file mode 100644 index 0000000000..870e7beaa1 --- /dev/null +++ b/test/runtime/samples/keyed-each-dev-unique/main.svelte @@ -0,0 +1,7 @@ + + +{#each array as item (item)} + {item} +{/each} From 3ba4f8abcad48b0380c9046d0f9230c1fc330964 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Wed, 22 Jan 2020 20:11:45 -0500 Subject: [PATCH 27/82] site: fix a couple absolute links --- site/content/blog/2019-04-20-write-less-code.md | 2 +- site/content/docs/01-component-format.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/site/content/blog/2019-04-20-write-less-code.md b/site/content/blog/2019-04-20-write-less-code.md index d6c8b77193..d56c5312e8 100644 --- a/site/content/blog/2019-04-20-write-less-code.md +++ b/site/content/blog/2019-04-20-write-less-code.md @@ -159,6 +159,6 @@ In Vue, meanwhile, we have a default export with a `data` function that returns ## Death to boilerplate -These are just some of the ways that Svelte helps you build user interfaces with a minimum of fuss. There are plenty of others — for example, [reactive declarations](https://svelte.dev/tutorial/reactive-declarations) essentially do the work of React's `useMemo`, `useCallback` and `useEffect` without the boilerplate (or indeed the garbage collection overhead of creating inline functions and arrays on each state change). +These are just some of the ways that Svelte helps you build user interfaces with a minimum of fuss. There are plenty of others — for example, [reactive declarations](tutorial/reactive-declarations) essentially do the work of React's `useMemo`, `useCallback` and `useEffect` without the boilerplate (or indeed the garbage collection overhead of creating inline functions and arrays on each state change). How? By choosing a different set of constraints. Because [Svelte is a compiler](blog/frameworks-without-the-framework), we're not bound to the peculiarities of JavaScript: we can *design* a component authoring experience, rather than having to fit it around the semantics of the language. Paradoxically, this results in *more* idiomatic code — for example using variables naturally rather than via proxies or hooks — while delivering significantly more performant apps. diff --git a/site/content/docs/01-component-format.md b/site/content/docs/01-component-format.md index e541292e13..026a2da5b3 100644 --- a/site/content/docs/01-component-format.md +++ b/site/content/docs/01-component-format.md @@ -197,7 +197,7 @@ You can `export` bindings from this block, and they will become exports of the c You cannot `export default`, since the default export is the component itself. -> Variables defined in `module` scripts are not reactive — reassigning them will not trigger a rerender even though the variable itself will update. For values shared between multiple components, consider using a [store](https://svelte.dev/docs#svelte_store). +> Variables defined in `module` scripts are not reactive — reassigning them will not trigger a rerender even though the variable itself will update. For values shared between multiple components, consider using a [store](docs#svelte_store). ```html + +

{$store}

From 9cdf0da160d906948a4b4bbaebaf856751906177 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Thu, 23 Jan 2020 10:44:35 -0500 Subject: [PATCH 32/82] fix changelog --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ff7d7603b..44253ce59c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,11 @@ # Svelte changelog -## 3.17.3 +## Unreleased * Make autosubscribing to a nullish store a no-op ([#2181](https://github.com/sveltejs/svelte/issues/2181)) + +## 3.17.3 + * Fix updating a `` inside an `{#if}` or other block ([#4292](https://github.com/sveltejs/svelte/issues/4292)) * Fix using RxJS observables in `derived` stores ([#4298](https://github.com/sveltejs/svelte/issues/4298)) * Add dev mode check to disallow duplicate keys in a keyed `{#each}` ([#4301](https://github.com/sveltejs/svelte/issues/4301)) From bf006a43e5fd2339adb6cfca1a322fdedcbf48da Mon Sep 17 00:00:00 2001 From: Dave Lunny <4298089+himynameisdave@users.noreply.github.com> Date: Thu, 23 Jan 2020 17:51:29 -0800 Subject: [PATCH 33/82] [Internals] Improve unit tests (#4262) --- .github/workflows/ci.yml | 6 ++++++ src/compiler/compile/utils/__test__.ts | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2488902b24..14824ecdfa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,3 +23,9 @@ jobs: - uses: actions/checkout@v1 - uses: actions/setup-node@v1 - run: 'npm i && npm run lint' + Unit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: actions/setup-node@v1 + - run: 'npm i && npm run test:unit' diff --git a/src/compiler/compile/utils/__test__.ts b/src/compiler/compile/utils/__test__.ts index 60ad681b47..7777bc6afb 100644 --- a/src/compiler/compile/utils/__test__.ts +++ b/src/compiler/compile/utils/__test__.ts @@ -10,7 +10,7 @@ describe('get_name_from_filename', () => { assert.equal(get_name_from_filename('path/to/Widget/index.svelte'), 'Widget'); }); - it('handles unusual filenames', () => { - assert.equal(get_name_from_filename('path/to/[...parts].svelte'), 'Parts'); + it('handles Windows filenames', () => { + assert.equal(get_name_from_filename('path\\to\\Widget.svelte'), 'Widget'); }); }); From 8e245dc30ea971da953b1806ed980044546a03a9 Mon Sep 17 00:00:00 2001 From: David Kondrad Date: Sat, 25 Jan 2020 12:21:55 -0500 Subject: [PATCH 34/82] Internals: Scheduler: Fix infinite loop in flush (#4316) --- src/runtime/internal/scheduler.ts | 7 ++++--- .../lifecycle-onmount-infinite-loop/Child.svelte | 1 + .../lifecycle-onmount-infinite-loop/_config.js | 6 ++++++ .../lifecycle-onmount-infinite-loop/main.svelte | 16 ++++++++++++++++ 4 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 test/runtime/samples/lifecycle-onmount-infinite-loop/Child.svelte create mode 100644 test/runtime/samples/lifecycle-onmount-infinite-loop/_config.js create mode 100644 test/runtime/samples/lifecycle-onmount-infinite-loop/main.svelte diff --git a/src/runtime/internal/scheduler.ts b/src/runtime/internal/scheduler.ts index 04c1264ab1..1ce255b217 100644 --- a/src/runtime/internal/scheduler.ts +++ b/src/runtime/internal/scheduler.ts @@ -31,8 +31,8 @@ export function add_flush_callback(fn) { flush_callbacks.push(fn); } +const seen_callbacks = new Set(); export function flush() { - const seen_callbacks = new Set(); do { // first, call beforeUpdate functions @@ -52,10 +52,10 @@ export function flush() { const callback = render_callbacks[i]; if (!seen_callbacks.has(callback)) { - callback(); - // ...so guard against infinite loops seen_callbacks.add(callback); + + callback(); } } @@ -67,6 +67,7 @@ export function flush() { } update_scheduled = false; + seen_callbacks.clear(); } function update($$) { diff --git a/test/runtime/samples/lifecycle-onmount-infinite-loop/Child.svelte b/test/runtime/samples/lifecycle-onmount-infinite-loop/Child.svelte new file mode 100644 index 0000000000..ef16875b64 --- /dev/null +++ b/test/runtime/samples/lifecycle-onmount-infinite-loop/Child.svelte @@ -0,0 +1 @@ +Child diff --git a/test/runtime/samples/lifecycle-onmount-infinite-loop/_config.js b/test/runtime/samples/lifecycle-onmount-infinite-loop/_config.js new file mode 100644 index 0000000000..a76a2686ac --- /dev/null +++ b/test/runtime/samples/lifecycle-onmount-infinite-loop/_config.js @@ -0,0 +1,6 @@ +export default { + test({ assert, component }) { + const { count } = component; + assert.deepEqual(count, 1); + } +}; diff --git a/test/runtime/samples/lifecycle-onmount-infinite-loop/main.svelte b/test/runtime/samples/lifecycle-onmount-infinite-loop/main.svelte new file mode 100644 index 0000000000..1fa4263e39 --- /dev/null +++ b/test/runtime/samples/lifecycle-onmount-infinite-loop/main.svelte @@ -0,0 +1,16 @@ + + +
From 5d4408ba23655c54e8b921e27461273dc5e63610 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Sat, 25 Jan 2020 12:23:50 -0500 Subject: [PATCH 35/82] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44253ce59c..b5784f4148 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +* Fix infinite loop when instantiating another component during `onMount` ([#3218](https://github.com/sveltejs/svelte/issues/3218)) * Make autosubscribing to a nullish store a no-op ([#2181](https://github.com/sveltejs/svelte/issues/2181)) ## 3.17.3 From efe8ab9ca569b8cc706cb4c231b39c160ba479e3 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Sat, 25 Jan 2020 12:26:19 -0500 Subject: [PATCH 36/82] -> v3.18.0 --- CHANGELOG.md | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5784f4148..ed6feef9d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Svelte changelog -## Unreleased +## 3.18.0 * Fix infinite loop when instantiating another component during `onMount` ([#3218](https://github.com/sveltejs/svelte/issues/3218)) * Make autosubscribing to a nullish store a no-op ([#2181](https://github.com/sveltejs/svelte/issues/2181)) diff --git a/package-lock.json b/package-lock.json index cfbc71cdd2..6300fd40ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.17.3", + "version": "3.18.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 2b0b09299b..791327d3f0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.17.3", + "version": "3.18.0", "description": "Cybernetically enhanced web apps", "module": "index.mjs", "main": "index", From 3eb298fd28ff68f782f632a9029b430745cb0c2b Mon Sep 17 00:00:00 2001 From: Conduitry Date: Mon, 27 Jan 2020 14:02:30 -0500 Subject: [PATCH 37/82] fix code generation with adjacent inline and block comments (#4327) --- CHANGELOG.md | 4 ++++ package-lock.json | 6 +++--- package.json | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed6feef9d4..217651e20b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Svelte changelog +## Unreleased + +* Fix code generation error with adjacent inline and block comments ([#4312](https://github.com/sveltejs/svelte/issues/4312)) + ## 3.18.0 * Fix infinite loop when instantiating another component during `onMount` ([#3218](https://github.com/sveltejs/svelte/issues/3218)) diff --git a/package-lock.json b/package-lock.json index 6300fd40ac..5ffb1ba0da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -597,9 +597,9 @@ "dev": true }, "code-red": { - "version": "0.0.31", - "resolved": "https://registry.npmjs.org/code-red/-/code-red-0.0.31.tgz", - "integrity": "sha512-7Gf3vm8pDbs+H/hKsaqOZe0xKlE9Neah12GCfs7qun3fBUaOXwexAMjn0Eo9cvJJvhRMaL0jgPiY9ZGLTWoe8A==", + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/code-red/-/code-red-0.0.32.tgz", + "integrity": "sha512-mE+EZc2vJ4HxiejW5S2CvcVDKtopFEmrqAd9DTBDLCNjLgxekPP8wLi/ZiwDTwZwwW3dzeetaubLaMlIvkhVNw==", "dev": true, "requires": { "acorn": "^7.1.0", diff --git a/package.json b/package.json index 791327d3f0..3b43d6fa46 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "acorn": "^7.1.0", "agadoo": "^1.1.0", "c8": "^5.0.1", - "code-red": "0.0.31", + "code-red": "0.0.32", "codecov": "^3.5.0", "css-tree": "1.0.0-alpha22", "eslint": "^6.3.0", From 43f19d93a9fe33ec71856d7b080aca1425460a3e Mon Sep 17 00:00:00 2001 From: Wolfr Date: Mon, 27 Jan 2020 20:28:06 +0100 Subject: [PATCH 38/82] site: Improve open graph images for website (#4328) --- site/src/template.html | 1 + 1 file changed, 1 insertion(+) diff --git a/site/src/template.html b/site/src/template.html index dc930a7f72..344684d766 100644 --- a/site/src/template.html +++ b/site/src/template.html @@ -15,6 +15,7 @@ + - -
{item.text}
+ +
{thing.text}
    {#each items as item}
  • - +
  • {/each}
@@ -1270,7 +1270,7 @@ Named slots can also expose values. The `let:` directive goes on the element wit ```html -
{item.text}
+
{item.text}

Copyright (c) 2019 Svelte Industries

@@ -1278,7 +1278,7 @@ Named slots can also expose values. The `let:` directive goes on the element wit
    {#each items as item}
  • - +
  • {/each}
From cb64fb2ac94b3f0a579f75abbbbbe4f4d4a6eef3 Mon Sep 17 00:00:00 2001 From: rixo Date: Sun, 23 Feb 2020 05:13:59 -0800 Subject: [PATCH 73/82] make $capture_state/$inject_state act on entire state (#3822) Previously, these methods only applied to exported props. Also, add $$inject option to constructor, which injects state before running the update loop. --- src/compiler/compile/render_dom/index.ts | 61 ++++-- src/runtime/internal/dev.ts | 4 + .../samples/capture-inject-state/_config.js | 5 + .../samples/capture-inject-state/expected.js | 197 ++++++++++++++++++ .../samples/capture-inject-state/input.svelte | 23 ++ test/js/samples/debug-empty/expected.js | 8 +- .../debug-foo-bar-baz-things/expected.js | 8 +- test/js/samples/debug-foo/expected.js | 8 +- test/js/samples/debug-hoisted/expected.js | 11 +- .../expected.js | 8 +- test/js/samples/loop-protect/expected.js | 8 +- 11 files changed, 298 insertions(+), 43 deletions(-) create mode 100644 test/js/samples/capture-inject-state/_config.js create mode 100644 test/js/samples/capture-inject-state/expected.js create mode 100644 test/js/samples/capture-inject-state/input.svelte diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index a7a55b8843..17e5eaf750 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -91,7 +91,10 @@ export default function dom( const accessors = []; const not_equal = component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`; - let dev_props_check; let inject_state; let capture_state; + let dev_props_check: Node[] | Node; + let inject_state: Expression; + let capture_state: Expression; + let props_inject: Node[] | Node; props.forEach(prop => { const variable = component.var_lookup.get(prop.name); @@ -164,27 +167,30 @@ export default function dom( `; } - capture_state = (uses_props || writable_props.length > 0) ? x` - () => { - return { ${component.vars.filter(prop => prop.writable).map(prop => p`${prop.name}`)} }; - } - ` : x` - () => { - return {}; - } - `; + const capturable_vars = component.vars.filter(v => !v.internal && !v.name.startsWith('$$')); - const writable_vars = component.vars.filter(variable => !variable.module && variable.writable); - inject_state = (uses_props || writable_vars.length > 0) ? x` - ${$$props} => { - ${uses_props && renderer.invalidate('$$props', x`$$props = @assign(@assign({}, $$props), $$new_props)`)} - ${writable_vars.map(prop => b` - if ('${prop.name}' in $$props) ${renderer.invalidate(prop.name, x`${prop.name} = ${$$props}.${prop.name}`)}; - `)} - } - ` : x` - ${$$props} => {} - `; + if (capturable_vars.length > 0) { + capture_state = x`() => ({ ${capturable_vars.map(prop => p`${prop.name}`)} })`; + } + + 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)`)} + ${injectable_vars.map( + v => b`if ('${v.name}' in $$props) ${renderer.invalidate(v.name, x`${v.name} = ${$$props}.${v.name}`)};` + )} + } + `; + + props_inject = b` + if ($$props && "$$inject" in $$props) { + $$self.$inject_state($$props.$$inject); + } + `; + } } // instrument assignments @@ -246,7 +252,12 @@ export default function dom( } const args = [x`$$self`]; - if (props.length > 0 || component.has_reactive_assignments || component.slots.size > 0) { + const has_invalidate = props.length > 0 || + component.has_reactive_assignments || + component.slots.size > 0 || + capture_state || + inject_state; + if (has_invalidate) { args.push(x`$$props`, x`$$invalidate`); } @@ -294,7 +305,9 @@ export default function dom( uses_props || component.partly_hoisted.length > 0 || initial_context.length > 0 || - component.reactive_declarations.length > 0 + component.reactive_declarations.length > 0 || + capture_state || + inject_state ); const definition = has_definition @@ -409,6 +422,8 @@ export default function dom( ${injected.map(name => b`let ${name};`)} + ${/* before reactive declarations */ props_inject} + ${reactive_declarations.length > 0 && b` $$self.$$.update = () => { ${reactive_declarations} diff --git a/src/runtime/internal/dev.ts b/src/runtime/internal/dev.ts index 7f4b52eb3b..2d60ffb985 100644 --- a/src/runtime/internal/dev.ts +++ b/src/runtime/internal/dev.ts @@ -120,6 +120,10 @@ export class SvelteComponentDev extends SvelteComponent { console.warn(`Component was already destroyed`); // eslint-disable-line no-console }; } + + $capture_state() {} + + $inject_state() {} } export function loop_guard(timeout) { diff --git a/test/js/samples/capture-inject-state/_config.js b/test/js/samples/capture-inject-state/_config.js new file mode 100644 index 0000000000..414b026a97 --- /dev/null +++ b/test/js/samples/capture-inject-state/_config.js @@ -0,0 +1,5 @@ +export default { + options: { + dev: true + } +}; diff --git a/test/js/samples/capture-inject-state/expected.js b/test/js/samples/capture-inject-state/expected.js new file mode 100644 index 0000000000..8593145565 --- /dev/null +++ b/test/js/samples/capture-inject-state/expected.js @@ -0,0 +1,197 @@ +/* generated by Svelte vX.Y.Z */ +import { + SvelteComponentDev, + add_location, + append_dev, + detach_dev, + dispatch_dev, + element, + init, + insert_dev, + noop, + safe_not_equal, + set_data_dev, + space, + subscribe, + text, + validate_store +} from "svelte/internal"; + +const file = undefined; + +function create_fragment(ctx) { + let p; + let t0; + let t1; + let t2; + let t3; + let t4; + let t5; + let t6; + let t7; + let t8; + let t9; + let t10; + + const block = { + c: function create() { + p = element("p"); + t0 = text(/*prop*/ ctx[0]); + t1 = space(); + t2 = text(/*realName*/ ctx[1]); + t3 = space(); + t4 = text(/*local*/ ctx[3]); + t5 = space(); + t6 = text(priv); + t7 = space(); + t8 = text(/*$prop*/ ctx[2]); + t9 = space(); + t10 = text(/*shadowedByModule*/ ctx[4]); + add_location(p, file, 22, 0, 430); + }, + l: function claim(nodes) { + throw new Error("options.hydrate only works if the component was compiled with the `hydratable: true` option"); + }, + m: function mount(target, anchor) { + insert_dev(target, p, anchor); + append_dev(p, t0); + append_dev(p, t1); + append_dev(p, t2); + append_dev(p, t3); + append_dev(p, t4); + append_dev(p, t5); + append_dev(p, t6); + append_dev(p, t7); + append_dev(p, t8); + append_dev(p, t9); + append_dev(p, t10); + }, + p: function update(ctx, [dirty]) { + if (dirty & /*prop*/ 1) set_data_dev(t0, /*prop*/ ctx[0]); + if (dirty & /*realName*/ 2) set_data_dev(t2, /*realName*/ ctx[1]); + if (dirty & /*$prop*/ 4) set_data_dev(t8, /*$prop*/ ctx[2]); + }, + i: noop, + o: noop, + d: function destroy(detaching) { + if (detaching) detach_dev(p); + } + }; + + dispatch_dev("SvelteRegisterBlock", { + block, + id: create_fragment.name, + type: "component", + source: "", + ctx + }); + + return block; +} + +let moduleLiveBinding; +const moduleContantProps = 4; +let moduleLet; +const moduleConst = 2; +let shadowedByModule; +const priv = "priv"; + +function instance($$self, $$props, $$invalidate) { + let $prop, + $$unsubscribe_prop = noop, + $$subscribe_prop = () => ($$unsubscribe_prop(), $$unsubscribe_prop = subscribe(prop, $$value => $$invalidate(2, $prop = $$value)), prop); + + $$self.$$.on_destroy.push(() => $$unsubscribe_prop()); + let { prop } = $$props; + validate_store(prop, "prop"); + $$subscribe_prop(); + let { alias: realName } = $$props; + let local; + let shadowedByModule; + const writable_props = ["prop", "alias"]; + + Object.keys($$props).forEach(key => { + if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(` was created with unknown prop '${key}'`); + }); + + $$self.$set = $$props => { + if ("prop" in $$props) $$subscribe_prop($$invalidate(0, prop = $$props.prop)); + if ("alias" in $$props) $$invalidate(1, realName = $$props.alias); + }; + + $$self.$capture_state = () => ({ + moduleLiveBinding, + moduleContantProps, + moduleLet, + moduleConst, + shadowedByModule, + prop, + realName, + local, + priv, + shadowedByModule, + computed, + $prop + }); + + $$self.$inject_state = $$props => { + if ("prop" in $$props) $$subscribe_prop($$invalidate(0, prop = $$props.prop)); + if ("realName" in $$props) $$invalidate(1, realName = $$props.realName); + if ("local" in $$props) $$invalidate(3, local = $$props.local); + if ("shadowedByModule" in $$props) $$invalidate(4, shadowedByModule = $$props.shadowedByModule); + if ("computed" in $$props) computed = $$props.computed; + }; + + let computed; + + if ($$props && "$$inject" in $$props) { + $$self.$inject_state($$props.$$inject); + } + + $: computed = local * 2; + return [prop, realName, $prop, local, shadowedByModule]; +} + +class Component extends SvelteComponentDev { + constructor(options) { + super(options); + init(this, options, instance, create_fragment, safe_not_equal, { prop: 0, alias: 1 }); + + dispatch_dev("SvelteRegisterComponent", { + component: this, + tagName: "Component", + options, + id: create_fragment.name + }); + + const { ctx } = this.$$; + const props = options.props || {}; + + if (/*prop*/ ctx[0] === undefined && !("prop" in props)) { + console.warn(" was created without expected prop 'prop'"); + } + + if (/*realName*/ ctx[1] === undefined && !("alias" in props)) { + console.warn(" was created without expected prop 'alias'"); + } + } + + get prop() { + throw new Error(": Props cannot be read directly from the component instance unless compiling with 'accessors: true' or ''"); + } + + set prop(value) { + throw new Error(": Props cannot be set directly on the component instance unless compiling with 'accessors: true' or ''"); + } + + get alias() { + throw new Error(": Props cannot be read directly from the component instance unless compiling with 'accessors: true' or ''"); + } + + set alias(value) { + throw new Error(": Props cannot be set directly on the component instance unless compiling with 'accessors: true' or ''"); + } +} + +export default Component; +export { moduleLiveBinding, moduleContantProps }; \ No newline at end of file diff --git a/test/js/samples/capture-inject-state/input.svelte b/test/js/samples/capture-inject-state/input.svelte new file mode 100644 index 0000000000..a1051bc147 --- /dev/null +++ b/test/js/samples/capture-inject-state/input.svelte @@ -0,0 +1,23 @@ + + + +

{prop} {realName} {local} {priv} {$prop} {shadowedByModule}

diff --git a/test/js/samples/debug-empty/expected.js b/test/js/samples/debug-empty/expected.js index 358363c661..87d78bd698 100644 --- a/test/js/samples/debug-empty/expected.js +++ b/test/js/samples/debug-empty/expected.js @@ -79,14 +79,16 @@ function instance($$self, $$props, $$invalidate) { if ("name" in $$props) $$invalidate(0, name = $$props.name); }; - $$self.$capture_state = () => { - return { name }; - }; + $$self.$capture_state = () => ({ name }); $$self.$inject_state = $$props => { if ("name" in $$props) $$invalidate(0, name = $$props.name); }; + if ($$props && "$$inject" in $$props) { + $$self.$inject_state($$props.$$inject); + } + return [name]; } diff --git a/test/js/samples/debug-foo-bar-baz-things/expected.js b/test/js/samples/debug-foo-bar-baz-things/expected.js index 3209afe8a2..589c4a7832 100644 --- a/test/js/samples/debug-foo-bar-baz-things/expected.js +++ b/test/js/samples/debug-foo-bar-baz-things/expected.js @@ -186,9 +186,7 @@ function instance($$self, $$props, $$invalidate) { if ("baz" in $$props) $$invalidate(3, baz = $$props.baz); }; - $$self.$capture_state = () => { - return { things, foo, bar, baz }; - }; + $$self.$capture_state = () => ({ things, foo, bar, baz }); $$self.$inject_state = $$props => { if ("things" in $$props) $$invalidate(0, things = $$props.things); @@ -197,6 +195,10 @@ function instance($$self, $$props, $$invalidate) { if ("baz" in $$props) $$invalidate(3, baz = $$props.baz); }; + if ($$props && "$$inject" in $$props) { + $$self.$inject_state($$props.$$inject); + } + return [things, foo, bar, baz]; } diff --git a/test/js/samples/debug-foo/expected.js b/test/js/samples/debug-foo/expected.js index 4b08be44d9..10129e1b28 100644 --- a/test/js/samples/debug-foo/expected.js +++ b/test/js/samples/debug-foo/expected.js @@ -176,15 +176,17 @@ function instance($$self, $$props, $$invalidate) { if ("foo" in $$props) $$invalidate(1, foo = $$props.foo); }; - $$self.$capture_state = () => { - return { things, foo }; - }; + $$self.$capture_state = () => ({ things, foo }); $$self.$inject_state = $$props => { if ("things" in $$props) $$invalidate(0, things = $$props.things); if ("foo" in $$props) $$invalidate(1, foo = $$props.foo); }; + if ($$props && "$$inject" in $$props) { + $$self.$inject_state($$props.$$inject); + } + return [things, foo]; } diff --git a/test/js/samples/debug-hoisted/expected.js b/test/js/samples/debug-hoisted/expected.js index eeb9465499..c77168ef4f 100644 --- a/test/js/samples/debug-hoisted/expected.js +++ b/test/js/samples/debug-hoisted/expected.js @@ -47,19 +47,20 @@ function create_fragment(ctx) { return block; } -function instance($$self) { +function instance($$self, $$props, $$invalidate) { let obj = { x: 5 }; let kobzol = 5; - - $$self.$capture_state = () => { - return {}; - }; + $$self.$capture_state = () => ({ obj, kobzol }); $$self.$inject_state = $$props => { if ("obj" in $$props) $$invalidate(0, obj = $$props.obj); if ("kobzol" in $$props) $$invalidate(1, kobzol = $$props.kobzol); }; + if ($$props && "$$inject" in $$props) { + $$self.$inject_state($$props.$$inject); + } + return [obj, kobzol]; } diff --git a/test/js/samples/dev-warning-missing-data-computed/expected.js b/test/js/samples/dev-warning-missing-data-computed/expected.js index cc16de67e6..9c28e04064 100644 --- a/test/js/samples/dev-warning-missing-data-computed/expected.js +++ b/test/js/samples/dev-warning-missing-data-computed/expected.js @@ -76,15 +76,17 @@ function instance($$self, $$props, $$invalidate) { if ("foo" in $$props) $$invalidate(0, foo = $$props.foo); }; - $$self.$capture_state = () => { - return { foo, bar }; - }; + $$self.$capture_state = () => ({ foo, bar }); $$self.$inject_state = $$props => { if ("foo" in $$props) $$invalidate(0, foo = $$props.foo); if ("bar" in $$props) $$invalidate(1, bar = $$props.bar); }; + if ($$props && "$$inject" in $$props) { + $$self.$inject_state($$props.$$inject); + } + $$self.$$.update = () => { if ($$self.$$.dirty & /*foo*/ 1) { $: $$invalidate(1, bar = foo * 2); diff --git a/test/js/samples/loop-protect/expected.js b/test/js/samples/loop-protect/expected.js index 554ccf23b1..127addf1d1 100644 --- a/test/js/samples/loop-protect/expected.js +++ b/test/js/samples/loop-protect/expected.js @@ -108,14 +108,16 @@ function instance($$self, $$props, $$invalidate) { }); } - $$self.$capture_state = () => { - return {}; - }; + $$self.$capture_state = () => ({ node, foo, console }); $$self.$inject_state = $$props => { if ("node" in $$props) $$invalidate(0, node = $$props.node); }; + if ($$props && "$$inject" in $$props) { + $$self.$inject_state($$props.$$inject); + } + $: { const guard_4 = loop_guard(100); From a7ffceefdd007a933eedd93dae53aed55b6290b9 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Sun, 23 Feb 2020 08:16:27 -0500 Subject: [PATCH 74/82] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 922bc08c92..05468d7368 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased * Fix indirect bindings involving elements with spreads ([#3680](https://github.com/sveltejs/svelte/issues/3680)) +* `$capture_state`/`$inject_state` now act on the component's entire state, rather than its props ([#3822](https://github.com/sveltejs/svelte/pull/3822)) * Warn when using `` and `Foo` is dynamic ([#4331](https://github.com/sveltejs/svelte/issues/4331)) * Display compilation warnings in `svelte/register` in dev mode ([#4364](https://github.com/sveltejs/svelte/issues/4364)) * Fix unneeded updating of keyed each blocks ([#4373](https://github.com/sveltejs/svelte/issues/4373)) From a972a47e14b110c50ca31993fae28e8ddf677a4b Mon Sep 17 00:00:00 2001 From: Conduitry Date: Sun, 23 Feb 2020 08:41:22 -0500 Subject: [PATCH 75/82] -> v3.19.0 --- CHANGELOG.md | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05468d7368..f81b198154 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Svelte changelog -## Unreleased +## 3.19.0 * Fix indirect bindings involving elements with spreads ([#3680](https://github.com/sveltejs/svelte/issues/3680)) * `$capture_state`/`$inject_state` now act on the component's entire state, rather than its props ([#3822](https://github.com/sveltejs/svelte/pull/3822)) diff --git a/package-lock.json b/package-lock.json index 8f0b7469f8..2454010c0d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.18.2", + "version": "3.19.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 6000e9d1c4..1cb0d10eeb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.18.2", + "version": "3.19.0", "description": "Cybernetically enhanced web apps", "module": "index.mjs", "main": "index", From 138213ca3c1ca91998461e961933d630aeb45b15 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Sun, 23 Feb 2020 16:35:25 -0500 Subject: [PATCH 76/82] fix dev mode each block validation when using strings (#4451) --- CHANGELOG.md | 4 ++++ src/runtime/internal/dev.ts | 2 +- test/runtime/samples/each-block-string/_config.js | 10 ++++++++++ test/runtime/samples/each-block-string/main.svelte | 3 +++ 4 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 test/runtime/samples/each-block-string/_config.js create mode 100644 test/runtime/samples/each-block-string/main.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index f81b198154..3f4460cde4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Svelte changelog +## Unreleased + +* Fix dev mode validation of `{#each}` blocks using strings ([#4450](https://github.com/sveltejs/svelte/issues/4450)) + ## 3.19.0 * Fix indirect bindings involving elements with spreads ([#3680](https://github.com/sveltejs/svelte/issues/3680)) diff --git a/src/runtime/internal/dev.ts b/src/runtime/internal/dev.ts index 2d60ffb985..8aefc88ba7 100644 --- a/src/runtime/internal/dev.ts +++ b/src/runtime/internal/dev.ts @@ -80,7 +80,7 @@ export function set_data_dev(text, data) { } export function validate_each_argument(arg) { - if (!arg || !('length' in arg)) { + if (typeof arg !== 'string' && !(arg && typeof arg === 'object' && 'length' in arg)) { let msg = '{#each} only iterates over array-like objects.'; if (typeof Symbol === 'function' && arg && Symbol.iterator in arg) { msg += ' You can use a spread to convert this iterable into an array.'; diff --git a/test/runtime/samples/each-block-string/_config.js b/test/runtime/samples/each-block-string/_config.js new file mode 100644 index 0000000000..7366c964eb --- /dev/null +++ b/test/runtime/samples/each-block-string/_config.js @@ -0,0 +1,10 @@ +export default { + compileOptions: { + dev: true + }, + html: ` +
f
+
o
+
o
+ ` +}; diff --git a/test/runtime/samples/each-block-string/main.svelte b/test/runtime/samples/each-block-string/main.svelte new file mode 100644 index 0000000000..ae60f0f6b3 --- /dev/null +++ b/test/runtime/samples/each-block-string/main.svelte @@ -0,0 +1,3 @@ +{#each 'foo' as c} +
{c}
+{/each} From 3bfa0e5cd6219ce6408a7bfa6689b3afa1945388 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Sun, 23 Feb 2020 17:50:57 -0500 Subject: [PATCH 77/82] deconflict `value` parameter in contextual bindings (#4452) --- CHANGELOG.md | 1 + .../render_dom/wrappers/InlineComponent/index.ts | 13 ++++++------- .../deconflict-contextual-bind/Widget.svelte | 3 +++ .../samples/deconflict-contextual-bind/_config.js | 3 +++ .../samples/deconflict-contextual-bind/main.svelte | 8 ++++++++ 5 files changed, 21 insertions(+), 7 deletions(-) create mode 100644 test/runtime/samples/deconflict-contextual-bind/Widget.svelte create mode 100644 test/runtime/samples/deconflict-contextual-bind/_config.js create mode 100644 test/runtime/samples/deconflict-contextual-bind/main.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f4460cde4..2166ca4c9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +* Deconflict `value` parameter name used in contextual bindings ([#4445](https://github.com/sveltejs/svelte/issues/4445)) * Fix dev mode validation of `{#each}` blocks using strings ([#4450](https://github.com/sveltejs/svelte/issues/4450)) ## 3.19.0 diff --git a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts index f1c57ed094..28b3c938f9 100644 --- a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts +++ b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts @@ -332,8 +332,7 @@ export default class InlineComponentWrapper extends Wrapper { contextual_dependencies.push(object.name, property.name); } - const value = block.get_unique_name('value'); - const params: any[] = [value]; + const params = [x`#value`]; if (contextual_dependencies.length > 0) { const args = []; @@ -349,23 +348,23 @@ export default class InlineComponentWrapper extends Wrapper { block.chunks.init.push(b` - function ${id}(${value}) { - ${callee}.call(null, ${value}, ${args}); + function ${id}(#value) { + ${callee}.call(null, #value, ${args}); } `); block.maintain_context = true; // TODO put this somewhere more logical } else { block.chunks.init.push(b` - function ${id}(${value}) { - ${callee}.call(null, ${value}); + function ${id}(#value) { + ${callee}.call(null, #value); } `); } const body = b` function ${id}(${params}) { - ${lhs} = ${value}; + ${lhs} = #value; ${renderer.invalidate(dependencies[0])}; } `; diff --git a/test/runtime/samples/deconflict-contextual-bind/Widget.svelte b/test/runtime/samples/deconflict-contextual-bind/Widget.svelte new file mode 100644 index 0000000000..3aaa59b747 --- /dev/null +++ b/test/runtime/samples/deconflict-contextual-bind/Widget.svelte @@ -0,0 +1,3 @@ + diff --git a/test/runtime/samples/deconflict-contextual-bind/_config.js b/test/runtime/samples/deconflict-contextual-bind/_config.js new file mode 100644 index 0000000000..7602cde023 --- /dev/null +++ b/test/runtime/samples/deconflict-contextual-bind/_config.js @@ -0,0 +1,3 @@ +export default { + preserveIdentifiers: true +}; diff --git a/test/runtime/samples/deconflict-contextual-bind/main.svelte b/test/runtime/samples/deconflict-contextual-bind/main.svelte new file mode 100644 index 0000000000..fe91deca17 --- /dev/null +++ b/test/runtime/samples/deconflict-contextual-bind/main.svelte @@ -0,0 +1,8 @@ + + +{#each values as value} + +{/each} From 19f1c9cb7c4818fdb10e56b650465e1b2f40c6bc Mon Sep 17 00:00:00 2001 From: Conduitry Date: Sun, 23 Feb 2020 18:19:06 -0500 Subject: [PATCH 78/82] don't treat $$-names as stores during invalidation (#4453) --- CHANGELOG.md | 1 + src/compiler/compile/render_dom/invalidate.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2166ca4c9c..d3687b3d2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +* Do not treat modifications to `$$props` as updates to a store called `$props` ([#4368](https://github.com/sveltejs/svelte/issues/4368)) * Deconflict `value` parameter name used in contextual bindings ([#4445](https://github.com/sveltejs/svelte/issues/4445)) * Fix dev mode validation of `{#each}` blocks using strings ([#4450](https://github.com/sveltejs/svelte/issues/4450)) diff --git a/src/compiler/compile/render_dom/invalidate.ts b/src/compiler/compile/render_dom/invalidate.ts index 20aff05d1e..28e4f37e3f 100644 --- a/src/compiler/compile/render_dom/invalidate.ts +++ b/src/compiler/compile/render_dom/invalidate.ts @@ -43,7 +43,7 @@ export function invalidate(renderer: Renderer, scope: Scope, node: Node, names: if (node.type === 'AssignmentExpression' && node.operator === '=' && nodes_match(node.left, node.right) && tail.length === 0) { return get_invalidated(head, node); } else { - const is_store_value = head.name[0] === '$'; + const is_store_value = head.name[0] === '$' && head.name[1] !== '$'; const extra_args = tail.map(variable => get_invalidated(variable)); const pass_value = ( From b8bf3643d4590fee3afe5aae733e2ccc4547c110 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Sun, 23 Feb 2020 18:28:30 -0500 Subject: [PATCH 79/82] -> v3.19.1 --- CHANGELOG.md | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3687b3d2a..28c24fa066 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Svelte changelog -## Unreleased +## 3.19.1 * Do not treat modifications to `$$props` as updates to a store called `$props` ([#4368](https://github.com/sveltejs/svelte/issues/4368)) * Deconflict `value` parameter name used in contextual bindings ([#4445](https://github.com/sveltejs/svelte/issues/4445)) diff --git a/package-lock.json b/package-lock.json index 2454010c0d..3b275078ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.19.0", + "version": "3.19.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 1cb0d10eeb..7a02f1da10 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.19.0", + "version": "3.19.1", "description": "Cybernetically enhanced web apps", "module": "index.mjs", "main": "index", From e1d2cfb74873f3dc5f1234fb31fef3be5415fb24 Mon Sep 17 00:00:00 2001 From: Jesse Skinner Date: Mon, 24 Feb 2020 20:39:42 -0500 Subject: [PATCH 80/82] Update wording to include prop behaviour In regards to #4442, this adds wording to explain that props are set to undefined when they are removed by the consumer. --- site/content/docs/01-component-format.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/content/docs/01-component-format.md b/site/content/docs/01-component-format.md index 026a2da5b3..2101a0c7de 100644 --- a/site/content/docs/01-component-format.md +++ b/site/content/docs/01-component-format.md @@ -42,7 +42,7 @@ Svelte uses the `export` keyword to mark a variable declaration as a *property* --- -You can specify a default value, which will be used if the component's consumer doesn't specify a prop. +You can specify a default value for a prop. It will be used if the component's consumer doesn't specify the prop on the component. Note that whenever a specified prop is removed by the consumer, the value is set to `undefined` rather than the default value. In development mode (see the [compiler options](docs#svelte_compile)), a warning will be printed if no default is provided and the consumer does not specify a value. To squelch this warning, ensure that a default is specified, even if it is `undefined`. From e496e81d6fce78f3799acc7938776ab272accef3 Mon Sep 17 00:00:00 2001 From: Jesse Skinner Date: Thu, 27 Feb 2020 08:42:20 -0500 Subject: [PATCH 81/82] Change 'default value' to 'initial value' to set expectations about behaviour, for issue #4442 --- site/content/docs/01-component-format.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/site/content/docs/01-component-format.md b/site/content/docs/01-component-format.md index 2101a0c7de..f202dd355c 100644 --- a/site/content/docs/01-component-format.md +++ b/site/content/docs/01-component-format.md @@ -42,13 +42,13 @@ Svelte uses the `export` keyword to mark a variable declaration as a *property* --- -You can specify a default value for a prop. It will be used if the component's consumer doesn't specify the prop on the component. Note that whenever a specified prop is removed by the consumer, the value is set to `undefined` rather than the default value. +You can specify an initial value for a prop. It will be used if the component's consumer doesn't specify the prop on the component. Note that whenever a specified prop is removed by the consumer, the value is set to `undefined` rather than the initial value. -In development mode (see the [compiler options](docs#svelte_compile)), a warning will be printed if no default is provided and the consumer does not specify a value. To squelch this warning, ensure that a default is specified, even if it is `undefined`. +In development mode (see the [compiler options](docs#svelte_compile)), a warning will be printed if no initial value is provided and the consumer does not specify a value. To squelch this warning, ensure that an initial value is specified, even if it is `undefined`. ```html ``` From bfe1b2c5306053999d006d7f7bc1af5af607e9b4 Mon Sep 17 00:00:00 2001 From: Jesse Skinner Date: Thu, 27 Feb 2020 12:11:16 -0500 Subject: [PATCH 82/82] Update site/content/docs/01-component-format.md Co-Authored-By: Luca Bonavita --- site/content/docs/01-component-format.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/content/docs/01-component-format.md b/site/content/docs/01-component-format.md index f202dd355c..95689c1a59 100644 --- a/site/content/docs/01-component-format.md +++ b/site/content/docs/01-component-format.md @@ -42,7 +42,7 @@ Svelte uses the `export` keyword to mark a variable declaration as a *property* --- -You can specify an initial value for a prop. It will be used if the component's consumer doesn't specify the prop on the component. Note that whenever a specified prop is removed by the consumer, the value is set to `undefined` rather than the initial value. +You can specify a default initial value for a prop. It will be used if the component's consumer doesn't specify the prop on the component (or if its initial value is `undefined`). Note that whenever a prop is removed by the consumer, its value is set to `undefined` rather than the initial value. In development mode (see the [compiler options](docs#svelte_compile)), a warning will be printed if no initial value is provided and the consumer does not specify a value. To squelch this warning, ensure that an initial value is specified, even if it is `undefined`.