diff --git a/src/compile/Component.ts b/src/compile/Component.ts index de43f13646..1529bb2230 100644 --- a/src/compile/Component.ts +++ b/src/compile/Component.ts @@ -75,6 +75,7 @@ export default class Component { props: Array<{ name: string, as: string }> = []; writable_declarations: Set = new Set(); initialised_declarations: Set = new Set(); + imported_declarations: Set = new Set(); hoistable_names: Set = new Set(); hoistable_nodes: Set = new Set(); node_for_declaration: Map = new Map(); @@ -468,21 +469,27 @@ export default class Component { } // imports need to be hoisted out of the IIFE - // TODO hoist other stuff where possible else if (node.type === 'ImportDeclaration') { removeNode(code, content.start, content.end, content.body, node); imports.push(node); node.specifiers.forEach((specifier: Node) => { this.userVars.add(specifier.local.name); - this.declarations.push(specifier.local.name); // TODO we don't really want this, but it's convenient for now + this.imported_declarations.add(specifier.local.name); }); } }); } extract_javascript(script) { - if (script.content.body.length === this.hoistable_nodes.size) return null; + const nodes_to_include = script.content.body.filter(node => { + if (this.hoistable_nodes.has(node)) return false; + if (node.type === 'ImportDeclaration') return false; + if (node.type === 'ExportDeclaration' && node.specifiers.length > 0) return false; + return true; + }); + + if (nodes_to_include.length === 0) return null; let a = script.content.start; while (/\s/.test(this.source[a])) a += 1; @@ -751,6 +758,13 @@ export default class Component { } } + qualify(name) { + if (this.hoistable_names.has(name)) return name; + if (this.imported_declarations.has(name)) return name; + if (this.declarations.indexOf(name) === -1) return name; + return `ctx.${name}`; + } + warn_if_undefined(node, template_scope: TemplateScope) { const { name } = node; if (this.module_scope && this.module_scope.declarations.has(name)) return; diff --git a/src/compile/nodes/shared/Expression.ts b/src/compile/nodes/shared/Expression.ts index 757ecb17a5..750203c39a 100644 --- a/src/compile/nodes/shared/Expression.ts +++ b/src/compile/nodes/shared/Expression.ts @@ -219,7 +219,7 @@ export default class Expression { dependencies.add(name); component.template_references.add(name); } - } else if (!is_synthetic) { + } else if (!is_synthetic && !component.hoistable_names.has(name)) { code.prependRight(node.start, key === 'key' && parent.shorthand ? `${name}: ctx.` : 'ctx.'); @@ -317,6 +317,7 @@ export default class Expression { // function can be hoisted inside the component init component.partly_hoisted.push(fn); component.declarations.push(name); + component.template_references.add(name); code.overwrite(node.start, node.end, `ctx.${name}`); } @@ -324,6 +325,7 @@ export default class Expression { // we need a combo block/init recipe component.partly_hoisted.push(fn); component.declarations.push(name); + component.template_references.add(name); code.overwrite(node.start, node.end, name); declarations.push(deindent` diff --git a/src/compile/render-dom/index.ts b/src/compile/render-dom/index.ts index b9642abd14..063b0d3f11 100644 --- a/src/compile/render-dom/index.ts +++ b/src/compile/render-dom/index.ts @@ -181,11 +181,18 @@ export default function dom( ${component.fully_hoisted.length > 0 && component.fully_hoisted.join('\n\n')} `); + const declarations = component.declarations.filter(name => { + if (component.props.find(p => p.as === name)) return true; + if (component.hoistable_names.has(name)) return false; + if (component.imported_declarations.has(name)) return false; + return component.template_references.has(name); + }); + const has_definition = ( component.javascript || component.props.length > 0 || component.partly_hoisted.length > 0 || - component.declarations.length > 0 + declarations.length > 0 ); const definition = has_definition @@ -202,7 +209,7 @@ export default function dom( ${component.partly_hoisted.length > 0 && component.partly_hoisted.join('\n\n')} - $$self.$$.get = () => (${stringifyProps(component.declarations)}); + ${declarations.length > 0 && `$$self.$$.get = () => (${stringifyProps(declarations)});`} ${set && `$$self.$$.set = ${set};`} @@ -228,10 +235,11 @@ export default function dom( @insert(options.target, this, options.anchor); } + ${component.props.length > 0 || component.meta.props && deindent` if (options.props) { this.$set(options.props); @flush(); - } + }`} } } diff --git a/src/compile/render-dom/wrappers/Element/index.ts b/src/compile/render-dom/wrappers/Element/index.ts index eec46534cd..78c9f8539b 100644 --- a/src/compile/render-dom/wrappers/Element/index.ts +++ b/src/compile/render-dom/wrappers/Element/index.ts @@ -403,17 +403,16 @@ export default class ElementWrapper extends Wrapper { if (lock) block.addVariable(lock, 'false'); const groups = events - .map(event => { - return { - events: event.eventNames, - bindings: mungedBindings.filter(binding => event.filter(this.node, binding.name)) - }; - }) + .map(event => ({ + events: event.eventNames, + bindings: mungedBindings.filter(binding => event.filter(this.node, binding.name)) + })) .filter(group => group.bindings.length); groups.forEach(group => { const handler = block.getUniqueName(`${this.var}_${group.events.join('_')}_handler`); renderer.component.declarations.push(handler); + renderer.component.template_references.add(handler); const needsLock = group.bindings.some(binding => binding.needsLock); @@ -707,6 +706,8 @@ export default class ElementWrapper extends Wrapper { addAnimation(block: Block) { if (!this.node.animation) return; + const { component } = this.renderer; + const rect = block.getUniqueName('rect'); const animation = block.getUniqueName('animation'); @@ -723,14 +724,20 @@ export default class ElementWrapper extends Wrapper { `); const params = this.node.animation.expression ? this.node.animation.expression.render() : '{}'; + + let { name } = this.node.animation; + if (!component.hoistable_names.has(name) && !component.imported_declarations.has(name)) { + name = `ctx.${name}`; + } + block.builders.animate.addBlock(deindent` if (${animation}) ${animation}.stop(); - ${animation} = @wrapAnimation(${this.var}, ${rect}, ctx.${this.node.animation.name}, ${params}); + ${animation} = @wrapAnimation(${this.var}, ${rect}, ${name}, ${params}); `); } addActions(block: Block) { - addActions(block, this.var, this.node.actions); + addActions(this.renderer.component, block, this.var, this.node.actions); } addClasses(block: Block) { diff --git a/src/compile/render-dom/wrappers/InlineComponent/index.ts b/src/compile/render-dom/wrappers/InlineComponent/index.ts index cb923d133a..3019236b30 100644 --- a/src/compile/render-dom/wrappers/InlineComponent/index.ts +++ b/src/compile/render-dom/wrappers/InlineComponent/index.ts @@ -81,11 +81,11 @@ export default class InlineComponentWrapper extends Wrapper { const name = this.var; - const componentInitProperties = []; + const component_opts = []; if (this.fragment) { const slots = Array.from(this._slots).map(name => `${quoteNameIfNecessary(name)}: @createFragment()`); - componentInitProperties.push(`slots: { ${slots.join(', ')} }`); + component_opts.push(`slots: { ${slots.join(', ')} }`); this.fragment.nodes.forEach((child: Wrapper) => { child.render(block, `${this.var}.$$.slotted.default`, 'nodes'); @@ -109,10 +109,10 @@ export default class InlineComponentWrapper extends Wrapper { if (this.node.attributes.length || this.node.bindings.length) { if (!usesSpread && this.node.bindings.length === 0) { - componentInitProperties.push(`props: ${attributeObject}`); + component_opts.push(`props: ${attributeObject}`); } else { props = block.getUniqueName(`${name}_props`); - componentInitProperties.push(`props: ${props}`); + component_opts.push(`props: ${props}`); } } @@ -121,7 +121,7 @@ export default class InlineComponentWrapper extends Wrapper { // will complain that options.target is missing. This would // work better if components had separate public and private // APIs - componentInitProperties.push(`$$inline: true`); + component_opts.push(`$$inline: true`); } if (!usesSpread && (this.node.attributes.filter(a => a.isDynamic).length || this.node.bindings.length)) { @@ -295,9 +295,7 @@ export default class InlineComponentWrapper extends Wrapper { ${(this.node.attributes.length || this.node.bindings.length) && deindent` ${props && `const ${props} = ${attributeObject};`}`} ${statements} - return { - ${componentInitProperties.join(',\n')} - }; + return ${stringifyProps(component_opts)}; } if (${switch_value}) { @@ -383,15 +381,13 @@ export default class InlineComponentWrapper extends Wrapper { } else { const expression = this.node.name === 'svelte:self' ? component.name - : `ctx.${this.node.name}`; + : component.qualify(this.node.name); block.builders.init.addBlock(deindent` ${(this.node.attributes.length || this.node.bindings.length) && deindent` ${props && `const ${props} = ${attributeObject};`}`} ${statements} - var ${name} = new ${expression}({ - ${componentInitProperties.join(',\n')} - }); + var ${name} = new ${expression}(${stringifyProps(component_opts)}); ${munged_bindings} ${munged_handlers} diff --git a/src/compile/render-dom/wrappers/Window.ts b/src/compile/render-dom/wrappers/Window.ts index a7e367d1c4..29e8ab82d3 100644 --- a/src/compile/render-dom/wrappers/Window.ts +++ b/src/compile/render-dom/wrappers/Window.ts @@ -44,7 +44,7 @@ export default class WindowWrapper extends Wrapper { const events = {}; const bindings: Record = {}; - addActions(block, 'window', this.node.actions); + addActions(component, block, 'window', this.node.actions); addEventHandlers(block, 'window', this.node.handlers); this.node.bindings.forEach(binding => { @@ -106,6 +106,7 @@ export default class WindowWrapper extends Wrapper { } component.declarations.push(handler_name); + component.template_references.add(handler_name); component.partly_hoisted.push(deindent` function ${handler_name}() { ${event === 'scroll' && deindent` diff --git a/src/compile/render-dom/wrappers/shared/addActions.ts b/src/compile/render-dom/wrappers/shared/addActions.ts index 42e35e2e22..1196418715 100644 --- a/src/compile/render-dom/wrappers/shared/addActions.ts +++ b/src/compile/render-dom/wrappers/shared/addActions.ts @@ -1,8 +1,10 @@ import Renderer from '../../Renderer'; import Block from '../../Block'; import Action from '../../../nodes/Action'; +import Component from '../../../Component'; export default function addActions( + component: Component, block: Block, target: string, actions: Action[] @@ -24,7 +26,9 @@ export default function addActions( ); block.addVariable(name); - const fn = `ctx.${action.name}`; + const fn = component.imported_declarations.has(action.name) || component.hoistable_names.has(action.name) + ? action.name + : `ctx.${action.name}`; block.builders.mount.addLine( `${name} = ${fn}.call(null, ${target}${snippet ? `, ${snippet}` : ''}) || {};` diff --git a/test/js/samples/action/expected.js b/test/js/samples/action/expected.js index 831a7c66ca..a86a4fbc2d 100644 --- a/test/js/samples/action/expected.js +++ b/test/js/samples/action/expected.js @@ -1,21 +1,6 @@ /* generated by Svelte vX.Y.Z-alpha1 */ import { SvelteComponent as SvelteComponent_1, createElement, detachNode, init, insert, noop, run, safe_not_equal } from "svelte/internal.js"; -function link(node) { - function onClick(event) { - event.preventDefault(); - history.pushState(null, null, event.target.href); - } - - node.addEventListener('click', onClick); - - return { - destroy() { - node.removeEventListener('click', onClick); - } - } -} - function create_fragment(component, ctx) { var a, link_action, current; @@ -51,6 +36,21 @@ function create_fragment(component, ctx) { }; } +function link(node) { + function onClick(event) { + event.preventDefault(); + history.pushState(null, null, event.target.href); + } + + node.addEventListener('click', onClick); + + return { + destroy() { + node.removeEventListener('click', onClick); + } + } +} + class SvelteComponent extends SvelteComponent_1 { constructor(options) { super(); diff --git a/test/js/samples/bind-width-height/expected.js b/test/js/samples/bind-width-height/expected.js index 30f1bc7fe4..b57e895541 100644 --- a/test/js/samples/bind-width-height/expected.js +++ b/test/js/samples/bind-width-height/expected.js @@ -36,9 +36,8 @@ function create_fragment(component, ctx) { }; } -function define($$self, $$make_dirty) { - let w; - let h; +function define($$self, $$props, $$make_dirty) { + let { w, h } = $$props; function div_resize_handler() { w = this.offsetWidth; diff --git a/test/js/samples/collapses-text-around-comments/expected.js b/test/js/samples/collapses-text-around-comments/expected.js index ffbf9e543a..f9ef4df604 100644 --- a/test/js/samples/collapses-text-around-comments/expected.js +++ b/test/js/samples/collapses-text-around-comments/expected.js @@ -32,7 +32,6 @@ function create_fragment(component, ctx) { i(target, anchor) { if (current) return; - this.m(target, anchor); }, diff --git a/test/js/samples/component-static-array/expected.js b/test/js/samples/component-static-array/expected.js index a3ffce0e49..fffc2db748 100644 --- a/test/js/samples/component-static-array/expected.js +++ b/test/js/samples/component-static-array/expected.js @@ -1,14 +1,10 @@ /* generated by Svelte vX.Y.Z-alpha1 */ import { SvelteComponent as SvelteComponent_1, init, mount_component, noop, safe_not_equal } from "svelte/internal.js"; -const Nested = window.Nested; - function create_fragment(component, ctx) { var current; - var nested = new Nested({ - props: { foo: [1, 2, 3] } - }); + var nested = new ctx.Nested({ props: { foo: [1, 2, 3] } }); return { c() { @@ -40,10 +36,16 @@ function create_fragment(component, ctx) { }; } +function define($$self) { + const Nested = window.Nested; + + $$self.$$.get = () => ({ Nested }); +} + class SvelteComponent extends SvelteComponent_1 { constructor(options) { super(); - init(this, options, noop, create_fragment, safe_not_equal); + init(this, options, define, create_fragment, safe_not_equal); } } diff --git a/test/js/samples/component-static-immutable2/expected.js b/test/js/samples/component-static-immutable2/expected.js index 27d78d6c93..4accd8e7bd 100644 --- a/test/js/samples/component-static-immutable2/expected.js +++ b/test/js/samples/component-static-immutable2/expected.js @@ -1,14 +1,10 @@ /* generated by Svelte vX.Y.Z-alpha1 */ import { SvelteComponent as SvelteComponent_1, init, mount_component, noop, not_equal } from "svelte/internal.js"; -const Nested = window.Nested; - function create_fragment(component, ctx) { var current; - var nested = new Nested({ - props: { foo: "bar" } - }); + var nested = new ctx.Nested({ props: { foo: "bar" } }); return { c() { @@ -40,10 +36,16 @@ function create_fragment(component, ctx) { }; } +function define($$self) { + const Nested = window.Nested; + + $$self.$$.get = () => ({ Nested }); +} + class SvelteComponent extends SvelteComponent_1 { constructor(options) { super(); - init(this, options, noop, create_fragment, not_equal); + init(this, options, define, create_fragment, not_equal); } } diff --git a/test/js/samples/component-static/expected.js b/test/js/samples/component-static/expected.js index cb61bdf9de..009dcea2e4 100644 --- a/test/js/samples/component-static/expected.js +++ b/test/js/samples/component-static/expected.js @@ -1,14 +1,10 @@ /* generated by Svelte vX.Y.Z-alpha1 */ import { SvelteComponent as SvelteComponent_1, init, mount_component, noop, safe_not_equal } from "svelte/internal.js"; -const Nested = window.Nested; - function create_fragment(component, ctx) { var current; - var nested = new Nested({ - props: { foo: "bar" } - }); + var nested = new ctx.Nested({ props: { foo: "bar" } }); return { c() { @@ -40,10 +36,16 @@ function create_fragment(component, ctx) { }; } +function define($$self) { + const Nested = window.Nested; + + $$self.$$.get = () => ({ Nested }); +} + class SvelteComponent extends SvelteComponent_1 { constructor(options) { super(); - init(this, options, noop, create_fragment, safe_not_equal); + init(this, options, define, create_fragment, safe_not_equal); } } diff --git a/test/js/samples/computed-collapsed-if/expected.js b/test/js/samples/computed-collapsed-if/expected.js index c2d076a4cf..66fa150c61 100644 --- a/test/js/samples/computed-collapsed-if/expected.js +++ b/test/js/samples/computed-collapsed-if/expected.js @@ -44,5 +44,14 @@ class SvelteComponent extends SvelteComponent_1 { this.$set({ x: value }); flush(); } + + get a() { + return this.$$.get().a; + } + + get b() { + return this.$$.get().b; + } } + export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/computed-collapsed-if/input.html b/test/js/samples/computed-collapsed-if/input.html index c1349afc99..779bb2fdaa 100644 --- a/test/js/samples/computed-collapsed-if/input.html +++ b/test/js/samples/computed-collapsed-if/input.html @@ -1,11 +1,11 @@ \ No newline at end of file diff --git a/test/js/samples/css-shadow-dom-keyframes/expected.js b/test/js/samples/css-shadow-dom-keyframes/expected.js index c53db44d8e..3296305b3e 100644 --- a/test/js/samples/css-shadow-dom-keyframes/expected.js +++ b/test/js/samples/css-shadow-dom-keyframes/expected.js @@ -1,5 +1,5 @@ /* generated by Svelte vX.Y.Z-alpha1 */ -import { SvelteComponent as SvelteComponent_1, SvelteElement, createElement, detachNode, flush, init, insert, noop, run, safe_not_equal } from "svelte/internal.js"; +import { SvelteElement, createElement, detachNode, init, insert, noop, run, safe_not_equal } from "svelte/internal.js"; function create_fragment(component, ctx) { var div, current; @@ -54,4 +54,5 @@ class SvelteComponent extends SvelteElement { } customElements.define("custom-element", SvelteComponent); + export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/dynamic-import/expected.js b/test/js/samples/dynamic-import/expected.js index f68300fe54..159afc1f7f 100644 --- a/test/js/samples/dynamic-import/expected.js +++ b/test/js/samples/dynamic-import/expected.js @@ -1,16 +1,10 @@ /* generated by Svelte vX.Y.Z-alpha1 */ -import { SvelteComponent as SvelteComponent_1, flush, init, mount_component, safe_not_equal } from "svelte/internal.js"; - -function func() { - return import('./Foo.html'); -} +import { SvelteComponent as SvelteComponent_1, init, mount_component, safe_not_equal } from "svelte/internal.js"; function create_fragment(component, ctx) { var current; - var lazyload = new ctx.LazyLoad({ - props: { load: func } - }); + var lazyload = new LazyLoad({ props: { load: func } }); return { c() { @@ -42,29 +36,14 @@ function create_fragment(component, ctx) { }; } -function define($$self, $$props) { - let { LazyLoad } = $$props; - - $$self.$$.get = () => ({ LazyLoad }); - - $$self.$$.set = $$props => { - if ('LazyLoad' in $$props) LazyLoad = $$props.LazyLoad; - }; +function func() { + return import('./Foo.html'); } class SvelteComponent extends SvelteComponent_1 { constructor(options) { super(); - init(this, options, define, create_fragment, safe_not_equal); - } - - get LazyLoad() { - return this.$$.get().LazyLoad; - } - - set LazyLoad(value) { - this.$set({ LazyLoad: value }); - flush(); + init(this, options, noop, create_fragment, safe_not_equal); } } diff --git a/test/js/samples/dynamic-import/input.html b/test/js/samples/dynamic-import/input.html index eb7ca117c5..3640ef5161 100644 --- a/test/js/samples/dynamic-import/input.html +++ b/test/js/samples/dynamic-import/input.html @@ -1 +1,5 @@ + + \ No newline at end of file diff --git a/test/js/samples/each-block-keyed-animated/expected.js b/test/js/samples/each-block-keyed-animated/expected.js index fc71eac904..8e94747d49 100644 --- a/test/js/samples/each-block-keyed-animated/expected.js +++ b/test/js/samples/each-block-keyed-animated/expected.js @@ -1,20 +1,6 @@ /* generated by Svelte vX.Y.Z-alpha1 */ import { SvelteComponent as SvelteComponent_1, append, blankObject, createComment, createElement, createText, detachNode, fixAndOutroAndDestroyBlock, fixPosition, flush, init, insert, run, safe_not_equal, setData, updateKeyedEach, wrapAnimation } from "svelte/internal.js"; -function foo(node, animation, params) { - const dx = animation.from.left - animation.to.left; - const dy = animation.from.top - animation.to.top; - - return { - delay: params.delay, - duration: 100, - tick: (t, u) => { - node.dx = u * dx; - node.dy = u * dy; - } - }; -} - function get_each_context(ctx, list, i) { const child_ctx = Object.create(ctx); child_ctx.thing = list[i]; @@ -120,10 +106,25 @@ function create_fragment(component, ctx) { }; } +function foo(node, animation, params) { + const dx = animation.from.left - animation.to.left; + const dy = animation.from.top - animation.to.top; + + return { + delay: params.delay, + duration: 100, + tick: (t, u) => { + node.dx = u * dx; + node.dy = u * dy; + } + }; +} + function define($$self, $$props) { let { things } = $$props; - // TODO only what's needed by the template + /* HOISTED */ + $$self.$$.get = () => ({ things }); $$self.$$.set = $$props => { diff --git a/test/js/samples/event-handlers-custom/expected.js b/test/js/samples/event-handlers-custom/expected.js index d101e33880..93a4ce1d76 100644 --- a/test/js/samples/event-handlers-custom/expected.js +++ b/test/js/samples/event-handlers-custom/expected.js @@ -1,22 +1,18 @@ /* generated by Svelte vX.Y.Z-alpha1 */ import { SvelteComponent as SvelteComponent_1, addListener, createElement, detachNode, flush, init, insert, noop, removeListener, run, safe_not_equal } from "svelte/internal.js"; -function handleFoo(bar) { - console.log(bar); -} - function create_fragment(component, ctx) { - var button, current; + var button, foo_action, current; return { c() { button = createElement("button"); button.textContent = "foo"; - addListener(button, "foo", ctx.foo_handler); }, m(target, anchor) { insert(target, button, anchor); + foo_action = foo.call(null, button, ctx.foo_function) || {}; current = true; }, @@ -34,24 +30,29 @@ function create_fragment(component, ctx) { detachNode(button); } - removeListener(button, "foo", ctx.foo_handler); + if (foo_action && typeof foo_action.destroy === 'function') foo_action.destroy(); } }; } +function handleFoo(bar) { + console.log(bar); +} + +function foo(node, callback) { + // code goes here +} + function define($$self, $$props) { let { bar } = $$props; - function foo(node, callback) { - // code goes here - } + /* HOISTED */ - function foo_handler() { + function foo_function() { return handleFoo(bar); } - // TODO only what's needed by the template - $$self.$$.get = () => ({ bar, foo_handler }); + $$self.$$.get = () => ({ bar, foo_function }); $$self.$$.set = $$props => { if ('bar' in $$props) bar = $$props.bar; diff --git a/test/js/samples/event-modifiers/expected.js b/test/js/samples/event-modifiers/expected.js index 7ff50d564a..7897a5abf0 100644 --- a/test/js/samples/event-modifiers/expected.js +++ b/test/js/samples/event-modifiers/expected.js @@ -1,14 +1,6 @@ /* generated by Svelte vX.Y.Z-alpha1 */ import { SvelteComponent as SvelteComponent_1, addListener, append, createElement, createText, detachNode, init, insert, noop, removeListener, run, safe_not_equal } from "svelte/internal.js"; -function handleTouchstart() { - // ... -} - -function handleClick() { - // ... -} - function create_fragment(component, ctx) { var div, button0, text1, button1, text3, button2, current; @@ -61,6 +53,14 @@ function create_fragment(component, ctx) { }; } +function handleTouchstart() { + // ... +} + +function handleClick() { + // ... +} + class SvelteComponent extends SvelteComponent_1 { constructor(options) { super();