@builder.io/mitosis
Version:
Write components once, run everywhere. Compiles to Vue, React, Solid, and Liquid. Import code from Figma and Builder.io
338 lines (337 loc) • 14.7 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.componentToQwik = void 0;
const babel_transform_1 = require("../../helpers/babel-transform");
const fast_clone_1 = require("../../helpers/fast-clone");
const merge_options_1 = require("../../helpers/merge-options");
const on_event_1 = require("../../helpers/on-event");
const process_code_1 = require("../../helpers/plugins/process-code");
const render_imports_1 = require("../../helpers/render-imports");
const replace_identifiers_1 = require("../../helpers/replace-identifiers");
const state_1 = require("../../helpers/state");
const collect_css_1 = require("../../helpers/styles/collect-css");
const standalone_1 = require("prettier/standalone");
const plugins_1 = require("../../modules/plugins");
const add_prevent_default_1 = require("./helpers/add-prevent-default");
const stable_inject_1 = require("./helpers/stable-inject");
const state_2 = require("./helpers/state");
const jsx_1 = require("./jsx");
const src_generator_1 = require("./src-generator");
Error.stackTraceLimit = 9999;
const DEBUG = false;
const PLUGINS = [
() => ({
json: {
post: (json) => {
(0, add_prevent_default_1.addPreventDefault)(json);
return json;
},
},
}),
(0, on_event_1.processOnEventHooksPlugin)({ setBindings: false, includeRootEvents: false }),
(0, process_code_1.CODE_PROCESSOR_PLUGIN)((codeType, json) => {
switch (codeType) {
case 'types':
return (c) => c;
case 'bindings':
case 'state':
case 'context-set':
case 'hooks':
case 'hooks-deps':
case 'hooks-deps-array':
case 'properties':
case 'dynamic-jsx-elements':
// update signal getters to have `.value`
return (code, k) => {
// `ref` should not update the signal value access
if (k === 'ref') {
return code;
}
Object.keys(json.refs).forEach((ref) => {
code = (0, replace_identifiers_1.replaceIdentifiers)({
code,
from: ref,
to: (x) => (x === ref ? `${x}.value` : `${ref}.value.${x}`),
});
});
// update signal getters to have `.value`
return (0, replace_identifiers_1.replaceStateIdentifier)((name) => {
const state = json.state[name];
switch (state === null || state === void 0 ? void 0 : state.type) {
case 'getter':
return `${name}.value`;
case 'function':
case 'method':
case 'property':
case undefined:
return `state.${name}`;
}
})(code);
};
}
}),
];
const DEFAULT_OPTIONS = {
plugins: PLUGINS,
};
const componentToQwik = (userOptions = {}) => ({ component: _component, path }) => {
var _a, _b, _c, _d;
// Make a copy we can safely mutate, similar to babel's toolchain
let component = (0, fast_clone_1.fastClone)(_component);
const options = (0, merge_options_1.initializeOptions)({
target: 'qwik',
component,
defaults: DEFAULT_OPTIONS,
userOptions: userOptions,
});
component = (0, plugins_1.runPreJsonPlugins)({ json: component, plugins: options.plugins });
component = (0, plugins_1.runPostJsonPlugins)({ json: component, plugins: options.plugins });
const isTypeScript = !!options.typescript;
const file = new src_generator_1.File(component.name + (isTypeScript ? '.ts' : '.js'), {
isPretty: true,
isJSX: true,
isTypeScript: isTypeScript,
isModule: true,
isBuilder: false,
}, '@builder.io/qwik', '');
try {
emitImports(file, component);
emitTypes(file, component);
emitExports(file, component);
const metadata = component.meta.useMetadata;
const isLightComponent = ((_b = (_a = metadata === null || metadata === void 0 ? void 0 : metadata.qwik) === null || _a === void 0 ? void 0 : _a.component) === null || _b === void 0 ? void 0 : _b.isLight) || false;
const mutable = ((_c = metadata === null || metadata === void 0 ? void 0 : metadata.qwik) === null || _c === void 0 ? void 0 : _c.mutable) || [];
const imports = ((_d = metadata === null || metadata === void 0 ? void 0 : metadata.qwik) === null || _d === void 0 ? void 0 : _d.imports) || {};
Object.keys(imports).forEach((key) => file.import(imports[key], key));
const state = (0, state_2.emitStateMethodsAndRewriteBindings)(file, component, metadata);
const hasState = (0, state_1.checkHasState)(component);
let css = null;
const emitStore = () => { var _a; return hasState && (0, state_2.emitUseStore)({ file, stateInit: state, isDeep: (_a = metadata === null || metadata === void 0 ? void 0 : metadata.qwik) === null || _a === void 0 ? void 0 : _a.hasDeepStore }); };
const componentFn = (0, src_generator_1.arrowFnBlock)(['props'], [
function () {
var _a, _b;
if ((_a = metadata === null || metadata === void 0 ? void 0 : metadata.qwik) === null || _a === void 0 ? void 0 : _a.setUseStoreFirst)
emitStore();
css = emitUseStyles(file, component);
emitUseComputed(file, component);
emitUseContext(file, component);
emitUseRef(file, component);
if (!((_b = metadata === null || metadata === void 0 ? void 0 : metadata.qwik) === null || _b === void 0 ? void 0 : _b.setUseStoreFirst))
emitStore();
emitUseOn(file, component);
emitUseContextProvider(file, component);
emitUseClientEffect(file, component);
emitUseMount(file, component);
emitUseTask(file, component);
emitJSX(file, component, mutable);
},
], [(component.propsTypeRef || 'any') + (isLightComponent ? '&{key?:any}' : '')]);
file.src.const(component.name, isLightComponent
? componentFn
: (0, src_generator_1.invoke)(file.import(file.qwikModule, 'component$'), [componentFn]), true, true);
file.exportDefault(component.name);
emitStyles(file, css);
DEBUG && file.exportConst('COMPONENT', JSON.stringify(component));
let sourceFile = file.toString();
sourceFile = (0, plugins_1.runPreCodePlugins)({
json: component,
code: sourceFile,
plugins: options.plugins,
});
sourceFile = (0, plugins_1.runPostCodePlugins)({
json: component,
code: sourceFile,
plugins: options.plugins,
});
return sourceFile;
}
catch (e) {
console.error(e);
throw e;
}
};
exports.componentToQwik = componentToQwik;
function emitExports(file, component) {
Object.keys(component.exports || {}).forEach((key) => {
const exportObj = component.exports[key];
const code = exportObj.code.startsWith('export ') ? exportObj.code : `export ${exportObj.code}`;
file.src.emit(code);
});
}
function emitUseClientEffect(file, component) {
component.hooks.onMount.forEach((onMount) => {
const code = onMount.code;
const hookToUse = onMount.onSSR ? 'useTask$' : 'useVisibleTask$';
file.src.emit(file.import(file.qwikModule, hookToUse).localName, '(()=>{', code, '});');
});
}
function emitUseMount(file, component) {
if (component.hooks.onInit) {
const code = component.hooks.onInit.code;
file.src.emit(file.import(file.qwikModule, 'useTask$').localName, '(()=>{', code, '});');
}
}
function emitUseTask(file, component) {
if (component.hooks.onUpdate) {
component.hooks.onUpdate.forEach((onUpdate) => {
file.src.emit(file.import(file.qwikModule, 'useTask$').localName, '(({track})=>{');
emitTrackExpressions(file.src, onUpdate.deps);
file.src.emit((0, babel_transform_1.convertTypeScriptToJS)(onUpdate.code));
file.src.emit('});');
});
}
}
function emitTrackExpressions(src, deps) {
if (!deps) {
return;
}
const dependencies = deps.substring(1, deps.length - 1).split(',');
dependencies.forEach((dep) => {
src.emit(`track(() => ${dep});`);
});
}
function emitJSX(file, component, mutable) {
const directives = new Map();
const handlers = new Map();
const styles = new Map();
const parentSymbolBindings = {};
if (file.options.isPretty) {
file.src.emit('\n\n');
}
file.src.emit('return ', (0, jsx_1.renderJSXNodes)(file, directives, handlers, component.children, styles, null, parentSymbolBindings));
}
function emitUseContextProvider(file, component) {
Object.entries(component.context.set).forEach(([_ctxKey, context]) => {
file.src.emit(`${file.import(file.qwikModule, 'useContextProvider').localName}(${context.name}, `);
if (context.ref) {
file.src.emit(`${context.ref}`);
}
else {
file.src.emit(`${file.import(file.qwikModule, 'useStore').localName}({`);
for (const [prop, propValue] of Object.entries(context.value || {})) {
file.src.emit(`${prop}: `);
switch (propValue === null || propValue === void 0 ? void 0 : propValue.type) {
case 'getter':
file.src.emit(`(()=>{
${extractGetterBody(propValue.code)}
})()`);
break;
case 'function':
case 'method':
throw new Error('Qwik: Functions are not supported in context');
case 'property':
file.src.emit((0, stable_inject_1.stableInject)(propValue.code));
break;
}
file.src.emit(',');
}
file.src.emit('})');
}
file.src.emit(');');
});
}
function emitUseContext(file, component) {
Object.keys(component.context.get).forEach((ctxKey) => {
const context = component.context.get[ctxKey];
file.src.emit('const ', ctxKey, '=', file.import(file.qwikModule, 'useContext').localName, '(', context.name, ');');
});
}
function emitUseOn(file, component) {
component.hooks.onEvent.forEach((hook) => {
const eventName = `"${hook.eventName}"`;
if (hook.isRoot) {
const wrappedHandlerFn = `${file.import(file.qwikModule, '$').localName}((${hook.eventArgName}, ${hook.elementArgName}) => {
${hook.code}
}) as Parameters<typeof useOn>[1]`; // this type hack is needed until https://github.com/BuilderIO/qwik/issues/5398 is fixed
file.src.emit(file.import(file.qwikModule, 'useOn').localName, `(${eventName}, ${wrappedHandlerFn});`);
}
else {
file.src.emit(file.import(file.qwikModule, 'useVisibleTask$').localName, `(() => {
${hook.refName}.value?.addEventListener(${eventName}, ${(0, on_event_1.getOnEventHandlerName)(hook)});
return () => ${hook.refName}.value?.removeEventListener(${eventName}, ${(0, on_event_1.getOnEventHandlerName)(hook)});
})
`);
}
});
}
function emitUseRef(file, component) {
Object.keys(component.refs).forEach((refKey) => {
file.src.emit(`const `, refKey, '=', file.import(file.qwikModule, 'useSignal').localName, `${file.options.isTypeScript ? '<Element>' : ''}();`);
});
}
function emitUseStyles(file, component) {
const css = (0, collect_css_1.collectCss)(component, { prefix: component.name });
if (css) {
file.src.emit(file.import(file.qwikModule, 'useStylesScoped$').localName, '(STYLES);');
if (file.options.isPretty) {
file.src.emit('\n\n');
}
}
return css;
}
function emitStyles(file, css) {
if (!css) {
return;
}
if (file.options.isPretty) {
file.src.emit('\n\n');
try {
css = (0, standalone_1.format)(css, {
parser: 'css',
plugins: [
// To support running in browsers
require('prettier/parser-postcss'),
],
});
}
catch (e) {
throw new Error(e +
'\n' +
'========================================================================\n' +
css +
'\n\n========================================================================');
}
}
file.exportConst('STYLES', '`\n' + css.replace(/`/g, '\\`') + '`\n');
}
function emitTypes(file, component) {
var _a;
if (file.options.isTypeScript) {
(_a = component.types) === null || _a === void 0 ? void 0 : _a.forEach((t) => file.src.emit(t, '\n'));
}
}
function emitImports(file, component) {
var _a;
// <SELF> is used for self-referencing within the file.
file.import('<SELF>', component.name);
(_a = component.imports) === null || _a === void 0 ? void 0 : _a.forEach((i) => {
const importPath = (0, render_imports_1.transformImportPath)({
target: 'qwik',
theImport: i,
preserveFileExtensions: false,
explicitImportFileExtension: false,
});
Object.keys(i.imports).forEach((key) => {
const keyValue = i.imports[key];
file.import(importPath, keyValue, key);
});
});
}
function extractGetterBody(code) {
const start = code.indexOf('{');
const end = code.lastIndexOf('}');
return code.substring(start + 1, end).trim();
}
function emitUseComputed(file, component) {
for (const [key, stateValue] of Object.entries(component.state)) {
switch (stateValue === null || stateValue === void 0 ? void 0 : stateValue.type) {
case 'getter':
file.src.const(`
${key} = ${file.import(file.qwikModule, 'useComputed$').localName}(() => {
${extractGetterBody(stateValue.code)}
})
`);
continue;
}
}
}
;