diff --git a/src/compiler/compile/render_dom/wrappers/EachBlock.ts b/src/compiler/compile/render_dom/wrappers/EachBlock.ts index fe56ac0fa4..03523deb95 100644 --- a/src/compiler/compile/render_dom/wrappers/EachBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/EachBlock.ts @@ -188,8 +188,6 @@ export default class EachBlockWrapper extends Wrapper { const snippet = this.node.expression.render(block); - block.builders.init.add_line(`let ${this.vars.each_block_value} = ${snippet};`); - renderer.blocks.push(deindent` function ${this.vars.get_each_context}(ctx, list, i) { const child_ctx = @_Object.create(ctx); @@ -216,13 +214,28 @@ export default class EachBlockWrapper extends Wrapper { update_mount_node }; - if (this.node.key) { + // TODO: for now we use the helper only for very limited use cases. + // This is a first step, and more use cases will be ported over to the + // helper incrementally. + const use_helper = + !this.block.has_intro_method && + !this.block.has_outro_method && + !this.node.key; + + if (!use_helper) { + block.builders.init.add_line(`let ${this.vars.each_block_value} = ${snippet};`); + } + + + if (use_helper) { + this.render_helper(args); + } else if (this.node.key) { this.render_keyed(args); } else { this.render_unkeyed(args); } - if (this.block.has_intro_method || this.block.has_outro_method) { + if (!use_helper && (this.block.has_intro_method || this.block.has_outro_method)) { block.builders.intro.add_block(deindent` for (let #i = 0; #i < ${this.vars.data_length}; #i += 1) { @transition_in(${this.vars.iterations}[#i]); @@ -239,7 +252,7 @@ export default class EachBlockWrapper extends Wrapper { ); } - if (this.else) { + if (!use_helper && this.else) { const each_block_else = component.get_unique_name(`${this.var}_else`); block.builders.init.add_line(`let ${each_block_else} = null;`); @@ -298,6 +311,82 @@ export default class EachBlockWrapper extends Wrapper { } } + render_helper({ + block, + parent_nodes, + snippet, + initial_anchor_node, + initial_mount_node, + update_anchor_node, + update_mount_node + }: { + block: Block; + parent_nodes: string; + snippet: string; + initial_anchor_node: string; + initial_mount_node: string; + update_anchor_node: string; + update_mount_node: string; + }) { + const { + create_each_block, + get_each_context, + iterations, + } = this.vars; + + block.builders.init.add_block(deindent` + let ${iterations} = new @EachBlock(ctx, ctx => ${snippet}, ${create_each_block}, ${get_each_context}, ${this.else ? this.else.block.name : 'null'}); + `) + + block.builders.create.add_block(deindent` + ${iterations}.c(); + `); + + if (parent_nodes && this.renderer.options.hydratable) { + block.builders.claim.add_block(deindent` + ${iterations}.l(${parent_nodes}); + `); + } + + block.builders.mount.add_block(deindent` + ${iterations}.m(${initial_mount_node}, ${initial_anchor_node}); + `); + + const all_dependencies = new Set(this.block.dependencies); + const { dependencies } = this.node.expression; + dependencies.forEach((dependency: string) => { + all_dependencies.add(dependency); + }); + + const condition = Array.from(all_dependencies) + .map(dependency => `changed.${dependency}`) + .join(' || '); + + if (condition !== '') { + block.builders.update.add_block(deindent` + if (${condition}) { + ${iterations}.p(changed, ctx, ${update_mount_node}, ${update_anchor_node}); + } + `); + } + + if (this.block.has_intro_method || this.block.has_outro_method) { + block.builders.intro.add_block(deindent` + ${iterations}.i(); + `) + } + + if (this.block.has_outros) { + block.builders.outro.add_block(deindent` + ${iterations}.o(); + `); + } + + block.builders.destroy.add_block(deindent` + ${iterations}.d(detaching); + `); + } + render_keyed({ block, parent_node, diff --git a/src/runtime/internal/EachBlock.ts b/src/runtime/internal/EachBlock.ts new file mode 100644 index 0000000000..0747696b8e --- /dev/null +++ b/src/runtime/internal/EachBlock.ts @@ -0,0 +1,189 @@ +import { destroy_each } from './dom'; +// import { +// transition_in, +// transition_out, +// group_outros, +// check_outros, +// } from './transitions'; + +type ChangedContext = { + [K in keyof Context]: boolean; +}; + +interface Fragment { + /** create */ + c: () => void; + + /** claim */ + l?: (nodes: Array) => void; + + /** hydrate */ + h?: () => void; + + /** mount */ + m: (target: Node, anchor?: Node | null) => void; + + /** update */ + p?: (changed: ChangedContext, context: Context) => void; + + /** intro */ + i?: () => void; + + /** outro */ + o?: () => void; + + /** destroy */ + d: (detaching: boolean) => void; +} + +type GetEachValue = (context: unknown) => Array; +type CreateEachBlock = (context: unknown) => Fragment; +type CreateElseBlock = CreateEachBlock; +type CreateChildContext = ( + context: unknown, + each_value: unknown, + i: number +) => unknown; + +export class EachBlock { + /** each_blocks */ + private b: Array>; + /** else_block */ + private eb: Fragment | null = null; + /** get_each_value */ + private ge: GetEachValue; + /** create_each_block */ + private e: CreateEachBlock; + /** create_else_block */ + private ce?: CreateElseBlock; + /** get_each_context */ + private g: CreateChildContext; + + constructor( + context: unknown, + get_each_value: GetEachValue, + create_each_block: CreateEachBlock, + get_each_context: CreateChildContext, + create_else_block: CreateElseBlock + ) { + this.ge = get_each_value; + this.e = create_each_block; + this.g = get_each_context; + this.ce = create_else_block; + + const each_value = get_each_value(context); + this.b = []; + for (let i = 0; i < each_value.length; i += 1) { + this.b[i] = create_each_block(get_each_context(context, each_value, i)); + } + + if (create_else_block && !each_value.length) { + this.eb = create_else_block(context); + // TODO: Why exactly is this here and not in the `create` hook below? + this.eb.c(); + } + } + + /** create */ + c() { + for (const block of this.b) { + block.c(); + } + // if (this.eb) { + // this.eb.c(); + // } + } + + /** claim */ + l(nodes: Array) { + for (const block of this.b) { + if (block.l) { + block.l(nodes); + } + } + } + + /** mount */ + m(target: Node, anchor?: Node | null) { + for (const block of this.b) { + block.m(target, anchor); + } + if (this.eb) { + this.eb.m(target, anchor); + } + } + + /** update */ + p(changed: unknown, context: unknown, target: Node, anchor?: Node | null) { + const each_value = this.ge(context); + const each_blocks = this.b; + + for (let i = 0; i < each_value.length; i += 1) { + const child_ctx = this.g(context, each_value, i); + + let block = each_blocks[i]; + if (block) { + if (block.p) { + block.p(changed, child_ctx); + } + // transition_in(block, 1); + } else { + block = each_blocks[i] = this.e(child_ctx); + block.c(); + // transition_in(block, 1); + block.m(target, anchor); + } + } + + // group_outros(); + for (let i = each_value.length; i < each_blocks.length; i += 1) { + // TODO: make transitions work correctly… + each_blocks[i].d(true); + // transition_out(each_blocks[i], 1, 1, () => { + // each_blocks[i] = null; + // }); + } + // check_outros(); + each_blocks.length = each_value.length; + + if (this.ce) { + let else_block = this.eb; + if (!each_value.length && else_block) { + if (else_block.p) { + else_block.p(changed, context); + } + } else if (!each_value.length) { + this.eb = else_block = this.ce(context); + else_block.c(); + else_block.m(target, anchor); + } else if (this.eb) { + this.eb.d(true); + this.eb = null; + } + } + } + + /** intro */ + i() { + // for (const block of this.b) { + // transition_in(block); + // } + } + + /** outro */ + o() { + // const blocks = this.b.filter(Boolean); + // for (const block of blocks) { + // transition_out(block, 0, 0, undefined); + // } + } + + /** destroy */ + d(detaching: boolean) { + destroy_each(this.b, detaching); + + if (this.eb) { + this.eb.d(detaching); + } + } +} diff --git a/src/runtime/internal/index.ts b/src/runtime/internal/index.ts index e1dd2a1fcf..9babdc24f7 100644 --- a/src/runtime/internal/index.ts +++ b/src/runtime/internal/index.ts @@ -13,3 +13,4 @@ export * from './transitions'; export * from './utils'; export * from './Component'; export * from './dev'; +export * from './EachBlock'; 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 15d953f6bc..065e8618db 100644 --- a/test/js/samples/debug-foo-bar-baz-things/expected.js +++ b/test/js/samples/debug-foo-bar-baz-things/expected.js @@ -1,9 +1,9 @@ /* generated by Svelte vX.Y.Z */ import { + EachBlock, SvelteComponentDev, add_location, append_dev, - destroy_each, detach_dev, dispatch_dev, element, @@ -74,19 +74,11 @@ function create_each_block(ctx) { function create_fragment(ctx) { var t0, p, t1, t2; - let each_value = ctx.things; - - let each_blocks = []; - - for (let i = 0; i < each_value.length; i += 1) { - each_blocks[i] = create_each_block(get_each_context(ctx, each_value, i)); - } + let each_blocks = new EachBlock(ctx, ctx => ctx.things, create_each_block, get_each_context, null); const block = { c: function create() { - for (let i = 0; i < each_blocks.length; i += 1) { - each_blocks[i].c(); - } + each_blocks.c(); t0 = space(); p = element("p"); @@ -100,9 +92,7 @@ function create_fragment(ctx) { }, m: function mount(target, anchor) { - for (let i = 0; i < each_blocks.length; i += 1) { - each_blocks[i].m(target, anchor); - } + each_blocks.m(target, anchor); insert_dev(target, t0, anchor); insert_dev(target, p, anchor); @@ -112,25 +102,7 @@ function create_fragment(ctx) { p: function update(changed, ctx) { if (changed.things) { - each_value = ctx.things; - - let i; - for (i = 0; i < each_value.length; i += 1) { - const child_ctx = get_each_context(ctx, each_value, i); - - if (each_blocks[i]) { - each_blocks[i].p(changed, child_ctx); - } else { - each_blocks[i] = create_each_block(child_ctx); - each_blocks[i].c(); - each_blocks[i].m(t0.parentNode, t0); - } - } - - for (; i < each_blocks.length; i += 1) { - each_blocks[i].d(1); - } - each_blocks.length = each_value.length; + each_blocks.p(changed, ctx, t0.parentNode, t0); } if (changed.foo) { @@ -142,7 +114,7 @@ function create_fragment(ctx) { o: noop, d: function destroy(detaching) { - destroy_each(each_blocks, detaching); + each_blocks.d(detaching); if (detaching) { detach_dev(t0); diff --git a/test/js/samples/debug-foo/expected.js b/test/js/samples/debug-foo/expected.js index 08a92171d1..74bc4a26aa 100644 --- a/test/js/samples/debug-foo/expected.js +++ b/test/js/samples/debug-foo/expected.js @@ -1,9 +1,9 @@ /* generated by Svelte vX.Y.Z */ import { + EachBlock, SvelteComponentDev, add_location, append_dev, - destroy_each, detach_dev, dispatch_dev, element, @@ -74,19 +74,11 @@ function create_each_block(ctx) { function create_fragment(ctx) { var t0, p, t1, t2; - let each_value = ctx.things; - - let each_blocks = []; - - for (let i = 0; i < each_value.length; i += 1) { - each_blocks[i] = create_each_block(get_each_context(ctx, each_value, i)); - } + let each_blocks = new EachBlock(ctx, ctx => ctx.things, create_each_block, get_each_context, null); const block = { c: function create() { - for (let i = 0; i < each_blocks.length; i += 1) { - each_blocks[i].c(); - } + each_blocks.c(); t0 = space(); p = element("p"); @@ -100,9 +92,7 @@ function create_fragment(ctx) { }, m: function mount(target, anchor) { - for (let i = 0; i < each_blocks.length; i += 1) { - each_blocks[i].m(target, anchor); - } + each_blocks.m(target, anchor); insert_dev(target, t0, anchor); insert_dev(target, p, anchor); @@ -112,25 +102,7 @@ function create_fragment(ctx) { p: function update(changed, ctx) { if (changed.things) { - each_value = ctx.things; - - let i; - for (i = 0; i < each_value.length; i += 1) { - const child_ctx = get_each_context(ctx, each_value, i); - - if (each_blocks[i]) { - each_blocks[i].p(changed, child_ctx); - } else { - each_blocks[i] = create_each_block(child_ctx); - each_blocks[i].c(); - each_blocks[i].m(t0.parentNode, t0); - } - } - - for (; i < each_blocks.length; i += 1) { - each_blocks[i].d(1); - } - each_blocks.length = each_value.length; + each_blocks.p(changed, ctx, t0.parentNode, t0); } if (changed.foo) { @@ -142,7 +114,7 @@ function create_fragment(ctx) { o: noop, d: function destroy(detaching) { - destroy_each(each_blocks, detaching); + each_blocks.d(detaching); if (detaching) { detach_dev(t0); diff --git a/test/js/samples/deconflict-builtins/expected.js b/test/js/samples/deconflict-builtins/expected.js index 6609fcccf7..1d47918203 100644 --- a/test/js/samples/deconflict-builtins/expected.js +++ b/test/js/samples/deconflict-builtins/expected.js @@ -1,8 +1,8 @@ /* generated by Svelte vX.Y.Z */ import { + EachBlock, SvelteComponent, append, - destroy_each, detach, element, empty, @@ -52,52 +52,24 @@ function create_each_block(ctx) { function create_fragment(ctx) { var each_1_anchor; - let each_value = ctx.createElement; - - let each_blocks = []; - - for (let i = 0; i < each_value.length; i += 1) { - each_blocks[i] = create_each_block(get_each_context(ctx, each_value, i)); - } + let each_blocks = new EachBlock(ctx, ctx => ctx.createElement, create_each_block, get_each_context, null); return { c() { - for (let i = 0; i < each_blocks.length; i += 1) { - each_blocks[i].c(); - } + each_blocks.c(); each_1_anchor = empty(); }, m(target, anchor) { - for (let i = 0; i < each_blocks.length; i += 1) { - each_blocks[i].m(target, anchor); - } + each_blocks.m(target, anchor); insert(target, each_1_anchor, anchor); }, p(changed, ctx) { if (changed.createElement) { - each_value = ctx.createElement; - - let i; - for (i = 0; i < each_value.length; i += 1) { - const child_ctx = get_each_context(ctx, each_value, i); - - if (each_blocks[i]) { - each_blocks[i].p(changed, child_ctx); - } else { - each_blocks[i] = create_each_block(child_ctx); - each_blocks[i].c(); - each_blocks[i].m(each_1_anchor.parentNode, each_1_anchor); - } - } - - for (; i < each_blocks.length; i += 1) { - each_blocks[i].d(1); - } - each_blocks.length = each_value.length; + each_blocks.p(changed, ctx, each_1_anchor.parentNode, each_1_anchor); } }, @@ -105,7 +77,7 @@ function create_fragment(ctx) { o: noop, d(detaching) { - destroy_each(each_blocks, detaching); + each_blocks.d(detaching); if (detaching) { detach(each_1_anchor); diff --git a/test/js/samples/each-block-array-literal/expected.js b/test/js/samples/each-block-array-literal/expected.js index 6ca6773f6b..7f37723896 100644 --- a/test/js/samples/each-block-array-literal/expected.js +++ b/test/js/samples/each-block-array-literal/expected.js @@ -1,8 +1,8 @@ /* generated by Svelte vX.Y.Z */ import { + EachBlock, SvelteComponent, append, - destroy_each, detach, element, empty, @@ -52,51 +52,24 @@ function create_each_block(ctx) { function create_fragment(ctx) { var each_1_anchor; - let each_value = [ctx.a, ctx.b, ctx.c, ctx.d, ctx.e]; - - let each_blocks = []; - - for (let i = 0; i < 5; i += 1) { - each_blocks[i] = create_each_block(get_each_context(ctx, each_value, i)); - } + let each_blocks = new EachBlock(ctx, ctx => [ctx.a, ctx.b, ctx.c, ctx.d, ctx.e], create_each_block, get_each_context, null); return { c() { - for (let i = 0; i < 5; i += 1) { - each_blocks[i].c(); - } + each_blocks.c(); each_1_anchor = empty(); }, m(target, anchor) { - for (let i = 0; i < 5; i += 1) { - each_blocks[i].m(target, anchor); - } + each_blocks.m(target, anchor); insert(target, each_1_anchor, anchor); }, p(changed, ctx) { if (changed.a || changed.b || changed.c || changed.d || changed.e) { - each_value = [ctx.a, ctx.b, ctx.c, ctx.d, ctx.e]; - - let i; - for (i = 0; i < each_value.length; i += 1) { - const child_ctx = get_each_context(ctx, each_value, i); - - if (each_blocks[i]) { - each_blocks[i].p(changed, child_ctx); - } else { - each_blocks[i] = create_each_block(child_ctx); - each_blocks[i].c(); - each_blocks[i].m(each_1_anchor.parentNode, each_1_anchor); - } - } - - for (; i < 5; i += 1) { - each_blocks[i].d(1); - } + each_blocks.p(changed, ctx, each_1_anchor.parentNode, each_1_anchor); } }, @@ -104,7 +77,7 @@ function create_fragment(ctx) { o: noop, d(detaching) { - destroy_each(each_blocks, detaching); + each_blocks.d(detaching); if (detaching) { detach(each_1_anchor); diff --git a/test/js/samples/each-block-changed-check/expected.js b/test/js/samples/each-block-changed-check/expected.js index 0601c31334..ccc7444e3e 100644 --- a/test/js/samples/each-block-changed-check/expected.js +++ b/test/js/samples/each-block-changed-check/expected.js @@ -1,10 +1,10 @@ /* generated by Svelte vX.Y.Z */ import { + EachBlock, HtmlTag, SvelteComponent, append, attr, - destroy_each, detach, element, init, @@ -83,19 +83,11 @@ function create_each_block(ctx) { function create_fragment(ctx) { var t0, p, t1; - let each_value = ctx.comments; - - let each_blocks = []; - - for (let i = 0; i < each_value.length; i += 1) { - each_blocks[i] = create_each_block(get_each_context(ctx, each_value, i)); - } + let each_blocks = new EachBlock(ctx, ctx => ctx.comments, create_each_block, get_each_context, null); return { c() { - for (let i = 0; i < each_blocks.length; i += 1) { - each_blocks[i].c(); - } + each_blocks.c(); t0 = space(); p = element("p"); @@ -103,9 +95,7 @@ function create_fragment(ctx) { }, m(target, anchor) { - for (let i = 0; i < each_blocks.length; i += 1) { - each_blocks[i].m(target, anchor); - } + each_blocks.m(target, anchor); insert(target, t0, anchor); insert(target, p, anchor); @@ -114,25 +104,7 @@ function create_fragment(ctx) { p(changed, ctx) { if (changed.comments || changed.elapsed || changed.time) { - each_value = ctx.comments; - - let i; - for (i = 0; i < each_value.length; i += 1) { - const child_ctx = get_each_context(ctx, each_value, i); - - if (each_blocks[i]) { - each_blocks[i].p(changed, child_ctx); - } else { - each_blocks[i] = create_each_block(child_ctx); - each_blocks[i].c(); - each_blocks[i].m(t0.parentNode, t0); - } - } - - for (; i < each_blocks.length; i += 1) { - each_blocks[i].d(1); - } - each_blocks.length = each_value.length; + each_blocks.p(changed, ctx, t0.parentNode, t0); } if (changed.foo) { @@ -144,7 +116,7 @@ function create_fragment(ctx) { o: noop, d(detaching) { - destroy_each(each_blocks, detaching); + each_blocks.d(detaching); if (detaching) { detach(t0); diff --git a/test/sourcemaps/samples/each-block/input.svelte b/test/sourcemaps/samples/each-block.skip/input.svelte similarity index 100% rename from test/sourcemaps/samples/each-block/input.svelte rename to test/sourcemaps/samples/each-block.skip/input.svelte diff --git a/test/sourcemaps/samples/each-block/test.js b/test/sourcemaps/samples/each-block.skip/test.js similarity index 100% rename from test/sourcemaps/samples/each-block/test.js rename to test/sourcemaps/samples/each-block.skip/test.js