Skip to content

Commit f209ae5

Browse files
committed
feat: merge neighboring text and expressions into 1 text node
1 parent 1273f97 commit f209ae5

File tree

36 files changed

+558
-112
lines changed

36 files changed

+558
-112
lines changed

src/compiler/compile/index.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { CompileOptions, Warning } from '../interfaces';
77
import Component from './Component';
88
import fuzzymatch from '../utils/fuzzymatch';
99
import get_name_from_filename from './utils/get_name_from_filename';
10+
import optimise from '../parse/optimise/index';
1011

1112
const valid_options = [
1213
'format',
@@ -25,7 +26,8 @@ const valid_options = [
2526
'tag',
2627
'css',
2728
'preserveComments',
28-
'preserveWhitespace'
29+
'preserveWhitespace',
30+
'optimiseAst',
2931
];
3032

3133
function validate_options(options: CompileOptions, warnings: Warning[]) {
@@ -57,7 +59,7 @@ function validate_options(options: CompileOptions, warnings: Warning[]) {
5759
}
5860

5961
export default function compile(source: string, options: CompileOptions = {}) {
60-
options = assign({ generate: 'dom', dev: false }, options);
62+
options = assign({ generate: 'dom', dev: false, optimiseAst: true }, options);
6163

6264
const stats = new Stats();
6365
const warnings = [];
@@ -68,6 +70,12 @@ export default function compile(source: string, options: CompileOptions = {}) {
6870
const ast = parse(source, options);
6971
stats.stop('parse');
7072

73+
if (options.optimiseAst) {
74+
stats.start('optimise-ast');
75+
optimise(ast);
76+
stats.stop('optimise-ast');
77+
}
78+
7179
stats.start('create component');
7280
const component = new Component(
7381
ast,
@@ -79,9 +87,10 @@ export default function compile(source: string, options: CompileOptions = {}) {
7987
);
8088
stats.stop('create component');
8189

82-
const js = options.generate === false
83-
? null
84-
: options.generate === 'ssr'
90+
const js =
91+
options.generate === false
92+
? null
93+
: options.generate === 'ssr'
8594
? render_ssr(component, options)
8695
: render_dom(component, options);
8796

src/compiler/compile/render_dom/wrappers/Element/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,13 @@ export default class ElementWrapper extends Wrapper {
297297
// @ts-ignore todo: should it be this.fragment.nodes[0].node.data instead?
298298
b`${node}.textContent = ${string_literal(this.fragment.nodes[0].data)};`
299299
);
300+
} else if (
301+
this.fragment.nodes.length === 1 &&
302+
this.fragment.nodes[0].node.type === 'MustacheTag' &&
303+
this.fragment.nodes[0].node.expression.node.type === 'TemplateLiteral') {
304+
block.chunks.create.push(
305+
b`${node}.textContent = ${this.fragment.nodes[0].node.expression.manipulate(block)};`
306+
);
300307
} else {
301308
const state = {
302309
quasi: {

src/compiler/compile/render_dom/wrappers/shared/Tag.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Renderer from '../../Renderer';
44
import Block from '../../Block';
55
import MustacheTag from '../../../nodes/MustacheTag';
66
import RawMustacheTag from '../../../nodes/RawMustacheTag';
7+
import { is_string } from './is_string';
78
import { Node } from 'estree';
89

910
export default class Tag extends Wrapper {
@@ -29,14 +30,20 @@ export default class Tag extends Wrapper {
2930
update: ((value: Node) => (Node | Node[]))
3031
) {
3132
const dependencies = this.node.expression.dynamic_dependencies();
33+
const should_extract = this.node.should_cache && dependencies.length > 0;
3234
let snippet = this.node.expression.manipulate(block);
35+
snippet = is_string(snippet) ? snippet : x`${snippet} + ""`;
36+
37+
if (should_extract) {
38+
const fn_name = block.get_unique_name(`${this.var.name}_fn`);
39+
block.add_variable(fn_name, x`(#ctx) => ${snippet}`);
40+
snippet = x`${fn_name}(#ctx)`;
41+
}
3342

3443
const value = this.node.should_cache && block.get_unique_name(`${this.var.name}_value`);
3544
const content = this.node.should_cache ? value : snippet;
3645

37-
snippet = x`${snippet} + ""`;
38-
39-
if (this.node.should_cache) block.add_variable(value, snippet); // TODO may need to coerce snippet to string
46+
if (this.node.should_cache) block.add_variable(value, snippet);
4047

4148
if (dependencies.length > 0) {
4249
let condition = x`#changed.${dependencies[0]}`;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function is_string(node) {
2+
return node.type === 'TemplateLiteral' || (node.type === 'Literal' && typeof node.value === 'string');
3+
}

src/compiler/interfaces.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ export interface CompileOptions {
124124

125125
preserveComments?: boolean;
126126
preserveWhitespace?: boolean;
127+
optimiseAst?: boolean;
127128
}
128129

129130
export interface ParserOptions {

src/compiler/parse/optimise/index.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { walk } from 'estree-walker';
2+
import { Text, MustacheTag } from '../../interfaces';
3+
4+
export default function optimise(ast) {
5+
walk(ast, {
6+
enter(node: any) {
7+
if (node.type === 'Element') {
8+
optimise_text_content(node.children);
9+
}
10+
},
11+
});
12+
}
13+
14+
const text_like_node_type = new Set(['MustacheTag', 'Text']);
15+
16+
function optimise_text_content(children) {
17+
let start = 0;
18+
let end = 0;
19+
20+
do {
21+
while (
22+
start < children.length &&
23+
!text_like_node_type.has(children[start].type)
24+
)
25+
start++;
26+
27+
end = start;
28+
29+
while (end < children.length && text_like_node_type.has(children[end].type))
30+
end++;
31+
32+
if (end > start) {
33+
const merged = merge_text_siblings(children.slice(start, end));
34+
children.splice(start, end - start, ...merged);
35+
start = end;
36+
}
37+
} while (start < children.length);
38+
}
39+
40+
function merge_text_siblings(children: Array<Text | MustacheTag>) {
41+
if (children.length < 3) {
42+
return children;
43+
}
44+
45+
const literal = {
46+
type: 'TemplateLiteral',
47+
expressions: [],
48+
quasis: [],
49+
};
50+
const state = {
51+
quasi: {
52+
type: 'TemplateElement',
53+
value: { raw: '' },
54+
start: children[0].start,
55+
end: children[0].start
56+
},
57+
};
58+
59+
for (const child of children) {
60+
if (child.type === 'MustacheTag') {
61+
literal.quasis.push(state.quasi);
62+
literal.expressions.push(child.expression);
63+
state.quasi = {
64+
type: 'TemplateElement',
65+
value: { raw: '' },
66+
// @ts-ignore
67+
start: child.expression.end + 1,
68+
// @ts-ignore
69+
end: child.expression.end + 1
70+
};
71+
} else if (child.type === 'Text') {
72+
state.quasi.value.raw += child.data;
73+
state.quasi.end = child.end;
74+
}
75+
}
76+
77+
literal.quasis.push(state.quasi);
78+
79+
return [{
80+
type: 'MustacheTag',
81+
expression: literal,
82+
start: children[0].start,
83+
end: children[children.length - 1].end,
84+
}];
85+
}

test/js/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ describe("js", () => {
3636

3737
const expected = fs.readFileSync(`${dir}/expected.js`, "utf-8");
3838

39+
if (process.env.UPDATE_EXPECTED) {
40+
fs.writeFileSync(`${dir}/expected.js`, actual);
41+
}
42+
3943
assert.equal(
4044
actual.trim().replace(/^[ \t]+$/gm, ""),
4145
expected.trim().replace(/^[ \t]+$/gm, "")

test/js/samples/capture-inject-dev-only/expected.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ function create_fragment(ctx) {
2424
return {
2525
c() {
2626
p = element("p");
27-
t0 = text(ctx.foo);
27+
t0 = text(ctx.foo + "");
2828
t1 = space();
2929
input = element("input");
3030
dispose = listen(input, "input", ctx.input_input_handler);
@@ -37,7 +37,7 @@ function create_fragment(ctx) {
3737
set_input_value(input, ctx.foo);
3838
},
3939
p(changed, ctx) {
40-
if (changed.foo) set_data(t0, ctx.foo);
40+
if (changed.foo) set_data(t0, ctx.foo + "");
4141

4242
if (changed.foo && input.value !== ctx.foo) {
4343
set_input_value(input, ctx.foo);

test/js/samples/collapses-text-around-comments/expected.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@ function create_fragment(ctx) {
2626
return {
2727
c() {
2828
p = element("p");
29-
t = text(ctx.foo);
29+
t = text(ctx.foo + "");
3030
attr(p, "class", "svelte-1a7i8ec");
3131
},
3232
m(target, anchor) {
3333
insert(target, p, anchor);
3434
append(p, t);
3535
},
3636
p(changed, ctx) {
37-
if (changed.foo) set_data(t, ctx.foo);
37+
if (changed.foo) set_data(t, ctx.foo + "");
3838
},
3939
i: noop,
4040
o: noop,

test/js/samples/component-store-access-invalidate/expected.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ function create_fragment(ctx) {
2121
return {
2222
c() {
2323
h1 = element("h1");
24-
t = text(ctx.$foo);
24+
t = text(ctx.$foo + "");
2525
},
2626
m(target, anchor) {
2727
insert(target, h1, anchor);
2828
append(h1, t);
2929
},
3030
p(changed, ctx) {
31-
if (changed.$foo) set_data(t, ctx.$foo);
31+
if (changed.$foo) set_data(t, ctx.$foo + "");
3232
},
3333
i: noop,
3434
o: noop,

test/js/samples/component-store-reassign-invalidate/expected.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ function create_fragment(ctx) {
2626
return {
2727
c() {
2828
h1 = element("h1");
29-
t0 = text(ctx.$foo);
29+
t0 = text(ctx.$foo + "");
3030
t1 = space();
3131
button = element("button");
3232
button.textContent = "reset";
@@ -39,7 +39,7 @@ function create_fragment(ctx) {
3939
insert(target, button, anchor);
4040
},
4141
p(changed, ctx) {
42-
if (changed.$foo) set_data(t0, ctx.$foo);
42+
if (changed.$foo) set_data(t0, ctx.$foo + "");
4343
},
4444
i: noop,
4545
o: noop,

test/js/samples/debug-empty/expected.js

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,16 @@ const file = undefined;
1818

1919
function create_fragment(ctx) {
2020
let h1;
21+
let t0_fn = ctx => `Hello ${ctx.name}!`;
22+
let t0_value = t0_fn(ctx);
2123
let t0;
2224
let t1;
23-
let t2;
24-
let t3;
2525

2626
const block = {
2727
c: function create() {
2828
h1 = element("h1");
29-
t0 = text("Hello ");
30-
t1 = text(ctx.name);
31-
t2 = text("!");
32-
t3 = space();
29+
t0 = text(t0_value);
30+
t1 = space();
3331
debugger;
3432
add_location(h1, file, 4, 0, 38);
3533
},
@@ -39,19 +37,17 @@ function create_fragment(ctx) {
3937
m: function mount(target, anchor) {
4038
insert_dev(target, h1, anchor);
4139
append_dev(h1, t0);
42-
append_dev(h1, t1);
43-
append_dev(h1, t2);
44-
insert_dev(target, t3, anchor);
40+
insert_dev(target, t1, anchor);
4541
},
4642
p: function update(changed, ctx) {
47-
if (changed.name) set_data_dev(t1, ctx.name);
43+
if (changed.name && t0_value !== (t0_value = t0_fn(ctx))) set_data_dev(t0, t0_value);
4844
debugger;
4945
},
5046
i: noop,
5147
o: noop,
5248
d: function destroy(detaching) {
5349
if (detaching) detach_dev(h1);
54-
if (detaching) detach_dev(t3);
50+
if (detaching) detach_dev(t1);
5551
}
5652
};
5753

test/js/samples/debug-foo-bar-baz-things/expected.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ function get_each_context(ctx, list, i) {
2525

2626
function create_each_block(ctx) {
2727
let span;
28-
let t0_value = ctx.thing.name + "";
28+
let t0_fn = ctx => ctx.thing.name + "";
29+
let t0_value = t0_fn(ctx);
2930
let t0;
3031
let t1;
3132

@@ -49,7 +50,7 @@ function create_each_block(ctx) {
4950
insert_dev(target, t1, anchor);
5051
},
5152
p: function update(changed, ctx) {
52-
if (changed.things && t0_value !== (t0_value = ctx.thing.name + "")) set_data_dev(t0, t0_value);
53+
if (changed.things && t0_value !== (t0_value = t0_fn(ctx))) set_data_dev(t0, t0_value);
5354

5455
if (changed.foo || changed.bar || changed.baz || changed.things) {
5556
const { foo, bar, baz, thing } = ctx;
@@ -95,7 +96,7 @@ function create_fragment(ctx) {
9596
t0 = space();
9697
p = element("p");
9798
t1 = text("foo: ");
98-
t2 = text(ctx.foo);
99+
t2 = text(ctx.foo + "");
99100
add_location(p, file, 12, 0, 182);
100101
},
101102
l: function claim(nodes) {
@@ -135,7 +136,7 @@ function create_fragment(ctx) {
135136
each_blocks.length = each_value.length;
136137
}
137138

138-
if (changed.foo) set_data_dev(t2, ctx.foo);
139+
if (changed.foo) set_data_dev(t2, ctx.foo + "");
139140
},
140141
i: noop,
141142
o: noop,

0 commit comments

Comments
 (0)