All files micro-app.ts

97.64% Statements 83/85
97.36% Branches 37/38
100% Functions 5/5
97.64% Lines 83/85

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102    2x                 2x 85x 85x 85x   85x 1x 1x 85x 23x 23x 23x     23x 85x 74x 74x 85x 85x   2x 433x 433x 433x   433x 670x 670x 481x 481x 189x   670x 670x 166x 166x 68x 68x 68x 1x 1x 68x 166x 166x 166x 66x 66x 166x 166x 166x 97x 97x 166x 188x 188x 670x   433x 676x 676x 676x 676x 3x 3x 673x 673x 673x 194x 193x 676x 192x 192x 506x 2x 2x 506x 1x 1x 478x 676x   433x 341x 341x 341x 341x 341x 341x 433x  
import type { Router } from './router';
import type { RouterMicroAppCallback, RouterMicroAppOptions } from './types';
import { isBrowser, isPlainObject } from './util';
 
/**
 * Resolves the root container element.
 * Supports a DOM selector string or a direct HTMLElement.
 *
 * @param rootConfig - The root container configuration, can be a selector string or an HTMLElement.
 * @returns The resolved HTMLElement.
 */
export function resolveRootElement(
    rootConfig?: string | HTMLElement
): HTMLElement {
    let el: HTMLElement | null = null;
    // Direct HTMLElement provided
    if (rootConfig instanceof HTMLElement) {
        el = rootConfig;
    }
    if (typeof rootConfig === 'string' && rootConfig) {
        try {
            el = document.querySelector(rootConfig);
        } catch (error) {
            console.warn(`Failed to resolve root element: ${rootConfig}`);
        }
    }
    if (el === null) {
        el = document.createElement('div');
    }
    return el;
}
 
export class MicroApp {
    public app: RouterMicroAppOptions | null = null;
    public root: HTMLElement | null = null;
    private _factory: RouterMicroAppCallback | null = null;
 
    public _update(router: Router, force = false) {
        const factory = this._getNextFactory(router);
        if (!force && factory === this._factory) {
            return;
        }
        const oldApp = this.app;
        // Create the new application
        const app = factory ? factory(router) : null;
        if (isBrowser && app) {
            let root: HTMLElement | null = this.root;
            if (root === null) {
                root = resolveRootElement(router.root);
                const { rootStyle } = router.parsedOptions;
                if (root && isPlainObject(rootStyle)) {
                    Object.assign(root.style, router.parsedOptions.rootStyle);
                }
            }
            if (root) {
                app.mount(root);
                if (root.parentNode === null) {
                    document.body.appendChild(root);
                }
                this.root = root;
            }
            if (oldApp) {
                oldApp.unmount();
            }
        }
        this.app = app;
        this._factory = factory;
    }
 
    private _getNextFactory({
        route,
        options
    }: Router): RouterMicroAppCallback | null {
        if (!route.matched || route.matched.length === 0) {
            return null;
        }
        const name = route.matched[0].app;
        if (
            typeof name === 'string' &&
            options.apps &&
            typeof options.apps === 'object'
        ) {
            return options.apps[name] || null;
        }
        if (typeof name === 'function') {
            return name;
        }
        if (typeof options.apps === 'function') {
            return options.apps;
        }
        return null;
    }
 
    public destroy() {
        this.app?.unmount();
        this.app = null;
        this.root?.remove();
        this.root = null;
        this._factory = null;
    }
}