@builder.io/mitosis
Version:
Write components once, run everywhere. Compiles to Vue, React, Solid, and Liquid. Import code from Figma and Builder.io
180 lines (179 loc) • 8.09 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.getContextKey = exports.checkIfContextHasStrName = exports.getContextValue = exports.processBinding = exports.getContextNames = exports.renameMitosisComponentsToKebabCase = exports.mapMitosisComponentToKebabCase = exports.encodeQuotes = exports.invertBooleanExpression = exports.getOnUpdateHookName = exports.addBindingsToJson = exports.addPropertiesToJson = void 0;
const babel_transform_1 = require("../../helpers/babel-transform");
const get_state_object_string_1 = require("../../helpers/get-state-object-string");
const patterns_1 = require("../../helpers/patterns");
const replace_identifiers_1 = require("../../helpers/replace-identifiers");
const slots_1 = require("../../helpers/slots");
const core_1 = require("@babel/core");
const function_1 = require("fp-ts/lib/function");
const lodash_1 = require("lodash");
const html_tags_1 = require("../../constants/html_tags");
const addPropertiesToJson = (properties) => (json) => ({
...json,
properties: {
...json.properties,
...properties,
},
});
exports.addPropertiesToJson = addPropertiesToJson;
const addBindingsToJson = (bindings) => (json) => ({
...json,
bindings: {
...json.bindings,
...bindings,
},
});
exports.addBindingsToJson = addBindingsToJson;
const ON_UPDATE_HOOK_NAME = 'onUpdateHook';
const getOnUpdateHookName = (index) => ON_UPDATE_HOOK_NAME + `${index}`;
exports.getOnUpdateHookName = getOnUpdateHookName;
const invertBooleanExpression = (expression) => `!Boolean(${expression})`;
exports.invertBooleanExpression = invertBooleanExpression;
function encodeQuotes(string) {
return string.replace(/"/g, '"');
}
exports.encodeQuotes = encodeQuotes;
const mapMitosisComponentToKebabCase = (componentName) => componentName.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
exports.mapMitosisComponentToKebabCase = mapMitosisComponentToKebabCase;
// Transform <FooBar> to <foo-bar> as Vue2 needs
const renameMitosisComponentsToKebabCase = (str) => str.replace(/<\/?\w+/g, (match) => {
const tagName = match.replaceAll('<', '').replaceAll('/', '');
if (html_tags_1.VALID_HTML_TAGS.includes(tagName)) {
return match;
}
else {
return (0, exports.mapMitosisComponentToKebabCase)(match);
}
});
exports.renameMitosisComponentsToKebabCase = renameMitosisComponentsToKebabCase;
function getContextNames(json) {
return Object.keys(json.context.get);
}
exports.getContextNames = getContextNames;
function shouldAppendValueToRef(path) {
const { parent, node } = path;
if (core_1.types.isFunctionDeclaration(parent) && parent.id === node) {
return false;
}
if (core_1.types.isCallExpression(parent)) {
return false;
}
const isMemberExpression = core_1.types.isMemberExpression(parent);
if (isMemberExpression &&
core_1.types.isThisExpression(parent.object) &&
core_1.types.isProgram(path.scope.block) &&
path.scope.hasReference(node.name)) {
return false;
}
if (isMemberExpression &&
core_1.types.isIdentifier(parent.object) &&
core_1.types.isIdentifier(parent.property) &&
parent.property.name === node.name) {
return false;
}
if (Object.keys(path.scope.bindings).includes(path.node.name)) {
return false;
}
if (path.parentPath.listKey === 'arguments' || path.parentPath.listKey === 'params') {
return false;
}
return true;
}
const getAllRefs = (component) => {
const refKeys = Object.keys(component.refs);
const stateKeys = Object.keys((0, lodash_1.pickBy)(component.state, (i) => (i === null || i === void 0 ? void 0 : i.type) === 'property'));
const allKeys = [...refKeys, ...stateKeys];
return allKeys;
};
function processRefs({ input, component, options, thisPrefix, }) {
const { api } = options;
const refs = api === 'options' ? getContextNames(component) : getAllRefs(component);
return (0, babel_transform_1.babelTransformExpression)(input, {
Identifier(path) {
const name = path.node.name;
// Composition api should use .value all the time
if (refs.includes(name) && (api === 'composition' || shouldAppendValueToRef(path))) {
const newValue = api === 'options' ? `${thisPrefix}.${name}` : `${name}.value`;
path.replaceWith(core_1.types.identifier(newValue));
}
},
});
}
function prefixMethodsWithThis(input, component, options) {
if (options.api === 'options') {
const allMethodNames = Object.entries(component.state)
.filter(([_key, value]) => (value === null || value === void 0 ? void 0 : value.type) === 'function')
.map(([key]) => key);
if (!allMethodNames.length)
return input;
return (0, replace_identifiers_1.replaceIdentifiers)({ code: input, from: allMethodNames, to: (name) => `this.${name}` });
}
else {
return input;
}
}
function optionsApiStateAndPropsReplace(name, thisPrefix, codeType) {
const prefixToUse = codeType === 'bindings' ? '' : thisPrefix + '.';
if (name === 'children' || name.startsWith('children.')) {
return `${prefixToUse}$slots.default`;
}
return (0, slots_1.isSlotProperty)(name)
? (0, slots_1.replaceSlotsInString)(name, (x) => `${prefixToUse}$slots.${x}`)
: `${prefixToUse}${name}`;
}
// TODO: migrate all stripStateAndPropsRefs to use this here
// to properly replace context refs
const processBinding = ({ code, options, json, preserveGetter = false, thisPrefix = 'this', codeType, }) => {
try {
return (0, function_1.pipe)(code, (0, replace_identifiers_1.replacePropsIdentifier)((name) => {
switch (options.api) {
// keep pointing to `props.${value}`
case 'composition':
const slotPrefix = codeType === 'bindings' ? '$slots' : 'useSlots()';
if (name === 'children' || name.startsWith('children.')) {
return `${slotPrefix}.default`;
}
return (0, slots_1.isSlotProperty)(name)
? (0, slots_1.replaceSlotsInString)(name, (x) => `${slotPrefix}.${x}`)
: codeType === 'bindings'
? name
: `props.${name}`;
case 'options':
return optionsApiStateAndPropsReplace(name, thisPrefix, codeType);
}
}), (0, replace_identifiers_1.replaceStateIdentifier)((name) => {
switch (options.api) {
case 'composition':
return name;
case 'options':
return optionsApiStateAndPropsReplace(name, thisPrefix, codeType);
}
}), codeType === 'bindings'
? function_1.identity
: (0, function_1.flow)((x) => processRefs({ input: x, component: json, options, thisPrefix }), (x) => prefixMethodsWithThis(x, json, options)), preserveGetter === false ? patterns_1.stripGetter : function_1.identity);
}
catch (e) {
console.error('could not process bindings in ', { code });
throw e;
}
};
exports.processBinding = processBinding;
const getContextValue = ({ name, ref, value }) => {
const valueStr = value ? (0, get_state_object_string_1.stringifyContextValue)(value) : ref;
return valueStr;
};
exports.getContextValue = getContextValue;
const checkIfContextHasStrName = (context) => {
// check if the name is wrapped in single or double quotes
const isStrName = context.name.startsWith("'") || context.name.startsWith('"');
return isStrName;
};
exports.checkIfContextHasStrName = checkIfContextHasStrName;
const getContextKey = (context) => {
const isStrName = (0, exports.checkIfContextHasStrName)(context);
const key = isStrName ? context.name : `${context.name}.key`;
return key;
};
exports.getContextKey = getContextKey;
;