fluorine-loader
Version:
A webpack loader to compile fluorine specializations and catalyst handlers.
188 lines • 7.65 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const md5 = require("md5-jkmyers");
const magic_string_1 = __importDefault(require("magic-string"));
const acorn = __importStar(require("acorn"));
const walk = __importStar(require("acorn-walk"));
const utils_1 = require("efreet/utils");
let now = Date.now; // Curse you, webpack...
function transform(input, opts = {}) {
return __awaiter(this, void 0, void 0, function* () {
if (opts.now)
now = opts.now;
let time_start = now();
let root = acorn.parse(input);
let state = {
root,
time: {
start: time_start,
parse: now(),
visit: 0,
md5: 0,
transform_element: 0,
transform_catalyst: 0
},
code: new magic_string_1.default(input)
};
walk.ancestor(root, visitors, undefined, state);
let { time, code } = state;
time.walk = now();
return {
code: code.toString(),
sourcemap: opts.sourcemap
? code.generateMap({
includeContent: true
})
: undefined,
performance: time
};
});
}
exports.transform = transform;
//----------------------------------------------------------------------
// Transformations
//----------------------------------------------------------------------
let visitors = {
CallExpression(node, state, ancestors) {
let { time, code } = state;
let time_visit_start = now();
if (is_react_create_element_call(node))
transform_react_create_element(node, state, ancestors);
if (is_catalyst_handler(node))
transform_catalyst_handler(node, state, ancestors);
time.visit += now() - time_visit_start;
}
};
//----------------------------------------------------------------------
// Transform React create element
//----------------------------------------------------------------------
function is_react_create_element_call(node) {
let callee = node.callee;
if (callee.type === "Identifier")
return false;
if (callee.property.name !== "createElement")
return false;
if (callee.object.type !== "Identifier")
return false;
if (callee.object.name !== "React")
return false;
return true;
}
function transform_react_create_element(node, state, ancestors) {
let { time, code } = state;
let time_transform_element_start = now();
let [tagname_node, props_node, ...children_nodes] = node.arguments;
let props_str = "";
let props = [];
if (props_node.type === "ObjectExpression") {
props_str = code.slice(props_node.start + 1, props_node.end - 1);
for (let { key } of props_node.properties) {
// @NOTE: We could make this even faster by adding an init step and separating static properties out.
if (key.type === "Identifier")
props.push(key.name);
else if (key.type === "Literal")
props.push("" + key.value);
else
throw new Error(`Unknown key type: '${key.type}'`);
}
}
let children = "";
if (children_nodes.length) {
children = code.slice(children_nodes[0].start, children_nodes[children_nodes.length - 1].end);
}
let time_md5_start = now();
let kind = props.length ? md5(props.join("__")) : "static";
time.md5 += now() - time_md5_start;
if (tagname_node.type === "Literal") {
let tagname = tagname_node.value;
let elem_str = `kind: "${kind}"`;
if (tagname)
elem_str += `, tagname: "${tagname}"`;
if (props_str)
elem_str += `, ${props_str}`;
if (children)
elem_str += `, children: [\n${children}\n]`;
code.overwrite(node.start, node.end, `{${elem_str}}`);
// specializations[kind] = props;
if (kind !== "static" && kind !== "text" && kind !== "default") {
code.appendLeft(ancestors[1].start, utils_1.inline_chunk `
React.renderer.specialize("${kind}", ${JSON.stringify(props)}); // Auto-generated Fluorine specialization.
` + "\n");
}
}
else {
let elem_str = `{${props_str}`;
if (children)
elem_str += `${elem_str.length > 1 ? ", " : ""}children: [\n${children}\n]`;
code.overwrite(node.start, node.end, `${tagname_node.name}(${elem_str}})`);
}
time.transform_element += now() - time_transform_element_start;
}
//----------------------------------------------------------------------
// Transform Catalyst handler
//----------------------------------------------------------------------
function is_catalyst_handler(node) {
return (node.callee.type === "MemberExpression" &&
node.callee.property.type === "Identifier" &&
node.callee.property.name === "handle" &&
node.callee.object.type === "Identifier" &&
(node.callee.object.name === "catalyst_1" ||
node.callee.object.name === "fluorine_1"));
}
let catalyst_handler_methods = {
stop: true,
stop_if: true,
prevent: true,
prevent_if: true,
when: true,
then: true
};
function transform_catalyst_handler(node, state, ancestors) {
let { time, code } = state;
let time_transform_catalyst_start = now();
let ix = ancestors.length - 2;
while (ix > 0) {
let parent = ancestors[ix];
if (!parent)
break;
if (parent.type !== "MemberExpression")
break;
if (!catalyst_handler_methods[parent.property.name])
break;
if (!ancestors[ix - 1] || ancestors[ix - 1].type !== "CallExpression")
break;
ix -= 2;
}
ix += 1;
let outer = ancestors[ix];
let handler_code = code.slice(outer.start, outer.end);
let time_md5_start = now();
let key = md5(handler_code);
time.md5 += now() - time_md5_start;
// @FIXME: Gotta check if these guys are function references (relocatable) or not before doing this :(
// Swapping to a memo-ized version with the key baked in should be a universally safe fallback.
code.appendLeft(ancestors[1].start, utils_1.inline_chunk `
React.handlers["${key}"] = ${handler_code}.compile(); // Auto-generated Catalyst compiled handler.
` + "\n");
code.overwrite(outer.start, outer.end, `React.handlers["${key}"]`);
time.transform_catalyst += now() - time_transform_catalyst_start;
}
//# sourceMappingURL=transformer.js.map