diff --git a/src/compiler/compile/render_dom/wrappers/AwaitBlock.js b/src/compiler/compile/render_dom/wrappers/AwaitBlock.js index 41d89304ec..910374ed38 100644 --- a/src/compiler/compile/render_dom/wrappers/AwaitBlock.js +++ b/src/compiler/compile/render_dom/wrappers/AwaitBlock.js @@ -9,213 +9,226 @@ import Expression from '../../nodes/shared/Expression.js'; /** @extends Wrapper */ class AwaitBlockBranch extends Wrapper { + /** @type {AwaitBlockWrapper} */ + parent; - /** @type {AwaitBlockWrapper} */ - parent; + /** @type {import('../../nodes/PendingBlock.js').default | import('../../nodes/ThenBlock.js').default | import('../../nodes/CatchBlock.js').default} */ + node; - /** @type {import('../../nodes/PendingBlock.js').default | import('../../nodes/ThenBlock.js').default | import('../../nodes/CatchBlock.js').default} */ - node; + /** @type {import('../Block.js').default} */ + block; - /** @type {import('../Block.js').default} */ - block; + /** @type {import('./Fragment.js').default} */ + fragment; - /** @type {import('./Fragment.js').default} */ - fragment; + /** @type {boolean} */ + is_dynamic; + /** */ + var = null; - /** @type {boolean} */ - is_dynamic; - /** */ - var = null; + /** @type {Status} */ + status; - /** @type {Status} */ - status; + /** @type {string} */ + value; - /** @type {string} */ - value; + /** @type {import('estree').Literal} */ + value_index; - /** @type {import('estree').Literal} */ - value_index; + /** @type {import('../../nodes/shared/Context.js').Context[]} */ + value_contexts; - /** @type {import('../../nodes/shared/Context.js').Context[]} */ - value_contexts; + /** @type {boolean} */ + is_destructured; - /** @type {boolean} */ - is_destructured; + /** + * @param {Status} status + * @param {import('../Renderer.js').default} renderer + * @param {import('../Block.js').default} block + * @param {AwaitBlockWrapper} parent + * @param {import('../../nodes/PendingBlock.js').default | import('../../nodes/ThenBlock.js').default | import('../../nodes/CatchBlock.js').default} node + * @param {boolean} strip_whitespace + * @param {import('./shared/Wrapper.js').default} next_sibling + */ + constructor(status, renderer, block, parent, node, strip_whitespace, next_sibling) { + super(renderer, block, parent, node); + this.status = status; + this.block = block.child({ + comment: create_debugging_comment(node, this.renderer.component), + name: this.renderer.component.get_unique_name(`create_${status}_block`), + type: status + }); + this.add_context(parent.node[status + '_node'], parent.node[status + '_contexts']); + this.fragment = new FragmentWrapper( + renderer, + this.block, + this.node.children, + parent, + strip_whitespace, + next_sibling + ); + this.is_dynamic = this.block.dependencies.size > 0; + } - /** - * @param {Status} status - * @param {import('../Renderer.js').default} renderer - * @param {import('../Block.js').default} block - * @param {AwaitBlockWrapper} parent - * @param {import('../../nodes/PendingBlock.js').default | import('../../nodes/ThenBlock.js').default | import('../../nodes/CatchBlock.js').default} node - * @param {boolean} strip_whitespace - * @param {import('./shared/Wrapper.js').default} next_sibling - */ - constructor(status, renderer, block, parent, node, strip_whitespace, next_sibling) { - super(renderer, block, parent, node); - this.status = status; - this.block = block.child({ - comment: create_debugging_comment(node, this.renderer.component), - name: this.renderer.component.get_unique_name(`create_${status}_block`), - type: status - }); - this.add_context(parent.node[status + '_node'], parent.node[status + '_contexts']); - this.fragment = new FragmentWrapper(renderer, this.block, this.node.children, parent, strip_whitespace, next_sibling); - this.is_dynamic = this.block.dependencies.size > 0; - } + /** + * @param {import('estree').Node | null} node + * @param {import('../../nodes/shared/Context.js').Context[]} contexts + */ + add_context(node, contexts) { + if (!node) return; + if (node.type === 'Identifier') { + this.value = node.name; + this.renderer.add_to_context(this.value, true); + } else { + contexts.forEach((context) => { + if (context.type !== 'DestructuredVariable') return; + this.renderer.add_to_context(context.key.name, true); + }); + this.value = this.block.parent.get_unique_name('value').name; + this.value_contexts = contexts; + this.renderer.add_to_context(this.value, true); + this.is_destructured = true; + } + this.value_index = this.renderer.context_lookup.get(this.value).index; + if (this.has_consts(this.node)) { + add_const_tags_context(this.renderer, this.node.const_tags); + } + } - /** - * @param {import('estree').Node | null} node - * @param {import('../../nodes/shared/Context.js').Context[]} contexts - */ - add_context(node, contexts) { - if (!node) - return; - if (node.type === 'Identifier') { - this.value = node.name; - this.renderer.add_to_context(this.value, true); - } - else { - contexts.forEach((context) => { - if (context.type !== 'DestructuredVariable') - return; - this.renderer.add_to_context(context.key.name, true); - }); - this.value = this.block.parent.get_unique_name('value').name; - this.value_contexts = contexts; - this.renderer.add_to_context(this.value, true); - this.is_destructured = true; - } - this.value_index = this.renderer.context_lookup.get(this.value).index; - if (this.has_consts(this.node)) { - add_const_tags_context(this.renderer, this.node.const_tags); - } - } + /** + * @param {import('../../nodes/PendingBlock.js').default | import('../../nodes/ThenBlock.js').default | import('../../nodes/CatchBlock.js').default} node + * @returns {boolean} + */ + has_consts(node) { + return node instanceof ThenBlock || node instanceof CatchBlock; + } - /** - * @param {import('../../nodes/PendingBlock.js').default | import('../../nodes/ThenBlock.js').default | import('../../nodes/CatchBlock.js').default} node - * @returns {boolean} - */ - has_consts(node) { - return node instanceof ThenBlock || node instanceof CatchBlock; - } - - /** - * @param {import('../Block.js').default} block - * @param {import('estree').Identifier} parent_node - * @param {import('estree').Identifier} parent_nodes - */ - render(block, parent_node, parent_nodes) { - this.fragment.render(block, parent_node, parent_nodes); - if (this.is_destructured || (this.has_consts(this.node) && this.node.const_tags.length > 0)) { - this.render_get_context(); - } - } - render_get_context() { - const props = this.is_destructured - ? this.value_contexts.map((prop) => { - if (prop.type === 'ComputedProperty') { - const expression = new Expression(this.renderer.component, this.node, this.has_consts(this.node) ? this.node.scope : null, prop.key); - return b `const ${prop.property_name} = ${expression.manipulate(this.block, '#ctx')};`; - } - else { - - /** @param {any} name */ - const to_ctx = (name) => this.renderer.reference(name); - return b `#ctx[${this.block.renderer.context_lookup.get(prop.key.name).index}] = ${prop.default_modifier(prop.modifier(x `#ctx[${this.value_index}]`), to_ctx)};`; - } - }) - : null; - const const_tags_props = this.has_consts(this.node) - ? add_const_tags(this.block, this.node.const_tags, '#ctx') - : null; - const get_context = this.block.renderer.component.get_unique_name(`get_${this.status}_context`); - this.block.renderer.blocks.push(b ` + /** + * @param {import('../Block.js').default} block + * @param {import('estree').Identifier} parent_node + * @param {import('estree').Identifier} parent_nodes + */ + render(block, parent_node, parent_nodes) { + this.fragment.render(block, parent_node, parent_nodes); + if (this.is_destructured || (this.has_consts(this.node) && this.node.const_tags.length > 0)) { + this.render_get_context(); + } + } + render_get_context() { + const props = this.is_destructured + ? this.value_contexts.map((prop) => { + if (prop.type === 'ComputedProperty') { + const expression = new Expression( + this.renderer.component, + this.node, + this.has_consts(this.node) ? this.node.scope : null, + prop.key + ); + return b`const ${prop.property_name} = ${expression.manipulate(this.block, '#ctx')};`; + } else { + /** @param {any} name */ + const to_ctx = (name) => this.renderer.reference(name); + return b`#ctx[${ + this.block.renderer.context_lookup.get(prop.key.name).index + }] = ${prop.default_modifier(prop.modifier(x`#ctx[${this.value_index}]`), to_ctx)};`; + } + }) + : null; + const const_tags_props = this.has_consts(this.node) + ? add_const_tags(this.block, this.node.const_tags, '#ctx') + : null; + const get_context = this.block.renderer.component.get_unique_name(`get_${this.status}_context`); + this.block.renderer.blocks.push(b` function ${get_context}(#ctx) { ${props} ${const_tags_props} } `); - this.block.chunks.declarations.push(b `${get_context}(#ctx)`); - if (this.block.has_update_method) { - this.block.chunks.update.unshift(b `${get_context}(#ctx)`); - } - } + this.block.chunks.declarations.push(b`${get_context}(#ctx)`); + if (this.block.has_update_method) { + this.block.chunks.update.unshift(b`${get_context}(#ctx)`); + } + } } /** @extends Wrapper */ export default class AwaitBlockWrapper extends Wrapper { + /** @type {import('../../nodes/AwaitBlock.js').default} */ + node; - /** @type {import('../../nodes/AwaitBlock.js').default} */ - node; - - /** @type {AwaitBlockBranch} */ - pending; + /** @type {AwaitBlockBranch} */ + pending; - /** @type {AwaitBlockBranch} */ - then; + /** @type {AwaitBlockBranch} */ + then; - /** @type {AwaitBlockBranch} */ - catch; + /** @type {AwaitBlockBranch} */ + catch; - /** @type {import('estree').Identifier} */ - var = { type: 'Identifier', name: 'await_block' }; + /** @type {import('estree').Identifier} */ + var = { type: 'Identifier', name: 'await_block' }; - /** - * @param {import('../Renderer.js').default} renderer - * @param {import('../Block.js').default} block - * @param {import('./shared/Wrapper.js').default} parent - * @param {import('../../nodes/AwaitBlock.js').default} node - * @param {boolean} strip_whitespace - * @param {import('./shared/Wrapper.js').default} next_sibling - */ - constructor(renderer, block, parent, node, strip_whitespace, next_sibling) { - super(renderer, block, parent, node); - block.add_dependencies(this.node.expression.dependencies); - let is_dynamic = false; - let has_intros = false; - let has_outros = false; - ['pending', 'then', 'catch'].forEach((status) => { - const child = this.node[status]; - const branch = new AwaitBlockBranch(status, renderer, block, this, child, strip_whitespace, next_sibling); - renderer.blocks.push(branch.block); - if (branch.is_dynamic) { - is_dynamic = true; - // TODO should blocks update their own parents? - block.add_dependencies(branch.block.dependencies); - } - if (branch.block.has_intros) - has_intros = true; - if (branch.block.has_outros) - has_outros = true; - this[status] = branch; - }); - ['pending', 'then', 'catch'].forEach((status) => { - this[status].block.has_update_method = is_dynamic; - this[status].block.has_intro_method = has_intros; - this[status].block.has_outro_method = has_outros; - }); - if (has_outros) { - block.add_outro(); - } - } + /** + * @param {import('../Renderer.js').default} renderer + * @param {import('../Block.js').default} block + * @param {import('./shared/Wrapper.js').default} parent + * @param {import('../../nodes/AwaitBlock.js').default} node + * @param {boolean} strip_whitespace + * @param {import('./shared/Wrapper.js').default} next_sibling + */ + constructor(renderer, block, parent, node, strip_whitespace, next_sibling) { + super(renderer, block, parent, node); + block.add_dependencies(this.node.expression.dependencies); + let is_dynamic = false; + let has_intros = false; + let has_outros = false; + ['pending', 'then', 'catch'].forEach((status) => { + const child = this.node[status]; + const branch = new AwaitBlockBranch( + status, + renderer, + block, + this, + child, + strip_whitespace, + next_sibling + ); + renderer.blocks.push(branch.block); + if (branch.is_dynamic) { + is_dynamic = true; + // TODO should blocks update their own parents? + block.add_dependencies(branch.block.dependencies); + } + if (branch.block.has_intros) has_intros = true; + if (branch.block.has_outros) has_outros = true; + this[status] = branch; + }); + ['pending', 'then', 'catch'].forEach((status) => { + this[status].block.has_update_method = is_dynamic; + this[status].block.has_intro_method = has_intros; + this[status].block.has_outro_method = has_outros; + }); + if (has_outros) { + block.add_outro(); + } + } - /** - * @param {import('../Block.js').default} block - * @param {import('estree').Identifier} parent_node - * @param {import('estree').Identifier} parent_nodes - */ - render(block, parent_node, parent_nodes) { - const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes); - const update_mount_node = this.get_update_mount_node(anchor); - const snippet = this.node.expression.manipulate(block); - const info = block.get_unique_name('info'); - const promise = block.get_unique_name('promise'); - block.add_variable(promise); - block.maintain_context = true; + /** + * @param {import('../Block.js').default} block + * @param {import('estree').Identifier} parent_node + * @param {import('estree').Identifier} parent_nodes + */ + render(block, parent_node, parent_nodes) { + const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes); + const update_mount_node = this.get_update_mount_node(anchor); + const snippet = this.node.expression.manipulate(block); + const info = block.get_unique_name('info'); + const promise = block.get_unique_name('promise'); + block.add_variable(promise); + block.maintain_context = true; - /** @type {any} */ - const info_props = x `{ + /** @type {any} */ + const info_props = x`{ ctx: #ctx, current: null, token: null, @@ -225,82 +238,79 @@ export default class AwaitBlockWrapper extends Wrapper { catch: ${this.catch.block.name}, value: ${this.then.value_index}, error: ${this.catch.value_index}, - blocks: ${this.pending.block.has_outro_method && x `[,,,]`} + blocks: ${this.pending.block.has_outro_method && x`[,,,]`} }`; - block.chunks.init.push(b ` + block.chunks.init.push(b` let ${info} = ${info_props}; `); - block.chunks.init.push(b ` + block.chunks.init.push(b` @handle_promise(${promise} = ${snippet}, ${info}); `); - block.chunks.create.push(b ` + block.chunks.create.push(b` ${info}.block.c(); `); - if (parent_nodes && this.renderer.options.hydratable) { - block.chunks.claim.push(b ` + if (parent_nodes && this.renderer.options.hydratable) { + block.chunks.claim.push(b` ${info}.block.l(${parent_nodes}); `); - } - const initial_mount_node = parent_node || '#target'; - const anchor_node = parent_node ? 'null' : '#anchor'; - const has_transitions = this.pending.block.has_intro_method || this.pending.block.has_outro_method; - block.chunks.mount.push(b ` + } + const initial_mount_node = parent_node || '#target'; + const anchor_node = parent_node ? 'null' : '#anchor'; + const has_transitions = + this.pending.block.has_intro_method || this.pending.block.has_outro_method; + block.chunks.mount.push(b` ${info}.block.m(${initial_mount_node}, ${info}.anchor = ${anchor_node}); ${info}.mount = () => ${update_mount_node}; ${info}.anchor = ${anchor}; `); - if (has_transitions) { - block.chunks.intro.push(b `@transition_in(${info}.block);`); - } - const dependencies = this.node.expression.dynamic_dependencies(); - const update_await_block_branch = b `@update_await_block_branch(${info}, #ctx, #dirty)`; - if (dependencies.length > 0) { - const condition = x ` + if (has_transitions) { + block.chunks.intro.push(b`@transition_in(${info}.block);`); + } + const dependencies = this.node.expression.dynamic_dependencies(); + const update_await_block_branch = b`@update_await_block_branch(${info}, #ctx, #dirty)`; + if (dependencies.length > 0) { + const condition = x` ${block.renderer.dirty(dependencies)} && ${promise} !== (${promise} = ${snippet}) && @handle_promise(${promise}, ${info})`; - block.chunks.update.push(b `${info}.ctx = #ctx;`); - if (this.pending.block.has_update_method) { - block.chunks.update.push(b ` + block.chunks.update.push(b`${info}.ctx = #ctx;`); + if (this.pending.block.has_update_method) { + block.chunks.update.push(b` if (${condition}) { } else { ${update_await_block_branch} } `); - } - else { - block.chunks.update.push(b ` + } else { + block.chunks.update.push(b` ${condition} `); - } - } - else { - if (this.pending.block.has_update_method) { - block.chunks.update.push(b ` + } + } else { + if (this.pending.block.has_update_method) { + block.chunks.update.push(b` ${update_await_block_branch} `); - } - } - if (this.pending.block.has_outro_method) { - block.chunks.outro.push(b ` + } + } + if (this.pending.block.has_outro_method) { + block.chunks.outro.push(b` for (let #i = 0; #i < 3; #i += 1) { const block = ${info}.blocks[#i]; @transition_out(block); } `); - } - block.chunks.destroy.push(b ` + } + block.chunks.destroy.push(b` ${info}.block.d(${parent_node ? null : 'detaching'}); ${info}.token = null; ${info} = null; `); - [this.pending, this.then, this.catch].forEach((branch) => { - branch.render(branch.block, null, /** @type {import('estree').Identifier} */ (x `#nodes`)); - }); - } + [this.pending, this.then, this.catch].forEach((branch) => { + branch.render(branch.block, null, /** @type {import('estree').Identifier} */ (x`#nodes`)); + }); + } } - /** @typedef {'pending' | 'then' | 'catch'} Status */ -