UNPKG

@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
"use strict"; 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, '&quot;'); } 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;