@builder.io/mitosis
Version:
Write components once, run everywhere. Compiles to Vue, React, Solid, and Liquid. Import code from Figma and Builder.io
188 lines (187 loc) • 8.96 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.replaceNodes = exports.replacePropsIdentifier = exports.replaceStateIdentifier = exports.replaceIdentifiers = void 0;
const core_1 = require("@babel/core");
const generator_1 = __importDefault(require("@babel/generator"));
const function_1 = require("fp-ts/lib/function");
const babel_transform_1 = require("./babel-transform");
/**
* Given a `to` function given by the user, figure out the best argument to provide to the `to` function.
* This function makes a best guess based on the AST structure it's dealing with.
*/
const getToParam = (path) => {
if (core_1.types.isMemberExpression(path.node) || core_1.types.isOptionalMemberExpression(path.node)) {
if (core_1.types.isIdentifier(path.node.property)) {
// if simple member expression e.g. `props.foo`, returns `foo`
const propertyName = path.node.property.name;
return propertyName;
}
else {
// if nested member expression e.g. `props.foo.bar.baz`, returns `foo.bar.baz`
const x = (0, generator_1.default)(path.node.property).code;
return x;
}
}
else {
// if naked identifier e.g. `foo`, returns `foo`
const nodeName = path.node.name;
return nodeName;
}
};
const _replaceIdentifiers = (path, { from, to }) => {
var _a, _b;
const memberExpressionObject = core_1.types.isIdentifier(path.node) ? path.node : path.node.object;
const normalizedFrom = Array.isArray(from) ? from : [from];
if (!core_1.types.isIdentifier(memberExpressionObject) ||
((_b = (_a = path.node) === null || _a === void 0 ? void 0 : _a._builder_meta) === null || _b === void 0 ? void 0 : _b.newlyGenerated)) {
return;
}
const matchesFrom = normalizedFrom.includes(memberExpressionObject.name);
if (matchesFrom) {
if (to) {
// `props.foo` to `state`, e.g. `state.foo`
if (typeof to === 'string') {
const cleanedIdentifier = (0, function_1.pipe)(
// Remove trailing `.` if it exists in the user-provided string, as the dot is generated
// by babel from the AST
to.endsWith('.') ? to.substring(0, to.length - 1) : to, core_1.types.identifier);
if (core_1.types.isIdentifier(path.node)) {
path.replaceWith(cleanedIdentifier);
}
else {
path.replaceWith(core_1.types.memberExpression(cleanedIdentifier, path.node.property));
}
// `props.foo` to (name) => `state.${name}.bar`, e.g. `state.foo.bar`
}
else {
try {
const newMemberExpression = (0, function_1.pipe)(getToParam(path), (x) => to(x, memberExpressionObject.name), (expression) => {
const [head, ...tail] = expression.split('.');
return [head, tail.join('.')];
}, ([obj, prop]) => {
const objIdentifier = core_1.types.identifier(obj);
if (prop === '') {
return objIdentifier;
}
else {
return core_1.types.memberExpression(objIdentifier, core_1.types.identifier(prop));
}
});
/**
* If both `path` and `newMemberExpression` are equal nodes, do nothing.
* This is to prevent infinite loops when the user-provided `to` function returns the same identifier.
*
* The infinite loop probably happens because we end up traversing the new `Identifier` node again?
*/
if ((0, generator_1.default)(path.node).code === (0, generator_1.default)(newMemberExpression).code) {
return;
}
newMemberExpression._builder_meta = { newlyGenerated: true };
path.replaceWith(newMemberExpression);
}
catch (err) {
console.debug('Could not replace node.');
// throw err;
}
}
}
else {
if (!core_1.types.isIdentifier(path.node)) {
// if we're looking at a member expression, e.g. `props.foo` and no `to` was provided, then we want to strip out
// the identifier and end up with `foo`. So we replace the member expression with just its `property` value.
path.replaceWith(path.node.property);
}
}
}
};
/**
* @deprecated Use `replaceNodes` instead.
*/
const replaceIdentifiers = ({ code, from, to }) => {
try {
return (0, function_1.pipe)((0, babel_transform_1.babelTransformExpression)(code, {
MemberExpression(path) {
_replaceIdentifiers(path, { from, to });
},
OptionalMemberExpression(path) {
_replaceIdentifiers(path, { from, to });
},
Identifier(path) {
// we only want to ignore certain identifiers:
if (
// (optional) member expressions are already handled in other visitors
!core_1.types.isMemberExpression(path.parent) &&
!core_1.types.isOptionalMemberExpression(path.parent) &&
// function declaration identifiers shouldn't be transformed
!core_1.types.isFunctionDeclaration(path.parent) &&
// variable declaration identifiers shouldn't be transformed
// !(types.isVariableDeclarator(path.parent) && path.parent.id === path.node)
// object -> { detail: { state: 'something' } } shouldn't be transformed to { detail: { this: 'something' } }
!core_1.types.isObjectProperty(path.parent)) {
_replaceIdentifiers(path, { from, to });
}
},
}),
// merely running `babel.transform` will add spaces around the code, even if we don't end up replacing anything.
// we have some other code downstream that cannot have untrimmed spaces, so we need to trim the output.
(code) => code.trim());
}
catch (err) {
throw err;
}
};
exports.replaceIdentifiers = replaceIdentifiers;
const replaceStateIdentifier = (to) => (code) => (0, exports.replaceIdentifiers)({ code, from: 'state', to });
exports.replaceStateIdentifier = replaceStateIdentifier;
const replacePropsIdentifier = (to) => (code) => (0, exports.replaceIdentifiers)({ code, from: 'props', to });
exports.replacePropsIdentifier = replacePropsIdentifier;
const isNewlyGenerated = (node) => { var _a; return (_a = node === null || node === void 0 ? void 0 : node._builder_meta) === null || _a === void 0 ? void 0 : _a.newlyGenerated; };
/**
* Replaces all instances of a Babel AST Node with a new Node within a code string.
* Uses `generate()` to convert the Node to a string and compare them.
*/
const replaceNodes = ({ code, nodeMaps }) => {
const searchAndReplace = (path) => {
if (isNewlyGenerated(path.node) || isNewlyGenerated(path.parent))
return;
for (const { from, to, condition } of nodeMaps) {
if (isNewlyGenerated(path.node) || isNewlyGenerated(path.parent))
return;
// if (path.node.type !== from.type) return;
const matchesCondition = condition ? condition(path) : true;
if ((0, generator_1.default)(path.node).code === (0, generator_1.default)(from).code && matchesCondition) {
const x = core_1.types.cloneNode(to);
x._builder_meta = { newlyGenerated: true };
try {
path.replaceWith(x);
}
catch (err) {
console.log('error replacing', {
code,
orig: (0, generator_1.default)(path.node).code,
to: (0, generator_1.default)(x).code,
});
// throw err;
}
}
}
};
return (0, babel_transform_1.babelTransformExpression)(code, {
ThisExpression(path) {
searchAndReplace(path);
},
MemberExpression(path) {
searchAndReplace(path);
},
Identifier(path) {
searchAndReplace(path);
},
OptionalMemberExpression(path) {
searchAndReplace(path);
},
});
};
exports.replaceNodes = replaceNodes;
;