UNPKG

@builder.io/mitosis

Version:

Write components once, run everywhere. Compiles to Vue, React, Solid, and Liquid. Import code from Figma and Builder.io

280 lines (266 loc) 14.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.componentToAngularSignals = void 0; const helpers_1 = require("../../../generators/angular/helpers"); const format_1 = require("../../../generators/angular/helpers/format"); const get_outputs_1 = require("../../../generators/angular/helpers/get-outputs"); const get_refs_1 = require("../../../generators/angular/helpers/get-refs"); const get_styles_1 = require("../../../generators/angular/helpers/get-styles"); const blocks_1 = require("../../../generators/angular/signals/blocks"); const helpers_2 = require("../../../generators/angular/signals/helpers"); const get_inputs_1 = require("../../../generators/angular/signals/helpers/get-inputs"); const get_code_processor_plugins_1 = require("../../../generators/angular/signals/plugins/get-code-processor-plugins"); const types_1 = require("../../../generators/angular/types"); const on_mount_1 = require("../../../generators/helpers/on-mount"); const dedent_1 = require("../../../helpers/dedent"); const event_handlers_1 = require("../../../helpers/event-handlers"); const fast_clone_1 = require("../../../helpers/fast-clone"); const get_child_components_1 = require("../../../helpers/get-child-components"); const get_components_used_1 = require("../../../helpers/get-components-used"); const get_props_1 = require("../../../helpers/get-props"); const get_state_object_string_1 = require("../../../helpers/get-state-object-string"); const is_upper_case_1 = require("../../../helpers/is-upper-case"); const merge_options_1 = require("../../../helpers/merge-options"); const render_imports_1 = require("../../../helpers/render-imports"); const strip_meta_properties_1 = require("../../../helpers/strip-meta-properties"); const attribute_passing_1 = require("../../../helpers/web-components/attribute-passing"); const plugins_1 = require("../../../modules/plugins"); const lodash_1 = require("lodash"); const componentToAngularSignals = (userOptions = {}) => { return ({ component }) => { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u; // Make a copy we can safely mutate, similar to babel's toolchain let json = (0, fast_clone_1.fastClone)(component); // Init compileContext json.compileContext = { angular: { hooks: { ngAfterViewInit: { code: '', }, }, extra: { importCalls: [], }, }, }; const options = (0, merge_options_1.initializeOptions)({ target: 'angular', component, defaults: types_1.DEFAULT_ANGULAR_OPTIONS, userOptions, }); options.typescript = true; // Angular uses ts all the time options.api = 'signals'; if (options.plugins) { json = (0, plugins_1.runPreJsonPlugins)({ json, plugins: options.plugins }); } const withAttributePassing = true; // We always want to pass attributes const rootRef = (0, attribute_passing_1.getAddAttributePassingRef)(json, options); const domRefs = (0, get_refs_1.getDomRefs)({ json, options, rootRef, withAttributePassing }); let props = Array.from((0, get_props_1.getProps)(json)); const events = props.filter((prop) => (0, event_handlers_1.checkIsEvent)(prop)); const childComponents = (0, get_child_components_1.getChildComponents)(json); props = props.filter((prop) => { // Best practise for Angular is to use Events without "on" // Stencil doesn't need children as a prop return prop !== 'children' && !(0, event_handlers_1.checkIsEvent)(prop); }); const processBindingOptions = { events, props, target: 'angular', skipAppendEmit: true, }; options.plugins = (0, get_code_processor_plugins_1.getCodeProcessorPlugins)(json, options, processBindingOptions); if (options.plugins) { json = (0, plugins_1.runPostJsonPlugins)({ json, plugins: options.plugins }); } // CSS const styles = (0, get_styles_1.getAngularStyles)({ json, options }); // Mitosis Metadata const useMetadata = (_a = json.meta) === null || _a === void 0 ? void 0 : _a.useMetadata; const onPush = ((_b = useMetadata === null || useMetadata === void 0 ? void 0 : useMetadata.angular) === null || _b === void 0 ? void 0 : _b.changeDetection) == 'OnPush'; const writeableSignals = ((_d = (_c = useMetadata === null || useMetadata === void 0 ? void 0 : useMetadata.angular) === null || _c === void 0 ? void 0 : _c.signals) === null || _d === void 0 ? void 0 : _d.writeable) || []; const requiredSignals = ((_f = (_e = useMetadata === null || useMetadata === void 0 ? void 0 : useMetadata.angular) === null || _e === void 0 ? void 0 : _e.signals) === null || _f === void 0 ? void 0 : _f.required) || []; // Context & Injectables const injectables = Object.entries(((_g = json === null || json === void 0 ? void 0 : json.context) === null || _g === void 0 ? void 0 : _g.get) || {}).map(([variableName, { name }]) => { return `public ${variableName} : ${name}`; }); const shouldUseSanitizer = !((_h = useMetadata === null || useMetadata === void 0 ? void 0 : useMetadata.angular) === null || _h === void 0 ? void 0 : _h.sanitizeInnerHTML) && (0, helpers_1.traverseAndCheckIfInnerHTMLIsUsed)(json); if (shouldUseSanitizer) { injectables.push('protected sanitizer: DomSanitizer'); } // HTML let template = json.children .map((item) => { var _a, _b, _c, _d; return (0, blocks_1.blockToAngularSignals)({ root: json, json: item, options, rootRef: withAttributePassing && rootRef === attribute_passing_1.ROOT_REF ? rootRef : undefined, // only pass rootRef if it's not the default blockOptions: { childComponents, nativeAttributes: (_b = (_a = useMetadata === null || useMetadata === void 0 ? void 0 : useMetadata.angular) === null || _a === void 0 ? void 0 : _a.nativeAttributes) !== null && _b !== void 0 ? _b : [], nativeEvents: (_d = (_c = useMetadata === null || useMetadata === void 0 ? void 0 : useMetadata.angular) === null || _c === void 0 ? void 0 : _c.nativeEvents) !== null && _d !== void 0 ? _d : [], sanitizeInnerHTML: !shouldUseSanitizer, }, }); }) .join('\n'); if (options.prettier !== false) { template = (0, format_1.tryFormat)(template, 'html'); } // Angular component settings const componentsUsed = Array.from((0, get_components_used_1.getComponentsUsed)(json)).filter((item) => item.length && (0, is_upper_case_1.isUpperCase)(item[0]) && !types_1.BUILT_IN_COMPONENTS.has(item)); const componentSettings = { selector: `'${(0, lodash_1.kebabCase)(json.name)}'`, standalone: 'true', imports: `[${['CommonModule', ...componentsUsed].join(', ')}]`, template: `\`${(0, helpers_1.getTemplateFormat)(template)}\``, }; if (onPush) { componentSettings.changeDetection = `'ChangeDetectionStrategy.OnPush'`; } if (styles) { componentSettings.styles = `\`${styles}\``; } (0, strip_meta_properties_1.stripMetaProperties)(json); const dataString = (0, get_state_object_string_1.getStateObjectStringFromComponent)(json, { format: 'class', data: true, functions: false, getters: false, valueMapper: (code, _, typeParameter) => { if (typeParameter && !code.length) { console.error(` Component ${json.name} has state property without an initial value'. This will cause an error in Angular. Please add a initial value for every state property even if it's \`undefined\`.`); } return `signal${typeParameter ? `<${typeParameter}>` : ''}(${code})`; }, }); const methodsString = (0, get_state_object_string_1.getStateObjectStringFromComponent)(json, { format: 'class', data: false, functions: true, getters: true, onlyValueMapper: true, valueMapper: (code, type, _, key) => { return code.startsWith('function') ? code.replace('function', '').trim() : code; }, }); // Imports const coreImports = (0, helpers_2.getAngularCoreImportsAsString)({ refs: domRefs.size !== 0, input: props.length !== 0, output: events.length !== 0, model: writeableSignals.length !== 0, effect: ((_j = json.hooks.onUpdate) === null || _j === void 0 ? void 0 : _j.length) !== 0, signal: dataString.length !== 0, onPush, }); let str = (0, dedent_1.dedent) ` import { ${coreImports} } from '@angular/core'; import { CommonModule } from '@angular/common'; ${shouldUseSanitizer ? `import { DomSanitizer } from '@angular/platform-browser';` : ''} ${json.types ? json.types.join('\n') : ''} ${(0, helpers_1.getDefaultProps)(json)} ${(0, render_imports_1.renderPreComponent)({ explicitImportFileExtension: options.explicitImportFileExtension, component: json, target: 'angular', preserveFileExtensions: options.preserveFileExtensions, importMapper: (_, theImport, importedValues) => { const { defaultImport } = importedValues; const { path } = theImport; if (defaultImport && componentsUsed.includes(defaultImport)) { return `import { ${defaultImport} } from '${path}';`; } return undefined; }, })} @Component({ ${Object.entries(componentSettings) .map(([k, v]) => `${k}: ${v}`) .join(',')} }) export class ${json.name} implements AfterViewInit { ${(0, lodash_1.uniq)(json.compileContext.angular.extra.importCalls) .map((importCall) => `protected readonly ${importCall} = ${importCall};`) .join('\n')} ${(0, get_inputs_1.getSignalInputs)({ json, writeableSignals, requiredSignals, props: Array.from(props), })} ${(0, get_outputs_1.getOutputs)({ json, outputVars: events, api: options.api })} ${Array.from(domRefs) .map((refName) => `${refName} = viewChild<ElementRef>("${refName}")`) .join('\n')} ${dataString} ${methodsString} constructor(${injectables.join(',\n')}) { ${((_k = json.hooks.onUpdate) === null || _k === void 0 ? void 0 : _k.length) ? (_l = json.hooks.onUpdate) === null || _l === void 0 ? void 0 : _l.map(({ code, depsArray }) => /** * We need allowSignalWrites only for Angular 17 https://angular.dev/api/core/CreateEffectOptions#allowSignalWrites * TODO: remove on 2025-05-15 https://angular.dev/reference/releases#actively-supported-versions */ `effect(() => { ${(depsArray === null || depsArray === void 0 ? void 0 : depsArray.length) ? ` // --- Mitosis: Workaround to make sure the effect() is triggered --- ${depsArray.join('\n')} // --- ` : ''} ${code} }, { allowSignalWrites: true, // Enable writing to signals inside effects } );`).join('\n') : ''} } ${withAttributePassing ? (0, attribute_passing_1.getAttributePassingString)(options.typescript) : ''} ${!json.hooks.onMount.length && !((_m = json.hooks.onInit) === null || _m === void 0 ? void 0 : _m.code) ? '' : `ngOnInit() { ${!((_o = json.hooks) === null || _o === void 0 ? void 0 : _o.onInit) ? '' : (_p = json.hooks.onInit) === null || _p === void 0 ? void 0 : _p.code} ${json.hooks.onMount.length > 0 ? (0, on_mount_1.stringifySingleScopeOnMount)(json) : ''} }`} ${ // hooks specific to Angular ((_r = (_q = json.compileContext) === null || _q === void 0 ? void 0 : _q.angular) === null || _r === void 0 ? void 0 : _r.hooks) ? Object.entries((_t = (_s = json.compileContext) === null || _s === void 0 ? void 0 : _s.angular) === null || _t === void 0 ? void 0 : _t.hooks) .map(([key, value]) => { return `${key}() { ${value.code} }`; }) .join('\n') : ''} ${json.hooks.onUnMount ? `ngOnDestroy() { ${((_u = json.hooks.onUnMount) === null || _u === void 0 ? void 0 : _u.code) || ''} }` : ''} } `; if (options.plugins) { str = (0, plugins_1.runPreCodePlugins)({ json, code: str, plugins: options.plugins }); } if (options.prettier !== false) { str = (0, format_1.tryFormat)(str, 'typescript'); } if (options.plugins) { str = (0, plugins_1.runPostCodePlugins)({ json, code: str, plugins: options.plugins }); } return str; }; }; exports.componentToAngularSignals = componentToAngularSignals;