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); }