add a path hoisting mechanism that will hoist a node to it's highest compatible scope, a compatible scope is considered to be one where all references inside can be resolved to, also adds an optimisation.react.constantElements transformer that uses this to much success facebook/react#3226

This commit is contained in:
Sebastian McKenzie 2015-03-30 00:16:19 +11:00
parent 6a698f7ae4
commit 52c6fe2bc1
18 changed files with 263 additions and 2 deletions

View File

@ -18,6 +18,7 @@ export default {
"spec.blockScopedFunctions": require("./spec/block-scoped-functions"),
"optimisation.react.constantElements": require("./optimisation/react.constant-elements"),
reactCompat: require("./other/react-compat"),
react: require("./other/react"),

View File

@ -0,0 +1,42 @@
import * as react from "../../helpers/react";
export var metadata = {
optional: true
};
var immutabilityVisitor = {
enter(node, parent, scope, state) {
var stop = () => {
state.isImmutable = false;
this.stop();
};
if (this.isJSXClosingElement()) {
this.skip();
return;
}
if (this.isJSXIdentifier({ name: "ref" }) && this.parentPath.isJSXAttribute({ name: node })) {
return stop();
}
if (this.isJSXIdentifier() || this.isIdentifier() || this.isJSXMemberExpression()) {
return;
}
if (!this.isImmutable()) stop();
}
};
export function JSXElement(node, parent, scope, file) {
if (node._ignoreConstant) return;
var state = { isImmutable: true };
this.traverse(immutabilityVisitor, state);
this.skip();
if (state.isImmutable) {
this.hoist();
node._ignoreConstant = true;
}
}

View File

@ -0,0 +1,106 @@
import * as react from "../../transformation/helpers/react";
import * as t from "../../types";
var referenceVisitor = {
enter(node, parent, scope, state) {
if (this.isJSXIdentifier() && react.isCompatTag(node.name)) {
return;
}
if (this.isJSXIdentifier() || this.isIdentifier()) {
// direct references that we need to track to hoist this to the highest scope we can
if (this.isReferenced()) {
var bindingInfo = scope.getBinding(node.name);
if (bindingInfo && bindingInfo.constant) {
state.bindings[node.name] = bindingInfo;
} else {
scope.dump();
state.foundIncompatible = true;
this.stop();
}
}
}
}
};
export default class PathHoister {
constructor(path) {
this.foundIncompatible = false;
this.bindings = {};
this.scopes = [];
this.path = path;
}
isCompatibleScope(scope) {
for (var key in this.bindings) {
var binding = this.bindings[key];
if (!scope.bindingIdentifierEquals(key, binding.identifier)) {
return false;
}
}
return true;
}
getCompatibleScopes() {
var checkScope = this.path.scope;
do {
if (this.isCompatibleScope(checkScope)) {
this.scopes.push(checkScope);
} else {
break;
}
} while(checkScope = checkScope.parent);
}
getAttachmentPath() {
var scopes = this.scopes;
var scope = scopes.pop();
if (scope.path.isFunction()) {
if (this.hasNonParamBindings()) {
// can't be attached to this scope
return this.getNextScopeStatementParent();
} else {
// needs to be attached to the body
return scope.path.get("body").get("body")[0];
}
} else if (scope.path.isProgram()) {
return this.getNextScopeStatementParent();
}
}
getNextScopeStatementParent() {
var scope = this.scopes.pop();
if (scope) return scope.path.getStatementParent();
}
hasNonParamBindings() {
for (var name in this.bindings) {
var binding = this.bindings[name];
if (binding.kind !== "param") return true;
}
return false;
}
run() {
this.path.traverse(referenceVisitor, this);
if (this.foundIncompatible) return;
this.getCompatibleScopes();
var path = this.getAttachmentPath();
if (!path) return;
var uid = path.scope.generateUidIdentifier("ref");
path.insertBefore([
t.variableDeclaration("var", [
t.variableDeclarator(uid, this.path.node)
])
]);
this.path.replaceWith(uid);
}
}

View File

@ -635,6 +635,14 @@ export default class TraversalPath {
traverse(opts, state) {
traverse(this.node, opts, this.scope, state, this);
/**
* Description
*/
hoist() {
var hoister = new PathHoister(this);
return hoister.run();
}
/**

View File

@ -156,11 +156,11 @@ export function isSpecifierDefault(specifier: Object): boolean {
export function isScope(node: Object, parent: Object): boolean {
if (t.isBlockStatement(node)) {
if (t.isLoop(parent.block, { body: node })) {
if (t.isLoop(parent, { body: node })) {
return false;
}
if (t.isFunction(parent.block, { body: node })) {
if (t.isFunction(parent, { body: node })) {
return false;
}
}

View File

@ -0,0 +1,5 @@
var Foo = require("Foo");
function render() {
return <Foo />;
}

View File

@ -0,0 +1,9 @@
"use strict";
var Foo = require("Foo");
var _ref = <Foo />;
function render() {
return _ref;
}

View File

@ -0,0 +1,13 @@
function render(text) {
return function () {
return <foo>{text}</foo>;
};
}
var Foo2 = require("Foo");
function createComponent(text) {
return function render() {
return <Foo2>{text}</Foo2>;
};
}

View File

@ -0,0 +1,19 @@
"use strict";
function render(text) {
var _ref = <foo>{text}</foo>;
return function () {
return _ref;
};
}
var Foo2 = require("Foo");
function createComponent(text) {
var _ref = <Foo2>{text}</Foo2>;
return function render() {
return _ref;
};
}

View File

@ -0,0 +1,7 @@
function render() {
return <foo />;
}
function render() {
return <div className="foo"><input type="checkbox" checked={true} /></div>;
}

View File

@ -0,0 +1,13 @@
"use strict";
var _ref = <foo />;
function render() {
return _ref;
}
var _ref = <div className="foo"><input type="checkbox" checked={true} /></div>;
function render() {
return _ref;
}

View File

@ -0,0 +1,6 @@
function render() {
var text = getText();
return function () {
return <foo>{text}</foo>;
};
}

View File

@ -0,0 +1,11 @@
"use strict";
function render() {
var text = getText();
var _ref = <foo>{text}</foo>;
return function () {
return _ref;
};
}

View File

@ -0,0 +1,5 @@
{
"noCheckAst": true,
"optional": ["optimisation.react.constantElements"],
"blacklist": ["react"]
}

View File

@ -0,0 +1,3 @@
function render() {
return <foo ref="foobar" />;
}

View File

@ -0,0 +1,5 @@
"use strict";
function render() {
return <foo ref="foobar" />;
}

View File

@ -0,0 +1,3 @@
function render() {
return <foo {...foobar} />;
}

View File

@ -0,0 +1,5 @@
"use strict";
function render() {
return <foo {...foobar} />;
}