@builder.io/mitosis
Version:
Write components once, run everywhere. Compiles to Vue, React, Solid, and Liquid. Import code from Figma and Builder.io
413 lines (401 loc) • 17.1 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.componentToSvelte = void 0;
const babel_transform_1 = require("../../helpers/babel-transform");
const dedent_1 = require("../../helpers/dedent");
const fast_clone_1 = require("../../helpers/fast-clone");
const get_props_1 = require("../../helpers/get-props");
const get_refs_1 = require("../../helpers/get-refs");
const get_state_object_string_1 = require("../../helpers/get-state-object-string");
const getters_to_functions_1 = require("../../helpers/getters-to-functions");
const is_mitosis_node_1 = require("../../helpers/is-mitosis-node");
const merge_options_1 = require("../../helpers/merge-options");
const on_event_1 = require("../../helpers/on-event");
const patterns_1 = require("../../helpers/patterns");
const process_code_1 = require("../../helpers/plugins/process-code");
const render_imports_1 = require("../../helpers/render-imports");
const slots_1 = require("../../helpers/slots");
const strip_meta_properties_1 = require("../../helpers/strip-meta-properties");
const collect_css_1 = require("../../helpers/styles/collect-css");
const helpers_1 = require("../../helpers/styles/helpers");
const function_1 = require("fp-ts/lib/function");
const legacy_1 = __importDefault(require("neotraverse/legacy"));
const prettierPluginSvelte = __importStar(require("prettier-plugin-svelte"));
const parser_babel_1 = __importDefault(require("prettier/parser-babel"));
const parser_html_1 = __importDefault(require("prettier/parser-html"));
const parser_postcss_1 = __importDefault(require("prettier/parser-postcss"));
const parser_typescript_1 = __importDefault(require("prettier/parser-typescript"));
const standalone_1 = require("prettier/standalone");
const plugins_1 = require("../../modules/plugins");
const context_1 = require("../helpers/context");
const functions_1 = require("../helpers/functions");
const blocks_1 = require("./blocks");
const helpers_2 = require("./helpers");
const getContextCode = (json) => {
const contextGetters = json.context.get;
return Object.entries(contextGetters)
.map(([key, context]) => {
const { name } = context;
const contextType = (0, context_1.getContextType)({ component: json, context });
switch (contextType) {
case 'reactive':
case 'normal':
return `let ${key} = getContext(${name}.key);`;
}
})
.join('\n');
};
const setContextCode = ({ json, options, }) => {
const processCode = (0, helpers_2.stripStateAndProps)({ json, options });
return Object.values(json.context.set)
.map((context) => {
const { value, name, ref } = context;
const nameIsStringLiteral = (name.startsWith("'") && name.endsWith("'")) ||
(name.startsWith('"') && name.endsWith('"'));
const key = nameIsStringLiteral ? name : `${name}.key`;
const valueStr = value
? processCode((0, get_state_object_string_1.stringifyContextValue)(value))
: ref
? processCode(ref)
: 'undefined';
const contextType = (0, context_1.getContextType)({ component: json, context });
switch (contextType) {
case 'normal':
return `setContext(${key}, ${valueStr});`;
case 'reactive':
const storeName = `${name}ContextStoreValue`;
return `
const ${storeName} = writable(${valueStr});
setContext(${key}, ${storeName});
`;
}
})
.join('\n');
};
/**
* Replace
* <input value={state.name} onChange={event => state.name = event.target.value}
* with
* <input bind:value={state.name}/>
* when easily identified, for more idiomatic svelte code
*/
const useBindValue = (json, options) => {
function normalizeStr(str) {
return str
.trim()
.replace(/\n|\r/g, '')
.replace(/^{/, '')
.replace(/}$/, '')
.replace(/;$/, '')
.replace(/\s+/g, '');
}
(0, legacy_1.default)(json).forEach(function (item) {
if ((0, is_mitosis_node_1.isMitosisNode)(item)) {
const { value, onChange } = item.bindings;
if (value && onChange) {
const { arguments: cusArgs = ['event'] } = onChange;
if (normalizeStr(onChange.code) === `${normalizeStr(value.code)}=${cusArgs[0]}.target.value`) {
delete item.bindings.value;
delete item.bindings.onChange;
item.bindings['bind:value'] = value;
}
}
}
});
};
const DEFAULT_OPTIONS = {
stateType: 'variables',
prettier: true,
};
const componentToSvelte = (userProvidedOptions) => ({ component }) => {
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
const options = (0, merge_options_1.initializeOptions)({
target: 'svelte',
component,
defaults: DEFAULT_OPTIONS,
userOptions: userProvidedOptions,
});
options.plugins = [
...(options.plugins || []),
(0, on_event_1.processOnEventHooksPlugin)(),
functions_1.FUNCTION_HACK_PLUGIN,
// Strip types from any JS code that ends up in the template, because Svelte does not support TS code in templates.
(0, process_code_1.CODE_PROCESSOR_PLUGIN)((codeType) => {
switch (codeType) {
case 'bindings':
case 'properties':
return babel_transform_1.convertTypeScriptToJS;
case 'hooks':
case 'hooks-deps':
case 'hooks-deps-array':
case 'state':
case 'context-set':
case 'dynamic-jsx-elements':
case 'types':
return (x) => x;
}
}),
(0, process_code_1.CODE_PROCESSOR_PLUGIN)((codeType) => {
switch (codeType) {
case 'hooks':
return (0, function_1.flow)((0, helpers_2.stripStateAndProps)({ json, options }), babel_transform_1.babelTransformCode);
case 'bindings':
case 'hooks-deps':
case 'state':
return (0, function_1.flow)((0, helpers_2.stripStateAndProps)({ json, options }), patterns_1.stripGetter);
case 'properties':
case 'context-set':
return (0, function_1.flow)((0, helpers_2.stripStateAndProps)({ json, options }));
case 'dynamic-jsx-elements':
case 'hooks-deps-array':
case 'types':
return (x) => x;
}
}),
];
// Make a copy we can safely mutate, similar to babel's toolchain
let json = (0, fast_clone_1.fastClone)(component);
json = (0, plugins_1.runPreJsonPlugins)({ json, plugins: options.plugins });
useBindValue(json, options);
(0, getters_to_functions_1.gettersToFunctions)(json);
const filteredProps = Array.from((0, get_props_1.getProps)(json))
.filter((prop) => !(0, slots_1.isSlotProperty)(prop))
// map $prop to prop for reactive state
.map((x) => (x.startsWith('$') ? x.slice(1) : x));
// this helps make sure we don't have duplicate props
const props = Array.from(new Set(filteredProps));
const refs = Array.from((0, get_refs_1.getRefs)(json))
.map((0, helpers_2.stripStateAndProps)({ json, options }))
.filter((x) => !props.includes(x));
json = (0, plugins_1.runPostJsonPlugins)({ json, plugins: options.plugins });
const css = (0, collect_css_1.collectCss)(json);
(0, strip_meta_properties_1.stripMetaProperties)(json);
let usesWritable = false;
const dataString = (0, function_1.pipe)((0, get_state_object_string_1.getStateObjectStringFromComponent)(json, {
data: true,
functions: false,
getters: false,
format: options.stateType === 'proxies' ? 'object' : 'variables',
keyPrefix: options.stateType === 'variables' ? 'let ' : '',
valueMapper: (code, _t, _p, key) => {
var _a;
if (((_a = json.state[key]) === null || _a === void 0 ? void 0 : _a.propertyType) === 'reactive') {
usesWritable = true;
return `writable(${code})`;
}
return code;
},
}), babel_transform_1.babelTransformCode);
const getterString = (0, function_1.pipe)((0, get_state_object_string_1.getStateObjectStringFromComponent)(json, {
data: false,
getters: true,
functions: false,
format: 'variables',
keyPrefix: '$: ',
valueMapper: (code) => {
return code
.trim()
.replace(/^([a-zA-Z_\$0-9]+)/, '$1 = ')
.replace(/\)/, ') => ');
},
}), babel_transform_1.babelTransformCode);
const functionsString = (0, function_1.pipe)((0, get_state_object_string_1.getStateObjectStringFromComponent)(json, {
data: false,
getters: false,
functions: true,
format: 'variables',
}), babel_transform_1.babelTransformCode);
const hasData = dataString.length > 4;
let str = '';
const tsLangAttribute = options.typescript ? `lang='ts'` : '';
if (options.typescript && ((_a = json.types) === null || _a === void 0 ? void 0 : _a.length)) {
str += (0, dedent_1.dedent) `
<script context='module' ${tsLangAttribute}>
${json.types ? json.types.join('\n\n') + '\n' : ''}
</script>
\n
\n
`;
}
// prepare svelte imports
let svelteImports = [];
let svelteStoreImports = [];
if (json.hooks.onMount.length) {
svelteImports.push('onMount');
}
if ((_c = (_b = json.hooks.onUpdate) === null || _b === void 0 ? void 0 : _b.filter((x) => !x.deps)) === null || _c === void 0 ? void 0 : _c.length) {
svelteImports.push('afterUpdate');
}
if ((_e = (_d = json.hooks.onUnMount) === null || _d === void 0 ? void 0 : _d.code) === null || _e === void 0 ? void 0 : _e.length) {
svelteImports.push('onDestroy');
}
if ((0, context_1.hasGetContext)(component)) {
svelteImports.push('getContext');
}
if ((0, context_1.hasSetContext)(component)) {
svelteImports.push('setContext');
}
if (usesWritable) {
svelteStoreImports.push('writable');
}
str += (0, dedent_1.dedent) `
<script ${tsLangAttribute}>
${!svelteImports.length ? '' : `import { ${svelteImports.sort().join(', ')} } from 'svelte'`}
${!svelteStoreImports.length
? ''
: `import { ${svelteStoreImports.sort().join(', ')} } from 'svelte/store'`}
${(0, render_imports_1.renderPreComponent)({
explicitImportFileExtension: options.explicitImportFileExtension,
component: json,
target: 'svelte',
})}
${!hasData || options.stateType === 'variables' ? '' : `import onChange from 'on-change'`}
${props
.map((name) => {
var _a, _b, _c;
if (name === 'children') {
return '';
}
let propDeclaration = `export let ${name}`;
if (options.typescript && json.propsTypeRef && json.propsTypeRef !== 'any') {
propDeclaration += `: ${json.propsTypeRef.split(' |')[0]}['${name}']`;
}
if (json.defaultProps && json.defaultProps.hasOwnProperty(name)) {
propDeclaration += `=${(_a = json.defaultProps[name]) === null || _a === void 0 ? void 0 : _a.code}`;
}
else if ((_c = (_b = json.props) === null || _b === void 0 ? void 0 : _b[name]) === null || _c === void 0 ? void 0 : _c.optional) {
propDeclaration += `= undefined`;
}
propDeclaration += ';';
return propDeclaration;
})
.join('\n')}
${
// https://github.com/sveltejs/svelte/issues/7311
(0, helpers_1.hasStyle)(json)
? (0, dedent_1.dedent) `
function stringifyStyles(stylesObj) {
let styles = '';
for (let key in stylesObj) {
const dashedKey = key.replace(/[A-Z]/g, function(match) {
return '-' + match.toLowerCase();
});
styles += dashedKey + ":" + stylesObj[key] + ";";
}
return styles;
}
`
: ''}
${getContextCode(json)}
${functionsString.length < 4 ? '' : functionsString}
${getterString.length < 4 ? '' : getterString}
${refs.map((ref) => `let ${ref}`).join('\n')}
${options.stateType === 'proxies'
? dataString.length < 4
? ''
: `let state = onChange(${dataString}, () => state = state)`
: dataString}
${(_g = (_f = json.hooks.onInit) === null || _f === void 0 ? void 0 : _f.code) !== null && _g !== void 0 ? _g : ''}
${json.hooks.onMount.map((hook) => `onMount(() => { ${hook.code} });`).join('\n')}
${((_h = json.hooks.onUpdate) === null || _h === void 0 ? void 0 : _h.map(({ code, deps }, index) => {
if (!deps) {
return `afterUpdate(() => { ${code} });`;
}
const fnName = `onUpdateFn_${index}`;
const depsArray = deps
.slice(1, deps.length - 1)
.split(',')
.map((x) => x.trim());
const getReactiveDepName = (dep) => `${fnName}_${dep.slice(1).replace(/(\.|\?)/g, '_')}`;
const isStoreAccessDep = (dep) => dep.startsWith('$');
const reactiveDepsWorkaround = depsArray
.filter(isStoreAccessDep)
.map((dep) => `$: ${getReactiveDepName(dep)} = ${dep};`)
.join('\n');
const depsArrayStr = depsArray
.map((x) => (isStoreAccessDep(x) ? getReactiveDepName(x) : x))
.join(', ');
/**
* We create a reactive value for each `onUpdate`'s dependency that
* accesses a store so that Svelte has accurate dependency tracking.
*
* Otherwise, if the dependency is a value within a store, Svelte will
* rerun the effect every time the parent store is changed in any way.
*/
return `
function ${fnName}(..._args${options.typescript ? ': any[]' : ''}) {
${code}
}
${reactiveDepsWorkaround}
$: ${fnName}(...[${depsArrayStr}]);
`;
}).join('\n')) || ''}
${
// make sure this is after all other state/code is initialized
setContextCode({ json, options })}
${!((_j = json.hooks.onUnMount) === null || _j === void 0 ? void 0 : _j.code) ? '' : `onDestroy(() => { ${json.hooks.onUnMount.code} });`}
</script>
${json.children
.map((item) => (0, blocks_1.blockToSvelte)({
json: item,
options: options,
parentComponent: json,
}))
.join('\n')}
${!css.trim().length
? ''
: `<style>
${css}
</style>`}
`;
str = (0, plugins_1.runPreCodePlugins)({ json, code: str, plugins: options.plugins });
if (options.prettier !== false) {
try {
str = (0, standalone_1.format)(str, {
parser: 'svelte',
plugins: [
// To support running in browsers
parser_html_1.default,
parser_postcss_1.default,
parser_babel_1.default,
parser_typescript_1.default,
prettierPluginSvelte,
],
});
}
catch (err) {
console.warn('Could not prettify');
console.warn(str, err);
}
}
str = str.replace(/<script>\n<\/script>/g, '').trim();
str = (0, plugins_1.runPostCodePlugins)({ json, code: str, plugins: options.plugins });
return str;
};
exports.componentToSvelte = componentToSvelte;