From bfdcf936eaa493695ce63cc21fa1e301e7de8e43 Mon Sep 17 00:00:00 2001 From: Chris Reeves Date: Fri, 11 Jan 2019 01:14:00 -0500 Subject: [PATCH 1/3] gather event listeners for slot default elements and render in a condition - fixes #1977 --- src/compile/render-dom/Block.ts | 50 ++++++++++--------- src/compile/render-dom/wrappers/Slot.ts | 4 ++ .../Nested.html | 7 +++ .../_config.js | 3 ++ .../main.html | 7 +++ 5 files changed, 48 insertions(+), 23 deletions(-) create mode 100644 test/runtime/samples/component-slot-used-with-default-event/Nested.html create mode 100644 test/runtime/samples/component-slot-used-with-default-event/_config.js create mode 100644 test/runtime/samples/component-slot-used-with-default-event/main.html diff --git a/src/compile/render-dom/Block.ts b/src/compile/render-dom/Block.ts index bee2072ef1..a7b53161a4 100644 --- a/src/compile/render-dom/Block.ts +++ b/src/compile/render-dom/Block.ts @@ -234,29 +234,7 @@ export default class Block { this.builders.mount.addLine(`${this.autofocus}.focus();`); } - if (this.event_listeners.length > 0) { - this.addVariable('#dispose'); - - if (this.event_listeners.length === 1) { - this.builders.hydrate.addLine( - `#dispose = ${this.event_listeners[0]};` - ); - - this.builders.destroy.addLine( - `#dispose();` - ) - } else { - this.builders.hydrate.addBlock(deindent` - #dispose = [ - ${this.event_listeners.join(',\n')} - ]; - `); - - this.builders.destroy.addLine( - `@run_all(#dispose);` - ); - } - } + this.renderListeners(); const properties = new CodeBuilder(); @@ -403,6 +381,32 @@ export default class Block { }); } + renderListeners(chunk: string = '') { + if (this.event_listeners.length > 0) { + this.addVariable(`#dispose${chunk}`); + + if (this.event_listeners.length === 1) { + this.builders.hydrate.addLine( + `#dispose${chunk} = ${this.event_listeners[0]};` + ); + + this.builders.destroy.addLine( + `#dispose${chunk}();` + ) + } else { + this.builders.hydrate.addBlock(deindent` + #dispose${chunk} = [ + ${this.event_listeners.join(',\n')} + ]; + `); + + this.builders.destroy.addLine( + `@run_all(#dispose${chunk});` + ); + } + } + } + toString() { const localKey = this.key && this.getUniqueName('key'); diff --git a/src/compile/render-dom/wrappers/Slot.ts b/src/compile/render-dom/wrappers/Slot.ts index 954c4c56ef..2788fc075b 100644 --- a/src/compile/render-dom/wrappers/Slot.ts +++ b/src/compile/render-dom/wrappers/Slot.ts @@ -72,7 +72,11 @@ export default class SlotWrapper extends Wrapper { block.builders.update.pushCondition(`!${content_name}`); block.builders.destroy.pushCondition(`!${content_name}`); + const listeners = block.event_listeners; + block.event_listeners = []; this.fragment.render(block, parentNode, parentNodes); + block.renderListeners(`_${content_name}`); + block.event_listeners = listeners; block.builders.create.popCondition(); block.builders.hydrate.popCondition(); diff --git a/test/runtime/samples/component-slot-used-with-default-event/Nested.html b/test/runtime/samples/component-slot-used-with-default-event/Nested.html new file mode 100644 index 0000000000..2682f7168b --- /dev/null +++ b/test/runtime/samples/component-slot-used-with-default-event/Nested.html @@ -0,0 +1,7 @@ + + +

+ +

\ No newline at end of file diff --git a/test/runtime/samples/component-slot-used-with-default-event/_config.js b/test/runtime/samples/component-slot-used-with-default-event/_config.js new file mode 100644 index 0000000000..4a174a9a05 --- /dev/null +++ b/test/runtime/samples/component-slot-used-with-default-event/_config.js @@ -0,0 +1,3 @@ +export default { + html: '

Hello

', show: true +}; diff --git a/test/runtime/samples/component-slot-used-with-default-event/main.html b/test/runtime/samples/component-slot-used-with-default-event/main.html new file mode 100644 index 0000000000..06a1f64cae --- /dev/null +++ b/test/runtime/samples/component-slot-used-with-default-event/main.html @@ -0,0 +1,7 @@ + + + + Hello + \ No newline at end of file From 4f4577aef35cb64b45bc6995b2fb148f5cab1145 Mon Sep 17 00:00:00 2001 From: Chris Reeves Date: Fri, 11 Jan 2019 01:15:23 -0500 Subject: [PATCH 2/3] use statement tree for code builder instead of strings to avoid conditional interference --- src/utils/CodeBuilder.ts | 199 ++++++++++++++------------------------- 1 file changed, 71 insertions(+), 128 deletions(-) diff --git a/src/utils/CodeBuilder.ts b/src/utils/CodeBuilder.ts index 05e26b232c..79fb19d85d 100644 --- a/src/utils/CodeBuilder.ts +++ b/src/utils/CodeBuilder.ts @@ -1,168 +1,111 @@ import repeat from './repeat'; -enum ChunkType { - Line, - Block +const whitespace = /^\s+$/; + +interface Chunk { + parent?: BlockChunk; + type: 'root'|'line'|'condition'; + children?: Chunk[]; + line?: string; + block?: boolean; + condition?: string; } -interface Condition { - condition: string; - used: boolean; +interface BlockChunk extends Chunk { + type: 'root'|'condition'; + children: Chunk[]; + parent: BlockChunk; } export default class CodeBuilder { - result: string; - first: ChunkType; - last: ChunkType; - lastCondition: string; - conditionStack: Condition[]; - indent: string; + root: BlockChunk = { type: 'root', children: [], parent: null }; + last: Chunk; + current: BlockChunk; constructor(str = '') { - this.result = str; - - const initial = str - ? /\n/.test(str) ? ChunkType.Block : ChunkType.Line - : null; - this.first = initial; - this.last = initial; - - this.lastCondition = null; - this.conditionStack = []; - this.indent = ''; + this.current = this.last = this.root; + this.addLine(str); } addConditional(condition: string, body: string) { - this.reifyConditions(); - - const indent = this.indent + (condition ? '\t' : ''); - body = body.replace(/^/gm, indent); - - if (condition === this.lastCondition) { - this.result += `\n${body}`; + if (this.last.type === 'condition' && this.last.condition === condition) { + if (body && !whitespace.test(body)) this.last.children.push({ type: 'line', line: body }); } else { - if (this.lastCondition) { - this.result += `\n${this.indent}}`; - } - - const block = condition - ? `if (${condition}) {\n${body}` - : body; - - this.result += `${this.last === ChunkType.Block ? '\n\n' : '\n'}${this.indent}${block}`; - this.lastCondition = condition; + const next = this.last = { type: 'condition', condition, parent: this.current, children: [] }; + this.current.children.push(next); + if (body && !whitespace.test(body)) next.children.push({ type: 'line', line: body }); } - - this.last = ChunkType.Block; } addLine(line: string) { - this.reifyConditions(); - - if (this.lastCondition) { - this.result += `\n${this.indent}}`; - this.lastCondition = null; - } - - if (this.last === ChunkType.Block) { - this.result += `\n\n${this.indent}${line}`; - } else if (this.last === ChunkType.Line) { - this.result += `\n${this.indent}${line}`; - } else { - this.result += line; - } - - this.last = ChunkType.Line; - if (!this.first) this.first = ChunkType.Line; + if (line && !whitespace.test(line)) this.current.children.push(this.last = { type: 'line', line }); } addLineAtStart(line: string) { - this.reifyConditions(); - - if (this.first === ChunkType.Block) { - this.result = `${line}\n\n${this.indent}${this.result}`; - } else if (this.first === ChunkType.Line) { - this.result = `${line}\n${this.indent}${this.result}`; - } else { - this.result += line; - } - - this.first = ChunkType.Line; - if (!this.last) this.last = ChunkType.Line; + if (line && !whitespace.test(line)) this.root.children.unshift({ type: 'line', line }); } addBlock(block: string) { - this.reifyConditions(); - - if (this.indent) block = block.replace(/^/gm, `${this.indent}`); - - if (this.lastCondition) { - this.result += `\n${this.indent}}`; - this.lastCondition = null; - } - - if (this.result) { - this.result += `\n\n${this.indent}${block}`; - } else { - this.result += block; - } - - this.last = ChunkType.Block; - if (!this.first) this.first = ChunkType.Block; + if (block && !whitespace.test(block)) this.current.children.push(this.last = { type: 'line', line: block, block: true }); } addBlockAtStart(block: string) { - this.reifyConditions(); - - if (this.result) { - this.result = `${block}\n\n${this.indent}${this.result}`; - } else { - this.result += block; - } - - this.first = ChunkType.Block; - if (!this.last) this.last = ChunkType.Block; + if (block && !whitespace.test(block)) this.root.children.unshift({ type: 'line', line: block, block: true }); } - isEmpty() { - return this.result === ''; - } + isEmpty() { return !findLine(this.root); } pushCondition(condition: string) { - this.conditionStack.push({ condition, used: false }); + if (this.last.type === 'condition' && this.last.condition === condition) { + this.current = this.last as BlockChunk; + } else { + const next = this.last = { type: 'condition', condition, parent: this.current, children: [] }; + this.current.children.push(next); + this.current = next; + } } popCondition() { - const { used } = this.conditionStack.pop(); + if (!this.current.parent) throw new Error(`Popping a condition that maybe wasn't pushed.`); + this.current = this.current.parent; + } - this.indent = repeat('\t', this.conditionStack.length); - if (used) this.addLine('}'); + toString() { + return chunkToString(this.root); } +} - reifyConditions() { - for (let i = 0; i < this.conditionStack.length; i += 1) { - const condition = this.conditionStack[i]; - if (!condition.used) { - const line = `if (${condition.condition}) {`; - - if (this.last === ChunkType.Block) { - this.result += `\n\n${this.indent}${line}`; - } else if (this.last === ChunkType.Line) { - this.result += `\n${this.indent}${line}`; - } else { - this.result += line; - } - - this.last = ChunkType.Line; - if (!this.first) this.first = ChunkType.Line; - - this.indent = repeat('\t', this.conditionStack.length); - condition.used = true; - } - } +function findLine(chunk: BlockChunk) { + for (const c of chunk.children) { + if (c.type === 'line' || findLine(c as BlockChunk)) return true; } + return false; +} - toString() { - return this.result.trim() + (this.lastCondition ? `\n}` : ``); +function chunkToString(chunk: Chunk, level: number = 0, lastBlock?: boolean, first?: boolean): string { + if (chunk.type === 'line') { + return `${lastBlock || (!first && chunk.block) ? '\n' : ''}${chunk.line.replace(/^/gm, repeat('\t', level))}`; + } else if (chunk.type === 'condition') { + let t = false; + const lines = chunk.children.map((c, i) => { + const str = chunkToString(c, level + 1, t, i === 0); + t = c.type !== 'line' || c.block; + return str; + }).filter(l => !!l); + + if (!lines.length) return ''; + + return `${lastBlock || (!first) ? '\n' : ''}${repeat('\t', level)}if (${chunk.condition}) {\n${lines.join('\n')}\n${repeat('\t', level)}}`; + } else if (chunk.type === 'root') { + let t = false; + const lines = chunk.children.map((c, i) => { + const str = chunkToString(c, 0, t, i === 0); + t = c.type !== 'line' || c.block; + return str; + }).filter(l => !!l); + + if (!lines.length) return ''; + + return lines.join('\n'); } } From 6dc5e2a6fc9eca8d66f880d714674443e0792878 Mon Sep 17 00:00:00 2001 From: Chris Reeves Date: Fri, 11 Jan 2019 01:18:12 -0500 Subject: [PATCH 3/3] fix newline-before-block diffs caused by code builder adjustments --- test/js/samples/if-block-no-update/expected.js | 1 + test/js/samples/if-block-simple/expected.js | 1 + test/js/samples/use-elements-as-anchors/expected.js | 2 ++ 3 files changed, 4 insertions(+) diff --git a/test/js/samples/if-block-no-update/expected.js b/test/js/samples/if-block-no-update/expected.js index cc01e9791a..3079c3fe6a 100644 --- a/test/js/samples/if-block-no-update/expected.js +++ b/test/js/samples/if-block-no-update/expected.js @@ -86,6 +86,7 @@ function create_fragment($$, ctx) { d(detach) { if_block.d(detach); + if (detach) { detachNode(if_block_anchor); } diff --git a/test/js/samples/if-block-simple/expected.js b/test/js/samples/if-block-simple/expected.js index 054ee7219b..b7faca7f19 100644 --- a/test/js/samples/if-block-simple/expected.js +++ b/test/js/samples/if-block-simple/expected.js @@ -62,6 +62,7 @@ function create_fragment($$, ctx) { d(detach) { if (if_block) if_block.d(detach); + if (detach) { detachNode(if_block_anchor); } diff --git a/test/js/samples/use-elements-as-anchors/expected.js b/test/js/samples/use-elements-as-anchors/expected.js index e6610407e4..aaead7e6f8 100644 --- a/test/js/samples/use-elements-as-anchors/expected.js +++ b/test/js/samples/use-elements-as-anchors/expected.js @@ -237,11 +237,13 @@ function create_fragment($$, ctx) { if (if_block1) if_block1.d(); if (if_block2) if_block2.d(); if (if_block3) if_block3.d(); + if (detach) { detachNode(text7); } if (if_block4) if_block4.d(detach); + if (detach) { detachNode(if_block4_anchor); }