From 4dd5fc55948360fbc0d49566d6d7beaed809b7d5 Mon Sep 17 00:00:00 2001 From: Rich Harris <richard.a.harris@gmail.com> Date: Mon, 14 Aug 2017 11:34:24 -0400 Subject: [PATCH] apply optimisation to raw tags --- src/generators/dom/visitors/RawMustacheTag.ts | 45 +++++++++++++------ src/utils/CodeBuilder.ts | 8 ++-- test/helpers.js | 2 +- .../expected-bundle.js | 4 +- .../each-block-changed-check/expected.js | 4 +- .../helpers-invoked-if-changed/_config.js | 38 ---------------- .../_config.js | 26 +++++++++++ .../counter.js | 3 +- .../main.html | 9 +--- .../ignore-unchanged-attribute/_config.js | 26 +++++++++++ .../ignore-unchanged-attribute/counter.js | 3 ++ .../ignore-unchanged-attribute/main.html | 15 +++++++ .../samples/ignore-unchanged-raw/_config.js | 26 +++++++++++ .../samples/ignore-unchanged-raw/counter.js | 3 ++ .../samples/ignore-unchanged-raw/main.html | 15 +++++++ .../samples/ignore-unchanged-tag/_config.js | 26 +++++++++++ .../samples/ignore-unchanged-tag/counter.js | 3 ++ .../samples/ignore-unchanged-tag/main.html | 15 +++++++ 18 files changed, 203 insertions(+), 68 deletions(-) delete mode 100644 test/runtime/samples/helpers-invoked-if-changed/_config.js create mode 100644 test/runtime/samples/ignore-unchanged-attribute-compound/_config.js rename test/runtime/samples/{helpers-invoked-if-changed => ignore-unchanged-attribute-compound}/counter.js (53%) rename test/runtime/samples/{helpers-invoked-if-changed => ignore-unchanged-attribute-compound}/main.html (53%) create mode 100644 test/runtime/samples/ignore-unchanged-attribute/_config.js create mode 100644 test/runtime/samples/ignore-unchanged-attribute/counter.js create mode 100644 test/runtime/samples/ignore-unchanged-attribute/main.html create mode 100644 test/runtime/samples/ignore-unchanged-raw/_config.js create mode 100644 test/runtime/samples/ignore-unchanged-raw/counter.js create mode 100644 test/runtime/samples/ignore-unchanged-raw/main.html create mode 100644 test/runtime/samples/ignore-unchanged-tag/_config.js create mode 100644 test/runtime/samples/ignore-unchanged-tag/counter.js create mode 100644 test/runtime/samples/ignore-unchanged-tag/main.html diff --git a/src/generators/dom/visitors/RawMustacheTag.ts b/src/generators/dom/visitors/RawMustacheTag.ts index 41c3af9aa4..624e424983 100644 --- a/src/generators/dom/visitors/RawMustacheTag.ts +++ b/src/generators/dom/visitors/RawMustacheTag.ts @@ -12,12 +12,21 @@ export default function visitRawMustacheTag( ) { const name = node._state.basename; const before = node._state.name; - const value = block.getUniqueName(`${name}_value`); const after = block.getUniqueName(`${name}_after`); - const { snippet } = block.contextualise(node.expression); + const { dependencies, indexes, snippet } = block.contextualise(node.expression); - block.addVariable(value); + const hasChangeableIndex = Array.from(indexes).some(index => block.changeableIndexes.get(index)); + + const shouldCache = ( + node.expression.type !== 'Identifier' || + block.contexts.has(node.expression.name) || + hasChangeableIndex + ); + + const value = shouldCache && block.getUniqueName(`${name}_value`); + const init = shouldCache ? `${value} = ${snippet}` : snippet; + if (shouldCache) block.addVariable(value); // we would have used comments here, but the `insertAdjacentHTML` api only // exists for `Element`s. @@ -38,17 +47,27 @@ export default function visitRawMustacheTag( const isToplevel = !state.parentNode; - const mountStatement = `${before}.insertAdjacentHTML( 'afterend', ${value} = ${snippet} );`; - const detachStatement = `@detachBetween( ${before}, ${after} );`; + block.builders.mount.addLine(`${before}.insertAdjacentHTML( 'afterend', ${init} );`); + block.builders.detachRaw.addBlock(`@detachBetween( ${before}, ${after} );`); + + if (dependencies.length || hasChangeableIndex) { + const changedCheck = ( + ( block.hasOutroMethod ? `#outroing || ` : '' ) + + dependencies.map(dependency => `'${dependency}' in changed`).join(' || ') + ); - block.builders.mount.addLine(mountStatement); + const updateCachedValue = `${value} !== ( ${value} = ${snippet} )`; - block.builders.update.addBlock(deindent` - if ( ${value} !== ( ${value} = ${snippet} ) ) { - ${detachStatement} - ${mountStatement} - } - `); + const condition = shouldCache ? + ( dependencies.length ? `( ${changedCheck} ) && ${updateCachedValue}` : updateCachedValue ) : + changedCheck; - block.builders.detachRaw.addBlock(detachStatement); + block.builders.update.addConditionalLine( + condition, + deindent` + @detachBetween( ${before}, ${after} ); + ${before}.insertAdjacentHTML( 'afterend', ${shouldCache ? value : snippet} ); + ` + ); + } } diff --git a/src/utils/CodeBuilder.ts b/src/utils/CodeBuilder.ts index 934f29b9a4..2a412a8a06 100644 --- a/src/utils/CodeBuilder.ts +++ b/src/utils/CodeBuilder.ts @@ -21,15 +21,17 @@ export default class CodeBuilder { this.lastCondition = null; } - addConditionalLine(condition: string, line: string) { + addConditionalLine(condition: string, body: string) { + body = body.replace(/^/gm, '\t'); + if (condition === this.lastCondition) { - this.result += `\n\t${line}`; + this.result += `\n${body}`; } else { if (this.lastCondition) { this.result += `\n}`; } - this.result += `${this.last === ChunkType.Block ? '\n\n' : '\n'}if ( ${condition} ) {\n\t${line}`; + this.result += `${this.last === ChunkType.Block ? '\n\n' : '\n'}if ( ${condition} ) {\n${body}`; this.lastCondition = condition; } diff --git a/test/helpers.js b/test/helpers.js index f4be292ef4..e3343d4aa8 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -125,7 +125,7 @@ export function normalizeHtml(window, html) { .replace(/>[\s\r\n]+</g, '><') .trim(); cleanChildren(node, ''); - return node.innerHTML; + return node.innerHTML.replace(/<\/?noscript\/?>/g, ''); } export function setupHtmlEqual() { diff --git a/test/js/samples/each-block-changed-check/expected-bundle.js b/test/js/samples/each-block-changed-check/expected-bundle.js index e0cb0b9ea4..b9f5b5b766 100644 --- a/test/js/samples/each-block-changed-check/expected-bundle.js +++ b/test/js/samples/each-block-changed-check/expected-bundle.js @@ -297,9 +297,9 @@ function create_each_block ( state, each_block_value, comment, i, component ) { text_4.data = text_4_value; } - if ( raw_value !== ( raw_value = comment.html ) ) { + if ( ( 'comments' in changed ) && raw_value !== ( raw_value = comment.html ) ) { detachBetween( raw_before, raw_after ); - raw_before.insertAdjacentHTML( 'afterend', raw_value = comment.html ); + raw_before.insertAdjacentHTML( 'afterend', raw_value ); } }, diff --git a/test/js/samples/each-block-changed-check/expected.js b/test/js/samples/each-block-changed-check/expected.js index 48ac7c6613..18ba61d59a 100644 --- a/test/js/samples/each-block-changed-check/expected.js +++ b/test/js/samples/each-block-changed-check/expected.js @@ -123,9 +123,9 @@ function create_each_block ( state, each_block_value, comment, i, component ) { text_4.data = text_4_value; } - if ( raw_value !== ( raw_value = comment.html ) ) { + if ( ( 'comments' in changed ) && raw_value !== ( raw_value = comment.html ) ) { detachBetween( raw_before, raw_after ); - raw_before.insertAdjacentHTML( 'afterend', raw_value = comment.html ); + raw_before.insertAdjacentHTML( 'afterend', raw_value ); } }, diff --git a/test/runtime/samples/helpers-invoked-if-changed/_config.js b/test/runtime/samples/helpers-invoked-if-changed/_config.js deleted file mode 100644 index fd8145ae74..0000000000 --- a/test/runtime/samples/helpers-invoked-if-changed/_config.js +++ /dev/null @@ -1,38 +0,0 @@ -import counter from './counter.js'; - -export default { - data: { - x: 1, - y: 2, - z: 3 - }, - - html: ` - <p>1</p> - <p class='2'>3</p> - `, - - test(assert, component) { - counter.y = counter.z = 0; - - component.set({ x: 4 }); - assert.equal(counter.y, 0); - assert.equal(counter.z, 0); - - component.set({ x: 5, y: 6 }); - assert.equal(counter.y, 1); - assert.equal(counter.z, 0); - - component.set({ x: 6, y: 6 }); - assert.equal(counter.y, 1); - assert.equal(counter.z, 0); - - component.set({ z: 7 }); - assert.equal(counter.y, 1); - assert.equal(counter.z, 1); - - component.set({ x: 8, z: 7 }); - assert.equal(counter.y, 1); - assert.equal(counter.z, 1); - } -}; diff --git a/test/runtime/samples/ignore-unchanged-attribute-compound/_config.js b/test/runtime/samples/ignore-unchanged-attribute-compound/_config.js new file mode 100644 index 0000000000..5780a83e6c --- /dev/null +++ b/test/runtime/samples/ignore-unchanged-attribute-compound/_config.js @@ -0,0 +1,26 @@ +import counter from './counter.js'; + +export default { + data: { + x: 1, + y: 2 + }, + + html: ` + <p>1</p> + <p class='-2-'></p> + `, + + test(assert, component) { + counter.count = 0; + + component.set({ x: 3 }); + assert.equal(counter.count, 0); + + component.set({ x: 4, y: 5 }); + assert.equal(counter.count, 1); + + component.set({ x: 5, y: 5 }); + assert.equal(counter.count, 1); + } +}; diff --git a/test/runtime/samples/helpers-invoked-if-changed/counter.js b/test/runtime/samples/ignore-unchanged-attribute-compound/counter.js similarity index 53% rename from test/runtime/samples/helpers-invoked-if-changed/counter.js rename to test/runtime/samples/ignore-unchanged-attribute-compound/counter.js index 5367ba3c2b..63872cd6a2 100644 --- a/test/runtime/samples/helpers-invoked-if-changed/counter.js +++ b/test/runtime/samples/ignore-unchanged-attribute-compound/counter.js @@ -1,4 +1,3 @@ export default { - y: 0, - z: 0 + count: 0 }; \ No newline at end of file diff --git a/test/runtime/samples/helpers-invoked-if-changed/main.html b/test/runtime/samples/ignore-unchanged-attribute-compound/main.html similarity index 53% rename from test/runtime/samples/helpers-invoked-if-changed/main.html rename to test/runtime/samples/ignore-unchanged-attribute-compound/main.html index 1bcd70c4c1..1e742befd9 100644 --- a/test/runtime/samples/helpers-invoked-if-changed/main.html +++ b/test/runtime/samples/ignore-unchanged-attribute-compound/main.html @@ -1,18 +1,13 @@ <p>{{x}}</p> -<p class='{{getClass(y)}}'>{{myHelper(z)}}</p> +<p class='-{{myHelper(y)}}-'></p> <script> import counter from './counter.js'; export default { helpers: { - getClass(value) { - counter.y += 1; - return value; - }, - myHelper(value) { - counter.z += 1; + counter.count += 1; return value; } } diff --git a/test/runtime/samples/ignore-unchanged-attribute/_config.js b/test/runtime/samples/ignore-unchanged-attribute/_config.js new file mode 100644 index 0000000000..eb62f5dd8e --- /dev/null +++ b/test/runtime/samples/ignore-unchanged-attribute/_config.js @@ -0,0 +1,26 @@ +import counter from './counter.js'; + +export default { + data: { + x: 1, + y: 2 + }, + + html: ` + <p>1</p> + <p class='2'></p> + `, + + test(assert, component) { + counter.count = 0; + + component.set({ x: 3 }); + assert.equal(counter.count, 0); + + component.set({ x: 4, y: 5 }); + assert.equal(counter.count, 1); + + component.set({ x: 5, y: 5 }); + assert.equal(counter.count, 1); + } +}; diff --git a/test/runtime/samples/ignore-unchanged-attribute/counter.js b/test/runtime/samples/ignore-unchanged-attribute/counter.js new file mode 100644 index 0000000000..63872cd6a2 --- /dev/null +++ b/test/runtime/samples/ignore-unchanged-attribute/counter.js @@ -0,0 +1,3 @@ +export default { + count: 0 +}; \ No newline at end of file diff --git a/test/runtime/samples/ignore-unchanged-attribute/main.html b/test/runtime/samples/ignore-unchanged-attribute/main.html new file mode 100644 index 0000000000..afaa83c85f --- /dev/null +++ b/test/runtime/samples/ignore-unchanged-attribute/main.html @@ -0,0 +1,15 @@ +<p>{{x}}</p> +<p class='{{myHelper(y)}}'></p> + +<script> + import counter from './counter.js'; + + export default { + helpers: { + myHelper(value) { + counter.count += 1; + return value; + } + } + }; +</script> diff --git a/test/runtime/samples/ignore-unchanged-raw/_config.js b/test/runtime/samples/ignore-unchanged-raw/_config.js new file mode 100644 index 0000000000..b8c58367b3 --- /dev/null +++ b/test/runtime/samples/ignore-unchanged-raw/_config.js @@ -0,0 +1,26 @@ +import counter from './counter.js'; + +export default { + data: { + x: 1, + y: 2 + }, + + html: ` + <p>1</p> + <p>2</p> + `, + + test(assert, component) { + counter.count = 0; + + component.set({ x: 3 }); + assert.equal(counter.count, 0); + + component.set({ x: 4, y: 5 }); + assert.equal(counter.count, 1); + + component.set({ x: 5, y: 5 }); + assert.equal(counter.count, 1); + } +}; diff --git a/test/runtime/samples/ignore-unchanged-raw/counter.js b/test/runtime/samples/ignore-unchanged-raw/counter.js new file mode 100644 index 0000000000..63872cd6a2 --- /dev/null +++ b/test/runtime/samples/ignore-unchanged-raw/counter.js @@ -0,0 +1,3 @@ +export default { + count: 0 +}; \ No newline at end of file diff --git a/test/runtime/samples/ignore-unchanged-raw/main.html b/test/runtime/samples/ignore-unchanged-raw/main.html new file mode 100644 index 0000000000..375985c0d2 --- /dev/null +++ b/test/runtime/samples/ignore-unchanged-raw/main.html @@ -0,0 +1,15 @@ +<p>{{x}}</p> +<p>{{{myHelper(y)}}}</p> + +<script> + import counter from './counter.js'; + + export default { + helpers: { + myHelper(value) { + counter.count += 1; + return value; + } + } + }; +</script> diff --git a/test/runtime/samples/ignore-unchanged-tag/_config.js b/test/runtime/samples/ignore-unchanged-tag/_config.js new file mode 100644 index 0000000000..b8c58367b3 --- /dev/null +++ b/test/runtime/samples/ignore-unchanged-tag/_config.js @@ -0,0 +1,26 @@ +import counter from './counter.js'; + +export default { + data: { + x: 1, + y: 2 + }, + + html: ` + <p>1</p> + <p>2</p> + `, + + test(assert, component) { + counter.count = 0; + + component.set({ x: 3 }); + assert.equal(counter.count, 0); + + component.set({ x: 4, y: 5 }); + assert.equal(counter.count, 1); + + component.set({ x: 5, y: 5 }); + assert.equal(counter.count, 1); + } +}; diff --git a/test/runtime/samples/ignore-unchanged-tag/counter.js b/test/runtime/samples/ignore-unchanged-tag/counter.js new file mode 100644 index 0000000000..63872cd6a2 --- /dev/null +++ b/test/runtime/samples/ignore-unchanged-tag/counter.js @@ -0,0 +1,3 @@ +export default { + count: 0 +}; \ No newline at end of file diff --git a/test/runtime/samples/ignore-unchanged-tag/main.html b/test/runtime/samples/ignore-unchanged-tag/main.html new file mode 100644 index 0000000000..f50864df94 --- /dev/null +++ b/test/runtime/samples/ignore-unchanged-tag/main.html @@ -0,0 +1,15 @@ +<p>{{x}}</p> +<p>{{myHelper(y)}}</p> + +<script> + import counter from './counter.js'; + + export default { + helpers: { + myHelper(value) { + counter.count += 1; + return value; + } + } + }; +</script>