UNPKG

@builder.io/mitosis

Version:

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

228 lines (227 loc) 10.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.parseJsx = void 0; const hooks_1 = require("../../constants/hooks"); const create_mitosis_component_1 = require("../../helpers/create-mitosis-component"); const filter_empty_text_nodes_1 = require("../../helpers/filter-empty-text-nodes"); const json_1 = require("../../helpers/json"); const replace_new_lines_in_strings_1 = require("../../helpers/replace-new-lines-in-strings"); const signals_1 = require("../../helpers/signals"); const traverse_nodes_1 = require("../../helpers/traverse-nodes"); const babel = __importStar(require("@babel/core")); const generator_1 = __importDefault(require("@babel/generator")); const preset_typescript_1 = __importDefault(require("@babel/preset-typescript")); const function_1 = require("fp-ts/lib/function"); const ast_1 = require("./ast"); const component_types_1 = require("./component-types"); const context_1 = require("./context"); const element_parser_1 = require("./element-parser"); const exports_1 = require("./exports"); const function_parser_1 = require("./function-parser"); const helpers_1 = require("./helpers"); const hooks_2 = require("./hooks"); const use_target_1 = require("./hooks/use-target"); const imports_1 = require("./imports"); const props_1 = require("./props"); const props_types_1 = require("./props-types"); const signals_2 = require("./signals"); const state_1 = require("./state"); const { types } = babel; const typescriptBabelPreset = [preset_typescript_1.default, { isTSX: true, allExtensions: true }]; const beforeParse = (path) => { path.traverse({ FunctionDeclaration(path) { (0, props_1.undoPropsDestructure)(path); }, }); }; /** * This function takes the raw string from a Mitosis component, and converts it into a JSON that can be processed by * each generator function. * * @param jsx string representation of the Mitosis component * @returns A JSON representation of the Mitosis component */ function parseJsx(jsx, _options = {}) { let subComponentFunctions = []; const options = { typescript: false, ..._options, }; const stateToScope = []; const jsxToUse = (0, helpers_1.babelStripTypes)(jsx, !options.typescript); const output = (0, helpers_1.babelDefaultTransform)(jsxToUse, { JSXExpressionContainer(path, context) { if (types.isJSXEmptyExpression(path.node.expression)) { path.remove(); } }, Program(path, context) { if (context.builder) { return; } beforeParse(path); context.builder = { component: (0, create_mitosis_component_1.createMitosisComponent)(), }; const keepStatements = path.node.body.filter((statement) => (0, helpers_1.isImportOrDefaultExport)(statement) || (0, component_types_1.isTypeOrInterface)(statement)); context.builder.component.exports = (0, exports_1.generateExports)(path); subComponentFunctions = path.node.body .filter((node) => !types.isExportDefaultDeclaration(node) && types.isFunctionDeclaration(node)) .map((node) => `export default ${(0, generator_1.default)(node).code}`); const preComponentCode = (0, function_1.pipe)(path, (0, hooks_2.collectModuleScopeHooks)(context, options), types.program, generator_1.default, (generatorResult) => generatorResult.code); // TODO: support multiple? e.g. for others to add imports? context.builder.component.hooks.preComponent = { code: preComponentCode }; path.replaceWith(types.program(keepStatements)); }, FunctionDeclaration(path, context) { const { node } = path; if (types.isIdentifier(node.id)) { const name = node.id.name; if (name[0].toUpperCase() === name[0]) { path.traverse({ /** * Plugin to find all `useTarget()` assignment calls inside of the component function body * and replace them with a magic string. */ CallExpression(path) { if (!types.isCallExpression(path.node)) return; if (!types.isIdentifier(path.node.callee)) return; if (path.node.callee.name !== hooks_1.HOOKS.TARGET) return; const targetBlock = (0, use_target_1.getUseTargetStatements)(path); if (!targetBlock) return; const blockId = (0, use_target_1.getTargetId)(context.builder.component); // replace the useTarget() call with a magic string path.replaceWith(types.stringLiteral((0, use_target_1.getMagicString)(blockId))); // store the target block in the component context.builder.component.targetBlocks = { ...context.builder.component.targetBlocks, [blockId]: targetBlock, }; }, }); path.replaceWith((0, ast_1.jsonToAst)((0, function_parser_1.componentFunctionToJson)(node, context, stateToScope))); } } }, ImportDeclaration(path, context) { (0, imports_1.handleImportDeclaration)({ options, path, context }); }, ExportDefaultDeclaration(path) { path.replaceWith(path.node.declaration); }, JSXElement(path) { const { node } = path; path.replaceWith((0, ast_1.jsonToAst)((0, element_parser_1.jsxElementToJson)(node))); }, ExportNamedDeclaration(path, context) { const { node } = path; if (babel.types.isTSInterfaceDeclaration(node.declaration) || babel.types.isTSTypeAliasDeclaration(node.declaration)) { (0, component_types_1.collectTypes)(path, context); } }, TSTypeAliasDeclaration(path, context) { (0, component_types_1.collectTypes)(path, context); }, TSInterfaceDeclaration(path, context) { (0, component_types_1.collectTypes)(path, context); }, }); if (!output || !output.code) { throw new Error('Could not parse JSX'); } const stringifiedMitosisComponent = (0, replace_new_lines_in_strings_1.stripNewlinesInStrings)(output.code .trim() // Occasional issues where comments get kicked to the top. Full fix should strip these sooner .replace(/^\/\*[\s\S]*?\*\/\s*/, '') // Weird bug with adding a newline in a normal at end of a normal string that can't have one // If not one-off find full solve and cause .replace(/\n"/g, '"') .replace(/^\({/, '{') .replace(/}\);$/, '}')); const mitosisComponent = (0, json_1.tryParseJson)(stringifiedMitosisComponent); (0, state_1.mapStateIdentifiers)(mitosisComponent, stateToScope); (0, context_1.extractContextComponents)(mitosisComponent); mitosisComponent.subComponents = subComponentFunctions.map((item) => parseJsx(item, options)); const signalTypeImportName = (0, signals_1.getSignalImportName)(jsxToUse); if (signalTypeImportName) { mitosisComponent.signals = { signalTypeImportName }; } if (options.tsProject && options.filePath) { // identify optional props. const optionalProps = (0, props_types_1.findOptionalProps)({ project: options.tsProject.project, filePath: options.filePath, }); optionalProps.forEach((prop) => { var _a; mitosisComponent.props = { ...mitosisComponent.props, [prop]: { ...(_a = mitosisComponent.props) === null || _a === void 0 ? void 0 : _a[prop], optional: true, }, }; }); const reactiveValues = (0, signals_2.findSignals)({ filePath: options.filePath, project: options.tsProject.project, }); reactiveValues.props.forEach((prop) => { var _a; mitosisComponent.props = { ...mitosisComponent.props, [prop]: { ...(_a = mitosisComponent.props) === null || _a === void 0 ? void 0 : _a[prop], propertyType: 'reactive', }, }; }); reactiveValues.state.forEach((state) => { if (!mitosisComponent.state[state]) return; mitosisComponent.state[state].propertyType = 'reactive'; }); reactiveValues.context.forEach((context) => { if (!mitosisComponent.context.get[context]) return; mitosisComponent.context.get[context].type = 'reactive'; }); } (0, traverse_nodes_1.traverseNodes)(mitosisComponent, (node) => { node.children = node.children.filter(filter_empty_text_nodes_1.filterEmptyTextNodes); }); return mitosisComponent; } exports.parseJsx = parseJsx;