Skip to content

Commit

Permalink
attach globals to state object on initialisation
Browse files Browse the repository at this point in the history
fixes #908
  • Loading branch information
Rich-Harris authored Nov 23, 2017
1 parent 60b883b commit 5190144
Show file tree
Hide file tree
Showing 29 changed files with 360 additions and 197 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"eslint": "^4.3.0",
"eslint-plugin-html": "^3.0.0",
"eslint-plugin-import": "^2.2.0",
"estree-walker": "^0.5.0",
"estree-walker": "^0.5.1",
"glob": "^7.1.1",
"jsdom": "^11.1.0",
"locate-character": "^2.0.0",
Expand Down
256 changes: 163 additions & 93 deletions src/generators/Generator.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import MagicString, { Bundle } from 'magic-string';
import { walk } from 'estree-walker';
import { walk, childKeys } from 'estree-walker';
import { getLocator } from 'locate-character';
import deindent from '../utils/deindent';
import CodeBuilder from '../utils/CodeBuilder';
import getCodeFrame from '../utils/getCodeFrame';
import isReference from '../utils/isReference';
import flattenReference from '../utils/flattenReference';
import globalWhitelist from '../utils/globalWhitelist';
import reservedNames from '../utils/reservedNames';
import namespaces from '../utils/namespaces';
import { removeNode, removeObjectKey } from '../utils/removeNode';
import wrapModule from './shared/utils/wrapModule';
import annotateWithScopes from '../utils/annotateWithScopes';
import annotateWithScopes, { Scope } from '../utils/annotateWithScopes';
import getName from '../utils/getName';
import clone from '../utils/clone';
import DomBlock from './dom/Block';
Expand Down Expand Up @@ -71,6 +70,19 @@ function removeIndentation(
}
}

// We need to tell estree-walker that it should always
// look for an `else` block, otherwise it might get
// the wrong idea about the shape of each/if blocks
childKeys.EachBlock = [
'children',
'else'
];

childKeys.IfBlock = [
'children',
'else'
];

export default class Generator {
ast: Parsed;
parsed: Parsed;
Expand Down Expand Up @@ -156,7 +168,12 @@ export default class Generator {
this.aliases = new Map();
this.usedNames = new Set();

this.parseJs(dom);
this.computations = [];
this.templateProperties = {};

this.walkJs(dom);
this.walkTemplate();

this.name = this.alias(name);

if (options.customElement === true) {
Expand Down Expand Up @@ -196,20 +213,18 @@ export default class Generator {
context: string,
isEventHandler: boolean
): {
dependencies: string[],
contexts: Set<string>,
indexes: Set<string>,
snippet: string
indexes: Set<string>
} {
this.addSourcemapLocations(expression);
// this.addSourcemapLocations(expression);

const usedContexts: Set<string> = new Set();
const usedIndexes: Set<string> = new Set();

const { code, helpers } = this;
const { contexts, indexes } = block;

let scope = annotateWithScopes(expression); // TODO this already happens in findDependencies
let scope: Scope;
let lexicalDepth = 0;

const self = this;
Expand All @@ -231,7 +246,7 @@ export default class Generator {
});
} else if (isReference(node, parent)) {
const { name } = flattenReference(node);
if (scope.has(name)) return;
if (scope && scope.has(name)) return;

if (name === 'event' && isEventHandler) {
// noop
Expand Down Expand Up @@ -267,16 +282,7 @@ export default class Generator {
}
}

if (globalWhitelist.has(name)) {
code.prependRight(node.start, `('${name}' in state ? state.`);
code.appendLeft(
node.object ? node.object.end : node.end,
` : ${name})`
);
} else {
code.prependRight(node.start, `state.`);
}

code.prependRight(node.start, `state.`);
usedContexts.add('state');
}

Expand All @@ -290,73 +296,12 @@ export default class Generator {
},
});

const dependencies: Set<string> = new Set(expression._dependencies || []);

if (expression._dependencies) {
expression._dependencies.forEach((prop: string) => {
if (this.indirectDependencies.has(prop)) {
this.indirectDependencies.get(prop).forEach(dependency => {
dependencies.add(dependency);
});
}
});
}

return {
dependencies: Array.from(dependencies),
contexts: usedContexts,
indexes: usedIndexes,
snippet: `[✂${expression.start}-${expression.end}✂]`,
indexes: usedIndexes
};
}

findDependencies(
contextDependencies: Map<string, string[]>,
indexes: Map<string, string>,
expression: Node
) {
if (expression._dependencies) return expression._dependencies;

let scope = annotateWithScopes(expression);
const dependencies: string[] = [];

const generator = this; // can't use arrow functions, because of this.skip()

walk(expression, {
enter(node: Node, parent: Node) {
if (node._scope) {
scope = node._scope;
return;
}

if (isReference(node, parent)) {
const { name } = flattenReference(node);
if (scope.has(name) || generator.helpers.has(name)) return;

if (contextDependencies.has(name)) {
dependencies.push(...contextDependencies.get(name));
} else if (!indexes.has(name)) {
dependencies.push(name);
}

this.skip();
}
},

leave(node: Node) {
if (node._scope) scope = scope.parent;
},
});

dependencies.forEach(name => {
if (!globalWhitelist.has(name)) {
this.expectedProperties.add(name);
}
});

return (expression._dependencies = dependencies);
}

generate(result: string, options: CompileOptions, { banner = '', sharedPath, helpers, name, format }: GenerateOptions ) {
const pattern = /\[✂(\d+)-(\d+)$/;

Expand Down Expand Up @@ -454,17 +399,19 @@ export default class Generator {
};
}

parseJs(dom: boolean) {
const { code, source } = this;
walkJs(dom: boolean) {
const {
code,
source,
computations,
templateProperties,
imports
} = this;

const { js } = this.parsed;

const imports = this.imports;
const computations: Computation[] = [];
const templateProperties: Record<string, Node> = {};
const componentDefinition = new CodeBuilder();

let namespace = null;

if (js) {
this.addSourcemapLocations(js.content);

Expand Down Expand Up @@ -637,7 +584,7 @@ export default class Generator {

if (templateProperties.namespace) {
const ns = templateProperties.namespace.value.value;
namespace = namespaces[ns] || ns;
this.namespace = namespaces[ns] || ns;
}

if (templateProperties.onrender) templateProperties.oncreate = templateProperties.onrender; // remove after v2
Expand Down Expand Up @@ -693,9 +640,132 @@ export default class Generator {
this.javascript = a === b ? null : `[✂${a}-${b}✂]`;
}
}
}

walkTemplate() {
const {
code,
expectedProperties,
helpers
} = this;
const { html } = this.parsed;

const contextualise = (node: Node, contextDependencies: Map<string, string[]>, indexes: Set<string>) => {
this.addSourcemapLocations(node); // TODO this involves an additional walk — can we roll it in somewhere else?
let scope = annotateWithScopes(node);

const dependencies: Set<string> = new Set();

walk(node, {
enter(node: Node, parent: Node) {
code.addSourcemapLocation(node.start);
code.addSourcemapLocation(node.end);

if (node._scope) {
scope = node._scope;
return;
}

if (isReference(node, parent)) {
const { name } = flattenReference(node);
if (scope && scope.has(name) || helpers.has(name)) return;

if (contextDependencies.has(name)) {
contextDependencies.get(name).forEach(dependency => {
dependencies.add(dependency);
});
} else if (!indexes.has(name)) {
dependencies.add(name);
}

this.skip();
}
},

leave(node: Node, parent: Node) {
if (node._scope) scope = scope.parent;
}
});

dependencies.forEach(dependency => {
expectedProperties.add(dependency);
});

return {
snippet: `[✂${node.start}-${node.end}✂]`,
dependencies: Array.from(dependencies)
};
}

const contextStack = [];
const indexStack = [];
const dependenciesStack = [];

let contextDependencies = new Map();
const contextDependenciesStack: Map<string, string[]>[] = [contextDependencies];

let indexes = new Set();
const indexesStack: Set<string>[] = [indexes];

walk(html, {
enter(node: Node, parent: Node) {
if (node.type === 'EachBlock') {
node.metadata = contextualise(node.expression, contextDependencies, indexes);

contextDependencies = new Map(contextDependencies);
contextDependencies.set(node.context, node.metadata.dependencies);

if (node.destructuredContexts) {
for (let i = 0; i < node.destructuredContexts.length; i += 1) {
const name = node.destructuredContexts[i];
const value = `${node.context}[${i}]`;

contextDependencies.set(name, node.metadata.dependencies);
}
}

contextDependenciesStack.push(contextDependencies);

if (node.index) {
indexes = new Set(indexes);
indexes.add(node.index);
indexesStack.push(indexes);
}
}

if (node.type === 'IfBlock') {
node.metadata = contextualise(node.expression, contextDependencies, indexes);
}

if (node.type === 'MustacheTag' || node.type === 'RawMustacheTag' || node.type === 'AttributeShorthand') {
node.metadata = contextualise(node.expression, contextDependencies, indexes);
this.skip();
}

if (node.type === 'Binding') {
node.metadata = contextualise(node.value, contextDependencies, indexes);
this.skip();
}

if (node.type === 'EventHandler' && node.expression) {
node.expression.arguments.forEach((arg: Node) => {
arg.metadata = contextualise(arg, contextDependencies, indexes);
});
this.skip();
}
},

leave(node: Node, parent: Node) {
if (node.type === 'EachBlock') {
contextDependenciesStack.pop();
contextDependencies = contextDependenciesStack[contextDependenciesStack.length - 1];

this.computations = computations;
this.namespace = namespace;
this.templateProperties = templateProperties;
if (node.index) {
indexesStack.pop();
indexes = indexesStack[indexesStack.length - 1];
}
}
}
});
}
}
Loading

0 comments on commit 5190144

Please sign in to comment.