From d3cc92c9172aecbb4836397b1fb6878df84684bf Mon Sep 17 00:00:00 2001 From: Cory Virok Date: Tue, 17 May 2022 02:09:53 -0700 Subject: [PATCH 1/8] Implemented a runtime optimization for SSR. Prior to this change, the compiler would generate a template literal that had many purely static string variables nested within it. This change collapses these static strings into the surrounding template literal which should result in (minor) size and performance improvements for the SSR generated code. --- src/compiler/compile/render_ssr/Renderer.ts | 41 +++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/compiler/compile/render_ssr/Renderer.ts b/src/compiler/compile/render_ssr/Renderer.ts index 64e9ee1f4e81..3abefc8fdd7e 100644 --- a/src/compiler/compile/render_ssr/Renderer.ts +++ b/src/compiler/compile/render_ssr/Renderer.ts @@ -106,6 +106,11 @@ export default class Renderer { this.current = last.current; } + // Optimize the TemplateLiteral to remove unnecessary nodes + // that both increase code size but also add additional and + // unnecessary string formatting at runtime. + collapse_literal(popped.literal) + return popped.literal; } @@ -121,3 +126,39 @@ export default class Renderer { }); } } + +// Collapse string literals together +function collapse_literal(literal: TemplateLiteral) { + if (literal.quasis.length) { + // flatMap() to produce an array containing [quasi, expr, quasi, expr, ..., quasi] + const zip = literal.quasis.reduce((acc, cur, index) => { + const expr = literal.expressions[index] + acc.push(cur) + if (expr) { + acc.push(expr) + } + return acc + }, []); + + // If an expression is a simple string literal, combine it with its preceeding + // and following quasi + let curQuasi = zip[0] + const newZip = [curQuasi] + for (let i = 1; i < zip.length; i += 2) { + const expr = zip[i] + const nextQuasi = zip[i + 1] + if (expr.type === 'Literal' && typeof expr.value === 'string') { + curQuasi.value.raw += escape_template(expr.value) + nextQuasi.value.raw + } else { + newZip.push(expr) + newZip.push(nextQuasi) + curQuasi = nextQuasi + } + } + + // Reconstitute the quasi and expressions arrays + literal.quasis = newZip.filter((_, index) => index % 2 === 0) + literal.expressions = newZip.filter((_, index) => index % 2 === 1) + } + +} From 737bc427a27b56e7f4f5f0e721bc4e580652e72c Mon Sep 17 00:00:00 2001 From: Cory Virok Date: Tue, 17 May 2022 02:42:39 -0700 Subject: [PATCH 2/8] Fixup styling --- src/compiler/compile/render_ssr/Renderer.ts | 33 ++++++++++----------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/compiler/compile/render_ssr/Renderer.ts b/src/compiler/compile/render_ssr/Renderer.ts index 3abefc8fdd7e..952f4a4adb61 100644 --- a/src/compiler/compile/render_ssr/Renderer.ts +++ b/src/compiler/compile/render_ssr/Renderer.ts @@ -107,9 +107,7 @@ export default class Renderer { } // Optimize the TemplateLiteral to remove unnecessary nodes - // that both increase code size but also add additional and - // unnecessary string formatting at runtime. - collapse_literal(popped.literal) + collapse_literal(popped.literal); return popped.literal; } @@ -132,33 +130,32 @@ function collapse_literal(literal: TemplateLiteral) { if (literal.quasis.length) { // flatMap() to produce an array containing [quasi, expr, quasi, expr, ..., quasi] const zip = literal.quasis.reduce((acc, cur, index) => { - const expr = literal.expressions[index] - acc.push(cur) + const expr = literal.expressions[index]; + acc.push(cur); if (expr) { - acc.push(expr) + acc.push(expr); } - return acc + return acc; }, []); // If an expression is a simple string literal, combine it with its preceeding // and following quasi - let curQuasi = zip[0] - const newZip = [curQuasi] + let curQuasi = zip[0]; + const newZip = [curQuasi]; for (let i = 1; i < zip.length; i += 2) { - const expr = zip[i] - const nextQuasi = zip[i + 1] + const expr = zip[i]; + const nextQuasi = zip[i + 1]; if (expr.type === 'Literal' && typeof expr.value === 'string') { - curQuasi.value.raw += escape_template(expr.value) + nextQuasi.value.raw + curQuasi.value.raw += escape_template(expr.value) + nextQuasi.value.raw; } else { - newZip.push(expr) - newZip.push(nextQuasi) - curQuasi = nextQuasi + newZip.push(expr); + newZip.push(nextQuasi); + curQuasi = nextQuasi; } } // Reconstitute the quasi and expressions arrays - literal.quasis = newZip.filter((_, index) => index % 2 === 0) - literal.expressions = newZip.filter((_, index) => index % 2 === 1) + literal.quasis = newZip.filter((_, index) => index % 2 === 0); + literal.expressions = newZip.filter((_, index) => index % 2 === 1); } - } From a77830b7bc0d2e31498d246f160d166b51a53a9b Mon Sep 17 00:00:00 2001 From: Cory Virok Date: Tue, 17 May 2022 11:10:25 -0700 Subject: [PATCH 3/8] Code review feedback - fixed style + added test --- src/compiler/compile/render_ssr/Renderer.ts | 37 +------------------ .../utils/collapse_template_literal.ts | 37 +++++++++++++++++++ .../samples/collapse-literal-ssr/_config.js | 6 +++ .../samples/collapse-literal-ssr/expected.js | 28 ++++++++++++++ .../samples/collapse-literal-ssr/input.svelte | 23 ++++++++++++ 5 files changed, 96 insertions(+), 35 deletions(-) create mode 100644 src/compiler/compile/utils/collapse_template_literal.ts create mode 100644 test/js/samples/collapse-literal-ssr/_config.js create mode 100644 test/js/samples/collapse-literal-ssr/expected.js create mode 100644 test/js/samples/collapse-literal-ssr/input.svelte diff --git a/src/compiler/compile/render_ssr/Renderer.ts b/src/compiler/compile/render_ssr/Renderer.ts index 952f4a4adb61..0e4724ea0e27 100644 --- a/src/compiler/compile/render_ssr/Renderer.ts +++ b/src/compiler/compile/render_ssr/Renderer.ts @@ -16,6 +16,7 @@ import Title from './handlers/Title'; import { AppendTarget, CompileOptions } from '../../interfaces'; import { INode } from '../nodes/interfaces'; import { Expression, TemplateLiteral, Identifier } from 'estree'; +import { collapse_template_literal } from '../utils/collapse_template_literal'; import { escape_template } from '../utils/stringify'; type Handler = (node: any, renderer: Renderer, options: CompileOptions) => void; @@ -107,7 +108,7 @@ export default class Renderer { } // Optimize the TemplateLiteral to remove unnecessary nodes - collapse_literal(popped.literal); + collapse_template_literal(popped.literal); return popped.literal; } @@ -125,37 +126,3 @@ export default class Renderer { } } -// Collapse string literals together -function collapse_literal(literal: TemplateLiteral) { - if (literal.quasis.length) { - // flatMap() to produce an array containing [quasi, expr, quasi, expr, ..., quasi] - const zip = literal.quasis.reduce((acc, cur, index) => { - const expr = literal.expressions[index]; - acc.push(cur); - if (expr) { - acc.push(expr); - } - return acc; - }, []); - - // If an expression is a simple string literal, combine it with its preceeding - // and following quasi - let curQuasi = zip[0]; - const newZip = [curQuasi]; - for (let i = 1; i < zip.length; i += 2) { - const expr = zip[i]; - const nextQuasi = zip[i + 1]; - if (expr.type === 'Literal' && typeof expr.value === 'string') { - curQuasi.value.raw += escape_template(expr.value) + nextQuasi.value.raw; - } else { - newZip.push(expr); - newZip.push(nextQuasi); - curQuasi = nextQuasi; - } - } - - // Reconstitute the quasi and expressions arrays - literal.quasis = newZip.filter((_, index) => index % 2 === 0); - literal.expressions = newZip.filter((_, index) => index % 2 === 1); - } -} diff --git a/src/compiler/compile/utils/collapse_template_literal.ts b/src/compiler/compile/utils/collapse_template_literal.ts new file mode 100644 index 000000000000..bace266ce52f --- /dev/null +++ b/src/compiler/compile/utils/collapse_template_literal.ts @@ -0,0 +1,37 @@ +import { TemplateLiteral } from 'estree'; +import { escape_template } from './stringify'; + +// Collapse string literals together +export function collapse_template_literal(literal: TemplateLiteral) { + if (literal.quasis.length) { + // flatMap() to produce an array containing [quasi, expr, quasi, expr, ..., quasi] + const zip = literal.quasis.reduce((acc, cur, index) => { + const expr = literal.expressions[index]; + acc.push(cur); + if (expr) { + acc.push(expr); + } + return acc; + }, []); + + // If an expression is a simple string literal, combine it with its preceding + // and following quasi + let cur_quasi = zip[0]; + const new_zip = [cur_quasi]; + for (let i = 1; i < zip.length; i += 2) { + const expr = zip[i]; + const next_quasi = zip[i + 1]; + if (expr.type === 'Literal' && typeof expr.value === 'string') { + cur_quasi.value.raw += escape_template(expr.value) + next_quasi.value.raw; + } else { + new_zip.push(expr); + new_zip.push(next_quasi); + cur_quasi = next_quasi; + } + } + + // Reconstitute the quasi and expressions arrays + literal.quasis = new_zip.filter((_, index) => index % 2 === 0); + literal.expressions = new_zip.filter((_, index) => index % 2 === 1); + } +} diff --git a/test/js/samples/collapse-literal-ssr/_config.js b/test/js/samples/collapse-literal-ssr/_config.js new file mode 100644 index 000000000000..c4070b9a3b0f --- /dev/null +++ b/test/js/samples/collapse-literal-ssr/_config.js @@ -0,0 +1,6 @@ +export default { + options: { + generate: 'ssr', + dev: true + } +}; diff --git a/test/js/samples/collapse-literal-ssr/expected.js b/test/js/samples/collapse-literal-ssr/expected.js new file mode 100644 index 000000000000..ee431fb51dc7 --- /dev/null +++ b/test/js/samples/collapse-literal-ssr/expected.js @@ -0,0 +1,28 @@ +/* generated by Svelte vX.Y.Z */ +import { add_attribute, create_ssr_component, escape } from "svelte/internal"; + +const const1 = 1; +const const2 = 'const2'; + +function foo() { + return ''; +} + +const Component = create_ssr_component(($$result, $$props, $$bindings, slots) => { + return ` +
-
+ + +- +- +- +- +- + + +
-
+
-
+
-
`; +}); + +export default Component; \ No newline at end of file diff --git a/test/js/samples/collapse-literal-ssr/input.svelte b/test/js/samples/collapse-literal-ssr/input.svelte new file mode 100644 index 000000000000..9c2826d0e28f --- /dev/null +++ b/test/js/samples/collapse-literal-ssr/input.svelte @@ -0,0 +1,23 @@ + + + +
-
+ + +
-
+
-
+
-
+
-
+
-
+ + +
-
+
-
+
-
From 797438ba7e479204979f27746f1adc0722a93793 Mon Sep 17 00:00:00 2001 From: Cory Virok Date: Tue, 17 May 2022 11:18:59 -0700 Subject: [PATCH 4/8] Remove duplicate test cases for template literal collapse --- test/js/samples/collapse-literal-ssr/expected.js | 3 --- test/js/samples/collapse-literal-ssr/input.svelte | 3 --- 2 files changed, 6 deletions(-) diff --git a/test/js/samples/collapse-literal-ssr/expected.js b/test/js/samples/collapse-literal-ssr/expected.js index ee431fb51dc7..8440a10082bb 100644 --- a/test/js/samples/collapse-literal-ssr/expected.js +++ b/test/js/samples/collapse-literal-ssr/expected.js @@ -15,9 +15,6 @@ const Component = create_ssr_component(($$result, $$props, $$bindings, slots) => - - -- -- --
-
diff --git a/test/js/samples/collapse-literal-ssr/input.svelte b/test/js/samples/collapse-literal-ssr/input.svelte index 9c2826d0e28f..83a7be9bda6c 100644 --- a/test/js/samples/collapse-literal-ssr/input.svelte +++ b/test/js/samples/collapse-literal-ssr/input.svelte @@ -13,9 +13,6 @@
-
-
-
-
-
-
-
-
-
From 1e62e0b3b40b8dfabf41692c2d8e90378fff5d44 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Mon, 27 Feb 2023 14:12:56 +0100 Subject: [PATCH 5/8] simplify --- src/compiler/compile/render_ssr/Renderer.ts | 1 - .../utils/collapse_template_literal.ts | 47 ++++++++----------- 2 files changed, 20 insertions(+), 28 deletions(-) diff --git a/src/compiler/compile/render_ssr/Renderer.ts b/src/compiler/compile/render_ssr/Renderer.ts index 0e4724ea0e27..99a2ee3d685f 100644 --- a/src/compiler/compile/render_ssr/Renderer.ts +++ b/src/compiler/compile/render_ssr/Renderer.ts @@ -125,4 +125,3 @@ export default class Renderer { }); } } - diff --git a/src/compiler/compile/utils/collapse_template_literal.ts b/src/compiler/compile/utils/collapse_template_literal.ts index bace266ce52f..7658e50f02b7 100644 --- a/src/compiler/compile/utils/collapse_template_literal.ts +++ b/src/compiler/compile/utils/collapse_template_literal.ts @@ -1,37 +1,30 @@ import { TemplateLiteral } from 'estree'; import { escape_template } from './stringify'; -// Collapse string literals together +/** + * Collapse string literals together + */ export function collapse_template_literal(literal: TemplateLiteral) { - if (literal.quasis.length) { - // flatMap() to produce an array containing [quasi, expr, quasi, expr, ..., quasi] - const zip = literal.quasis.reduce((acc, cur, index) => { - const expr = literal.expressions[index]; - acc.push(cur); - if (expr) { - acc.push(expr); - } - return acc; - }, []); + const collapsed_quasis = []; + const collapsed_expressions = []; + let cur_quasi = literal.quasis[0]; + + // An expression always follows a quasi and vice versa, ending with a quasi + for (let i = 0; i < literal.quasis.length; i++) { + const expr = literal.expressions[i]; + const next_quasi = literal.quasis[i + 1]; // If an expression is a simple string literal, combine it with its preceding // and following quasi - let cur_quasi = zip[0]; - const new_zip = [cur_quasi]; - for (let i = 1; i < zip.length; i += 2) { - const expr = zip[i]; - const next_quasi = zip[i + 1]; - if (expr.type === 'Literal' && typeof expr.value === 'string') { - cur_quasi.value.raw += escape_template(expr.value) + next_quasi.value.raw; - } else { - new_zip.push(expr); - new_zip.push(next_quasi); - cur_quasi = next_quasi; - } + if (next_quasi && expr.type === 'Literal' && typeof expr.value === 'string') { + cur_quasi.value.raw += escape_template(expr.value) + next_quasi.value.raw; + } else { + collapsed_expressions.push(expr); + collapsed_quasis.push(next_quasi); + cur_quasi = next_quasi; } - - // Reconstitute the quasi and expressions arrays - literal.quasis = new_zip.filter((_, index) => index % 2 === 0); - literal.expressions = new_zip.filter((_, index) => index % 2 === 1); } + + literal.quasis = collapsed_quasis; + literal.expressions = collapsed_expressions; } From 2c30a5e560abb13ceefc855e3fd5838988c26de9 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Mon, 27 Feb 2023 14:42:38 +0100 Subject: [PATCH 6/8] woops --- .../compile/utils/collapse_template_literal.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/compiler/compile/utils/collapse_template_literal.ts b/src/compiler/compile/utils/collapse_template_literal.ts index 7658e50f02b7..8d9efafe858a 100644 --- a/src/compiler/compile/utils/collapse_template_literal.ts +++ b/src/compiler/compile/utils/collapse_template_literal.ts @@ -16,11 +16,15 @@ export function collapse_template_literal(literal: TemplateLiteral) { const next_quasi = literal.quasis[i + 1]; // If an expression is a simple string literal, combine it with its preceding // and following quasi - if (next_quasi && expr.type === 'Literal' && typeof expr.value === 'string') { + if (next_quasi && expr && expr.type === 'Literal' && typeof expr.value === 'string') { cur_quasi.value.raw += escape_template(expr.value) + next_quasi.value.raw; } else { - collapsed_expressions.push(expr); - collapsed_quasis.push(next_quasi); + if (expr) { + collapsed_expressions.push(expr); + } + if (next_quasi) { + collapsed_quasis.push(next_quasi); + } cur_quasi = next_quasi; } } From 297570ea90342206c4f289288a5f258f379d5285 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Mon, 27 Feb 2023 15:31:03 +0100 Subject: [PATCH 7/8] fix --- src/compiler/compile/utils/collapse_template_literal.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/compile/utils/collapse_template_literal.ts b/src/compiler/compile/utils/collapse_template_literal.ts index 8d9efafe858a..5c5f78b8a30b 100644 --- a/src/compiler/compile/utils/collapse_template_literal.ts +++ b/src/compiler/compile/utils/collapse_template_literal.ts @@ -5,6 +5,8 @@ import { escape_template } from './stringify'; * Collapse string literals together */ export function collapse_template_literal(literal: TemplateLiteral) { + if (!literal.quasis.length) return; + const collapsed_quasis = []; const collapsed_expressions = []; @@ -22,9 +24,7 @@ export function collapse_template_literal(literal: TemplateLiteral) { if (expr) { collapsed_expressions.push(expr); } - if (next_quasi) { - collapsed_quasis.push(next_quasi); - } + collapsed_quasis.push(cur_quasi); cur_quasi = next_quasi; } } From 7a7f50a2f590e89c93781047044000eb265b5979 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Mon, 27 Feb 2023 16:25:12 +0100 Subject: [PATCH 8/8] fix tests --- test/validator/samples/assignment-to-const-5/errors.json | 9 +++------ test/validator/samples/assignment-to-const-7/errors.json | 9 +++------ 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/test/validator/samples/assignment-to-const-5/errors.json b/test/validator/samples/assignment-to-const-5/errors.json index 21de70863c1e..bca92bf26617 100644 --- a/test/validator/samples/assignment-to-const-5/errors.json +++ b/test/validator/samples/assignment-to-const-5/errors.json @@ -4,14 +4,11 @@ "message": "You are assigning to a const", "start": { "line": 3, - "column": 1, - "character": 31 + "column": 1 }, "end": { "line": 3, - "column": 33, - "character": 63 - }, - "pos": 31 + "column": 33 + } } ] \ No newline at end of file diff --git a/test/validator/samples/assignment-to-const-7/errors.json b/test/validator/samples/assignment-to-const-7/errors.json index 34c88bebf64b..5304a61c4bb5 100644 --- a/test/validator/samples/assignment-to-const-7/errors.json +++ b/test/validator/samples/assignment-to-const-7/errors.json @@ -4,14 +4,11 @@ "message": "You are assigning to a const", "start": { "line": 3, - "column": 1, - "character": 43 + "column": 1 }, "end": { "line": 3, - "column": 42, - "character": 84 - }, - "pos": 43 + "column": 42 + } } ] \ No newline at end of file