From ec6360487ef4c419d9cd12bf749c6e32a0d1b186 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 23 Aug 2018 22:02:16 -0400 Subject: [PATCH 1/6] correctly set select value on mount - fixes #1666 --- src/compile/nodes/Attribute.ts | 2 +- .../select-dynamic-value/expected-bundle.js | 224 ++++++++++++++++++ .../samples/select-dynamic-value/expected.js | 73 ++++++ .../samples/select-dynamic-value/input.html | 4 + 4 files changed, 302 insertions(+), 1 deletion(-) create mode 100644 test/js/samples/select-dynamic-value/expected-bundle.js create mode 100644 test/js/samples/select-dynamic-value/expected.js create mode 100644 test/js/samples/select-dynamic-value/input.html diff --git a/src/compile/nodes/Attribute.ts b/src/compile/nodes/Attribute.ts index 0165dc98fb..b0c26c534d 100644 --- a/src/compile/nodes/Attribute.ts +++ b/src/compile/nodes/Attribute.ts @@ -210,7 +210,7 @@ export default class Attribute extends Node { } `; - block.builders.hydrate.addBlock(deindent` + block.builders.mount.addBlock(deindent` ${last} = ${value}; ${updater} `); diff --git a/test/js/samples/select-dynamic-value/expected-bundle.js b/test/js/samples/select-dynamic-value/expected-bundle.js new file mode 100644 index 0000000000..1230856164 --- /dev/null +++ b/test/js/samples/select-dynamic-value/expected-bundle.js @@ -0,0 +1,224 @@ +function noop() {} + +function assign(tar, src) { + for (var k in src) tar[k] = src[k]; + return tar; +} + +function append(target, node) { + target.appendChild(node); +} + +function insert(target, node, anchor) { + target.insertBefore(node, anchor); +} + +function detachNode(node) { + node.parentNode.removeChild(node); +} + +function createElement(name) { + return document.createElement(name); +} + +function createText(data) { + return document.createTextNode(data); +} + +function blankObject() { + return Object.create(null); +} + +function destroy(detach) { + this.destroy = noop; + this.fire('destroy'); + this.set = noop; + + this._fragment.d(detach !== false); + this._fragment = null; + this._state = {}; +} + +function _differs(a, b) { + return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function'); +} + +function fire(eventName, data) { + var handlers = + eventName in this._handlers && this._handlers[eventName].slice(); + if (!handlers) return; + + for (var i = 0; i < handlers.length; i += 1) { + var handler = handlers[i]; + + if (!handler.__calling) { + try { + handler.__calling = true; + handler.call(this, data); + } finally { + handler.__calling = false; + } + } + } +} + +function flush(component) { + component._lock = true; + callAll(component._beforecreate); + callAll(component._oncreate); + callAll(component._aftercreate); + component._lock = false; +} + +function get() { + return this._state; +} + +function init(component, options) { + component._handlers = blankObject(); + component._slots = blankObject(); + component._bind = options._bind; + + component.options = options; + component.root = options.root || component; + component.store = options.store || component.root.store; + + if (!options.root) { + component._beforecreate = []; + component._oncreate = []; + component._aftercreate = []; + } +} + +function on(eventName, handler) { + var handlers = this._handlers[eventName] || (this._handlers[eventName] = []); + handlers.push(handler); + + return { + cancel: function() { + var index = handlers.indexOf(handler); + if (~index) handlers.splice(index, 1); + } + }; +} + +function set(newState) { + this._set(assign({}, newState)); + if (this.root._lock) return; + flush(this.root); +} + +function _set(newState) { + var oldState = this._state, + changed = {}, + dirty = false; + + for (var key in newState) { + if (this._differs(newState[key], oldState[key])) changed[key] = dirty = true; + } + if (!dirty) return; + + this._state = assign(assign({}, oldState), newState); + this._recompute(changed, this._state); + if (this._bind) this._bind(changed, this._state); + + if (this._fragment) { + this.fire("state", { changed: changed, current: this._state, previous: oldState }); + this._fragment.p(changed, this._state); + this.fire("update", { changed: changed, current: this._state, previous: oldState }); + } +} + +function callAll(fns) { + while (fns && fns.length) fns.shift()(); +} + +function _mount(target, anchor) { + this._fragment[this._fragment.i ? 'i' : 'm'](target, anchor || null); +} + +var proto = { + destroy, + get, + fire, + on, + set, + _recompute: noop, + _set, + _mount, + _differs +}; + +/* generated by Svelte vX.Y.Z */ + +function create_main_fragment(component, ctx) { + var select, option, text, option_1, text_1, select_value_value; + + return { + c() { + select = createElement("select"); + option = createElement("option"); + text = createText("1"); + option_1 = createElement("option"); + text_1 = createText("2"); + option.__value = "1"; + option.value = option.__value; + option_1.__value = "2"; + option_1.value = option_1.__value; + }, + + m(target, anchor) { + insert(target, select, anchor); + append(select, option); + append(option, text); + append(select, option_1); + append(option_1, text_1); + + select_value_value = ctx.current; + for (var i = 0; i < select.options.length; i += 1) { + var option_2 = select.options[i]; + + if (option_2.__value === select_value_value) { + option_2.selected = true; + break; + } + } + }, + + p(changed, ctx) { + if ((changed.current) && select_value_value !== (select_value_value = ctx.current)) { + for (var i = 0; i < select.options.length; i += 1) { + var option_2 = select.options[i]; + + if (option_2.__value === select_value_value) { + option_2.selected = true; + break; + } + } + } + }, + + d(detach) { + if (detach) { + detachNode(select); + } + } + }; +} + +function SvelteComponent(options) { + init(this, options); + this._state = assign({}, options.data); + this._intro = true; + + this._fragment = create_main_fragment(this, this._state); + + if (options.target) { + this._fragment.c(); + this._mount(options.target, options.anchor); + } +} + +assign(SvelteComponent.prototype, proto); + +export default SvelteComponent; diff --git a/test/js/samples/select-dynamic-value/expected.js b/test/js/samples/select-dynamic-value/expected.js new file mode 100644 index 0000000000..9bf99b528d --- /dev/null +++ b/test/js/samples/select-dynamic-value/expected.js @@ -0,0 +1,73 @@ +/* generated by Svelte vX.Y.Z */ +import { append, assign, createElement, createText, detachNode, init, insert, proto } from "svelte/shared.js"; + +function create_main_fragment(component, ctx) { + var select, option, text, option_1, text_1, select_value_value; + + return { + c() { + select = createElement("select"); + option = createElement("option"); + text = createText("1"); + option_1 = createElement("option"); + text_1 = createText("2"); + option.__value = "1"; + option.value = option.__value; + option_1.__value = "2"; + option_1.value = option_1.__value; + }, + + m(target, anchor) { + insert(target, select, anchor); + append(select, option); + append(option, text); + append(select, option_1); + append(option_1, text_1); + + select_value_value = ctx.current; + for (var i = 0; i < select.options.length; i += 1) { + var option_2 = select.options[i]; + + if (option_2.__value === select_value_value) { + option_2.selected = true; + break; + } + } + }, + + p(changed, ctx) { + if ((changed.current) && select_value_value !== (select_value_value = ctx.current)) { + for (var i = 0; i < select.options.length; i += 1) { + var option_2 = select.options[i]; + + if (option_2.__value === select_value_value) { + option_2.selected = true; + break; + } + } + } + }, + + d(detach) { + if (detach) { + detachNode(select); + } + } + }; +} + +function SvelteComponent(options) { + init(this, options); + this._state = assign({}, options.data); + this._intro = true; + + this._fragment = create_main_fragment(this, this._state); + + if (options.target) { + this._fragment.c(); + this._mount(options.target, options.anchor); + } +} + +assign(SvelteComponent.prototype, proto); +export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/select-dynamic-value/input.html b/test/js/samples/select-dynamic-value/input.html new file mode 100644 index 0000000000..2c7d174d92 --- /dev/null +++ b/test/js/samples/select-dynamic-value/input.html @@ -0,0 +1,4 @@ + \ No newline at end of file From 154ee7376509d49a30cc108cfa8acad232676482 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 23 Aug 2018 22:53:17 -0400 Subject: [PATCH 2/6] support debug tag in SSR mode - fixes #1659 --- src/compile/nodes/DebugTag.ts | 21 +++++++- src/shared/ssr.js | 6 +++ test/js/samples/debug-ssr-foo/_config.js | 6 +++ .../samples/debug-ssr-foo/expected-bundle.js | 46 +++++++++++++++++ test/js/samples/debug-ssr-foo/expected.js | 51 +++++++++++++++++++ test/js/samples/debug-ssr-foo/input.html | 6 +++ 6 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 test/js/samples/debug-ssr-foo/_config.js create mode 100644 test/js/samples/debug-ssr-foo/expected-bundle.js create mode 100644 test/js/samples/debug-ssr-foo/expected.js create mode 100644 test/js/samples/debug-ssr-foo/input.html diff --git a/src/compile/nodes/DebugTag.ts b/src/compile/nodes/DebugTag.ts index e70d6a33a2..b73208961a 100644 --- a/src/compile/nodes/DebugTag.ts +++ b/src/compile/nodes/DebugTag.ts @@ -4,6 +4,7 @@ import Block from '../dom/Block'; import Expression from './shared/Expression'; import deindent from '../../utils/deindent'; import addToSet from '../../utils/addToSet'; +import { stringify } from '../../utils/stringify'; export default class DebugTag extends Node { expressions: Expression[]; @@ -25,8 +26,8 @@ export default class DebugTag extends Node { const { code } = this.compiler; - // Debug all if (this.expressions.length === 0) { + // Debug all code.overwrite(this.start + 1, this.start + 7, 'debugger', { storeName: true }); @@ -67,4 +68,22 @@ export default class DebugTag extends Node { `); } } + + ssr() { + if (!this.compiler.options.dev) return; + + const filename = this.compiler.file || null; + const { line, column } = this.compiler.locate(this.start + 1); + + const obj = this.expressions.length === 0 + ? `ctx` + : `{ ${this.expressions + .map(e => e.node.name) + .map(name => `${name}: ctx.${name}`) + .join(', ')} }`; + + const str = '${@debug(' + `${filename && stringify(filename)}, ${line}, ${column}, ${obj})}`; + + this.compiler.target.append(str); + } } \ No newline at end of file diff --git a/src/shared/ssr.js b/src/shared/ssr.js index 3e5a4568e6..5031bfa0e5 100644 --- a/src/shared/ssr.js +++ b/src/shared/ssr.js @@ -54,4 +54,10 @@ export function validateSsrComponent(component, name) { } return component; +} + +export function debug(file, line, column, values) { + console.log(`{@debug} ${file ? file + ' ' : ''}(${line}:${column})`); + console.log(values); + return ''; } \ No newline at end of file diff --git a/test/js/samples/debug-ssr-foo/_config.js b/test/js/samples/debug-ssr-foo/_config.js new file mode 100644 index 0000000000..39bfbe7e62 --- /dev/null +++ b/test/js/samples/debug-ssr-foo/_config.js @@ -0,0 +1,6 @@ +export default { + options: { + generate: 'ssr', + dev: true + } +}; \ No newline at end of file diff --git a/test/js/samples/debug-ssr-foo/expected-bundle.js b/test/js/samples/debug-ssr-foo/expected-bundle.js new file mode 100644 index 0000000000..dcfc5c6d02 --- /dev/null +++ b/test/js/samples/debug-ssr-foo/expected-bundle.js @@ -0,0 +1,46 @@ +var { debug, each, escape } = require("svelte/shared.js"); + +var SvelteComponent = {}; +SvelteComponent.data = function() { + return {}; +}; + +SvelteComponent.render = function(state, options = {}) { + var components = new Set(); + + function addComponent(component) { + components.add(component); + } + + var result = { head: '', addComponent }; + var html = SvelteComponent._render(result, state, options); + + var cssCode = Array.from(components).map(c => c.css && c.css.code).filter(Boolean).join('\n'); + + return { + html, + head: result.head, + css: { code: cssCode, map: null }, + toString() { + return html; + } + }; +}; + +SvelteComponent._render = function(__result, ctx, options) { + __result.addComponent(SvelteComponent); + + ctx = Object.assign({}, ctx); + + return `${ each(ctx.things, item => Object.assign({}, ctx, { thing: item }), ctx => `${escape(ctx.thing.name)} + ${debug(null, 2, 2, { foo: ctx.foo })}`)} + +

foo: ${escape(ctx.foo)}

`; +}; + +SvelteComponent.css = { + code: '', + map: null +}; + +module.exports = SvelteComponent; diff --git a/test/js/samples/debug-ssr-foo/expected.js b/test/js/samples/debug-ssr-foo/expected.js new file mode 100644 index 0000000000..43f1be1db6 --- /dev/null +++ b/test/js/samples/debug-ssr-foo/expected.js @@ -0,0 +1,51 @@ +"use strict"; + +var { debug, each, escape } = require("svelte/shared.js"); + +var SvelteComponent = {};; + +SvelteComponent.data = function() { + return {}; +}; + +SvelteComponent.render = function(state, options = {}) { + var components = new Set(); + + function addComponent(component) { + components.add(component); + } + + var result = { head: '', addComponent }; + var html = SvelteComponent._render(result, state, options); + + var cssCode = Array.from(components).map(c => c.css && c.css.code).filter(Boolean).join('\n'); + + return { + html, + head: result.head, + css: { code: cssCode, map: null }, + toString() { + return html; + } + }; +} + +SvelteComponent._render = function(__result, ctx, options) { + __result.addComponent(SvelteComponent); + + ctx = Object.assign({}, ctx); + + return `${ each(ctx.things, item => Object.assign({}, ctx, { thing: item }), ctx => `${escape(ctx.thing.name)} + ${debug(null, 2, 2, { foo: ctx.foo })}`)} + +

foo: ${escape(ctx.foo)}

`; +}; + +SvelteComponent.css = { + code: '', + map: null +}; + +var warned = false; + +module.exports = SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/debug-ssr-foo/input.html b/test/js/samples/debug-ssr-foo/input.html new file mode 100644 index 0000000000..6e926a4015 --- /dev/null +++ b/test/js/samples/debug-ssr-foo/input.html @@ -0,0 +1,6 @@ +{#each things as thing} + {thing.name} + {@debug foo} +{/each} + +

foo: {foo}

\ No newline at end of file From c52a6f011b383532feb91304d2c6bc38e1e20254 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 24 Aug 2018 08:22:41 -0400 Subject: [PATCH 3/6] dont warn on empty block for nbsp - fixes #1658 --- src/validate/html/index.ts | 2 +- test/validator/samples/non-empty-block-dev/_config.js | 3 +++ test/validator/samples/non-empty-block-dev/input.html | 3 +++ test/validator/samples/non-empty-block-dev/warnings.json | 1 + 4 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 test/validator/samples/non-empty-block-dev/_config.js create mode 100644 test/validator/samples/non-empty-block-dev/input.html create mode 100644 test/validator/samples/non-empty-block-dev/warnings.json diff --git a/src/validate/html/index.ts b/src/validate/html/index.ts index ed4eaf4d38..eabe9d8872 100644 --- a/src/validate/html/index.ts +++ b/src/validate/html/index.ts @@ -14,7 +14,7 @@ function isEmptyBlock(node: Node) { if (!/Block$/.test(node.type) || !node.children) return false; if (node.children.length > 1) return false; const child = node.children[0]; - return !child || (child.type === 'Text' && !/\S/.test(child.data)); + return !child || (child.type === 'Text' && !/[^ \r\n\f\v\t]/.test(child.data)); } export default function validateHtml(validator: Validator, html: Node) { diff --git a/test/validator/samples/non-empty-block-dev/_config.js b/test/validator/samples/non-empty-block-dev/_config.js new file mode 100644 index 0000000000..e26996239d --- /dev/null +++ b/test/validator/samples/non-empty-block-dev/_config.js @@ -0,0 +1,3 @@ +export default { + dev: true +}; \ No newline at end of file diff --git a/test/validator/samples/non-empty-block-dev/input.html b/test/validator/samples/non-empty-block-dev/input.html new file mode 100644 index 0000000000..ed4b9e6274 --- /dev/null +++ b/test/validator/samples/non-empty-block-dev/input.html @@ -0,0 +1,3 @@ +{#if x} +   +{/if} \ No newline at end of file diff --git a/test/validator/samples/non-empty-block-dev/warnings.json b/test/validator/samples/non-empty-block-dev/warnings.json new file mode 100644 index 0000000000..0637a088a0 --- /dev/null +++ b/test/validator/samples/non-empty-block-dev/warnings.json @@ -0,0 +1 @@ +[] \ No newline at end of file From 54dc1e58492494debd2e9c39940a45610c6c4b19 Mon Sep 17 00:00:00 2001 From: Christian Kaisermann Date: Fri, 24 Aug 2018 12:16:09 -0300 Subject: [PATCH 4/6] Set style attribute correctly when spread attr present --- src/shared/dom.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/shared/dom.js b/src/shared/dom.js index d428e92f1c..accd2abecf 100644 --- a/src/shared/dom.js +++ b/src/shared/dom.js @@ -87,7 +87,9 @@ export function setAttribute(node, attribute, value) { export function setAttributes(node, attributes) { for (var key in attributes) { - if (key in node) { + if (key === 'style') { + node.style.cssText = attributes[key]; + } else if (key in node) { node[key] = attributes[key]; } else { if (attributes[key] === undefined) removeAttribute(node, key); @@ -238,4 +240,4 @@ export function addResizeListener(element, fn) { element.removeChild(object); } }; -} \ No newline at end of file +} From 96c4455af933636e3450df8386d65fabb7b4db68 Mon Sep 17 00:00:00 2001 From: Jacob Wright Date: Fri, 24 Aug 2018 14:01:51 -0600 Subject: [PATCH 5/6] Fixes an error with outros and elseifs This is a fix for when an elseif doesn't have a final else, and the following error was thrown: ``` TypeError: Cannot read property 'o' of undefined ``` See https://svelte.technology/repl?version=2.12.0&gist=c33d308077447f8ba06b79d8ef5ab1e4 --- src/compile/nodes/IfBlock.ts | 7 ++++--- .../if-block-outro-nested-else/Component.html | 1 + .../if-block-outro-nested-else/_config.js | 8 ++++++++ .../samples/if-block-outro-nested-else/main.html | 16 ++++++++++++++++ 4 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 test/runtime/samples/if-block-outro-nested-else/Component.html create mode 100644 test/runtime/samples/if-block-outro-nested-else/_config.js create mode 100644 test/runtime/samples/if-block-outro-nested-else/main.html diff --git a/src/compile/nodes/IfBlock.ts b/src/compile/nodes/IfBlock.ts index 2a5a49af16..eef654b90b 100644 --- a/src/compile/nodes/IfBlock.ts +++ b/src/compile/nodes/IfBlock.ts @@ -139,9 +139,10 @@ export default class IfBlock extends Node { this.buildCompoundWithOutros(block, parentNode, parentNodes, branches, dynamic, vars); if (this.compiler.options.nestedTransitions) { - block.builders.outro.addLine( - `${name}.o(#outrocallback);` - ); + block.builders.outro.addBlock(deindent` + if (${name}) ${name}.o(#outrocallback); + else #outrocallback(); + `); } } else { this.buildCompound(block, parentNode, parentNodes, branches, dynamic, vars); diff --git a/test/runtime/samples/if-block-outro-nested-else/Component.html b/test/runtime/samples/if-block-outro-nested-else/Component.html new file mode 100644 index 0000000000..281c6866c3 --- /dev/null +++ b/test/runtime/samples/if-block-outro-nested-else/Component.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/test/runtime/samples/if-block-outro-nested-else/_config.js b/test/runtime/samples/if-block-outro-nested-else/_config.js new file mode 100644 index 0000000000..4fe5eaa926 --- /dev/null +++ b/test/runtime/samples/if-block-outro-nested-else/_config.js @@ -0,0 +1,8 @@ +export default { + nestedTransitions: true, + + test ( assert, component, target ) { + // Would cause "TypeError: Cannot read property 'o' of undefined" + component.set({ foo: false }); + } +}; diff --git a/test/runtime/samples/if-block-outro-nested-else/main.html b/test/runtime/samples/if-block-outro-nested-else/main.html new file mode 100644 index 0000000000..02e000b3c3 --- /dev/null +++ b/test/runtime/samples/if-block-outro-nested-else/main.html @@ -0,0 +1,16 @@ +{#if foo} + {#if false} + + {:elseif false} + + {/if} +{/if} + + From e26dcadecfdc678e40382c863e6f35c93277db68 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 24 Aug 2018 19:52:47 -0400 Subject: [PATCH 6/6] -> v2.12.1 --- CHANGELOG.md | 11 +++++++++++ package.json | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9c18fafd1..fddab48189 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Svelte changelog +## 2.12.1 + +* Allow actions to take any expression ([#1676](https://github.com/sveltejs/svelte/issues/1676)) +* Run transitions in component context ([#1675](https://github.com/sveltejs/svelte/issues/1675)) +* Correctly set select value on mount ([#1666](https://github.com/sveltejs/svelte/issues/1666)) +* Support `{@debug}` in SSR ([#1659](https://github.com/sveltejs/svelte/issues/1659)) +* Don't treat ` ` as empty whitespace ([#1658](https://github.com/sveltejs/svelte/issues/1658)) +* Fix outros for if blocks with no else ([#1688](https://github.com/sveltejs/svelte/pull/1688)) +* Set `style.cssText` in spread attributes ([#1684](https://github.com/sveltejs/svelte/pull/1684)) + + ## 2.12.0 * Initialise actions on mount rather than hydrate ([#1653](https://github.com/sveltejs/svelte/pull/1653)) diff --git a/package.json b/package.json index 8bd1fc7b08..905bfae7f8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "2.12.0", + "version": "2.12.1", "description": "The magical disappearing UI framework", "main": "compiler/svelte.js", "bin": {