Passed
Push — trunk ( 4efcfa...f6a318 )
by Christian
15:05 queued 13s
created

src/Administration/Resources/app/administration/src/app/adapter/view/vue.adapter.ts   D

Complexity

Total Complexity 59
Complexity/F 2.11

Size

Lines of Code 675
Function Count 28

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 448
dl 0
loc 675
rs 4.08
c 0
b 0
f 0
wmc 59
mnd 31
bc 31
fnc 28
bpm 1.1071
cpm 2.1071
noi 0

23 Functions

Rating   Name   Duplication   Size   Complexity  
A VueAdapter.init 0 12 4
A VueAdapter.initModuleLocales 0 13 1
A VueAdapter.initTitle 0 38 4
A VueAdapter.initFilters 0 14 1
A VueAdapter.getComponents 0 7 1
A VueAdapter.setLocaleFromUser 0 8 2
B VueAdapter.initPlugins 0 40 7
A VueAdapter.resolveMixins 0 21 5
A VueAdapter.buildAndCreateComponent 0 17 2
A VueAdapter.initDirectives 0 14 1
B VueAdapter.initDependencies 0 72 1
A VueAdapter.getComponentForRoute 0 7 1
A VueAdapter.initLocales 0 39 4
A VueAdapter.deleteReactive 0 6 1
A VueAdapter.setReactive 0 6 1
B VueAdapter.initVue2 0 57 5
A VueAdapter.getComponent 0 10 2
A VueAdapter.componentResolver 0 13 2
B VueAdapter.initVue3 0 93 5
A VueAdapter.getWrapper 0 6 1
B VueAdapter.createComponent 0 69 6
A VueAdapter.initComponents 0 16 1
A VueAdapter.getName 0 6 1

How to fix   Complexity   

Complexity

Complex classes like src/Administration/Resources/app/administration/src/app/adapter/view/vue.adapter.ts often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
// @ts-nocheck
2
3
/**
4
 * @package admin
5
 */
6
import ViewAdapter from 'src/core/adapter/view.adapter';
7
8
// Vue3 imports
9
import { createI18n } from 'vue-i18n_v3';
10
import type { FallbackLocale, I18n } from 'vue-i18n_v3';
11
import type Router from 'vue-router_v3';
12
13
// Vue2 imports
14
import VueRouter from 'vue-router';
15
import VueI18n from 'vue-i18n';
16
import VueMeta from 'vue-meta';
17
18
import Vue, { createApp, defineAsyncComponent, h } from 'vue';
19
import type { AsyncComponent, Component as VueComponent, PluginObject } from 'vue';
20
import VuePlugins from 'src/app/plugin';
21
import setupShopwareDevtools from 'src/app/adapter/view/sw-vue-devtools';
22
import type ApplicationBootstrapper from 'src/core/application';
23
import type { ComponentConfig } from 'src/core/factory/async-component.factory';
24
import type { Store } from 'vuex';
25
26
const { Component, State, Mixin } = Shopware;
27
28
/**
29
 * @deprecated tag:v6.6.0 - Will be private
30
 */
31
export default class VueAdapter extends ViewAdapter {
32
    private resolvedComponentConfigs: Map<string, ComponentConfig>;
33
34
    private vueComponents: {
35
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
        [componentName: string]: VueComponent<any, any, any, any> | AsyncComponent<any, any, any, any>
37
    };
38
39
    private i18n?: I18n;
40
41
    public app;
42
43
    private vue3 = false;
44
45
    constructor(Application: ApplicationBootstrapper) {
46
        super(Application);
47
48
        this.i18n = undefined;
49
        this.resolvedComponentConfigs = new Map();
50
        this.vueComponents = {};
51
        // @ts-expect-error
52
        // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
53
        this.vue3 = !!window._features_?.vue3;
54
55
        if (this.vue3) {
56
            // eslint-disable-next-line @typescript-eslint/no-unsafe-call
57
            this.app = createApp({ name: 'ShopwareAdministration', template: '<sw-admin />' }) as Vue;
58
        }
59
    }
60
61
    /**
62
     * Creates the main instance for the view layer.
63
     * Is used on startup process of the main application.
64
     */
65
    // @ts-expect-error
66
    init(renderElement: string, router: Router, providers: { [key: string]: unknown }): Vue {
67
        if (this.vue3) {
68
            return this.initVue3(renderElement, router, providers);
69
        }
70
71
        return this.initVue2(renderElement, router as VueRouter, providers);
72
    }
73
74
    // @ts-expect-error
75
    initVue3(renderElement: string, router: Router, providers: { [key: string]: unknown }): Vue {
76
        this.initPlugins();
77
        this.initDirectives();
78
        this.initFilters();
79
        this.initTitle();
80
81
        const store = State._store;
82
        const i18n = this.initLocales(store) as VueI18n;
83
84
        // add router to View
85
        this.router = router as VueRouter;
86
        // add i18n to View
87
        this.i18n = i18n as I18n;
88
89
        // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
90
        this.app.config.compilerOptions.whitespace = 'preserve';
91
        // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
92
        this.app.config.performance = process.env.NODE_ENV !== 'production';
93
        // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access
94
        this.app.config.globalProperties.$t = i18n.global.t;
95
        // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access
96
        this.app.config.globalProperties.$tc = i18n.global.tc;
97
98
        /**
99
         * This is a hack for providing the services to the components.
100
         * We shouldn't use this anymore because it is not supported well
101
         * in Vue3 (because the services are lazy loaded).
102
         *
103
         * So we should convert from provide/inject to Shopware.Service
104
         */
105
        Object.keys(providers).forEach((provideKey) => {
106
            // @ts-expect-error
107
            // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
108
            Object.defineProperty(this.app._context.provides, provideKey, {
109
                get: () => providers[provideKey],
110
                enumerable: true,
111
                configurable: true,
112
                // eslint-disable-next-line @typescript-eslint/no-empty-function
113
                set() {},
114
            });
115
        });
116
117
        this.root = this.app;
118
119
        // eslint-disable-next-line @typescript-eslint/no-unsafe-call
120
        this.app.use(router);
121
        // eslint-disable-next-line @typescript-eslint/no-unsafe-call
122
        this.app.use(store);
123
        // eslint-disable-next-line @typescript-eslint/no-unsafe-call
124
        this.app.use(i18n);
125
126
        // Add global properties to root view instance
127
        // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access
128
        this.app.$tc = i18n.global.tc;
129
        // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access
130
        this.app.$t = i18n.global.t;
131
132
        /* eslint-disable max-len */
133
        // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access
134
        this.app.config.globalProperties.$createTitle = function createTitle(this: Vue, identifier: string|null = null, ...additionalParams): string {
135
            if (!this.$root) {
136
                return '';
137
            }
138
139
            const baseTitle = this.$root.$tc('global.sw-admin-menu.textShopwareAdmin');
140
141
            if (!this.$route.meta || !this.$route.meta.$module) {
142
                return '';
143
            }
144
145
            // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access
146
            const pageTitle = this.$root.$tc(this.$route.meta.$module.title);
147
148
            // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
149
            const params = [baseTitle, pageTitle, identifier, ...additionalParams].filter((item) => {
150
                // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
151
                return item !== null && item.trim() !== '';
152
            });
153
154
            return params.reverse().join(' | ');
155
        };
156
        /* eslint-enable max-len */
157
158
        // eslint-disable-next-line @typescript-eslint/no-unsafe-call
159
        this.app.mount(renderElement);
160
161
        if (process.env.NODE_ENV === 'development') {
162
            setupShopwareDevtools(this.root);
163
        }
164
165
        return this.root;
166
    }
167
168
    initVue2(renderElement: string, router: VueRouter, providers: { [key: string]: unknown }): Vue {
169
        this.initPlugins();
170
        this.initDirectives();
171
        this.initFilters();
172
        this.initTitle();
173
174
        const store = State._store;
175
        const i18n = this.initLocales(store);
176
        const components = this.getComponents();
177
178
        // add router to View
179
        this.router = router;
180
        // add i18n to View
181
        this.i18n = i18n;
182
183
        // Enable performance measurements in development mode
184
        Vue.config.performance = process.env.NODE_ENV !== 'production';
185
186
        this.root = new Vue({
187
            el: renderElement,
188
            template: '<sw-admin />',
189
            router,
190
            store,
191
            i18n,
192
            provide() {
193
                /**
194
                 * Vue 2.7 creates a new copy for each provided value. This caused problems with bottlejs.
195
                 * There should be only one instance of each provided value. Therefore we use a getter wrapper.
196
                 */
197
                return Object.keys(providers).reduce<{
198
                    [key: string]: unknown
199
                }>((acc, provideKey) => {
200
                    Object.defineProperty(acc, provideKey, {
201
                        get: () => providers[provideKey],
202
                        enumerable: true,
203
                        configurable: true,
204
                        // eslint-disable-next-line @typescript-eslint/no-empty-function
205
                        set() {},
206
                    });
207
208
                    return acc;
209
                }, {});
210
            },
211
            components,
212
            data() {
213
                return {
214
                    initError: {},
215
                };
216
            },
217
        });
218
219
        if (process.env.NODE_ENV === 'development') {
220
            setupShopwareDevtools(this.root);
221
        }
222
223
        return this.root;
224
    }
225
226
    /**
227
     * Initialize of all dependencies.
228
     */
229
    async initDependencies() {
230
        const initContainer = this.Application.getContainer('init');
231
232
        // make specific components synchronous
233
        [
234
            'sw-admin',
235
            'sw-admin-menu',
236
            'sw-button',
237
            'sw-button-process',
238
            'sw-card',
239
            'sw-card-section',
240
            'sw-card-view',
241
            'sw-container',
242
            'sw-desktop',
243
            'sw-empty-state',
244
            'sw-entity-listing',
245
            'sw-entity-multi-select',
246
            'sw-entity-multi-id-select',
247
            'sw-entity-single-select',
248
            'sw-error-boundary',
249
            'sw-extension-component-section',
250
            'sw-field',
251
            'sw-ignore-class',
252
            'sw-loader',
253
            'sw-modal',
254
            'sw-multi-select',
255
            'sw-notification-center',
256
            'sw-notifications',
257
            'sw-page',
258
            'sw-router-link',
259
            'sw-search-bar',
260
            'sw-select-result',
261
            'sw-single-select',
262
            'sw-skeleton',
263
            'sw-skeleton-bar',
264
            'sw-tabs',
265
            'sw-tabs-item',
266
            'sw-version',
267
            /**
268
             * Quickfix for modules with refs and sync behavior.
269
             * They should be removed from the list in the future
270
             * when their async problems got fixed.
271
             */
272
            'sw-sales-channel-products-assignment-single-products',
273
            'sw-sales-channel-product-assignment-categories',
274
            'sw-sales-channel-products-assignment-dynamic-product-groups',
275
            'sw-upload-listener',
276
            'sw-media-list-selection-v2',
277
            'sw-media-list-selection-item-v2',
278
            'sw-settings-document-detail',
279
            'sw-settings-product-feature-sets-detail',
280
            'sw-system-config',
281
            'sw-settings-search-searchable-content',
282
        ].forEach(componentName => {
283
            Component.markComponentAsSync(componentName);
284
        });
285
286
        // initialize all components
287
        await this.initComponents();
288
289
        // initialize all module locales
290
        this.initModuleLocales();
291
292
        // initialize all module routes
293
        const allRoutes = this.applicationFactory.module.getModuleRoutes();
294
        // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
295
        initContainer.router.addModuleRoutes(allRoutes);
296
297
        // create routes for core and plugins
298
        // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
299
        initContainer.router.createRouterInstance();
300
    }
301
302
303
    /**
304
     * Initializes all core components as Vue components.
305
     */
306
    async initComponents() {
307
        const componentRegistry = this.componentFactory.getComponentRegistry();
308
        this.componentFactory.resolveComponentTemplates();
309
310
        const initializedComponents = [...componentRegistry.keys()].map((name) => {
311
            return this.createComponent(name);
312
        });
313
314
        await Promise.all(initializedComponents);
315
316
        return this.vueComponents;
317
    }
318
319
    /**
320
     * Initializes all core components as Vue components.
321
     */
322
    initModuleLocales() {
323
        // Extend default snippets with module specific snippets
324
        const moduleSnippets = this.applicationFactory.module.getModuleSnippets();
325
326
        Object.entries(moduleSnippets).forEach(([key, moduleSnippet]) => {
327
            this.applicationFactory.locale.extend(key, moduleSnippet);
328
        });
329
330
        return this.applicationFactory.locale;
331
    }
332
333
    /**
334
     * Returns the component as a Vue component.
335
     * Includes the full rendered template with all overrides.
336
     */
337
    createComponent(componentName: string): Promise<Vue> {
338
        return new Promise((resolve) => {
339
            // load sync components directly
340
            if (Component.isSyncComponent && Component.isSyncComponent(componentName)) {
341
                const resolvedComponent = this.componentResolver(componentName);
342
343
                if (resolvedComponent === undefined) {
344
                    return;
345
                }
346
347
                // eslint-disable-next-line @typescript-eslint/no-unsafe-call
348
                void resolvedComponent.then((component) => {
349
                    let vueComponent;
350
351
                    if (this.vue3) {
352
                        // eslint-disable-next-line @typescript-eslint/no-unsafe-call
353
                        this.app.component(componentName, component);
354
                        // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment
355
                        vueComponent = this.app.component(componentName);
356
                    } else {
357
                        // @ts-expect-error - resolved config does not match completely a standard vue component
358
                        // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
359
                        vueComponent = Vue.component(componentName, component);
360
                    }
361
362
363
                    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
364
                    this.vueComponents[componentName] = vueComponent;
365
                    resolve(vueComponent as unknown as Vue);
366
                });
367
368
                return;
369
            }
370
371
            // load async components
372
            let vueComponent;
373
            if (this.vue3) {
374
                // eslint-disable-next-line @typescript-eslint/no-unsafe-call
375
                this.app.component(componentName, defineAsyncComponent({
376
                    // the loader function
377
                    loader: () => this.componentResolver(componentName),
378
                    // Delay before showing the loading component. Default: 200ms.
379
                    delay: 0,
380
                    loadingComponent: {
381
                        name: 'async-loading-component',
382
                        inheritAttrs: false,
383
                        render() {
384
                            return h('div', {
385
                                style: { display: 'none' },
386
                            });
387
                        },
388
                    },
389
                }));
390
391
                // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call
392
                vueComponent = this.app.component(componentName);
393
            } else {
394
                vueComponent = Vue.component(componentName, () => this.componentResolver(componentName));
395
            }
396
397
            // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
398
            this.vueComponents[componentName] = vueComponent;
399
400
            resolve(vueComponent as unknown as Vue);
401
        });
402
    }
403
404
    componentResolver(componentName: string) {
405
        if (!this.resolvedComponentConfigs.has(componentName)) {
406
            this.resolvedComponentConfigs.set(componentName, new Promise((resolve) => {
407
                void Component.build(componentName).then((componentConfig) => {
408
                    this.resolveMixins(componentConfig);
409
410
                    resolve(componentConfig);
411
                });
412
            }));
413
        }
414
415
        return this.resolvedComponentConfigs.get(componentName);
416
    }
417
418
    /**
419
     * Builds and creates a Vue component using the provided component configuration.
420
     */
421
    buildAndCreateComponent(componentConfig: ComponentConfig) {
422
        if (!componentConfig.name) {
423
            throw new Error('Component name is missing');
424
        }
425
426
        const componentName = componentConfig.name;
427
        this.resolveMixins(componentConfig);
428
429
        // @ts-expect-error - resolved config does not match completely a standard vue component
430
        const vueComponent = Vue.component(componentConfig.name, componentConfig);
431
        this.vueComponents[componentName] = vueComponent;
432
433
        return vueComponent;
434
    }
435
436
    /**
437
     * Returns a final Vue component by its name.
438
     */
439
    getComponent(componentName: string) {
440
        if (!this.vueComponents[componentName]) {
441
            return null;
442
        }
443
444
        return this.vueComponents[componentName] as Vue;
445
    }
446
447
    /**
448
     * Returns a final Vue component by its name without defineAsyncComponent
449
     * which cannot be used in the router.
450
     */
451
    getComponentForRoute(componentName: string) {
452
        return () => this.componentResolver(componentName);
453
    }
454
455
    /**
456
     * Returns the complete set of available Vue components.
457
     */
458
    // @ts-expect-error - resolved config for each component does not match completely a standard vue component
459
    getComponents() {
460
        return this.vueComponents;
461
    }
462
463
    /**
464
     * Returns the adapter wrapper
465
     */
466
    getWrapper() {
467
        return Vue;
468
    }
469
470
    /**
471
     * Returns the name of the adapter
472
     */
473
    getName(): string {
474
        return 'Vue.js';
475
    }
476
477
    /**
478
     * Returns the Vue.set function
479
     */
480
    setReactive(target: Vue, propertyName: string, value: unknown) {
481
        return Vue.set(target, propertyName, value);
482
    }
483
484
    /**
485
     * Returns the Vue.delete function
486
     */
487
    deleteReactive(target: Vue, propertyName: string) {
488
        return Vue.delete(target, propertyName);
489
    }
490
491
    /**
492
     * Private methods
493
     */
494
495
    /**
496
     * Initialises all plugins for VueJS
497
     *
498
     * @private
499
     */
500
    initPlugins() {
501
        // placeholder variable because import is not filterable
502
        let plugins = VuePlugins;
503
504
        if (!this.vue3) {
505
            // Add the community plugins to the plugin list
506
            plugins.push(VueRouter, VueI18n, VueMeta);
507
508
            // Remove our meta info plugin because we use the vue-meta plugin with Vue 2
509
            plugins = plugins.filter((plugin) => {
510
                // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
511
                return !plugin?.isMetaInfoPluginInstalled;
512
            });
513
        }
514
515
        VuePlugins.forEach((plugin) => {
516
            // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
517
            if (plugin?.install?.installed) {
518
                return;
519
            }
520
521
            if (this.vue3) {
522
                // eslint-disable-next-line @typescript-eslint/no-unsafe-call
523
                this.app.use(plugin as PluginObject<unknown>);
524
            } else {
525
                Vue.use(plugin as PluginObject<unknown>);
526
            }
527
        });
528
529
        return true;
530
    }
531
532
    /**
533
     * Initializes all custom directives.
534
     *
535
     * @private
536
     */
537
    initDirectives() {
538
        const registry = this.Application.getContainer('factory').directive.getDirectiveRegistry();
539
540
        registry.forEach((directive, name) => {
541
            Vue.directive(name, directive);
542
        });
543
544
        return true;
545
    }
546
547
    /**
548
     * Initialises helpful filters for global use
549
     *
550
     * @private
551
     */
552
    initFilters() {
553
        const registry = this.Application.getContainer('factory').filter.getRegistry();
554
555
        registry.forEach((factoryMethod, name) => {
556
            Vue.filter(name, factoryMethod);
557
        });
558
559
        return true;
560
    }
561
562
    /**
563
     * Initialises the standard locales.
564
     */
565
    initLocales(store: Store<VuexRootState>) {
566
        const registry = this.localeFactory.getLocaleRegistry();
567
        const messages = {};
568
        const fallbackLocale = Shopware.Context.app.fallbackLocale as FallbackLocale;
569
570
        registry.forEach((localeMessages, key) => {
571
            store.commit('registerAdminLocale', key);
572
            // @ts-expect-error - key is safe because we iterate through the registry
573
            messages[key] = localeMessages;
574
        });
575
576
        const lastKnownLocale = this.localeFactory.getLastKnownLocale();
577
        void store.dispatch('setAdminLocale', lastKnownLocale);
578
579
        const options = {
580
            locale: lastKnownLocale,
581
            fallbackLocale,
582
            silentFallbackWarn: true,
583
            sync: true,
584
            messages,
585
        };
586
587
        // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
588
        const i18n = window._features_?.vue3 ? createI18n(options) : new VueI18n(options);
589
590
        store.subscribe(({ type }, state) => {
591
            if (type === 'setAdminLocale') {
592
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
593
                i18n.locale = state.session.currentLocale!;
594
            }
595
        });
596
597
        this.setLocaleFromUser(store);
598
599
        return i18n;
600
    }
601
602
    setLocaleFromUser(store: Store<VuexRootState>) {
603
        const currentUser = store.state.session.currentUser;
604
605
        if (currentUser) {
606
            const userLocaleId = currentUser.localeId;
607
            // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
608
            Shopware.Service('localeHelper').setLocaleWithId(userLocaleId);
609
        }
610
    }
611
612
    /**
613
     * Extends Vue prototype to access $createTitle function
614
     *
615
     * @private
616
     */
617
    initTitle() {
618
        // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
619
        if (Vue.prototype.hasOwnProperty('$createTitle')) {
620
            return;
621
        }
622
623
        /**
624
         * Generates the document title out of the given VueComponent and parameters
625
         */
626
        // @ts-expect-error - additionalParams is not typed
627
        // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,max-len
628
        Vue.prototype.$createTitle = function createTitle(this: Vue, identifier: string|null = null, ...additionalParams): string {
629
            if (!this.$root) {
630
                return '';
631
            }
632
633
            const baseTitle = this.$root.$tc('global.sw-admin-menu.textShopwareAdmin');
634
635
            if (!this.$route.meta || !this.$route.meta.$module) {
636
                return '';
637
            }
638
639
            // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access
640
            const pageTitle = this.$root.$tc(this.$route.meta.$module.title);
641
642
            // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
643
            const params = [baseTitle, pageTitle, identifier, ...additionalParams].filter((item) => {
644
                // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
645
                return item !== null && item.trim() !== '';
646
            });
647
648
            return params.reverse().join(' | ');
649
        };
650
    }
651
652
    /**
653
     * Recursively resolves mixins referenced by name
654
     *
655
     * @private
656
     */
657
    resolveMixins(componentConfig: ComponentConfig) {
658
        // If the mixin is a string, use our mixin registry
659
        if (componentConfig.mixins?.length) {
660
            componentConfig.mixins = componentConfig.mixins.map((mixin) => {
661
                if (typeof mixin === 'string') {
662
                    return Mixin.getByName(mixin);
663
                }
664
665
                return mixin;
666
            });
667
        }
668
669
        if (componentConfig.extends) {
670
            // @ts-expect-error - extends can be a string or a component config
671
            this.resolveMixins(componentConfig.extends);
672
        }
673
    }
674
}
675