UNPKG

@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
"use strict"; 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;