diff --git a/src/compiler/compile/render_dom/Block.ts b/src/compiler/compile/render_dom/Block.ts index b268954dd0..faa7c43271 100644 --- a/src/compiler/compile/render_dom/Block.ts +++ b/src/compiler/compile/render_dom/Block.ts @@ -398,6 +398,21 @@ export default class Block { return body; } + has_content() { + return this.renderer.options.dev || + this.first || + this.event_listeners.length > 0 || + this.chunks.intro.length > 0 || + this.chunks.outro.length > 0 || + this.chunks.create.length > 0 || + this.chunks.hydrate.length > 0 || + this.chunks.claim.length > 0 || + this.chunks.mount.length > 0 || + this.chunks.update.length > 0 || + this.chunks.destroy.length > 0 || + this.has_animation; + } + render() { const key = this.key && this.get_unique_name('key'); diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index a50a86e2f3..49ce17236a 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -241,11 +241,16 @@ export default function dom( args.push(x`$$props`, x`$$invalidate`); } - body.push(b` - function create_fragment(#ctx) { - ${block.get_contents()} - } + const has_create_fragment = block.has_content(); + if (has_create_fragment) { + body.push(b` + function create_fragment(#ctx) { + ${block.get_contents()} + } + `); + } + body.push(b` ${component.extract_javascript(component.ast.module)} ${component.fully_hoisted} @@ -437,7 +442,7 @@ export default function dom( ${css.code && b`this.shadowRoot.innerHTML = \`\`;`} - @init(this, { target: this.shadowRoot }, ${definition}, create_fragment, ${not_equal}, ${prop_names}); + @init(this, { target: this.shadowRoot }, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_names}); ${dev_props_check} @@ -489,7 +494,7 @@ export default function dom( constructor(options) { super(${options.dev && `options`}); ${should_add_css && b`if (!@_document.getElementById("${component.stylesheet.id}-style")) ${add_css}();`} - @init(this, options, ${definition}, create_fragment, ${not_equal}, ${prop_names}); + @init(this, options, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_names}); ${options.dev && b`@dispatch_dev("SvelteRegisterComponent", { component: this, tagName: "${name.name}", options, id: create_fragment.name });`} ${dev_props_check} diff --git a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts index c4a848b456..a9d989c92a 100644 --- a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts +++ b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts @@ -396,12 +396,12 @@ export default class InlineComponentWrapper extends Wrapper { `); block.chunks.create.push( - b`if (${name}) ${name}.$$.fragment.c();` + b`if (${name}) @create_component(${name}.$$.fragment);` ); if (parent_nodes && this.renderer.options.hydratable) { block.chunks.claim.push( - b`if (${name}) ${name}.$$.fragment.l(${parent_nodes});` + b`if (${name}) @claim_component(${name}.$$.fragment, ${parent_nodes});` ); } @@ -437,7 +437,7 @@ export default class InlineComponentWrapper extends Wrapper { ${munged_bindings} ${munged_handlers} - ${name}.$$.fragment.c(); + @create_component(${name}.$$.fragment); @transition_in(${name}.$$.fragment, 1); @mount_component(${name}, ${update_mount_node}, ${anchor}); } else { @@ -472,11 +472,11 @@ export default class InlineComponentWrapper extends Wrapper { ${munged_handlers} `); - block.chunks.create.push(b`${name}.$$.fragment.c();`); + block.chunks.create.push(b`@create_component(${name}.$$.fragment);`); if (parent_nodes && this.renderer.options.hydratable) { block.chunks.claim.push( - b`${name}.$$.fragment.l(${parent_nodes});` + b`@claim_component(${name}.$$.fragment, ${parent_nodes});` ); } diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index 3855d3c361..0ce3b3209b 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -4,6 +4,21 @@ import { blank_object, is_function, run, run_all, noop, has_prop } from './utils import { children } from './dom'; import { transition_in } from './transitions'; +interface Fragment { + key: string|null; + first: null; + /* create */ c: () => void; + /* claim */ l: (nodes: any) => void; + /* hydrate */ h: () => void; + /* mount */ m: (target: HTMLElement, anchor: any) => void; + /* update */ p: (changed: any, ctx: any) => void; + /* measure */ r: () => void; + /* fix */ f: () => void; + /* animate */ a: () => void; + /* intro */ i: (local: any) => void; + /* outro */ o: (local: any) => void; + /* destroy */ d: (detaching: 0|1) => void; +} // eslint-disable-next-line @typescript-eslint/class-name-casing interface T$$ { dirty: null; @@ -13,7 +28,7 @@ interface T$$ { callbacks: any; after_update: any[]; props: Record; - fragment: null|any; + fragment: null|false|Fragment; not_equal: any; before_update: any[]; context: Map; @@ -29,10 +44,18 @@ export function bind(component, name, callback) { } } +export function create_component(block) { + block && block.c(); +} + +export function claim_component(block, parent_nodes) { + block && block.l(parent_nodes); +} + export function mount_component(component, target, anchor) { const { fragment, on_mount, on_destroy, after_update } = component.$$; - fragment.m(target, anchor); + fragment && fragment.m(target, anchor); // onMount happens before the initial afterUpdate add_render_callback(() => { @@ -51,15 +74,16 @@ export function mount_component(component, target, anchor) { } export function destroy_component(component, detaching) { - if (component.$$.fragment) { - run_all(component.$$.on_destroy); + const $$ = component.$$; + if ($$.fragment !== null) { + run_all($$.on_destroy); - component.$$.fragment.d(detaching); + $$.fragment && $$.fragment.d(detaching); // TODO null out other refs, including component.$$ (but need to // preserve final state?) - component.$$.on_destroy = component.$$.fragment = null; - component.$$.ctx = {}; + $$.on_destroy = $$.fragment = null; + $$.ctx = {}; } } @@ -115,15 +139,17 @@ export function init(component, options, instance, create_fragment, not_equal, p $$.update(); ready = true; run_all($$.before_update); - $$.fragment = create_fragment($$.ctx); + + // `false` as a special case of no DOM component + $$.fragment = create_fragment ? create_fragment($$.ctx) : false; if (options.target) { if (options.hydrate) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - $$.fragment!.l(children(options.target)); + $$.fragment && $$.fragment!.l(children(options.target)); } else { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - $$.fragment!.c(); + $$.fragment && $$.fragment!.c(); } if (options.intro) transition_in(component.$$.fragment); diff --git a/src/runtime/internal/scheduler.ts b/src/runtime/internal/scheduler.ts index e3d7181fcb..7cb00c085b 100644 --- a/src/runtime/internal/scheduler.ts +++ b/src/runtime/internal/scheduler.ts @@ -70,10 +70,10 @@ export function flush() { } function update($$) { - if ($$.fragment) { + if ($$.fragment !== null) { $$.update($$.dirty); run_all($$.before_update); - $$.fragment.p($$.dirty, $$.ctx); + $$.fragment && $$.fragment.p($$.dirty, $$.ctx); $$.dirty = null; $$.after_update.forEach(add_render_callback); diff --git a/test/js/samples/component-static-array/expected.js b/test/js/samples/component-static-array/expected.js index 2c38d80f30..0f32421e91 100644 --- a/test/js/samples/component-static-array/expected.js +++ b/test/js/samples/component-static-array/expected.js @@ -1,5 +1,6 @@ import { SvelteComponent, + create_component, destroy_component, init, mount_component, @@ -15,7 +16,7 @@ function create_fragment(ctx) { return { c() { - nested.$$.fragment.c(); + create_component(nested.$$.fragment); }, m(target, anchor) { mount_component(nested, target, anchor); diff --git a/test/js/samples/component-static-immutable/expected.js b/test/js/samples/component-static-immutable/expected.js index d60aeb3939..817db4e494 100644 --- a/test/js/samples/component-static-immutable/expected.js +++ b/test/js/samples/component-static-immutable/expected.js @@ -1,5 +1,6 @@ import { SvelteComponent, + create_component, destroy_component, init, mount_component, @@ -15,7 +16,7 @@ function create_fragment(ctx) { return { c() { - nested.$$.fragment.c(); + create_component(nested.$$.fragment); }, m(target, anchor) { mount_component(nested, target, anchor); diff --git a/test/js/samples/component-static-immutable2/expected.js b/test/js/samples/component-static-immutable2/expected.js index d60aeb3939..817db4e494 100644 --- a/test/js/samples/component-static-immutable2/expected.js +++ b/test/js/samples/component-static-immutable2/expected.js @@ -1,5 +1,6 @@ import { SvelteComponent, + create_component, destroy_component, init, mount_component, @@ -15,7 +16,7 @@ function create_fragment(ctx) { return { c() { - nested.$$.fragment.c(); + create_component(nested.$$.fragment); }, m(target, anchor) { mount_component(nested, target, anchor); diff --git a/test/js/samples/component-static-var/expected.js b/test/js/samples/component-static-var/expected.js index e1372a7b6d..0120b2c8cf 100644 --- a/test/js/samples/component-static-var/expected.js +++ b/test/js/samples/component-static-var/expected.js @@ -1,5 +1,6 @@ import { SvelteComponent, + create_component, destroy_component, detach, element, @@ -28,9 +29,9 @@ function create_fragment(ctx) { return { c() { - foo.$$.fragment.c(); + create_component(foo.$$.fragment); t0 = space(); - bar.$$.fragment.c(); + create_component(bar.$$.fragment); t1 = space(); input = element("input"); dispose = listen(input, "input", ctx.input_input_handler); diff --git a/test/js/samples/component-static/expected.js b/test/js/samples/component-static/expected.js index 14b85a917a..76321cfd82 100644 --- a/test/js/samples/component-static/expected.js +++ b/test/js/samples/component-static/expected.js @@ -1,5 +1,6 @@ import { SvelteComponent, + create_component, destroy_component, init, mount_component, @@ -15,7 +16,7 @@ function create_fragment(ctx) { return { c() { - nested.$$.fragment.c(); + create_component(nested.$$.fragment); }, m(target, anchor) { mount_component(nested, target, anchor); diff --git a/test/js/samples/component-store-file-invalidate/expected.js b/test/js/samples/component-store-file-invalidate/expected.js index 987f3971a1..5a466d7786 100644 --- a/test/js/samples/component-store-file-invalidate/expected.js +++ b/test/js/samples/component-store-file-invalidate/expected.js @@ -2,24 +2,12 @@ import { SvelteComponent, component_subscribe, init, - noop, safe_not_equal, set_store_value } from "svelte/internal"; import { count } from "./store.js"; -function create_fragment(ctx) { - return { - c: noop, - m: noop, - p: noop, - i: noop, - o: noop, - d: noop - }; -} - function instance($$self, $$props, $$invalidate) { let $count; component_subscribe($$self, count, $$value => $$invalidate("$count", $count = $$value)); @@ -34,7 +22,7 @@ function instance($$self, $$props, $$invalidate) { class Component extends SvelteComponent { constructor(options) { super(); - init(this, options, instance, create_fragment, safe_not_equal, { increment: 0 }); + init(this, options, instance, null, safe_not_equal, { increment: 0 }); } get increment() { diff --git a/test/js/samples/computed-collapsed-if/expected.js b/test/js/samples/computed-collapsed-if/expected.js index a63ee30ca0..d4b0289212 100644 --- a/test/js/samples/computed-collapsed-if/expected.js +++ b/test/js/samples/computed-collapsed-if/expected.js @@ -1,15 +1,4 @@ -import { SvelteComponent, init, noop, safe_not_equal } from "svelte/internal"; - -function create_fragment(ctx) { - return { - c: noop, - m: noop, - p: noop, - i: noop, - o: noop, - d: noop - }; -} +import { SvelteComponent, init, safe_not_equal } from "svelte/internal"; function instance($$self, $$props, $$invalidate) { let { x } = $$props; @@ -32,7 +21,7 @@ function instance($$self, $$props, $$invalidate) { class Component extends SvelteComponent { constructor(options) { super(); - init(this, options, instance, create_fragment, safe_not_equal, { x: 0, a: 0, b: 0 }); + init(this, options, instance, null, safe_not_equal, { x: 0, a: 0, b: 0 }); } get a() { diff --git a/test/js/samples/deconflict-globals/expected.js b/test/js/samples/deconflict-globals/expected.js index bf67b6aae4..92492dcd05 100644 --- a/test/js/samples/deconflict-globals/expected.js +++ b/test/js/samples/deconflict-globals/expected.js @@ -1,17 +1,6 @@ -import { SvelteComponent, init, noop, safe_not_equal } from "svelte/internal"; +import { SvelteComponent, init, safe_not_equal } from "svelte/internal"; import { onMount } from "svelte"; -function create_fragment(ctx) { - return { - c: noop, - m: noop, - p: noop, - i: noop, - o: noop, - d: noop - }; -} - function instance($$self, $$props, $$invalidate) { let { foo = "bar" } = $$props; @@ -29,7 +18,7 @@ function instance($$self, $$props, $$invalidate) { class Component extends SvelteComponent { constructor(options) { super(); - init(this, options, instance, create_fragment, safe_not_equal, { foo: 0 }); + init(this, options, instance, null, safe_not_equal, { foo: 0 }); } } diff --git a/test/js/samples/dynamic-import/expected.js b/test/js/samples/dynamic-import/expected.js index 95a50f4a4d..605f19983f 100644 --- a/test/js/samples/dynamic-import/expected.js +++ b/test/js/samples/dynamic-import/expected.js @@ -1,5 +1,6 @@ import { SvelteComponent, + create_component, destroy_component, init, mount_component, @@ -17,7 +18,7 @@ function create_fragment(ctx) { return { c() { - lazyload.$$.fragment.c(); + create_component(lazyload.$$.fragment); }, m(target, anchor) { mount_component(lazyload, target, anchor); diff --git a/test/js/samples/empty-dom/expected.js b/test/js/samples/empty-dom/expected.js new file mode 100644 index 0000000000..6ae0bc2999 --- /dev/null +++ b/test/js/samples/empty-dom/expected.js @@ -0,0 +1,15 @@ +import { SvelteComponent, init, safe_not_equal } from "svelte/internal"; + +function instance($$self) { + const a = 1 + 2; + return {}; +} + +class Component extends SvelteComponent { + constructor(options) { + super(); + init(this, options, instance, null, safe_not_equal, {}); + } +} + +export default Component; \ No newline at end of file diff --git a/test/js/samples/empty-dom/input.svelte b/test/js/samples/empty-dom/input.svelte new file mode 100644 index 0000000000..3098443ea3 --- /dev/null +++ b/test/js/samples/empty-dom/input.svelte @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/test/js/samples/non-imported-component/expected.js b/test/js/samples/non-imported-component/expected.js index d9176f35f7..be1ff282c3 100644 --- a/test/js/samples/non-imported-component/expected.js +++ b/test/js/samples/non-imported-component/expected.js @@ -1,5 +1,6 @@ import { SvelteComponent, + create_component, destroy_component, detach, init, @@ -22,9 +23,9 @@ function create_fragment(ctx) { return { c() { - imported.$$.fragment.c(); + create_component(imported.$$.fragment); t = space(); - nonimported.$$.fragment.c(); + create_component(nonimported.$$.fragment); }, m(target, anchor) { mount_component(imported, target, anchor); diff --git a/test/js/samples/reactive-values-non-topologically-ordered/expected.js b/test/js/samples/reactive-values-non-topologically-ordered/expected.js index abd22c4da0..69fd368ee8 100644 --- a/test/js/samples/reactive-values-non-topologically-ordered/expected.js +++ b/test/js/samples/reactive-values-non-topologically-ordered/expected.js @@ -1,15 +1,4 @@ -import { SvelteComponent, init, noop, safe_not_equal } from "svelte/internal"; - -function create_fragment(ctx) { - return { - c: noop, - m: noop, - p: noop, - i: noop, - o: noop, - d: noop - }; -} +import { SvelteComponent, init, safe_not_equal } from "svelte/internal"; function instance($$self, $$props, $$invalidate) { let { x } = $$props; @@ -36,7 +25,7 @@ function instance($$self, $$props, $$invalidate) { class Component extends SvelteComponent { constructor(options) { super(); - init(this, options, instance, create_fragment, safe_not_equal, { x: 0 }); + init(this, options, instance, null, safe_not_equal, { x: 0 }); } } diff --git a/test/js/samples/reactive-values-non-writable-dependencies/expected.js b/test/js/samples/reactive-values-non-writable-dependencies/expected.js index 6b68368894..233e9ef835 100644 --- a/test/js/samples/reactive-values-non-writable-dependencies/expected.js +++ b/test/js/samples/reactive-values-non-writable-dependencies/expected.js @@ -1,15 +1,4 @@ -import { SvelteComponent, init, noop, safe_not_equal } from "svelte/internal"; - -function create_fragment(ctx) { - return { - c: noop, - m: noop, - p: noop, - i: noop, - o: noop, - d: noop - }; -} +import { SvelteComponent, init, safe_not_equal } from "svelte/internal"; function instance($$self, $$props, $$invalidate) { let { a = 1 } = $$props; @@ -32,7 +21,7 @@ function instance($$self, $$props, $$invalidate) { class Component extends SvelteComponent { constructor(options) { super(); - init(this, options, instance, create_fragment, safe_not_equal, { a: 0, b: 0 }); + init(this, options, instance, null, safe_not_equal, { a: 0, b: 0 }); } } diff --git a/test/js/samples/setup-method/expected.js b/test/js/samples/setup-method/expected.js index 40fc049383..e22398748e 100644 --- a/test/js/samples/setup-method/expected.js +++ b/test/js/samples/setup-method/expected.js @@ -1,16 +1,4 @@ -import { SvelteComponent, init, noop, safe_not_equal } from "svelte/internal"; - -function create_fragment(ctx) { - return { - c: noop, - m: noop, - p: noop, - i: noop, - o: noop, - d: noop - }; -} - +import { SvelteComponent, init, safe_not_equal } from "svelte/internal"; const SOME_CONSTANT = 42; function foo(bar) { @@ -20,7 +8,7 @@ function foo(bar) { class Component extends SvelteComponent { constructor(options) { super(); - init(this, options, null, create_fragment, safe_not_equal, { foo: 0 }); + init(this, options, null, null, safe_not_equal, { foo: 0 }); } get foo() { diff --git a/test/runtime/samples/empty-dom/_config.js b/test/runtime/samples/empty-dom/_config.js new file mode 100644 index 0000000000..e3e3d0ecd5 --- /dev/null +++ b/test/runtime/samples/empty-dom/_config.js @@ -0,0 +1,3 @@ +export default { + html: '', +}; \ No newline at end of file diff --git a/test/runtime/samples/empty-dom/main.svelte b/test/runtime/samples/empty-dom/main.svelte new file mode 100644 index 0000000000..3098443ea3 --- /dev/null +++ b/test/runtime/samples/empty-dom/main.svelte @@ -0,0 +1,3 @@ + \ No newline at end of file