UNPKG

@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
"use strict"; 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; } } }