From 2b1aa77f294c0f34bf343ab5736a65610f9ab1f0 Mon Sep 17 00:00:00 2001 From: Richard Harris Date: Mon, 18 Mar 2019 09:15:27 -0400 Subject: [PATCH] collapse consecutive whitespace characters in the absense of options.preserveWhitespace or a

---
 src/compile/Component.ts                        |  7 +++++--
 src/compile/index.ts                            |  3 ++-
 src/compile/nodes/Text.ts                       | 17 ++++++++++++++++-
 src/compile/render-dom/wrappers/Text.ts         |  2 +-
 src/interfaces.ts                               |  3 ++-
 src/internal/dom.js                             |  4 ++++
 test/helpers.js                                 |  4 ++--
 test/js/samples/debug-empty/expected.js         |  3 ++-
 .../debug-foo-bar-baz-things/expected.js        |  5 +++--
 test/js/samples/debug-foo/expected.js           |  5 +++--
 .../expected.js                                 |  3 ++-
 test/js/samples/do-use-dataset/expected.js      |  4 ++--
 .../dont-use-dataset-in-legacy/expected.js      |  4 ++--
 .../each-block-changed-check/expected.js        |  7 ++++---
 test/js/samples/event-modifiers/expected.js     |  8 ++++----
 .../inline-style-unoptimized/expected.js        |  4 ++--
 .../expected.js                                 |  3 ++-
 .../expected.js                                 |  3 ++-
 .../expected.js                                 |  3 ++-
 .../expected.js                                 |  3 ++-
 .../samples/non-imported-component/expected.js  |  4 ++--
 .../samples/use-elements-as-anchors/expected.js | 14 +++++++-------
 .../samples/binding-input-checkbox/_config.js   | 10 ++++++++--
 .../samples/component-data-dynamic/_config.js   | 16 ++++++++++++++--
 test/runtime/samples/custom-method/_config.js   | 15 ++++++++++++---
 25 files changed, 107 insertions(+), 47 deletions(-)

diff --git a/src/compile/Component.ts b/src/compile/Component.ts
index f7c1c7dc5d..ea3004e7f0 100644
--- a/src/compile/Component.ts
+++ b/src/compile/Component.ts
@@ -25,6 +25,7 @@ type ComponentOptions = {
 	tag?: string;
 	immutable?: boolean;
 	accessors?: boolean;
+	preserveWhitespace?: boolean;
 };
 
 // We need to tell estree-walker that it should always
@@ -1195,7 +1196,8 @@ function process_component_options(component: Component, nodes) {
 		immutable: component.compile_options.immutable || false,
 		accessors: 'accessors' in component.compile_options
 			? component.compile_options.accessors
-			: !!component.compile_options.customElement
+			: !!component.compile_options.customElement,
+		preserveWhitespace: !!component.compile_options.preserveWhitespace
 	};
 
 	const node = nodes.find(node => node.name === 'svelte:options');
@@ -1271,6 +1273,7 @@ function process_component_options(component: Component, nodes) {
 
 					case 'accessors':
 					case 'immutable':
+					case 'preserveWhitespace':
 						const code = `invalid-${name}-value`;
 						const message = `${name} attribute must be true or false`
 						const value = get_value(attribute, code, message);
@@ -1291,7 +1294,7 @@ function process_component_options(component: Component, nodes) {
 			else {
 				component.error(attribute, {
 					code: `invalid-options-attribute`,
-					message: ` can only have static 'tag', 'namespace', 'accessors' and 'immutable' attributes`
+					message: ` can only have static 'tag', 'namespace', 'accessors', 'immutable' and 'preserveWhitespace' attributes`
 				});
 			}
 		});
diff --git a/src/compile/index.ts b/src/compile/index.ts
index c3c3ed5912..b8eace97b5 100644
--- a/src/compile/index.ts
+++ b/src/compile/index.ts
@@ -23,7 +23,8 @@ const valid_options = [
 	'customElement',
 	'tag',
 	'css',
-	'preserveComments'
+	'preserveComments',
+	'preserveWhitespace'
 ];
 
 function validate_options(options: CompileOptions, warnings: Warning[]) {
diff --git a/src/compile/nodes/Text.ts b/src/compile/nodes/Text.ts
index 9600ebf97c..1c31c9d83d 100644
--- a/src/compile/nodes/Text.ts
+++ b/src/compile/nodes/Text.ts
@@ -1,11 +1,26 @@
 import Node from './shared/Node';
+import Component from '../Component';
+import TemplateScope from './shared/TemplateScope';
 
 export default class Text extends Node {
 	type: 'Text';
 	data: string;
+	use_space = false;
 
-	constructor(component, parent, scope, info) {
+	constructor(component: Component, parent: Node, scope: TemplateScope, info: any) {
 		super(component, parent, scope, info);
 		this.data = info.data;
+
+		if (!component.component_options.preserveWhitespace && !/\S/.test(info.data)) {
+			let node = parent;
+			while (node) {
+				if (node.type === 'Element' && node.name === 'pre') {
+					return;
+				}
+				node = node.parent;
+			}
+
+			this.use_space = true;
+		}
 	}
 }
\ No newline at end of file
diff --git a/src/compile/render-dom/wrappers/Text.ts b/src/compile/render-dom/wrappers/Text.ts
index c83fd38f2c..ceacae29ba 100644
--- a/src/compile/render-dom/wrappers/Text.ts
+++ b/src/compile/render-dom/wrappers/Text.ts
@@ -54,7 +54,7 @@ export default class TextWrapper extends Wrapper {
 
 		block.add_element(
 			this.var,
-			`@text(${stringify(this.data)})`,
+			this.node.use_space ? `@space()` : `@text(${stringify(this.data)})`,
 			parent_nodes && `@claim_text(${parent_nodes}, ${stringify(this.data)})`,
 			parent_node
 		);
diff --git a/src/interfaces.ts b/src/interfaces.ts
index ff10c4b13a..fd34f31ea1 100644
--- a/src/interfaces.ts
+++ b/src/interfaces.ts
@@ -57,7 +57,8 @@ export interface CompileOptions {
 	tag?: string;
 	css?: boolean;
 
-	preserveComments?: boolean | false;
+	preserveComments?: boolean;
+	preserveWhitespace?: boolean;
 }
 
 export interface Visitor {
diff --git a/src/internal/dom.js b/src/internal/dom.js
index 0b8173c3d1..3df4690a1e 100644
--- a/src/internal/dom.js
+++ b/src/internal/dom.js
@@ -46,6 +46,10 @@ export function text(data) {
 	return document.createTextNode(data);
 }
 
+export function space() {
+	return text(' ');
+}
+
 export function comment() {
 	return document.createComment('');
 }
diff --git a/test/helpers.js b/test/helpers.js
index b7064c99eb..e07d7c9b06 100644
--- a/test/helpers.js
+++ b/test/helpers.js
@@ -85,11 +85,11 @@ function cleanChildren(node) {
 				node.removeChild(child);
 			}
 
-			child.data = child.data.replace(/\s{2,}/g, '\n');
+			child.data = child.data.replace(/\s+/g, '\n');
 
 			if (previous && previous.nodeType === 3) {
 				previous.data += child.data;
-				previous.data = previous.data.replace(/\s{2,}/g, '\n');
+				previous.data = previous.data.replace(/\s+/g, '\n');
 
 				node.removeChild(child);
 				child = previous;
diff --git a/test/js/samples/debug-empty/expected.js b/test/js/samples/debug-empty/expected.js
index f122a7b979..ffaf5e6f0f 100644
--- a/test/js/samples/debug-empty/expected.js
+++ b/test/js/samples/debug-empty/expected.js
@@ -10,6 +10,7 @@ import {
 	noop,
 	safe_not_equal,
 	set_data,
+	space,
 	text
 } from "svelte/internal";
 
@@ -24,7 +25,7 @@ function create_fragment(ctx) {
 			t0 = text("Hello ");
 			t1 = text(ctx.name);
 			t2 = text("!");
-			t3 = text("\n");
+			t3 = space();
 			debugger;
 			add_location(h1, file, 4, 0, 38);
 		},
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 b8b134606c..1ca6fdfed9 100644
--- a/test/js/samples/debug-foo-bar-baz-things/expected.js
+++ b/test/js/samples/debug-foo-bar-baz-things/expected.js
@@ -11,6 +11,7 @@ import {
 	noop,
 	safe_not_equal,
 	set_data,
+	space,
 	text
 } from "svelte/internal";
 
@@ -30,7 +31,7 @@ function create_each_block(ctx) {
 		c: function create() {
 			span = element("span");
 			t0 = text(t0_value);
-			t1 = text("\n\t");
+			t1 = space();
 
 			{
 				const { foo, bar, baz, thing } = ctx;
@@ -84,7 +85,7 @@ function create_fragment(ctx) {
 				each_blocks[i].c();
 			}
 
-			t0 = text("\n\n");
+			t0 = space();
 			p = element("p");
 			t1 = text("foo: ");
 			t2 = text(ctx.foo);
diff --git a/test/js/samples/debug-foo/expected.js b/test/js/samples/debug-foo/expected.js
index 94f580ed9d..3a57e81f3e 100644
--- a/test/js/samples/debug-foo/expected.js
+++ b/test/js/samples/debug-foo/expected.js
@@ -11,6 +11,7 @@ import {
 	noop,
 	safe_not_equal,
 	set_data,
+	space,
 	text
 } from "svelte/internal";
 
@@ -30,7 +31,7 @@ function create_each_block(ctx) {
 		c: function create() {
 			span = element("span");
 			t0 = text(t0_value);
-			t1 = text("\n\t");
+			t1 = space();
 
 			{
 				const { foo } = ctx;
@@ -84,7 +85,7 @@ function create_fragment(ctx) {
 				each_blocks[i].c();
 			}
 
-			t0 = text("\n\n");
+			t0 = space();
 			p = element("p");
 			t1 = text("foo: ");
 			t2 = text(ctx.foo);
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 447477bea5..51e2c54938 100644
--- a/test/js/samples/dev-warning-missing-data-computed/expected.js
+++ b/test/js/samples/dev-warning-missing-data-computed/expected.js
@@ -10,6 +10,7 @@ import {
 	noop,
 	safe_not_equal,
 	set_data,
+	space,
 	text
 } from "svelte/internal";
 
@@ -22,7 +23,7 @@ function create_fragment(ctx) {
 		c: function create() {
 			p = element("p");
 			t0 = text(t0_value);
-			t1 = text("\n\t");
+			t1 = space();
 			t2 = text(ctx.bar);
 			add_location(p, file, 7, 0, 67);
 		},
diff --git a/test/js/samples/do-use-dataset/expected.js b/test/js/samples/do-use-dataset/expected.js
index 51c5a83f9d..60a8c54869 100644
--- a/test/js/samples/do-use-dataset/expected.js
+++ b/test/js/samples/do-use-dataset/expected.js
@@ -7,7 +7,7 @@ import {
 	insert,
 	noop,
 	safe_not_equal,
-	text
+	space
 } from "svelte/internal";
 
 function create_fragment(ctx) {
@@ -16,7 +16,7 @@ function create_fragment(ctx) {
 	return {
 		c() {
 			div0 = element("div");
-			t = text("\n");
+			t = space();
 			div1 = element("div");
 			div0.dataset.foo = "bar";
 			div1.dataset.foo = ctx.bar;
diff --git a/test/js/samples/dont-use-dataset-in-legacy/expected.js b/test/js/samples/dont-use-dataset-in-legacy/expected.js
index 700280901f..4d63cb8c73 100644
--- a/test/js/samples/dont-use-dataset-in-legacy/expected.js
+++ b/test/js/samples/dont-use-dataset-in-legacy/expected.js
@@ -8,7 +8,7 @@ import {
 	insert,
 	noop,
 	safe_not_equal,
-	text
+	space
 } from "svelte/internal";
 
 function create_fragment(ctx) {
@@ -17,7 +17,7 @@ function create_fragment(ctx) {
 	return {
 		c() {
 			div0 = element("div");
-			t = text("\n");
+			t = space();
 			div1 = element("div");
 			attr(div0, "data-foo", "bar");
 			attr(div1, "data-foo", ctx.bar);
diff --git a/test/js/samples/each-block-changed-check/expected.js b/test/js/samples/each-block-changed-check/expected.js
index 89906806bc..c65cd3479a 100644
--- a/test/js/samples/each-block-changed-check/expected.js
+++ b/test/js/samples/each-block-changed-check/expected.js
@@ -11,6 +11,7 @@ import {
 	noop,
 	safe_not_equal,
 	set_data,
+	space,
 	text
 } from "svelte/internal";
 
@@ -30,13 +31,13 @@ function create_each_block(ctx) {
 			div = element("div");
 			strong = element("strong");
 			t0 = text(ctx.i);
-			t1 = text("\n\n\t\t");
+			t1 = space();
 			span = element("span");
 			t2 = text(t2_value);
 			t3 = text(" wrote ");
 			t4 = text(t4_value);
 			t5 = text(" ago:");
-			t6 = text("\n\n\t\t");
+			t6 = space();
 			raw_before = element('noscript');
 			span.className = "meta";
 			div.className = "comment";
@@ -97,7 +98,7 @@ function create_fragment(ctx) {
 				each_blocks[i].c();
 			}
 
-			t0 = text("\n\n");
+			t0 = space();
 			p = element("p");
 			t1 = text(ctx.foo);
 		},
diff --git a/test/js/samples/event-modifiers/expected.js b/test/js/samples/event-modifiers/expected.js
index 9d83341f1b..617ccff197 100644
--- a/test/js/samples/event-modifiers/expected.js
+++ b/test/js/samples/event-modifiers/expected.js
@@ -11,8 +11,8 @@ import {
 	prevent_default,
 	run_all,
 	safe_not_equal,
-	stop_propagation,
-	text
+	space,
+	stop_propagation
 } from "svelte/internal";
 
 function create_fragment(ctx) {
@@ -23,10 +23,10 @@ function create_fragment(ctx) {
 			div = element("div");
 			button0 = element("button");
 			button0.textContent = "click me";
-			t1 = text("\n\t");
+			t1 = space();
 			button1 = element("button");
 			button1.textContent = "or me";
-			t3 = text("\n\t");
+			t3 = space();
 			button2 = element("button");
 			button2.textContent = "or me!";
 			dispose = [
diff --git a/test/js/samples/inline-style-unoptimized/expected.js b/test/js/samples/inline-style-unoptimized/expected.js
index 0516cc3ef1..f2e52ebb48 100644
--- a/test/js/samples/inline-style-unoptimized/expected.js
+++ b/test/js/samples/inline-style-unoptimized/expected.js
@@ -7,7 +7,7 @@ import {
 	insert,
 	noop,
 	safe_not_equal,
-	text
+	space
 } from "svelte/internal";
 
 function create_fragment(ctx) {
@@ -16,7 +16,7 @@ function create_fragment(ctx) {
 	return {
 		c() {
 			div0 = element("div");
-			t = text("\n");
+			t = space();
 			div1 = element("div");
 			div0.style.cssText = ctx.style;
 			div1.style.cssText = div1_style_value = "" + ctx.key + ": " + ctx.value;
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 bb6e3f8ef5..4788fc2e09 100644
--- a/test/js/samples/instrumentation-script-if-no-block/expected.js
+++ b/test/js/samples/instrumentation-script-if-no-block/expected.js
@@ -10,6 +10,7 @@ import {
 	noop,
 	safe_not_equal,
 	set_data,
+	space,
 	text
 } from "svelte/internal";
 
@@ -20,7 +21,7 @@ function create_fragment(ctx) {
 		c() {
 			button = element("button");
 			button.textContent = "foo";
-			t1 = text("\n\n");
+			t1 = space();
 			p = element("p");
 			t2 = text("x: ");
 			t3 = text(ctx.x);
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 015dd39415..165c4e27b0 100644
--- a/test/js/samples/instrumentation-script-x-equals-x/expected.js
+++ b/test/js/samples/instrumentation-script-x-equals-x/expected.js
@@ -10,6 +10,7 @@ import {
 	noop,
 	safe_not_equal,
 	set_data,
+	space,
 	text
 } from "svelte/internal";
 
@@ -20,7 +21,7 @@ function create_fragment(ctx) {
 		c() {
 			button = element("button");
 			button.textContent = "foo";
-			t1 = text("\n\n");
+			t1 = space();
 			p = element("p");
 			t2 = text("number of things: ");
 			t3 = text(t3_value);
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 fc50a8b850..38f7f7fcd0 100644
--- a/test/js/samples/instrumentation-template-if-no-block/expected.js
+++ b/test/js/samples/instrumentation-template-if-no-block/expected.js
@@ -10,6 +10,7 @@ import {
 	noop,
 	safe_not_equal,
 	set_data,
+	space,
 	text
 } from "svelte/internal";
 
@@ -20,7 +21,7 @@ function create_fragment(ctx) {
 		c() {
 			button = element("button");
 			button.textContent = "foo";
-			t1 = text("\n\n");
+			t1 = space();
 			p = element("p");
 			t2 = text("x: ");
 			t3 = text(ctx.x);
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 14a7e61274..4adc229ff7 100644
--- a/test/js/samples/instrumentation-template-x-equals-x/expected.js
+++ b/test/js/samples/instrumentation-template-x-equals-x/expected.js
@@ -10,6 +10,7 @@ import {
 	noop,
 	safe_not_equal,
 	set_data,
+	space,
 	text
 } from "svelte/internal";
 
@@ -20,7 +21,7 @@ function create_fragment(ctx) {
 		c() {
 			button = element("button");
 			button.textContent = "foo";
-			t1 = text("\n\n");
+			t1 = space();
 			p = element("p");
 			t2 = text("number of things: ");
 			t3 = text(t3_value);
diff --git a/test/js/samples/non-imported-component/expected.js b/test/js/samples/non-imported-component/expected.js
index ccde3230d3..2fb8bb1798 100644
--- a/test/js/samples/non-imported-component/expected.js
+++ b/test/js/samples/non-imported-component/expected.js
@@ -7,7 +7,7 @@ import {
 	mount_component,
 	noop,
 	safe_not_equal,
-	text
+	space
 } from "svelte/internal";
 import Imported from "Imported.svelte";
 
@@ -21,7 +21,7 @@ function create_fragment(ctx) {
 	return {
 		c() {
 			imported.$$.fragment.c();
-			t = text("\n");
+			t = space();
 			nonimported.$$.fragment.c();
 		},
 
diff --git a/test/js/samples/use-elements-as-anchors/expected.js b/test/js/samples/use-elements-as-anchors/expected.js
index 4a7d84bf01..7277dc8b7d 100644
--- a/test/js/samples/use-elements-as-anchors/expected.js
+++ b/test/js/samples/use-elements-as-anchors/expected.js
@@ -9,7 +9,7 @@ import {
 	insert,
 	noop,
 	safe_not_equal,
-	text
+	space
 } from "svelte/internal";
 
 // (10:1) {#if a}
@@ -139,19 +139,19 @@ function create_fragment(ctx) {
 		c() {
 			div = element("div");
 			if (if_block0) if_block0.c();
-			t0 = text("\n\n\t");
+			t0 = space();
 			p0 = element("p");
 			p0.textContent = "this can be used as an anchor";
-			t2 = text("\n\n\t");
+			t2 = space();
 			if (if_block1) if_block1.c();
-			t3 = text("\n\n\t");
+			t3 = space();
 			if (if_block2) if_block2.c();
-			t4 = text("\n\n\t");
+			t4 = space();
 			p1 = element("p");
 			p1.textContent = "so can this";
-			t6 = text("\n\n\t");
+			t6 = space();
 			if (if_block3) if_block3.c();
-			t7 = text("\n\n");
+			t7 = space();
 			if (if_block4) if_block4.c();
 			if_block4_anchor = comment();
 		},
diff --git a/test/runtime/samples/binding-input-checkbox/_config.js b/test/runtime/samples/binding-input-checkbox/_config.js
index 93f673aa07..f6b553ee23 100644
--- a/test/runtime/samples/binding-input-checkbox/_config.js
+++ b/test/runtime/samples/binding-input-checkbox/_config.js
@@ -22,10 +22,16 @@ export default {
 		input.checked = false;
 		await input.dispatchEvent(event);
 
-		assert.equal(target.innerHTML, `\n

false

`); + assert.htmlEqual(target.innerHTML, ` + +

false

+ `); component.foo = true; assert.equal(input.checked, true); - assert.equal(target.innerHTML, `\n

true

`); + assert.htmlEqual(target.innerHTML, ` + +

true

+ `); } }; diff --git a/test/runtime/samples/component-data-dynamic/_config.js b/test/runtime/samples/component-data-dynamic/_config.js index 3a765c5b04..40436066fa 100644 --- a/test/runtime/samples/component-data-dynamic/_config.js +++ b/test/runtime/samples/component-data-dynamic/_config.js @@ -5,13 +5,25 @@ export default { compound: 'piece of', go: { deeper: 'core' } }, - html: `

foo: lol

\n

baz: 42 (number)

\n

qux: this is a piece of string

\n

quux: core

`, + + html: ` +

foo: lol

+

baz: 42 (number)

+

qux: this is a piece of string

+

quux: core

+ `, + test({ assert, component, target }) { component.bar = 'wut'; component.x = 3; component.compound = 'rather boring'; component.go = { deeper: 'heart' }; - assert.equal( target.innerHTML, `

foo: wut

\n

baz: 43 (number)

\n

qux: this is a rather boring string

\n

quux: heart

` ); + assert.htmlEqual(target.innerHTML, ` +

foo: wut

+

baz: 43 (number)

+

qux: this is a rather boring string

+

quux: heart

+ `); } }; diff --git a/test/runtime/samples/custom-method/_config.js b/test/runtime/samples/custom-method/_config.js index b198731896..debaa15a66 100644 --- a/test/runtime/samples/custom-method/_config.js +++ b/test/runtime/samples/custom-method/_config.js @@ -1,5 +1,8 @@ export default { - html: '\n\n

0

', + html: ` + +

0

+ `, async test({ assert, component, target, window }) { const button = target.querySelector('button'); @@ -7,11 +10,17 @@ export default { await button.dispatchEvent(event); assert.equal(component.counter, 1); - assert.equal(target.innerHTML, '\n\n

1

'); + assert.htmlEqual(target.innerHTML, ` + +

1

+ `); await button.dispatchEvent(event); assert.equal(component.counter, 2); - assert.equal(target.innerHTML, '\n\n

2

'); + assert.htmlEqual(target.innerHTML, ` + +

2

+ `); assert.equal(component.foo(), 42); }