From e5a5d891d29e46de0c077d74d7b033551a83f211 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 23 Nov 2018 16:21:09 -0500 Subject: [PATCH] unified approach to hoisted functions --- src/compile/Component.ts | 7 +- src/compile/nodes/Action.ts | 3 + src/compile/nodes/EventHandler.ts | 26 +-- src/compile/nodes/shared/Expression.ts | 149 ++++++++++++++---- src/compile/render-dom/Block.ts | 4 +- src/compile/render-dom/index.ts | 10 +- .../render-dom/wrappers/Element/index.ts | 93 ++++------- .../wrappers/InlineComponent/index.ts | 30 ++-- src/compile/render-dom/wrappers/Window.ts | 49 +----- src/utils/deindent.ts | 18 ++- 10 files changed, 193 insertions(+), 196 deletions(-) diff --git a/src/compile/Component.ts b/src/compile/Component.ts index 919cfb1171..8a9169a564 100644 --- a/src/compile/Component.ts +++ b/src/compile/Component.ts @@ -54,7 +54,8 @@ export default class Component { writable_declarations: Set = new Set(); initialised_declarations: Set = new Set(); exports: Array<{ name: string, as: string }> = []; - event_handlers: Array<{ name: string, body: string }> = []; + partly_hoisted: string[] = []; + fully_hoisted: string[] = []; code: MagicString; @@ -192,7 +193,9 @@ export default class Component { return { name, alias }; }); - const sharedPath = options.shared || 'svelte/internal.js'; + const sharedPath = typeof options.shared === 'string' + ? options.shared + : 'svelte/internal.js'; const module = wrapModule(result, format, name, options, banner, sharedPath, importedHelpers, this.imports, this.source); diff --git a/src/compile/nodes/Action.ts b/src/compile/nodes/Action.ts index 79b8f7bdcb..87565a0151 100644 --- a/src/compile/nodes/Action.ts +++ b/src/compile/nodes/Action.ts @@ -5,6 +5,7 @@ export default class Action extends Node { type: 'Action'; name: string; expression: Expression; + usesContext: boolean; constructor(component, parent, scope, info) { super(component, parent, scope, info); @@ -14,5 +15,7 @@ export default class Action extends Node { this.expression = info.expression ? new Expression(component, this, scope, info.expression) : null; + + this.usesContext = this.expression && this.expression.usesContext; } } \ No newline at end of file diff --git a/src/compile/nodes/EventHandler.ts b/src/compile/nodes/EventHandler.ts index ff75bf5309..46f8f7ab5d 100644 --- a/src/compile/nodes/EventHandler.ts +++ b/src/compile/nodes/EventHandler.ts @@ -27,32 +27,10 @@ export default class EventHandler extends Node { this.modifiers = new Set(info.modifiers); if (info.expression) { - this.expression = new Expression(component, parent, template_scope, info.expression, true); + this.expression = new Expression(component, this, template_scope, info.expression, true); this.snippet = this.expression.snippet; - let { scope, map } = createScopes(info.expression); - - walk(info.expression, { - enter: (node, parent) => { - if (map.has(node)) { - scope = map.get(node); - } - - if (node.type === 'AssignmentExpression') { - const { name } = flattenReference(node.left); - - if (!scope.has(name)) { - component.instrument(node, parent, name, true); - } - } - }, - - leave(node) { - if (map.has(node)) { - scope = scope.parent; - } - } - }); + this.usesContext = this.expression.usesContext; } else { component.init_uses_self = true; this.snippet = `e => @bubble($$self, e)` diff --git a/src/compile/nodes/shared/Expression.ts b/src/compile/nodes/shared/Expression.ts index 5b43be6c33..b7e973d988 100644 --- a/src/compile/nodes/shared/Expression.ts +++ b/src/compile/nodes/shared/Expression.ts @@ -6,6 +6,8 @@ import { createScopes } from '../../../utils/annotateWithScopes'; import { Node } from '../../../interfaces'; import addToSet from '../../../utils/addToSet'; import globalWhitelist from '../../../utils/globalWhitelist'; +import deindent from '../../../utils/deindent'; +import Wrapper from '../../render-dom/wrappers/shared/Wrapper'; const binaryOperators: Record = { '**': 15, @@ -63,12 +65,11 @@ export default class Expression { dependencies: Set; contextual_dependencies: Set; + declarations: string[] = []; usesContext = false; usesEvent = false; - thisReferences: Array<{ start: number, end: number }>; - - constructor(component, parent, scope, info, isEventHandler?: boolean) { + constructor(component: Component, owner: Wrapper, scope, info, isEventHandler?: boolean) { // TODO revert to direct property access in prod? Object.defineProperties(this, { component: { @@ -77,19 +78,24 @@ export default class Expression { }); this.node = info; - this.thisReferences = []; this.snippet = `[✂${info.start}-${info.end}✂]`; - const dependencies = new Set(); - const contextual_dependencies = new Set(); + const expression_dependencies = new Set(); + const expression_contextual_dependencies = new Set(); + + let dependencies = expression_dependencies; + let contextual_dependencies = expression_contextual_dependencies; + const { declarations } = this; const { code } = component; let { map, scope: currentScope } = createScopes(info); const expression = this; - const isSynthetic = parent.isSynthetic; + const isSynthetic = owner.isSynthetic; + + let function_expression; walk(info, { enter(node: any, parent: any, key: string) { @@ -101,24 +107,6 @@ export default class Expression { if (map.has(node)) { currentScope = map.get(node); - return; - } - - if (node.type === 'ThisExpression') { - expression.thisReferences.push(node); - } - - if (node.type === 'CallExpression') { - if (node.callee.type === 'Identifier') { - const dependencies_for_invocation = component.findDependenciesForFunctionCall(node.callee.name); - if (dependencies_for_invocation) { - addToSet(dependencies, dependencies_for_invocation); - } else { - dependencies.add('$$BAIL$$'); - } - } else { - dependencies.add('$$BAIL$$'); - } } if (isReference(node, parent)) { @@ -127,9 +115,7 @@ export default class Expression { if (currentScope.has(name)) return; if (globalWhitelist.has(name) && component.declarations.indexOf(name) === -1) return; - expression.usesContext = true; - - if (!isSynthetic && !isEventHandler) { + if (!isSynthetic && !function_expression) { //