@builder.io/mitosis
Version:
Write components once, run everywhere. Compiles to Vue, React, Solid, and Liquid. Import code from Figma and Builder.io
361 lines (360 loc) • 18.7 kB
JavaScript
"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.componentFunctionToJson = void 0;
const babel = __importStar(require("@babel/core"));
const generator_1 = __importDefault(require("@babel/generator"));
const types_1 = require("@babel/types");
const hooks_1 = require("../../constants/hooks");
const create_mitosis_component_1 = require("../../helpers/create-mitosis-component");
const get_bindings_1 = require("../../helpers/get-bindings");
const trace_reference_to_module_path_1 = require("../../helpers/trace-reference-to-module-path");
const component_types_1 = require("./component-types");
const element_parser_1 = require("./element-parser");
const helpers_1 = require("./helpers");
const hooks_2 = require("./hooks");
const helpers_2 = require("./hooks/helpers");
const state_1 = require("./state");
const { types } = babel;
/**
* Parses function declarations within the Mitosis copmonent's body to JSON
*/
const componentFunctionToJson = (node, context, stateToScope) => {
var _a;
const hooks = {
onMount: [],
onEvent: [],
};
const state = {};
const accessedContext = {};
const setContext = {};
const refs = {};
for (const item of node.body.body) {
if (types.isExpressionStatement(item)) {
const expression = item.expression;
if (types.isCallExpression(expression) && types.isIdentifier(expression.callee)) {
switch (expression.callee.name) {
case hooks_1.HOOKS.SET_CONTEXT: {
const keyNode = expression.arguments[0];
const valueNode = expression.arguments[1];
if (types.isIdentifier(keyNode)) {
const key = keyNode.name;
const keyPath = (0, trace_reference_to_module_path_1.traceReferenceToModulePath)(context.builder.component.imports, key);
if (valueNode) {
if (types.isObjectExpression(valueNode)) {
const value = (0, state_1.parseStateObjectToMitosisState)(valueNode);
setContext[keyPath] = {
name: keyNode.name,
value,
};
}
else {
const ref = (0, generator_1.default)(valueNode).code;
setContext[keyPath] = {
name: keyNode.name,
ref,
};
}
}
}
else if (types.isStringLiteral(keyNode)) {
if (types.isExpression(valueNode)) {
setContext[keyNode.value] = {
name: `"${keyNode.value}"`,
ref: (0, generator_1.default)(valueNode).code,
};
}
}
break;
}
case hooks_1.HOOKS.MOUNT: {
const firstArg = expression.arguments[0];
const hookOptions = expression.arguments[1];
if (types.isFunctionExpression(firstArg) || types.isArrowFunctionExpression(firstArg)) {
const code = (0, helpers_2.processHookCode)(firstArg);
let onSSR = false;
if (types.isObjectExpression(hookOptions)) {
const onSSRProp = hookOptions.properties.find((property) => types.isProperty(property) &&
types.isIdentifier(property.key) &&
property.key.name === 'onSSR');
if (types.isObjectProperty(onSSRProp) && types.isBooleanLiteral(onSSRProp.value)) {
onSSR = onSSRProp.value.value;
}
}
hooks.onMount.push({
code,
onSSR,
});
}
break;
}
case hooks_1.HOOKS.EVENT: {
const firstArg = expression.arguments[0];
const secondArg = expression.arguments[1];
const thirdArg = expression.arguments[2];
const fourthArg = expression.arguments[3];
if (!types.isStringLiteral(firstArg)) {
console.warn('`onEvent` hook skipped. Event name must be a string literal: ', (0, generator_1.default)(expression).code);
break;
}
if (!types.isFunctionExpression(secondArg) &&
!types.isArrowFunctionExpression(secondArg)) {
console.warn('`onEvent` hook skipped. Event handler must be a function: ', (0, generator_1.default)(expression).code);
break;
}
if (!types.isIdentifier(thirdArg)) {
console.warn('`onEvent` hook skipped. Element ref must be a value: ', (0, generator_1.default)(expression).code);
break;
}
const isRoot = types.isBooleanLiteral(fourthArg) ? fourthArg.value : false;
const eventArgName = types.isIdentifier(secondArg.params[0])
? secondArg.params[0].name
: 'event';
const elementArgName = types.isIdentifier(secondArg.params[1])
? secondArg.params[1].name
: 'element';
hooks.onEvent.push({
eventName: firstArg.value,
code: (0, helpers_2.processHookCode)(secondArg),
refName: thirdArg.name,
isRoot,
eventArgName,
elementArgName,
});
break;
}
case hooks_1.HOOKS.UPDATE: {
const firstArg = expression.arguments[0];
const secondArg = expression.arguments[1];
if (types.isFunctionExpression(firstArg) || types.isArrowFunctionExpression(firstArg)) {
const code = (0, helpers_2.processHookCode)(firstArg);
if (!secondArg ||
(types.isArrayExpression(secondArg) && secondArg.elements.length > 0)) {
const depsCode = secondArg ? (0, generator_1.default)(secondArg).code : '';
const depsArray = [];
if (secondArg && secondArg.elements) {
for (const element of secondArg.elements) {
if ((0, types_1.isIdentifier)(element)) {
depsArray.push(element.name);
}
else if ((0, types_1.isMemberExpression)(element) &&
(0, types_1.isIdentifier)(element.object) &&
(0, types_1.isIdentifier)(element.property)) {
depsArray.push(`${element.object.name}.${element.property.name}`);
}
}
}
hooks.onUpdate = [
...(hooks.onUpdate || []),
{
code,
deps: depsCode,
depsArray,
},
];
}
}
break;
}
case hooks_1.HOOKS.UNMOUNT: {
const firstArg = expression.arguments[0];
if (types.isFunctionExpression(firstArg) || types.isArrowFunctionExpression(firstArg)) {
const code = (0, helpers_2.processHookCode)(firstArg);
hooks.onUnMount = { code };
}
break;
}
case hooks_1.HOOKS.INIT: {
const firstArg = expression.arguments[0];
if (types.isFunctionExpression(firstArg) || types.isArrowFunctionExpression(firstArg)) {
const code = (0, helpers_2.processHookCode)(firstArg);
hooks.onInit = { code };
}
break;
}
case hooks_1.HOOKS.DEFAULT_PROPS: {
(0, hooks_2.parseDefaultPropsHook)(context.builder.component, expression);
break;
}
case hooks_1.HOOKS.STYLE: {
context.builder.component.style = (0, hooks_2.generateUseStyleCode)(expression);
break;
}
case hooks_1.HOOKS.METADATA: {
context.builder.component.meta[hooks_1.HOOKS.METADATA] = {
...context.builder.component.meta[hooks_1.HOOKS.METADATA],
...(0, helpers_1.parseCodeJson)(expression.arguments[0]),
};
break;
}
}
}
}
if (types.isFunctionDeclaration(item)) {
if (types.isIdentifier(item.id)) {
state[item.id.name] = {
code: (0, generator_1.default)(item).code,
type: 'function',
};
stateToScope.push(item.id.name);
}
}
if (types.isVariableDeclaration(item)) {
const declaration = item.declarations[0];
const init = declaration.init;
if (types.isCallExpression(init) && types.isIdentifier(init.callee)) {
// React format, like:
// const [foo, setFoo] = useState(...)
if (types.isArrayPattern(declaration.id) && init.callee.name === hooks_1.HOOKS.STATE) {
const varName = types.isIdentifier(declaration.id.elements[0]) && declaration.id.elements[0].name;
if (varName) {
const value = init.arguments[0];
// Function as init, like:
// useState(() => true)
if (types.isArrowFunctionExpression(value)) {
state[varName] = {
code: (0, helpers_1.parseCode)(value.body),
type: 'function',
};
}
else {
const stateOptions = init.arguments[1];
let propertyType = 'normal';
if (types.isObjectExpression(stateOptions)) {
for (const prop of stateOptions.properties) {
if (!types.isProperty(prop) || !types.isIdentifier(prop.key))
continue;
const isReactive = prop.key.name === 'reactive';
if (isReactive && types.isBooleanLiteral(prop.value) && prop.value.value) {
propertyType = 'reactive';
}
}
}
// Value as init, like:
// useState(true)
state[varName] = {
code: (0, helpers_1.parseCode)(value),
type: 'property',
propertyType,
};
}
stateToScope.push(varName);
// Typescript Parameter
if (types.isTSTypeParameterInstantiation(init.typeParameters)) {
state[varName].typeParameter = (0, generator_1.default)(init.typeParameters.params[0]).code;
}
}
}
else if (init.callee.name === hooks_1.HOOKS.STORE) {
const firstArg = init.arguments[0];
if (types.isObjectExpression(firstArg)) {
const useStoreState = (0, state_1.parseStateObjectToMitosisState)(firstArg);
Object.assign(state, useStoreState);
const stateKeys = Object.keys(useStoreState);
if (types.isTSTypeParameterInstantiation(init.typeParameters)) {
const type = (0, generator_1.default)(init.typeParameters.params[0]);
// Type for store has to be an object so we can use it like this
for (const key of stateKeys) {
state[key].typeParameter = `${type.code}["${key}"]`;
}
}
}
}
else if (init.callee.name === hooks_1.HOOKS.CONTEXT) {
const firstArg = init.arguments[0];
if (types.isVariableDeclarator(declaration) && types.isIdentifier(declaration.id)) {
if (types.isIdentifier(firstArg)) {
const varName = declaration.id.name;
const name = firstArg.name;
accessedContext[varName] = {
name,
path: (0, trace_reference_to_module_path_1.traceReferenceToModulePath)(context.builder.component.imports, name),
};
}
else {
const varName = declaration.id.name;
const name = (0, generator_1.default)(firstArg).code;
accessedContext[varName] = {
name,
path: '',
};
}
}
}
else if (init.callee.name === hooks_1.HOOKS.REF) {
if (types.isIdentifier(declaration.id)) {
const firstArg = init.arguments[0];
const varName = declaration.id.name;
refs[varName] = {
argument: (0, generator_1.default)(firstArg).code,
};
// Typescript Parameter
if (types.isTSTypeParameterInstantiation(init.typeParameters)) {
refs[varName].typeParameter = (0, generator_1.default)(init.typeParameters.params[0]).code;
}
}
}
}
}
}
const theReturn = node.body.body.find((item) => types.isReturnStatement(item));
const children = [];
if (theReturn) {
const value = theReturn.argument;
if (types.isJSXElement(value) || types.isJSXFragment(value)) {
const jsxElement = (0, element_parser_1.jsxElementToJson)(value);
if (jsxElement) {
children.push(jsxElement);
}
}
}
const { exports: localExports } = context.builder.component;
if (localExports) {
const bindingsCode = (0, get_bindings_1.getBindingsCode)(children);
Object.keys(localExports).forEach((name) => {
const found = bindingsCode.find((code) => code.match(new RegExp(`\\b${name}\\b`)));
localExports[name].usedInLocal = Boolean(found);
});
context.builder.component.exports = localExports;
}
const propsTypeRef = (0, component_types_1.getPropsTypeRef)(node, context);
return (0, create_mitosis_component_1.createMitosisComponent)({
...context.builder.component,
name: (_a = node.id) === null || _a === void 0 ? void 0 : _a.name,
state,
children,
refs: refs,
hooks,
context: {
get: accessedContext,
set: setContext,
},
propsTypeRef,
});
};
exports.componentFunctionToJson = componentFunctionToJson;