diff --git a/src/compiler/compile/index.ts b/src/compiler/compile/index.ts index 98004ba5daf1..a0c831723b64 100644 --- a/src/compiler/compile/index.ts +++ b/src/compiler/compile/index.ts @@ -7,6 +7,7 @@ import { CompileOptions, Warning } from '../interfaces'; import Component from './Component'; import fuzzymatch from '../utils/fuzzymatch'; import get_name_from_filename from './utils/get_name_from_filename'; +import optimise from '../parse/optimise/index'; const valid_options = [ 'format', @@ -25,7 +26,8 @@ const valid_options = [ 'tag', 'css', 'preserveComments', - 'preserveWhitespace' + 'preserveWhitespace', + 'optimiseAst', ]; function validate_options(options: CompileOptions, warnings: Warning[]) { @@ -57,7 +59,7 @@ function validate_options(options: CompileOptions, warnings: Warning[]) { } export default function compile(source: string, options: CompileOptions = {}) { - options = assign({ generate: 'dom', dev: false }, options); + options = assign({ generate: 'dom', dev: false, optimiseAst: true }, options); const stats = new Stats(); const warnings = []; @@ -68,6 +70,12 @@ export default function compile(source: string, options: CompileOptions = {}) { const ast = parse(source, options); stats.stop('parse'); + if (options.optimiseAst) { + stats.start('optimise-ast'); + optimise(ast); + stats.stop('optimise-ast'); + } + stats.start('create component'); const component = new Component( ast, @@ -79,9 +87,10 @@ export default function compile(source: string, options: CompileOptions = {}) { ); stats.stop('create component'); - const js = options.generate === false - ? null - : options.generate === 'ssr' + const js = + options.generate === false + ? null + : options.generate === 'ssr' ? render_ssr(component, options) : render_dom(component, options); diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index a80db8416975..245b55ae4994 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -297,6 +297,13 @@ export default class ElementWrapper extends Wrapper { // @ts-ignore todo: should it be this.fragment.nodes[0].node.data instead? b`${node}.textContent = ${string_literal(this.fragment.nodes[0].data)};` ); + } else if ( + this.fragment.nodes.length === 1 && + this.fragment.nodes[0].node.type === 'MustacheTag' && + this.fragment.nodes[0].node.expression.node.type === 'TemplateLiteral') { + block.chunks.create.push( + b`${node}.textContent = ${this.fragment.nodes[0].node.expression.manipulate(block)};` + ); } else { const state = { quasi: { diff --git a/src/compiler/compile/render_dom/wrappers/shared/Tag.ts b/src/compiler/compile/render_dom/wrappers/shared/Tag.ts index 20bfd3597b4d..7f31383123d2 100644 --- a/src/compiler/compile/render_dom/wrappers/shared/Tag.ts +++ b/src/compiler/compile/render_dom/wrappers/shared/Tag.ts @@ -4,6 +4,7 @@ import Renderer from '../../Renderer'; import Block from '../../Block'; import MustacheTag from '../../../nodes/MustacheTag'; import RawMustacheTag from '../../../nodes/RawMustacheTag'; +import { is_string } from './is_string'; import { Node } from 'estree'; export default class Tag extends Wrapper { @@ -29,14 +30,20 @@ export default class Tag extends Wrapper { update: ((value: Node) => (Node | Node[])) ) { const dependencies = this.node.expression.dynamic_dependencies(); + const should_extract = this.node.should_cache && dependencies.length > 0; let snippet = this.node.expression.manipulate(block); + snippet = is_string(snippet) ? snippet : x`${snippet} + ""`; + + if (should_extract) { + const fn_name = block.get_unique_name(`${this.var.name}_fn`); + block.add_variable(fn_name, x`(#ctx) => ${snippet}`); + snippet = x`${fn_name}(#ctx)`; + } const value = this.node.should_cache && block.get_unique_name(`${this.var.name}_value`); const content = this.node.should_cache ? value : snippet; - snippet = x`${snippet} + ""`; - - if (this.node.should_cache) block.add_variable(value, snippet); // TODO may need to coerce snippet to string + if (this.node.should_cache) block.add_variable(value, snippet); if (dependencies.length > 0) { let condition = x`#changed.${dependencies[0]}`; diff --git a/src/compiler/compile/render_dom/wrappers/shared/is_string.ts b/src/compiler/compile/render_dom/wrappers/shared/is_string.ts new file mode 100644 index 000000000000..37b678638040 --- /dev/null +++ b/src/compiler/compile/render_dom/wrappers/shared/is_string.ts @@ -0,0 +1,3 @@ +export function is_string(node) { + return node.type === 'TemplateLiteral' || (node.type === 'Literal' && typeof node.value === 'string'); +} \ No newline at end of file diff --git a/src/compiler/interfaces.ts b/src/compiler/interfaces.ts index f877b93b56f8..b0463b961b27 100644 --- a/src/compiler/interfaces.ts +++ b/src/compiler/interfaces.ts @@ -124,6 +124,7 @@ export interface CompileOptions { preserveComments?: boolean; preserveWhitespace?: boolean; + optimiseAst?: boolean; } export interface ParserOptions { diff --git a/src/compiler/parse/optimise/index.ts b/src/compiler/parse/optimise/index.ts new file mode 100644 index 000000000000..e8921bf70152 --- /dev/null +++ b/src/compiler/parse/optimise/index.ts @@ -0,0 +1,85 @@ +import { walk } from 'estree-walker'; +import { Text, MustacheTag } from '../../interfaces'; + +export default function optimise(ast) { + walk(ast, { + enter(node: any) { + if (node.type === 'Element') { + optimise_text_content(node.children); + } + }, + }); +} + +const text_like_node_type = new Set(['MustacheTag', 'Text']); + +function optimise_text_content(children) { + let start = 0; + let end = 0; + + do { + while ( + start < children.length && + !text_like_node_type.has(children[start].type) + ) + start++; + + end = start; + + while (end < children.length && text_like_node_type.has(children[end].type)) + end++; + + if (end > start) { + const merged = merge_text_siblings(children.slice(start, end)); + children.splice(start, end - start, ...merged); + start = end; + } + } while (start < children.length); +} + +function merge_text_siblings(children: Array) { + if (children.length < 3) { + return children; + } + + const literal = { + type: 'TemplateLiteral', + expressions: [], + quasis: [], + }; + const state = { + quasi: { + type: 'TemplateElement', + value: { raw: '' }, + start: children[0].start, + end: children[0].start + }, + }; + + for (const child of children) { + if (child.type === 'MustacheTag') { + literal.quasis.push(state.quasi); + literal.expressions.push(child.expression); + state.quasi = { + type: 'TemplateElement', + value: { raw: '' }, + // @ts-ignore + start: child.expression.end + 1, + // @ts-ignore + end: child.expression.end + 1 + }; + } else if (child.type === 'Text') { + state.quasi.value.raw += child.data; + state.quasi.end = child.end; + } + } + + literal.quasis.push(state.quasi); + + return [{ + type: 'MustacheTag', + expression: literal, + start: children[0].start, + end: children[children.length - 1].end, + }]; +} diff --git a/test/js/index.js b/test/js/index.js index 5fd632d60629..bd7a7edd1cb3 100644 --- a/test/js/index.js +++ b/test/js/index.js @@ -36,6 +36,10 @@ describe("js", () => { const expected = fs.readFileSync(`${dir}/expected.js`, "utf-8"); + if (process.env.UPDATE_EXPECTED) { + fs.writeFileSync(`${dir}/expected.js`, actual); + } + assert.equal( actual.trim().replace(/^[ \t]+$/gm, ""), expected.trim().replace(/^[ \t]+$/gm, "") diff --git a/test/js/samples/capture-inject-dev-only/expected.js b/test/js/samples/capture-inject-dev-only/expected.js index 66e5b75fa3a8..1c7e04b97e96 100644 --- a/test/js/samples/capture-inject-dev-only/expected.js +++ b/test/js/samples/capture-inject-dev-only/expected.js @@ -24,7 +24,7 @@ function create_fragment(ctx) { return { c() { p = element("p"); - t0 = text(ctx.foo); + t0 = text(ctx.foo + ""); t1 = space(); input = element("input"); dispose = listen(input, "input", ctx.input_input_handler); @@ -37,7 +37,7 @@ function create_fragment(ctx) { set_input_value(input, ctx.foo); }, p(changed, ctx) { - if (changed.foo) set_data(t0, ctx.foo); + if (changed.foo) set_data(t0, ctx.foo + ""); if (changed.foo && input.value !== ctx.foo) { set_input_value(input, ctx.foo); diff --git a/test/js/samples/collapses-text-around-comments/expected.js b/test/js/samples/collapses-text-around-comments/expected.js index 84cfaf35eff7..296b56cba454 100644 --- a/test/js/samples/collapses-text-around-comments/expected.js +++ b/test/js/samples/collapses-text-around-comments/expected.js @@ -26,7 +26,7 @@ function create_fragment(ctx) { return { c() { p = element("p"); - t = text(ctx.foo); + t = text(ctx.foo + ""); attr(p, "class", "svelte-1a7i8ec"); }, m(target, anchor) { @@ -34,7 +34,7 @@ function create_fragment(ctx) { append(p, t); }, p(changed, ctx) { - if (changed.foo) set_data(t, ctx.foo); + if (changed.foo) set_data(t, ctx.foo + ""); }, i: noop, o: noop, diff --git a/test/js/samples/component-store-access-invalidate/expected.js b/test/js/samples/component-store-access-invalidate/expected.js index 713c4108b7a6..87143e557584 100644 --- a/test/js/samples/component-store-access-invalidate/expected.js +++ b/test/js/samples/component-store-access-invalidate/expected.js @@ -21,14 +21,14 @@ function create_fragment(ctx) { return { c() { h1 = element("h1"); - t = text(ctx.$foo); + t = text(ctx.$foo + ""); }, m(target, anchor) { insert(target, h1, anchor); append(h1, t); }, p(changed, ctx) { - if (changed.$foo) set_data(t, ctx.$foo); + if (changed.$foo) set_data(t, ctx.$foo + ""); }, i: noop, o: noop, diff --git a/test/js/samples/component-store-reassign-invalidate/expected.js b/test/js/samples/component-store-reassign-invalidate/expected.js index 407cfc97308a..1e80b0e20ec1 100644 --- a/test/js/samples/component-store-reassign-invalidate/expected.js +++ b/test/js/samples/component-store-reassign-invalidate/expected.js @@ -26,7 +26,7 @@ function create_fragment(ctx) { return { c() { h1 = element("h1"); - t0 = text(ctx.$foo); + t0 = text(ctx.$foo + ""); t1 = space(); button = element("button"); button.textContent = "reset"; @@ -39,7 +39,7 @@ function create_fragment(ctx) { insert(target, button, anchor); }, p(changed, ctx) { - if (changed.$foo) set_data(t0, ctx.$foo); + if (changed.$foo) set_data(t0, ctx.$foo + ""); }, i: noop, o: noop, diff --git a/test/js/samples/debug-empty/expected.js b/test/js/samples/debug-empty/expected.js index 2238fa3ecf14..079682fde131 100644 --- a/test/js/samples/debug-empty/expected.js +++ b/test/js/samples/debug-empty/expected.js @@ -18,18 +18,16 @@ const file = undefined; function create_fragment(ctx) { let h1; + let t0_fn = ctx => `Hello ${ctx.name}!`; + let t0_value = t0_fn(ctx); let t0; let t1; - let t2; - let t3; const block = { c: function create() { h1 = element("h1"); - t0 = text("Hello "); - t1 = text(ctx.name); - t2 = text("!"); - t3 = space(); + t0 = text(t0_value); + t1 = space(); debugger; add_location(h1, file, 4, 0, 38); }, @@ -39,19 +37,17 @@ function create_fragment(ctx) { m: function mount(target, anchor) { insert_dev(target, h1, anchor); append_dev(h1, t0); - append_dev(h1, t1); - append_dev(h1, t2); - insert_dev(target, t3, anchor); + insert_dev(target, t1, anchor); }, p: function update(changed, ctx) { - if (changed.name) set_data_dev(t1, ctx.name); + if (changed.name && t0_value !== (t0_value = t0_fn(ctx))) set_data_dev(t0, t0_value); debugger; }, i: noop, o: noop, d: function destroy(detaching) { if (detaching) detach_dev(h1); - if (detaching) detach_dev(t3); + if (detaching) detach_dev(t1); } }; diff --git a/test/js/samples/debug-foo-bar-baz-things/expected.js b/test/js/samples/debug-foo-bar-baz-things/expected.js index 4a8c145f5ee0..1499b5701aee 100644 --- a/test/js/samples/debug-foo-bar-baz-things/expected.js +++ b/test/js/samples/debug-foo-bar-baz-things/expected.js @@ -25,7 +25,8 @@ function get_each_context(ctx, list, i) { function create_each_block(ctx) { let span; - let t0_value = ctx.thing.name + ""; + let t0_fn = ctx => ctx.thing.name + ""; + let t0_value = t0_fn(ctx); let t0; let t1; @@ -49,7 +50,7 @@ function create_each_block(ctx) { insert_dev(target, t1, anchor); }, p: function update(changed, ctx) { - if (changed.things && t0_value !== (t0_value = ctx.thing.name + "")) set_data_dev(t0, t0_value); + if (changed.things && t0_value !== (t0_value = t0_fn(ctx))) set_data_dev(t0, t0_value); if (changed.foo || changed.bar || changed.baz || changed.things) { const { foo, bar, baz, thing } = ctx; @@ -95,7 +96,7 @@ function create_fragment(ctx) { t0 = space(); p = element("p"); t1 = text("foo: "); - t2 = text(ctx.foo); + t2 = text(ctx.foo + ""); add_location(p, file, 12, 0, 182); }, l: function claim(nodes) { @@ -135,7 +136,7 @@ function create_fragment(ctx) { each_blocks.length = each_value.length; } - if (changed.foo) set_data_dev(t2, ctx.foo); + if (changed.foo) set_data_dev(t2, ctx.foo + ""); }, i: noop, o: noop, diff --git a/test/js/samples/debug-foo/expected.js b/test/js/samples/debug-foo/expected.js index a91ed932c8ca..1a471077350a 100644 --- a/test/js/samples/debug-foo/expected.js +++ b/test/js/samples/debug-foo/expected.js @@ -25,7 +25,8 @@ function get_each_context(ctx, list, i) { function create_each_block(ctx) { let span; - let t0_value = ctx.thing.name + ""; + let t0_fn = ctx => ctx.thing.name + ""; + let t0_value = t0_fn(ctx); let t0; let t1; @@ -49,7 +50,7 @@ function create_each_block(ctx) { insert_dev(target, t1, anchor); }, p: function update(changed, ctx) { - if (changed.things && t0_value !== (t0_value = ctx.thing.name + "")) set_data_dev(t0, t0_value); + if (changed.things && t0_value !== (t0_value = t0_fn(ctx))) set_data_dev(t0, t0_value); if (changed.foo) { const { foo } = ctx; @@ -95,7 +96,7 @@ function create_fragment(ctx) { t0 = space(); p = element("p"); t1 = text("foo: "); - t2 = text(ctx.foo); + t2 = text(ctx.foo + ""); add_location(p, file, 10, 0, 131); }, l: function claim(nodes) { @@ -135,7 +136,7 @@ function create_fragment(ctx) { each_blocks.length = each_value.length; } - if (changed.foo) set_data_dev(t2, ctx.foo); + if (changed.foo) set_data_dev(t2, ctx.foo + ""); }, i: noop, o: noop, diff --git a/test/js/samples/deconflict-builtins/expected.js b/test/js/samples/deconflict-builtins/expected.js index 6d9574fd034c..9ca087a3d519 100644 --- a/test/js/samples/deconflict-builtins/expected.js +++ b/test/js/samples/deconflict-builtins/expected.js @@ -21,7 +21,8 @@ function get_each_context(ctx, list, i) { function create_each_block(ctx) { let span; - let t_value = ctx.node + ""; + let t_fn = ctx => ctx.node + ""; + let t_value = t_fn(ctx); let t; return { @@ -34,7 +35,7 @@ function create_each_block(ctx) { append(span, t); }, p(changed, ctx) { - if (changed.createElement && t_value !== (t_value = ctx.node + "")) set_data(t, t_value); + if (changed.createElement && t_value !== (t_value = t_fn(ctx))) set_data(t, t_value); }, d(detaching) { if (detaching) detach(span); diff --git a/test/js/samples/dev-warning-missing-data-computed/expected.js b/test/js/samples/dev-warning-missing-data-computed/expected.js index 27e0b797a26c..af5a62b9cdd6 100644 --- a/test/js/samples/dev-warning-missing-data-computed/expected.js +++ b/test/js/samples/dev-warning-missing-data-computed/expected.js @@ -10,7 +10,6 @@ import { noop, safe_not_equal, set_data_dev, - space, text } from "svelte/internal"; @@ -18,17 +17,19 @@ const file = undefined; function create_fragment(ctx) { let p; - let t0_value = Math.max(0, ctx.foo) + ""; - let t0; - let t1; - let t2; + + let t_fn = ctx => ` + ${Math.max(0, ctx.foo)} + ${ctx.bar} +`; + + let t_value = t_fn(ctx); + let t; const block = { c: function create() { p = element("p"); - t0 = text(t0_value); - t1 = space(); - t2 = text(ctx.bar); + t = text(t_value); add_location(p, file, 7, 0, 67); }, l: function claim(nodes) { @@ -36,13 +37,10 @@ function create_fragment(ctx) { }, m: function mount(target, anchor) { insert_dev(target, p, anchor); - append_dev(p, t0); - append_dev(p, t1); - append_dev(p, t2); + append_dev(p, t); }, p: function update(changed, ctx) { - if (changed.foo && t0_value !== (t0_value = Math.max(0, ctx.foo) + "")) set_data_dev(t0, t0_value); - if (changed.bar) set_data_dev(t2, ctx.bar); + if ((changed.foo || changed.bar) && t_value !== (t_value = t_fn(ctx))) set_data_dev(t, t_value); }, i: noop, o: noop, diff --git a/test/js/samples/each-block-array-literal/expected.js b/test/js/samples/each-block-array-literal/expected.js index c687748961ed..b7b64ae06017 100644 --- a/test/js/samples/each-block-array-literal/expected.js +++ b/test/js/samples/each-block-array-literal/expected.js @@ -21,7 +21,8 @@ function get_each_context(ctx, list, i) { function create_each_block(ctx) { let span; - let t_value = ctx.num + ""; + let t_fn = ctx => ctx.num + ""; + let t_value = t_fn(ctx); let t; return { @@ -34,7 +35,7 @@ function create_each_block(ctx) { append(span, t); }, p(changed, ctx) { - if ((changed.a || changed.b || changed.c || changed.d || changed.e) && t_value !== (t_value = ctx.num + "")) set_data(t, t_value); + if ((changed.a || changed.b || changed.c || changed.d || changed.e) && t_value !== (t_value = t_fn(ctx))) set_data(t, t_value); }, d(detaching) { if (detaching) detach(span); diff --git a/test/js/samples/each-block-changed-check/expected.js b/test/js/samples/each-block-changed-check/expected.js index 871b5570ca77..ecd3c1a21643 100644 --- a/test/js/samples/each-block-changed-check/expected.js +++ b/test/js/samples/each-block-changed-check/expected.js @@ -28,28 +28,27 @@ function create_each_block(ctx) { let t0; let t1; let span; - let t2_value = ctx.comment.author + ""; + + let t2_fn = ctx => ` + ${ctx.comment.author} wrote ${ctx.elapsed(ctx.comment.time, ctx.time)} ago: + `; + + let t2_value = t2_fn(ctx); let t2; let t3; - let t4_value = ctx.elapsed(ctx.comment.time, ctx.time) + ""; - let t4; - let t5; - let t6; let html_tag; - let raw_value = ctx.comment.html + ""; + let raw_fn = ctx => ctx.comment.html + ""; + let raw_value = raw_fn(ctx); return { c() { div = element("div"); strong = element("strong"); - t0 = text(ctx.i); + t0 = text(ctx.i + ""); t1 = space(); span = element("span"); t2 = text(t2_value); - t3 = text(" wrote "); - t4 = text(t4_value); - t5 = text(" ago:"); - t6 = space(); + t3 = space(); attr(span, "class", "meta"); html_tag = new HtmlTag(raw_value, null); attr(div, "class", "comment"); @@ -61,16 +60,12 @@ function create_each_block(ctx) { append(div, t1); append(div, span); append(span, t2); - append(span, t3); - append(span, t4); - append(span, t5); - append(div, t6); + append(div, t3); html_tag.m(div); }, p(changed, ctx) { - if (changed.comments && t2_value !== (t2_value = ctx.comment.author + "")) set_data(t2, t2_value); - if ((changed.elapsed || changed.comments || changed.time) && t4_value !== (t4_value = ctx.elapsed(ctx.comment.time, ctx.time) + "")) set_data(t4, t4_value); - if (changed.comments && raw_value !== (raw_value = ctx.comment.html + "")) html_tag.p(raw_value); + if ((changed.comments || changed.elapsed || changed.time) && t2_value !== (t2_value = t2_fn(ctx))) set_data(t2, t2_value); + if (changed.comments && raw_value !== (raw_value = raw_fn(ctx))) html_tag.p(raw_value); }, d(detaching) { if (detaching) detach(div); @@ -97,7 +92,7 @@ function create_fragment(ctx) { t0 = space(); p = element("p"); - t1 = text(ctx.foo); + t1 = text(ctx.foo + ""); }, m(target, anchor) { for (let i = 0; i < each_blocks.length; i += 1) { @@ -132,7 +127,7 @@ function create_fragment(ctx) { each_blocks.length = each_value.length; } - if (changed.foo) set_data(t1, ctx.foo); + if (changed.foo) set_data(t1, ctx.foo + ""); }, i: noop, o: noop, diff --git a/test/js/samples/each-block-keyed-animated/expected.js b/test/js/samples/each-block-keyed-animated/expected.js index 9ca72054bef2..e4b3721af995 100644 --- a/test/js/samples/each-block-keyed-animated/expected.js +++ b/test/js/samples/each-block-keyed-animated/expected.js @@ -24,7 +24,8 @@ function get_each_context(ctx, list, i) { function create_each_block(key_1, ctx) { let div; - let t_value = ctx.thing.name + ""; + let t_fn = ctx => ctx.thing.name + ""; + let t_value = t_fn(ctx); let t; let rect; let stop_animation = noop; @@ -42,7 +43,7 @@ function create_each_block(key_1, ctx) { append(div, t); }, p(changed, ctx) { - if (changed.things && t_value !== (t_value = ctx.thing.name + "")) set_data(t, t_value); + if (changed.things && t_value !== (t_value = t_fn(ctx))) set_data(t, t_value); }, r() { rect = div.getBoundingClientRect(); diff --git a/test/js/samples/each-block-keyed/expected.js b/test/js/samples/each-block-keyed/expected.js index a845fc833b6d..f1294a4bb446 100644 --- a/test/js/samples/each-block-keyed/expected.js +++ b/test/js/samples/each-block-keyed/expected.js @@ -22,7 +22,8 @@ function get_each_context(ctx, list, i) { function create_each_block(key_1, ctx) { let div; - let t_value = ctx.thing.name + ""; + let t_fn = ctx => ctx.thing.name + ""; + let t_value = t_fn(ctx); let t; return { @@ -38,7 +39,7 @@ function create_each_block(key_1, ctx) { append(div, t); }, p(changed, ctx) { - if (changed.things && t_value !== (t_value = ctx.thing.name + "")) set_data(t, t_value); + if (changed.things && t_value !== (t_value = t_fn(ctx))) set_data(t, t_value); }, d(detaching) { if (detaching) detach(div); diff --git a/test/js/samples/instrumentation-script-if-no-block/expected.js b/test/js/samples/instrumentation-script-if-no-block/expected.js index 6e9c04f161f3..f30de26b52f4 100644 --- a/test/js/samples/instrumentation-script-if-no-block/expected.js +++ b/test/js/samples/instrumentation-script-if-no-block/expected.js @@ -28,7 +28,7 @@ function create_fragment(ctx) { t1 = space(); p = element("p"); t2 = text("x: "); - t3 = text(ctx.x); + t3 = text(ctx.x + ""); dispose = listen(button, "click", ctx.foo); }, m(target, anchor) { @@ -39,7 +39,7 @@ function create_fragment(ctx) { append(p, t3); }, p(changed, ctx) { - if (changed.x) set_data(t3, ctx.x); + if (changed.x) set_data(t3, ctx.x + ""); }, i: noop, o: noop, diff --git a/test/js/samples/instrumentation-script-x-equals-x/expected.js b/test/js/samples/instrumentation-script-x-equals-x/expected.js index c7652815d88c..455e329e7f9e 100644 --- a/test/js/samples/instrumentation-script-x-equals-x/expected.js +++ b/test/js/samples/instrumentation-script-x-equals-x/expected.js @@ -18,7 +18,8 @@ function create_fragment(ctx) { let t1; let p; let t2; - let t3_value = ctx.things.length + ""; + let t3_fn = ctx => ctx.things.length + ""; + let t3_value = t3_fn(ctx); let t3; let dispose; @@ -40,7 +41,7 @@ function create_fragment(ctx) { append(p, t3); }, p(changed, ctx) { - if (changed.things && t3_value !== (t3_value = ctx.things.length + "")) set_data(t3, t3_value); + if (changed.things && t3_value !== (t3_value = t3_fn(ctx))) set_data(t3, t3_value); }, i: noop, o: noop, diff --git a/test/js/samples/instrumentation-template-if-no-block/expected.js b/test/js/samples/instrumentation-template-if-no-block/expected.js index aa815dfe0067..a52dd318e833 100644 --- a/test/js/samples/instrumentation-template-if-no-block/expected.js +++ b/test/js/samples/instrumentation-template-if-no-block/expected.js @@ -28,7 +28,7 @@ function create_fragment(ctx) { t1 = space(); p = element("p"); t2 = text("x: "); - t3 = text(ctx.x); + t3 = text(ctx.x + ""); dispose = listen(button, "click", ctx.click_handler); }, m(target, anchor) { @@ -39,7 +39,7 @@ function create_fragment(ctx) { append(p, t3); }, p(changed, ctx) { - if (changed.x) set_data(t3, ctx.x); + if (changed.x) set_data(t3, ctx.x + ""); }, i: noop, o: noop, diff --git a/test/js/samples/instrumentation-template-x-equals-x/expected.js b/test/js/samples/instrumentation-template-x-equals-x/expected.js index 5af948efb13e..b75e8ed96ae1 100644 --- a/test/js/samples/instrumentation-template-x-equals-x/expected.js +++ b/test/js/samples/instrumentation-template-x-equals-x/expected.js @@ -18,7 +18,8 @@ function create_fragment(ctx) { let t1; let p; let t2; - let t3_value = ctx.things.length + ""; + let t3_fn = ctx => ctx.things.length + ""; + let t3_value = t3_fn(ctx); let t3; let dispose; @@ -40,7 +41,7 @@ function create_fragment(ctx) { append(p, t3); }, p(changed, ctx) { - if (changed.things && t3_value !== (t3_value = ctx.things.length + "")) set_data(t3, t3_value); + if (changed.things && t3_value !== (t3_value = t3_fn(ctx))) set_data(t3, t3_value); }, i: noop, o: noop, diff --git a/test/js/samples/optimise-ast/expected.js b/test/js/samples/optimise-ast/expected.js new file mode 100644 index 000000000000..99dce25b5614 --- /dev/null +++ b/test/js/samples/optimise-ast/expected.js @@ -0,0 +1,65 @@ +import { + SvelteComponent, + append, + detach, + element, + init, + insert, + listen, + noop, + safe_not_equal, + set_data, + text +} from "svelte/internal"; + +function create_fragment(ctx) { + let button; + + let t_fn = ctx => ` + Clicked ${ctx.count} ${ctx.count === 1 ? "time" : "times"} +`; + + let t_value = t_fn(ctx); + let t; + let dispose; + + return { + c() { + button = element("button"); + t = text(t_value); + dispose = listen(button, "click", ctx.increment); + }, + m(target, anchor) { + insert(target, button, anchor); + append(button, t); + }, + p(changed, ctx) { + if (changed.count && t_value !== (t_value = t_fn(ctx))) set_data(t, t_value); + }, + i: noop, + o: noop, + d(detaching) { + if (detaching) detach(button); + dispose(); + } + }; +} + +function instance($$self, $$props, $$invalidate) { + let count = 0; + + function increment() { + $$invalidate("count", count = count + 1); + } + + return { count, increment }; +} + +class Component extends SvelteComponent { + constructor(options) { + super(); + init(this, options, instance, create_fragment, safe_not_equal, {}); + } +} + +export default Component; \ No newline at end of file diff --git a/test/js/samples/optimise-ast/input.svelte b/test/js/samples/optimise-ast/input.svelte new file mode 100644 index 000000000000..0bf3da0c6598 --- /dev/null +++ b/test/js/samples/optimise-ast/input.svelte @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/test/js/samples/unchanged-expression/expected.js b/test/js/samples/unchanged-expression/expected.js index 1dc7c6fd72a7..612cd718518a 100644 --- a/test/js/samples/unchanged-expression/expected.js +++ b/test/js/samples/unchanged-expression/expected.js @@ -40,7 +40,7 @@ function create_fragment(ctx) { div1 = element("div"); p3 = element("p"); t8 = text("Hello "); - t9 = text(ctx.world3); + t9 = text(ctx.world3 + ""); }, m(target, anchor) { insert(target, div0, anchor); @@ -56,7 +56,7 @@ function create_fragment(ctx) { append(p3, t9); }, p(changed, ctx) { - if (changed.world3) set_data(t9, ctx.world3); + if (changed.world3) set_data(t9, ctx.world3 + ""); }, i: noop, o: noop, diff --git a/test/js/samples/unreferenced-state-not-invalidated/expected.js b/test/js/samples/unreferenced-state-not-invalidated/expected.js index 9b53d50915e7..ca3596938835 100644 --- a/test/js/samples/unreferenced-state-not-invalidated/expected.js +++ b/test/js/samples/unreferenced-state-not-invalidated/expected.js @@ -20,14 +20,14 @@ function create_fragment(ctx) { return { c() { p = element("p"); - t = text(ctx.y); + t = text(ctx.y + ""); }, m(target, anchor) { insert(target, p, anchor); append(p, t); }, p(changed, ctx) { - if (changed.y) set_data(t, ctx.y); + if (changed.y) set_data(t, ctx.y + ""); }, i: noop, o: noop, diff --git a/test/js/samples/window-binding-scroll/expected.js b/test/js/samples/window-binding-scroll/expected.js index 87f31505d71e..9b8279d2f657 100644 --- a/test/js/samples/window-binding-scroll/expected.js +++ b/test/js/samples/window-binding-scroll/expected.js @@ -31,7 +31,7 @@ function create_fragment(ctx) { c() { p = element("p"); t0 = text("scrolled to "); - t1 = text(ctx.y); + t1 = text(ctx.y + ""); dispose = listen(window, "scroll", () => { scrolling = true; @@ -53,7 +53,7 @@ function create_fragment(ctx) { scrolling_timeout = setTimeout(clear_scrolling, 100); } - if (changed.y) set_data(t1, ctx.y); + if (changed.y) set_data(t1, ctx.y + ""); }, i: noop, o: noop, diff --git a/test/parser/index.js b/test/parser/index.js index 0188fac43126..a0dd02b8eb92 100644 --- a/test/parser/index.js +++ b/test/parser/index.js @@ -23,9 +23,10 @@ describe('parse', () => { const expectedError = tryToLoadJson(`${__dirname}/samples/${dir}/error.json`); try { - const { ast } = svelte.compile(input, Object.assign(options, { - generate: false - })); + const { ast } = svelte.compile( + input, + Object.assign({ optimiseAst: false }, options, { generate: false }) + ); fs.writeFileSync(`${__dirname}/samples/${dir}/_actual.json`, JSON.stringify(ast, null, '\t')); diff --git a/test/parser/samples/optimised-ast-text-content/input.svelte b/test/parser/samples/optimised-ast-text-content/input.svelte new file mode 100644 index 000000000000..ce63f4697bd8 --- /dev/null +++ b/test/parser/samples/optimised-ast-text-content/input.svelte @@ -0,0 +1,4 @@ + +
Hello {name}!
+
Hello {name}!
+
Hello {name}!
\ No newline at end of file diff --git a/test/parser/samples/optimised-ast-text-content/options.json b/test/parser/samples/optimised-ast-text-content/options.json new file mode 100644 index 000000000000..5e8e1d5274a3 --- /dev/null +++ b/test/parser/samples/optimised-ast-text-content/options.json @@ -0,0 +1 @@ +{ "optimiseAst": true } \ No newline at end of file diff --git a/test/parser/samples/optimised-ast-text-content/output.json b/test/parser/samples/optimised-ast-text-content/output.json new file mode 100644 index 000000000000..ebdb8dce6c0f --- /dev/null +++ b/test/parser/samples/optimised-ast-text-content/output.json @@ -0,0 +1,249 @@ +{ + "html": { + "start": 27, + "end": 127, + "type": "Fragment", + "children": [ + { + "start": 26, + "end": 27, + "type": "Text", + "raw": "\n", + "data": "\n" + }, + { + "start": 27, + "end": 51, + "type": "Element", + "name": "div", + "attributes": [], + "children": [ + { + "type": "MustacheTag", + "expression": { + "type": "TemplateLiteral", + "expressions": [ + { + "type": "Identifier", + "start": 39, + "end": 43, + "name": "name" + } + ], + "quasis": [ + { + "type": "TemplateElement", + "value": { + "raw": "Hello " + }, + "start": 32, + "end": 38 + }, + { + "type": "TemplateElement", + "value": { + "raw": "!" + }, + "start": 44, + "end": 45 + } + ] + }, + "start": 32, + "end": 45 + } + ] + }, + { + "start": 51, + "end": 52, + "type": "Text", + "raw": "\n", + "data": "\n" + }, + { + "start": 52, + "end": 89, + "type": "Element", + "name": "div", + "attributes": [], + "children": [ + { + "start": 57, + "end": 63, + "type": "Text", + "raw": "Hello ", + "data": "Hello " + }, + { + "start": 63, + "end": 82, + "type": "Element", + "name": "span", + "attributes": [], + "children": [ + { + "start": 69, + "end": 75, + "type": "MustacheTag", + "expression": { + "type": "Identifier", + "start": 70, + "end": 74, + "name": "name" + } + } + ] + }, + { + "start": 82, + "end": 83, + "type": "Text", + "raw": "!", + "data": "!" + } + ] + }, + { + "start": 89, + "end": 90, + "type": "Text", + "raw": "\n", + "data": "\n" + }, + { + "start": 90, + "end": 127, + "type": "Element", + "name": "div", + "attributes": [], + "children": [ + { + "start": 95, + "end": 113, + "type": "Element", + "name": "span", + "attributes": [], + "children": [ + { + "start": 101, + "end": 106, + "type": "Text", + "raw": "Hello", + "data": "Hello" + } + ] + }, + { + "type": "MustacheTag", + "expression": { + "type": "TemplateLiteral", + "expressions": [ + { + "type": "Identifier", + "start": 115, + "end": 119, + "name": "name" + } + ], + "quasis": [ + { + "type": "TemplateElement", + "value": { + "raw": " " + }, + "start": 113, + "end": 114 + }, + { + "type": "TemplateElement", + "value": { + "raw": "!" + }, + "start": 120, + "end": 121 + } + ] + }, + "start": 113, + "end": 121 + } + ] + } + ] + }, + "instance": { + "type": "Script", + "start": 0, + "end": 26, + "context": "default", + "content": { + "type": "Program", + "start": 8, + "end": 17, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 17 + } + }, + "body": [ + { + "type": "VariableDeclaration", + "start": 8, + "end": 17, + "loc": { + "start": { + "line": 1, + "column": 8 + }, + "end": { + "line": 1, + "column": 17 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 12, + "end": 16, + "loc": { + "start": { + "line": 1, + "column": 12 + }, + "end": { + "line": 1, + "column": 16 + } + }, + "id": { + "type": "Identifier", + "start": 12, + "end": 16, + "loc": { + "start": { + "line": 1, + "column": 12 + }, + "end": { + "line": 1, + "column": 16 + } + }, + "name": "name" + }, + "init": null + } + ], + "kind": "let" + } + ], + "sourceType": "module" + } + } +} \ No newline at end of file diff --git a/test/runtime/samples/optimise-ast/_config.js b/test/runtime/samples/optimise-ast/_config.js new file mode 100644 index 000000000000..245ac6d6d7b1 --- /dev/null +++ b/test/runtime/samples/optimise-ast/_config.js @@ -0,0 +1,12 @@ +export default { + html: ``, + async test({ assert, target, window }) { + const buttons = target.querySelectorAll('button'); + const event = new window.MouseEvent('click'); + await buttons[0].dispatchEvent(event); + assert.htmlEqual(target.innerHTML, ``); + + await buttons[0].dispatchEvent(event); + assert.htmlEqual(target.innerHTML, ``); + }, +}; diff --git a/test/runtime/samples/optimise-ast/main.svelte b/test/runtime/samples/optimise-ast/main.svelte new file mode 100644 index 000000000000..0bf3da0c6598 --- /dev/null +++ b/test/runtime/samples/optimise-ast/main.svelte @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/test/sourcemaps/samples/basic/test.js b/test/sourcemaps/samples/basic/test.js index f2ce4198fdb3..b367265251ff 100644 --- a/test/sourcemaps/samples/basic/test.js +++ b/test/sourcemaps/samples/basic/test.js @@ -1,26 +1,9 @@ export function test({ assert, smc, locateInSource, locateInGenerated }) { const expected = locateInSource( 'foo.bar.baz' ); - let start; - let actual; + const start = locateInGenerated('foo.bar.baz'); - start = locateInGenerated('foo.bar.baz'); - - actual = smc.originalPositionFor({ - line: start.line + 1, - column: start.column - }); - - assert.deepEqual( actual, { - source: 'input.svelte', - name: null, - line: expected.line + 1, - column: expected.column - }); - - start = locateInGenerated( 'foo.bar.baz', start.character + 1 ); - - actual = smc.originalPositionFor({ + const actual = smc.originalPositionFor({ line: start.line + 1, column: start.column });