diff --git a/src/compiler/compile/index.ts b/src/compiler/compile/index.ts index ac52f59471c8..3772cd37723b 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', @@ -26,7 +27,8 @@ const valid_options = [ 'css', 'loopGuardTimeout', 'preserveComments', - 'preserveWhitespace' + 'preserveWhitespace', + 'optimiseAst', ]; function validate_options(options: CompileOptions, warnings: Warning[]) { @@ -68,7 +70,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 = []; @@ -79,6 +81,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, @@ -90,9 +98,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 a34091f5b6b3..b36660072ecd 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -311,6 +311,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 ccb7c6ea40f4..1b1ca02a027d 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'; import { changed } from './changed'; @@ -30,14 +31,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 = changed(dependencies); 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 e7362b9313ea..b32093bc0493 100644 --- a/src/compiler/interfaces.ts +++ b/src/compiler/interfaces.ts @@ -125,6 +125,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..592628fafc16 --- /dev/null +++ b/src/compiler/parse/optimise/index.ts @@ -0,0 +1,84 @@ +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++; + + // based on heuristic, + // require more than 2 neighouring text contents to merge yields smaller content + if (end > start + 2) { + 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) { + + 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 b16c25fe5032..799be215f2e1 100644 --- a/test/js/index.js +++ b/test/js/index.js @@ -1,7 +1,7 @@ import * as assert from "assert"; import * as fs from "fs"; import * as path from "path"; -import * as kleur from "kleur"; +import * as colors from "kleur"; import { loadConfig, svelte, shouldUpdateExpected } from "../helpers.js"; describe("js", () => { diff --git a/test/js/samples/capture-inject-dev-only/expected.js b/test/js/samples/capture-inject-dev-only/expected.js index ee2f6d0dc929..90022a95ac24 100644 --- a/test/js/samples/capture-inject-dev-only/expected.js +++ b/test/js/samples/capture-inject-dev-only/expected.js @@ -25,7 +25,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); @@ -38,7 +38,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 5fb7aaef1519..b8e7d5f45694 100644 --- a/test/js/samples/collapses-text-around-comments/expected.js +++ b/test/js/samples/collapses-text-around-comments/expected.js @@ -27,7 +27,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) { @@ -35,7 +35,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 b3eedd3d6e7a..3e1dd734f56a 100644 --- a/test/js/samples/component-store-access-invalidate/expected.js +++ b/test/js/samples/component-store-access-invalidate/expected.js @@ -22,14 +22,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 890e28f79c3c..12883f860938 100644 --- a/test/js/samples/component-store-reassign-invalidate/expected.js +++ b/test/js/samples/component-store-reassign-invalidate/expected.js @@ -27,7 +27,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"; @@ -40,7 +40,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 b545823c575c..b6ce846ba76d 100644 --- a/test/js/samples/debug-empty/expected.js +++ b/test/js/samples/debug-empty/expected.js @@ -19,18 +19,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); }, @@ -40,19 +38,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 448fc82176f3..0096627237e6 100644 --- a/test/js/samples/debug-foo-bar-baz-things/expected.js +++ b/test/js/samples/debug-foo-bar-baz-things/expected.js @@ -27,7 +27,8 @@ function get_each_context(ctx, list, i) { // (8:0) {#each things as thing} 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; @@ -51,7 +52,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; @@ -97,7 +98,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) { @@ -137,7 +138,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 8abfd1bae100..dff566494dff 100644 --- a/test/js/samples/debug-foo/expected.js +++ b/test/js/samples/debug-foo/expected.js @@ -27,7 +27,8 @@ function get_each_context(ctx, list, i) { // (6:0) {#each things as thing} 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; @@ -51,7 +52,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; @@ -97,7 +98,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) { @@ -137,7 +138,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 54448a91b491..7047c77f8e22 100644 --- a/test/js/samples/deconflict-builtins/expected.js +++ b/test/js/samples/deconflict-builtins/expected.js @@ -23,7 +23,8 @@ function get_each_context(ctx, list, i) { // (5:0) {#each createElement as node} 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 { @@ -36,7 +37,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 5da2b4aead10..571eda9da7c3 100644 --- a/test/js/samples/dev-warning-missing-data-computed/expected.js +++ b/test/js/samples/dev-warning-missing-data-computed/expected.js @@ -11,7 +11,6 @@ import { noop, safe_not_equal, set_data_dev, - space, text } from "svelte/internal"; @@ -19,17 +18,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) { @@ -37,13 +38,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 3804cab6bbd0..1cdaa4d3d166 100644 --- a/test/js/samples/each-block-array-literal/expected.js +++ b/test/js/samples/each-block-array-literal/expected.js @@ -23,7 +23,8 @@ function get_each_context(ctx, list, i) { // (9:0) {#each [a, b, c, d, e] as num} 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 { @@ -36,7 +37,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 a2735ae9c4ef..5604b01d72fa 100644 --- a/test/js/samples/each-block-changed-check/expected.js +++ b/test/js/samples/each-block-changed-check/expected.js @@ -30,28 +30,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"); @@ -63,16 +62,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); @@ -99,7 +94,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) { @@ -134,7 +129,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 e3661764b1f6..39da31b864f7 100644 --- a/test/js/samples/each-block-keyed-animated/expected.js +++ b/test/js/samples/each-block-keyed-animated/expected.js @@ -26,7 +26,8 @@ function get_each_context(ctx, list, i) { // (19:0) {#each things as thing (thing.id)} 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; @@ -44,7 +45,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 beb5a613bc04..126b629d67dc 100644 --- a/test/js/samples/each-block-keyed/expected.js +++ b/test/js/samples/each-block-keyed/expected.js @@ -24,7 +24,8 @@ function get_each_context(ctx, list, i) { // (5:0) {#each things as thing (thing.id)} 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 { @@ -40,7 +41,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/event-handler-dynamic/expected.js b/test/js/samples/event-handler-dynamic/expected.js index 515d7e36a9c4..eb10bb8f494c 100644 --- a/test/js/samples/event-handler-dynamic/expected.js +++ b/test/js/samples/event-handler-dynamic/expected.js @@ -37,7 +37,7 @@ function create_fragment(ctx) { button1.textContent = "set handler 2"; t3 = space(); p1 = element("p"); - t4 = text(ctx.number); + t4 = text(ctx.number + ""); t5 = space(); button2 = element("button"); button2.textContent = "click"; @@ -63,7 +63,7 @@ function create_fragment(ctx) { }, p(changed, new_ctx) { ctx = new_ctx; - if (changed.number) set_data(t4, ctx.number); + if (changed.number) set_data(t4, ctx.number + ""); }, i: noop, o: noop, 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 4b0574b79dda..9fb200bc4308 100644 --- a/test/js/samples/instrumentation-script-if-no-block/expected.js +++ b/test/js/samples/instrumentation-script-if-no-block/expected.js @@ -29,7 +29,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) { @@ -40,7 +40,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 8a3fa9366a3b..bbc58692820a 100644 --- a/test/js/samples/instrumentation-script-x-equals-x/expected.js +++ b/test/js/samples/instrumentation-script-x-equals-x/expected.js @@ -19,7 +19,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; @@ -41,7 +42,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 e2d8c4466f95..d3cb4dc131d0 100644 --- a/test/js/samples/instrumentation-template-if-no-block/expected.js +++ b/test/js/samples/instrumentation-template-if-no-block/expected.js @@ -29,7 +29,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) { @@ -40,7 +40,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 eefda55f31d8..15c5c876c122 100644 --- a/test/js/samples/instrumentation-template-x-equals-x/expected.js +++ b/test/js/samples/instrumentation-template-x-equals-x/expected.js @@ -19,7 +19,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; @@ -41,7 +42,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..dce16d38a327 --- /dev/null +++ b/test/js/samples/optimise-ast/expected.js @@ -0,0 +1,66 @@ +/* generated by Svelte vX.Y.Z */ +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 231960bb49a3..4a215b8c9b48 100644 --- a/test/js/samples/unchanged-expression/expected.js +++ b/test/js/samples/unchanged-expression/expected.js @@ -41,7 +41,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); @@ -57,7 +57,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 f0409c3cca6e..f293135bb3b5 100644 --- a/test/js/samples/unreferenced-state-not-invalidated/expected.js +++ b/test/js/samples/unreferenced-state-not-invalidated/expected.js @@ -21,14 +21,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 85d894119b14..6367050e017f 100644 --- a/test/js/samples/window-binding-scroll/expected.js +++ b/test/js/samples/window-binding-scroll/expected.js @@ -32,7 +32,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; @@ -54,7 +54,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 27c5ec1563ce..ccc1fc203928 100644 --- a/test/parser/index.js +++ b/test/parser/index.js @@ -25,9 +25,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 });