UNPKG

@builder.io/mitosis

Version:

Write components once, run everywhere. Compiles to Vue, React, Solid, and Liquid. Import code from Figma and Builder.io

195 lines (194 loc) 9.44 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getCodeProcessorPlugins = void 0; const babel_transform_1 = require("../../../../helpers/babel-transform"); const class_components_1 = require("../../../../helpers/class-components"); const event_handlers_1 = require("../../../../helpers/event-handlers"); const process_code_1 = require("../../../../helpers/plugins/process-code"); const mitosis_node_1 = require("../../../../types/mitosis-node"); const types_1 = require("@babel/types"); const isStateOrPropsExpression = (path) => { return ((0, types_1.isMemberExpression)(path.node) && (0, types_1.isIdentifier)(path.node.object) && (0, types_1.isIdentifier)(path.node.property) && (path.node.object.name === 'props' || path.node.object.name === 'state')); }; const handleAssignmentExpression = (path) => { if ((0, types_1.isMemberExpression)(path.node.left) && (0, types_1.isIdentifier)(path.node.left.object) && (0, types_1.isIdentifier)(path.node.left.property) && path.node.left.object.name === 'state') { const root = (0, types_1.memberExpression)(path.node.left, (0, types_1.identifier)('set')); root.extra = { ...root.extra, updateExpression: true }; const call = (0, types_1.callExpression)(root, [path.node.right]); path.replaceWith(call); } }; const handleMemberExpression = (path) => { var _a, _b, _c, _d; if (((_a = path.node.extra) === null || _a === void 0 ? void 0 : _a.makeCallExpressionDone) || ((_c = (_b = path.parentPath) === null || _b === void 0 ? void 0 : _b.node.extra) === null || _c === void 0 ? void 0 : _c.updateExpression)) { // Don't add a function if we've done it already return; } if ((0, types_1.isCallExpression)(path.parent) && (0, types_1.isMemberExpression)(path.parent.callee) && (0, types_1.isIdentifier)(path.parent.callee.object) && (path.parent.callee.object.name === 'props' || path.parent.callee.object.name === 'state') && !((_d = path.parent.callee.extra) === null || _d === void 0 ? void 0 : _d.updateExpression)) { // Don't add a function if it is already return; } if (isStateOrPropsExpression(path)) { path.node.extra = { ...path.node.extra, makeCallExpressionDone: true }; path.replaceWith((0, types_1.callExpression)(path.node, [])); } }; const handleHookAndStateOnEvents = (path, isHookDepArray) => { if ((0, types_1.isIdentifier)(path.node.property) && (0, event_handlers_1.checkIsEvent)(path.node.property.name)) { if ((0, types_1.isIfStatement)(path.parent)) { // We don't do anything if the event is in an IfStatement path.node.extra = { ...path.node.extra, updateExpression: true }; return true; } else if ((0, types_1.isCallExpression)(path.parent) && (0, types_1.isIdentifier)(path.node.object) && (0, types_1.isMemberExpression)(path.parent.callee)) { // We add "emit" to events const root = (0, types_1.memberExpression)(path.node, (0, types_1.identifier)('emit')); root.extra = { ...root.extra, updateExpression: true }; path.replaceWith(root); } else if (isHookDepArray && (0, types_1.isIdentifier)(path.node.object)) { const iden = (0, types_1.identifier)(`// "${path.node.object.name}.${path.node.property.name}" is an event skip it.`); path.replaceWith(iden); return true; } } return false; }; const transformHooksAndState = (code, isHookDepArray) => { return (0, babel_transform_1.babelTransformExpression)(code, { AssignmentExpression(path) { handleAssignmentExpression(path); }, UpdateExpression(path) { /* * If we have a function like this: * `state._counter++;` * * We need to convert it and use the "update" example from https://angular.dev/guide/signals#writable-signals: * `state._counter.update(_counter=>_counter++)` * */ if ((0, types_1.isMemberExpression)(path.node.argument) && (0, types_1.isIdentifier)(path.node.argument.object) && path.node.argument.object.name === 'state' && (0, types_1.isIdentifier)(path.node.argument.property)) { const root = (0, types_1.memberExpression)(path.node.argument, (0, types_1.identifier)('update')); root.extra = { ...root.extra, updateExpression: true }; const argument = path.node.argument.property; const block = (0, types_1.blockStatement)([ (0, types_1.expressionStatement)((0, types_1.updateExpression)(path.node.operator, argument)), (0, types_1.returnStatement)(argument), ]); const arrowFunction = (0, types_1.arrowFunctionExpression)([argument], block); const call = (0, types_1.callExpression)(root, [arrowFunction]); path.replaceWith(call); } }, MemberExpression(path) { const skip = handleHookAndStateOnEvents(path, isHookDepArray); if (skip) { return; } handleMemberExpression(path); }, }); }; const addToImportCall = (json, importName) => { const isImportCall = json.imports.find((imp) => imp.imports[importName]); const isExportCall = json.exports ? !!json.exports[importName] : false; if (isImportCall || isExportCall) { json.compileContext.angular.extra.importCalls.push(importName); } }; const transformBindings = (json, code) => { return (0, babel_transform_1.babelTransformExpression)(code, { BlockStatement() { console.error(` Component ${json.name} has a BlockStatement inside JSX'. This will cause an error in Angular. Please create and call a new function instead with this code: ${code}`); }, CallExpression(path) { // If we call a function from an import we need to add it to the Component as well if ((0, types_1.isIdentifier)(path.node.callee)) { addToImportCall(json, path.node.callee.name); } }, Identifier(path) { // If we use a constant from an import we need to add it to the Component as well if ((0, types_1.isIdentifier)(path.node)) { addToImportCall(json, path.node.name); } }, StringLiteral(path) { var _a; // We cannot use " for string literal in template if ((_a = path.node.extra) === null || _a === void 0 ? void 0 : _a.raw) { path.node.extra.raw = path.node.extra.raw.replaceAll('"', "'"); } }, AssignmentExpression(path) { handleAssignmentExpression(path); }, MemberExpression(path) { handleMemberExpression(path); }, }); }; const getCodeProcessorPlugins = (json, options, processBindingOptions) => { return [ ...(options.plugins || []), (0, process_code_1.CODE_PROCESSOR_PLUGIN)((codeType) => { switch (codeType) { case 'bindings': return (code, key, context) => { var _a, _b, _c; let replaceWith = ''; if (key === 'key') { /** * If we have a key attribute we need to check if it is inside a @for loop. * We will create a new function for the key attribute. * Therefore, we need to add "this" to state and props. */ const isForParent = ((_c = (_b = (_a = context === null || context === void 0 ? void 0 : context.parent) === null || _a === void 0 ? void 0 : _a.parent) === null || _b === void 0 ? void 0 : _b.node) === null || _c === void 0 ? void 0 : _c.name) === mitosis_node_1.ForNodeName; if (isForParent) { replaceWith = 'this.'; } } return (0, class_components_1.processClassComponentBinding)(json, transformBindings(json, code), { ...processBindingOptions, replaceWith, }); }; case 'hooks-deps-array': case 'hooks': case 'state': return (code) => { return (0, class_components_1.processClassComponentBinding)(json, transformHooksAndState(code, codeType === 'hooks-deps-array'), processBindingOptions); }; case 'properties': case 'hooks-deps': case 'context-set': case 'dynamic-jsx-elements': case 'types': return (code) => { return (0, class_components_1.processClassComponentBinding)(json, code, processBindingOptions); }; } }), ]; }; exports.getCodeProcessorPlugins = getCodeProcessorPlugins;