From df71497cc941010cf4fe0f46315ac94d9e2e8fd3 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Tue, 11 Jun 2019 20:41:46 -0400 Subject: [PATCH 1/2] only set attributes via properties when truly necessary (#1434) --- .../render-dom/wrappers/Element/Attribute.ts | 179 +----------------- 1 file changed, 8 insertions(+), 171 deletions(-) diff --git a/src/compiler/compile/render-dom/wrappers/Element/Attribute.ts b/src/compiler/compile/render-dom/wrappers/Element/Attribute.ts index 669a3774e31a..f55e731fdbfa 100644 --- a/src/compiler/compile/render-dom/wrappers/Element/Attribute.ts +++ b/src/compiler/compile/render-dom/wrappers/Element/Attribute.ts @@ -224,79 +224,20 @@ export default class AttributeWrapper { } } -// source: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes +// source: https://html.spec.whatwg.org/multipage/indices.html const attribute_lookup = { - accept: { applies_to: ['form', 'input'] }, - 'accept-charset': { property_name: 'acceptCharset', applies_to: ['form'] }, - accesskey: { property_name: 'accessKey' }, - action: { applies_to: ['form'] }, - align: { - applies_to: [ - 'applet', - 'caption', - 'col', - 'colgroup', - 'hr', - 'iframe', - 'img', - 'table', - 'tbody', - 'td', - 'tfoot', - 'th', - 'thead', - 'tr', - ], - }, allowfullscreen: { property_name: 'allowFullscreen', applies_to: ['iframe'] }, - alt: { applies_to: ['applet', 'area', 'img', 'input'] }, + allowpaymentrequest: { property_name: 'allowPaymentRequest', applies_to: ['iframe'] }, async: { applies_to: ['script'] }, - autocomplete: { applies_to: ['form', 'input'] }, autofocus: { applies_to: ['button', 'input', 'keygen', 'select', 'textarea'] }, autoplay: { applies_to: ['audio', 'video'] }, - autosave: { applies_to: ['input'] }, - bgcolor: { - property_name: 'bgColor', - applies_to: [ - 'body', - 'col', - 'colgroup', - 'marquee', - 'table', - 'tbody', - 'tfoot', - 'td', - 'th', - 'tr', - ], - }, - border: { applies_to: ['img', 'object', 'table'] }, - buffered: { applies_to: ['audio', 'video'] }, - challenge: { applies_to: ['keygen'] }, - charset: { applies_to: ['meta', 'script'] }, - checked: { applies_to: ['command', 'input'] }, - cite: { applies_to: ['blockquote', 'del', 'ins', 'q'] }, - class: { property_name: 'className' }, - code: { applies_to: ['applet'] }, - codebase: { property_name: 'codeBase', applies_to: ['applet'] }, - color: { applies_to: ['basefont', 'font', 'hr'] }, - cols: { applies_to: ['textarea'] }, - colspan: { property_name: 'colSpan', applies_to: ['td', 'th'] }, - content: { applies_to: ['meta'] }, - contenteditable: { property_name: 'contentEditable' }, - contextmenu: {}, + checked: { applies_to: ['input'] }, controls: { applies_to: ['audio', 'video'] }, - coords: { applies_to: ['area'] }, - data: { applies_to: ['object'] }, - datetime: { property_name: 'dateTime', applies_to: ['del', 'ins', 'time'] }, default: { applies_to: ['track'] }, defer: { applies_to: ['script'] }, - dir: {}, - dirname: { property_name: 'dirName', applies_to: ['input', 'textarea'] }, disabled: { applies_to: [ 'button', - 'command', 'fieldset', 'input', 'keygen', @@ -306,119 +247,21 @@ const attribute_lookup = { 'textarea', ], }, - download: { applies_to: ['a', 'area'] }, - draggable: {}, - dropzone: {}, - enctype: { applies_to: ['form'] }, - for: { property_name: 'htmlFor', applies_to: ['label', 'output'] }, - formaction: { applies_to: ['input', 'button'] }, - headers: { applies_to: ['td', 'th'] }, - height: { - applies_to: ['canvas', 'embed', 'iframe', 'img', 'input', 'object', 'video'], - }, + formnovalidate: { property_name: 'formNoValidate', applies_to: ['button', 'input'] }, hidden: {}, - high: { applies_to: ['meter'] }, - href: { applies_to: ['a', 'area', 'base', 'link'] }, - hreflang: { applies_to: ['a', 'area', 'link'] }, - 'http-equiv': { property_name: 'httpEquiv', applies_to: ['meta'] }, - icon: { applies_to: ['command'] }, - id: {}, indeterminate: { applies_to: ['input'] }, ismap: { property_name: 'isMap', applies_to: ['img'] }, - itemprop: {}, - keytype: { applies_to: ['keygen'] }, - kind: { applies_to: ['track'] }, - label: { applies_to: ['track'] }, - lang: {}, - language: { applies_to: ['script'] }, - loop: { applies_to: ['audio', 'bgsound', 'marquee', 'video'] }, - low: { applies_to: ['meter'] }, - manifest: { applies_to: ['html'] }, - max: { applies_to: ['input', 'meter', 'progress'] }, - maxlength: { property_name: 'maxLength', applies_to: ['input', 'textarea'] }, - media: { applies_to: ['a', 'area', 'link', 'source', 'style'] }, - method: { applies_to: ['form'] }, - min: { applies_to: ['input', 'meter'] }, + loop: { applies_to: ['audio', 'bgsound', 'video'] }, multiple: { applies_to: ['input', 'select'] }, muted: { applies_to: ['audio', 'video'] }, - name: { - applies_to: [ - 'button', - 'form', - 'fieldset', - 'iframe', - 'input', - 'keygen', - 'object', - 'output', - 'select', - 'textarea', - 'map', - 'meta', - 'param', - ], - }, + nomodule: { property_name: 'noModule', applies_to: ['script'] }, novalidate: { property_name: 'noValidate', applies_to: ['form'] }, - open: { applies_to: ['details'] }, - optimum: { applies_to: ['meter'] }, - pattern: { applies_to: ['input'] }, - ping: { applies_to: ['a', 'area'] }, - placeholder: { applies_to: ['input', 'textarea'] }, - poster: { applies_to: ['video'] }, - preload: { applies_to: ['audio', 'video'] }, - radiogroup: { applies_to: ['command'] }, + open: { applies_to: ['details', 'dialog'] }, + playsinline: { property_name: 'playsInline', applies_to: ['video'] }, readonly: { property_name: 'readOnly', applies_to: ['input', 'textarea'] }, - rel: { applies_to: ['a', 'area', 'link'] }, required: { applies_to: ['input', 'select', 'textarea'] }, reversed: { applies_to: ['ol'] }, - rows: { applies_to: ['textarea'] }, - rowspan: { property_name: 'rowSpan', applies_to: ['td', 'th'] }, - sandbox: { applies_to: ['iframe'] }, - scope: { applies_to: ['th'] }, - scoped: { applies_to: ['style'] }, - seamless: { applies_to: ['iframe'] }, selected: { applies_to: ['option'] }, - shape: { applies_to: ['a', 'area'] }, - size: { applies_to: ['input', 'select'] }, - sizes: { applies_to: ['link', 'img', 'source'] }, - span: { applies_to: ['col', 'colgroup'] }, - spellcheck: {}, - src: { - applies_to: [ - 'audio', - 'embed', - 'iframe', - 'img', - 'input', - 'script', - 'source', - 'track', - 'video', - ], - }, - srcdoc: { applies_to: ['iframe'] }, - srclang: { applies_to: ['track'] }, - srcset: { applies_to: ['img'] }, - start: { applies_to: ['ol'] }, - step: { applies_to: ['input'] }, - style: { property_name: 'style.cssText' }, - summary: { applies_to: ['table'] }, - tabindex: { property_name: 'tabIndex' }, - target: { applies_to: ['a', 'area', 'base', 'form'] }, - title: {}, - type: { - applies_to: [ - 'button', - 'command', - 'embed', - 'object', - 'script', - 'source', - 'style', - 'menu', - ], - }, - usemap: { property_name: 'useMap', applies_to: ['img', 'input', 'object'] }, value: { applies_to: [ 'button', @@ -432,12 +275,6 @@ const attribute_lookup = { 'textarea', ], }, - volume: { applies_to: ['audio', 'video'] }, - playbackRate: { applies_to: ['audio', 'video'] }, - width: { - applies_to: ['canvas', 'embed', 'iframe', 'img', 'input', 'object', 'video'], - }, - wrap: { applies_to: ['textarea'] }, }; Object.keys(attribute_lookup).forEach(name => { From b3a52b483ffdeb127978539388aa584ccd0702ad Mon Sep 17 00:00:00 2001 From: Conduitry Date: Tue, 11 Jun 2019 20:42:37 -0400 Subject: [PATCH 2/2] update tests (#1434) (#2935) --- test/js/samples/action/expected.js | 3 ++- .../collapses-text-around-comments/expected.js | 3 ++- test/js/samples/css-media-query/expected.js | 3 ++- .../samples/each-block-changed-check/expected.js | 5 +++-- .../samples/event-handler-no-passive/expected.js | 3 ++- test/js/samples/head-no-whitespace/expected.js | 9 +++++---- .../samples/inline-style-unoptimized/expected.js | 9 +++++---- .../samples/contenteditable-html/_config.js | 14 +++++++------- .../samples/contenteditable-text/_config.js | 6 +++--- test/runtime/samples/set-undefined-attr/_config.js | 4 ++-- .../runtime/samples/set-undefined-attr/main.svelte | 3 ++- 11 files changed, 35 insertions(+), 27 deletions(-) diff --git a/test/js/samples/action/expected.js b/test/js/samples/action/expected.js index cbbcb0d3174e..ed0a0a74304b 100644 --- a/test/js/samples/action/expected.js +++ b/test/js/samples/action/expected.js @@ -1,6 +1,7 @@ /* generated by Svelte vX.Y.Z */ import { SvelteComponent, + attr, detach, element, init, @@ -16,7 +17,7 @@ function create_fragment(ctx) { c() { a = element("a"); a.textContent = "Test"; - a.href = "#"; + attr(a, "href", "#"); }, m(target, anchor) { diff --git a/test/js/samples/collapses-text-around-comments/expected.js b/test/js/samples/collapses-text-around-comments/expected.js index 12164d0579b0..09b40a1e9827 100644 --- a/test/js/samples/collapses-text-around-comments/expected.js +++ b/test/js/samples/collapses-text-around-comments/expected.js @@ -2,6 +2,7 @@ import { SvelteComponent, append, + attr, detach, element, init, @@ -26,7 +27,7 @@ function create_fragment(ctx) { c() { p = element("p"); t = text(ctx.foo); - p.className = "svelte-1a7i8ec"; + attr(p, "class", "svelte-1a7i8ec"); }, m(target, anchor) { diff --git a/test/js/samples/css-media-query/expected.js b/test/js/samples/css-media-query/expected.js index d4be134376cc..82b7c5dfc800 100644 --- a/test/js/samples/css-media-query/expected.js +++ b/test/js/samples/css-media-query/expected.js @@ -2,6 +2,7 @@ import { SvelteComponent, append, + attr, detach, element, init, @@ -23,7 +24,7 @@ function create_fragment(ctx) { return { c() { div = element("div"); - div.className = "svelte-1slhpfn"; + attr(div, "class", "svelte-1slhpfn"); }, m(target, anchor) { diff --git a/test/js/samples/each-block-changed-check/expected.js b/test/js/samples/each-block-changed-check/expected.js index 951565bae4b0..b4d4577df345 100644 --- a/test/js/samples/each-block-changed-check/expected.js +++ b/test/js/samples/each-block-changed-check/expected.js @@ -2,6 +2,7 @@ import { SvelteComponent, append, + attr, destroy_each, detach, detach_after, @@ -39,8 +40,8 @@ function create_each_block(ctx) { t5 = text(" ago:"); t6 = space(); raw_before = element('noscript'); - span.className = "meta"; - div.className = "comment"; + attr(span, "class", "meta"); + attr(div, "class", "comment"); }, m(target, anchor) { diff --git a/test/js/samples/event-handler-no-passive/expected.js b/test/js/samples/event-handler-no-passive/expected.js index 41fcbeeb2a84..e8ca72c556d2 100644 --- a/test/js/samples/event-handler-no-passive/expected.js +++ b/test/js/samples/event-handler-no-passive/expected.js @@ -1,6 +1,7 @@ /* generated by Svelte vX.Y.Z */ import { SvelteComponent, + attr, detach, element, init, @@ -17,7 +18,7 @@ function create_fragment(ctx) { c() { a = element("a"); a.textContent = "this should not navigate to example.com"; - a.href = "https://example.com"; + attr(a, "href", "https://example.com"); dispose = listen(a, "touchstart", touchstart_handler); }, diff --git a/test/js/samples/head-no-whitespace/expected.js b/test/js/samples/head-no-whitespace/expected.js index b95177bba781..457df77dc861 100644 --- a/test/js/samples/head-no-whitespace/expected.js +++ b/test/js/samples/head-no-whitespace/expected.js @@ -2,6 +2,7 @@ import { SvelteComponent, append, + attr, detach, element, init, @@ -16,10 +17,10 @@ function create_fragment(ctx) { c() { meta0 = element("meta"); meta1 = element("meta"); - meta0.name = "twitter:creator"; - meta0.content = "@sveltejs"; - meta1.name = "twitter:title"; - meta1.content = "Svelte"; + attr(meta0, "name", "twitter:creator"); + attr(meta0, "content", "@sveltejs"); + attr(meta1, "name", "twitter:title"); + attr(meta1, "content", "Svelte"); }, m(target, anchor) { diff --git a/test/js/samples/inline-style-unoptimized/expected.js b/test/js/samples/inline-style-unoptimized/expected.js index 8f3b66882790..9349ade12caa 100644 --- a/test/js/samples/inline-style-unoptimized/expected.js +++ b/test/js/samples/inline-style-unoptimized/expected.js @@ -1,6 +1,7 @@ /* generated by Svelte vX.Y.Z */ import { SvelteComponent, + attr, detach, element, init, @@ -18,8 +19,8 @@ function create_fragment(ctx) { div0 = element("div"); t = space(); div1 = element("div"); - div0.style.cssText = ctx.style; - div1.style.cssText = div1_style_value = "" + ctx.key + ": " + ctx.value; + attr(div0, "style", ctx.style); + attr(div1, "style", div1_style_value = "" + ctx.key + ": " + ctx.value); }, m(target, anchor) { @@ -30,11 +31,11 @@ function create_fragment(ctx) { p(changed, ctx) { if (changed.style) { - div0.style.cssText = ctx.style; + attr(div0, "style", ctx.style); } if ((changed.key || changed.value) && div1_style_value !== (div1_style_value = "" + ctx.key + ": " + ctx.value)) { - div1.style.cssText = div1_style_value; + attr(div1, "style", div1_style_value); } }, diff --git a/test/runtime/samples/contenteditable-html/_config.js b/test/runtime/samples/contenteditable-html/_config.js index cd2a82265547..285512b6c98c 100644 --- a/test/runtime/samples/contenteditable-html/_config.js +++ b/test/runtime/samples/contenteditable-html/_config.js @@ -4,7 +4,7 @@ export default { }, html: ` - world + world

hello world

`, @@ -19,24 +19,24 @@ export default { el.innerHTML = 'everybody'; - // No updates to data yet + // No updates to data yet assert.htmlEqual(target.innerHTML, ` - everybody + everybody

hello world

`); - // Handle user input - const event = new window.Event('input'); + // Handle user input + const event = new window.Event('input'); await el.dispatchEvent(event); assert.htmlEqual(target.innerHTML, ` - everybody + everybody

hello everybody

`); component.name = 'goodbye'; assert.equal(el.innerHTML, 'goodbye'); assert.htmlEqual(target.innerHTML, ` - goodbye + goodbye

hello goodbye

`); }, diff --git a/test/runtime/samples/contenteditable-text/_config.js b/test/runtime/samples/contenteditable-text/_config.js index 4935a3a9a751..059cda7cfeac 100644 --- a/test/runtime/samples/contenteditable-text/_config.js +++ b/test/runtime/samples/contenteditable-text/_config.js @@ -4,7 +4,7 @@ export default { }, html: ` - world + world

hello world

`, @@ -23,14 +23,14 @@ export default { await el.dispatchEvent(event); assert.htmlEqual(target.innerHTML, ` - everybody + everybody

hello everybody

`); component.name = 'goodbye'; assert.equal(el.textContent, 'goodbye'); assert.htmlEqual(target.innerHTML, ` - goodbye + goodbye

hello goodbye

`); }, diff --git a/test/runtime/samples/set-undefined-attr/_config.js b/test/runtime/samples/set-undefined-attr/_config.js index e28bad8257ed..b23f51dfc962 100644 --- a/test/runtime/samples/set-undefined-attr/_config.js +++ b/test/runtime/samples/set-undefined-attr/_config.js @@ -1,5 +1,5 @@ export default { - html: '
', + html: `
`, - ssrHtml: '
' + ssrHtml: `
`, }; diff --git a/test/runtime/samples/set-undefined-attr/main.svelte b/test/runtime/samples/set-undefined-attr/main.svelte index 8191acbefff6..77a1885415dc 100644 --- a/test/runtime/samples/set-undefined-attr/main.svelte +++ b/test/runtime/samples/set-undefined-attr/main.svelte @@ -3,10 +3,11 @@ export let foo = 1; export let bar; + export let _class; onMount(() => { foo = undefined; }); -
+