From abc39eed20607a8d015b1994b3a10f6c0785d6c6 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 24 Nov 2018 15:53:50 -0500 Subject: [PATCH] spread props --- src/compile/Component.ts | 112 ++++++++++-------- src/compile/render-dom/index.ts | 15 ++- .../samples/spread-component/_config.js | 16 ++- .../samples/spread-own-props/Widget.html | 4 + .../samples/spread-own-props/_config.js | 31 +++++ .../samples/spread-own-props/main.html | 11 ++ 6 files changed, 133 insertions(+), 56 deletions(-) create mode 100644 test/runtime/samples/spread-own-props/Widget.html create mode 100644 test/runtime/samples/spread-own-props/_config.js create mode 100644 test/runtime/samples/spread-own-props/main.html diff --git a/src/compile/Component.ts b/src/compile/Component.ts index 2173356a1a..ab7c58d10f 100644 --- a/src/compile/Component.ts +++ b/src/compile/Component.ts @@ -17,6 +17,14 @@ import getCodeFrame from '../utils/getCodeFrame'; import flattenReference from '../utils/flattenReference'; import addToSet from '../utils/addToSet'; +type Meta = { + namespace?: string; + tag?: string; + immutable?: boolean; + props?: string; + props_object?: string; +}; + // We need to tell estree-walker that it should always // look for an `else` block, otherwise it might get // the wrong idea about the shape of each/if blocks @@ -34,11 +42,7 @@ export default class Component { fragment: Fragment; scope: Scope; - meta: { - namespace?: string; - tag?: string; - immutable?: boolean; - }; + meta: Meta; customElement: CustomElementOptions; tag: string; @@ -108,12 +112,12 @@ export default class Component { this.walkJs(); this.name = this.alias(name); - const meta = process_meta(this, this.ast.html.children); - this.namespace = namespaces[meta.namespace] || meta.namespace; + this.meta = process_meta(this, this.ast.html.children); + this.namespace = namespaces[this.meta.namespace] || this.meta.namespace; if (options.customElement === true) { this.customElement = { - tag: meta.tag, + tag: this.meta.tag, props: [] // TODO!!! }; } else { @@ -478,66 +482,78 @@ export default class Component { } } -type Meta = { - namespace?: string; - tag?: string; - immutable?: boolean; -}; - function process_meta(component, nodes) { const meta: Meta = {}; const node = nodes.find(node => node.name === 'svelte:meta'); if (node) { node.attributes.forEach(attribute => { - if (attribute.type !== 'Attribute') { - // TODO implement bindings on - component.error(attribute, { - code: `invalid-meta-attribute`, - message: ` can only have 'tag' and 'namespace' attributes` - }); - } + if (attribute.type === 'Attribute') { + const { name, value } = attribute; - const { name, value } = attribute; + if (value.length > 1 || (value[0] && value[0].type !== 'Text')) { + component.error(attribute, { + code: `invalid-meta-attribute`, + message: ` cannot have dynamic attributes` + }); + } - if (value.length > 1 || (value[0] && value[0].type !== 'Text')) { - component.error(attribute, { - code: `invalid-meta-attribute`, - message: ` cannot have dynamic attributes` - }); - } + const { data } = value[0]; - const { data } = value[0]; + switch (name) { + case 'tag': + case 'namespace': + if (!data) { + component.error(attribute, { + code: `invalid-meta-attribute`, + message: ` ${name} attribute must have a string value` + }); + } - switch (name) { - case 'tag': - case 'namespace': - if (!data) { - component.error(attribute, { - code: `invalid-meta-attribute`, - message: ` ${name} attribute must have a string value` - }); - } + meta[name] = data; + break; + + case 'immutable': + if (data && (data !== 'true' && data !== 'false')) { + component.error(attribute, { + code: `invalid-meta-attribute`, + message: ` immutable attribute must be true or false` + }); + } - meta[name] = data; - break; + meta.immutable = data !== 'false'; - case 'immutable': - if (data && (data !== 'true' && data !== 'false')) { + default: component.error(attribute, { code: `invalid-meta-attribute`, - message: ` immutable attribute must be true or false` + message: ` unknown attribute` }); - } - - meta.immutable = data !== 'false'; + } + } - default: + else if (attribute.type === 'Binding') { + if (attribute.name !== 'props') { component.error(attribute, { code: `invalid-meta-attribute`, - message: ` unknown attribute` + message: ` only supports bind:props` }); + } + + const { start, end } = attribute.expression; + const { name } = flattenReference(attribute.expression); + + meta.props = `[✂${start}-${end}✂]`; + meta.props_object = name; } + + else { + component.error(attribute, { + code: `invalid-meta-attribute`, + message: ` can only have static 'tag', 'namespace' and 'immutable' attributes, or a bind:props directive` + }); + } + + }); } diff --git a/src/compile/render-dom/index.ts b/src/compile/render-dom/index.ts index bdaef47edf..186eacfb64 100644 --- a/src/compile/render-dom/index.ts +++ b/src/compile/render-dom/index.ts @@ -123,19 +123,24 @@ export default function dom( const props = component.exports.filter(x => component.writable_declarations.has(x.name)); - const inject_props = props.length > 0 + const inject_props = component.meta.props || props.length > 0 ? deindent` - props => { + $$props => { + ${component.meta.props && deindent` + if (!${component.meta.props}) ${component.meta.props} = {}; + @assign(${component.meta.props}, $$props); + $$make_dirty('${component.meta.props_object}'); + `} ${props.map(prop => - `if ('${prop.as}' in props) ${prop.name} = props.${prop.as};`)} + `if ('${prop.as}' in $$props) ${prop.name} = $$props.${prop.as};`)} } ` : `@noop`; const inject_refs = refs.length > 0 ? deindent` - refs => { - ${refs.map(name => `${name} = refs.${name};`)} + $$refs => { + ${refs.map(name => `${name} = $$refs.${name};`)} } ` : `@noop`; diff --git a/test/runtime/samples/spread-component/_config.js b/test/runtime/samples/spread-component/_config.js index 47a9af89c0..159ece1b7c 100644 --- a/test/runtime/samples/spread-component/_config.js +++ b/test/runtime/samples/spread-component/_config.js @@ -8,9 +8,14 @@ export default { } }, - html: `

foo: lol

\n

baz: 42 (number)

\n

qux: named

\n

quux: core

`, + html: ` +

foo: lol

+

baz: 42 (number)

+

qux: named

+

quux: core

+ `, - test ( assert, component, target ) { + test(assert, component, target) { component.props = { foo: 'wut', baz: 40 + 3, @@ -18,6 +23,11 @@ export default { quux: 'heart' }; - assert.equal( target.innerHTML, `

foo: wut

\n

baz: 43 (number)

\n

qux: named

\n

quux: heart

` ); + assert.htmlEqual(target.innerHTML, ` +

foo: wut

+

baz: 43 (number)

+

qux: named

+

quux: heart

+ `); } }; diff --git a/test/runtime/samples/spread-own-props/Widget.html b/test/runtime/samples/spread-own-props/Widget.html new file mode 100644 index 0000000000..a60afb741c --- /dev/null +++ b/test/runtime/samples/spread-own-props/Widget.html @@ -0,0 +1,4 @@ +

foo: {foo}

+

baz: {baz} ({typeof baz})

+

qux: {qux}

+

quux: {quux}

diff --git a/test/runtime/samples/spread-own-props/_config.js b/test/runtime/samples/spread-own-props/_config.js new file mode 100644 index 0000000000..9fa96ab7e3 --- /dev/null +++ b/test/runtime/samples/spread-own-props/_config.js @@ -0,0 +1,31 @@ +export default { + props: { + foo: 'lol', + baz: 40 + 2, + qux: `this is a ${'piece of'} string`, + quux: 'core' + }, + + html: ` +

foo: lol

+

baz: 42 (number)

+

qux: named

+

quux: core

+ `, + + async test(assert, component, target) { + await component.$set({ + foo: 'wut', + baz: 40 + 3, + qux: `this is a ${'rather boring'} string`, + quux: 'heart' + }); + + assert.htmlEqual(target.innerHTML, ` +

foo: wut

+

baz: 43 (number)

+

qux: named

+

quux: heart

+ `); + } +}; diff --git a/test/runtime/samples/spread-own-props/main.html b/test/runtime/samples/spread-own-props/main.html new file mode 100644 index 0000000000..a41baf94dd --- /dev/null +++ b/test/runtime/samples/spread-own-props/main.html @@ -0,0 +1,11 @@ + + + + +
+ +