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:
parent
6a698f7ae4
commit
52c6fe2bc1
@ -18,6 +18,7 @@ export default {
|
|||||||
|
|
||||||
"spec.blockScopedFunctions": require("./spec/block-scoped-functions"),
|
"spec.blockScopedFunctions": require("./spec/block-scoped-functions"),
|
||||||
|
|
||||||
|
"optimisation.react.constantElements": require("./optimisation/react.constant-elements"),
|
||||||
reactCompat: require("./other/react-compat"),
|
reactCompat: require("./other/react-compat"),
|
||||||
react: require("./other/react"),
|
react: require("./other/react"),
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
106
src/babel/traversal/path/hoister.js
Normal file
106
src/babel/traversal/path/hoister.js
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -635,6 +635,14 @@ export default class TraversalPath {
|
|||||||
|
|
||||||
traverse(opts, state) {
|
traverse(opts, state) {
|
||||||
traverse(this.node, opts, this.scope, state, this);
|
traverse(this.node, opts, this.scope, state, this);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Description
|
||||||
|
*/
|
||||||
|
|
||||||
|
hoist() {
|
||||||
|
var hoister = new PathHoister(this);
|
||||||
|
return hoister.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -156,11 +156,11 @@ export function isSpecifierDefault(specifier: Object): boolean {
|
|||||||
|
|
||||||
export function isScope(node: Object, parent: Object): boolean {
|
export function isScope(node: Object, parent: Object): boolean {
|
||||||
if (t.isBlockStatement(node)) {
|
if (t.isBlockStatement(node)) {
|
||||||
if (t.isLoop(parent.block, { body: node })) {
|
if (t.isLoop(parent, { body: node })) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (t.isFunction(parent.block, { body: node })) {
|
if (t.isFunction(parent, { body: node })) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,5 @@
|
|||||||
|
var Foo = require("Foo");
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
return <Foo />;
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
var Foo = require("Foo");
|
||||||
|
|
||||||
|
var _ref = <Foo />;
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
return _ref;
|
||||||
|
}
|
||||||
@ -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>;
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
function render() {
|
||||||
|
return <foo />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
return <div className="foo"><input type="checkbox" checked={true} /></div>;
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
function render() {
|
||||||
|
var text = getText();
|
||||||
|
return function () {
|
||||||
|
return <foo>{text}</foo>;
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
var text = getText();
|
||||||
|
|
||||||
|
var _ref = <foo>{text}</foo>;
|
||||||
|
|
||||||
|
return function () {
|
||||||
|
return _ref;
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"noCheckAst": true,
|
||||||
|
"optional": ["optimisation.react.constantElements"],
|
||||||
|
"blacklist": ["react"]
|
||||||
|
}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
function render() {
|
||||||
|
return <foo ref="foobar" />;
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
return <foo ref="foobar" />;
|
||||||
|
}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
function render() {
|
||||||
|
return <foo {...foobar} />;
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
return <foo {...foobar} />;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user