@analogjs/router
Version: 
Filesystem-based routing for Angular
208 lines (201 loc) • 7.08 kB
JavaScript
import { ɵresetCompiledComponents as _resetCompiledComponents, InjectionToken, assertInInjectionContext, inject, TransferState, makeStateKey, reflectComponentType, APP_ID, ɵConsole as _Console, enableProdMode } from '@angular/core';
import { ɵSERVER_CONTEXT as _SERVER_CONTEXT, provideServerRendering, renderApplication } from '@angular/platform-server';
import { REQUEST, RESPONSE, BASE_URL } from '@analogjs/router/tokens';
import { bootstrapApplication } from '@angular/platform-browser';
import { getHeader, createEvent, readBody } from 'h3';
function provideServerContext({ req, res, }) {
    const baseUrl = getBaseUrl(req);
    if (import.meta.env.DEV) {
        _resetCompiledComponents();
    }
    return [
        { provide: _SERVER_CONTEXT, useValue: 'ssr-analog' },
        { provide: REQUEST, useValue: req },
        { provide: RESPONSE, useValue: res },
        { provide: BASE_URL, useValue: baseUrl },
    ];
}
function getBaseUrl(req) {
    const protocol = getRequestProtocol(req);
    const { originalUrl, headers } = req;
    const parsedUrl = new URL('', `${protocol}://${headers.host}${originalUrl.endsWith('/')
        ? originalUrl.substring(0, originalUrl.length - 1)
        : originalUrl}`);
    const baseUrl = parsedUrl.origin;
    return baseUrl;
}
function getRequestProtocol(req, opts = {}) {
    if (opts.xForwardedProto !== false &&
        req.headers['x-forwarded-proto'] === 'https') {
        return 'https';
    }
    return req.connection?.encrypted ? 'https' : 'http';
}
const STATIC_PROPS = new InjectionToken('Static Props');
function provideStaticProps(props) {
    return {
        provide: STATIC_PROPS,
        useFactory() {
            return props;
        },
    };
}
function injectStaticProps() {
    assertInInjectionContext(injectStaticProps);
    return inject(STATIC_PROPS);
}
function injectStaticOutputs() {
    const transferState = inject(TransferState);
    const outputsKey = makeStateKey('_analog_output');
    return {
        set(data) {
            transferState.set(outputsKey, data);
        },
    };
}
function serverComponentRequest(serverContext) {
    const serverComponentId = getHeader(createEvent(serverContext.req, serverContext.res), 'X-Analog-Component');
    if (!serverComponentId &&
        serverContext.req.url &&
        serverContext.req.url.startsWith('/_analog/components')) {
        const componentId = serverContext.req.url.split('/')?.[3];
        return componentId;
    }
    return serverComponentId;
}
const components = import.meta.glob([
    '/src/server/components/**/*.{ts,analog,ag}',
]);
async function renderServerComponent(url, serverContext, config) {
    const componentReqId = serverComponentRequest(serverContext);
    const { componentLoader, componentId } = getComponentLoader(componentReqId);
    if (!componentLoader) {
        return new Response(`Server Component Not Found ${componentId}`, {
            status: 404,
        });
    }
    const component = (await componentLoader())['default'];
    if (!component) {
        return new Response(`No default export for ${componentId}`, {
            status: 422,
        });
    }
    const mirror = reflectComponentType(component);
    const selector = mirror?.selector.split(',')?.[0] || 'server-component';
    const event = createEvent(serverContext.req, serverContext.res);
    const body = (await readBody(event)) || {};
    const appId = `analog-server-${selector.toLowerCase()}-${new Date().getTime()}`;
    const bootstrap = (context) => bootstrapApplication(component, {
        providers: [
            provideServerRendering(),
            provideStaticProps(body),
            { provide: _SERVER_CONTEXT, useValue: 'analog-server-component' },
            {
                provide: APP_ID,
                useFactory() {
                    return appId;
                },
            },
            ...(config?.providers || []),
        ],
    }, context);
    const html = await renderApplication(bootstrap, {
        url,
        document: `<${selector}></${selector}>`,
        platformProviders: [
            {
                provide: _Console,
                useFactory() {
                    return {
                        warn: () => { },
                        log: () => { },
                    };
                },
            },
        ],
    });
    const outputs = retrieveTransferredState(html, appId);
    const responseData = {
        html,
        outputs,
    };
    return new Response(JSON.stringify(responseData), {
        headers: {
            'X-Analog-Component': 'true',
        },
    });
}
function getComponentLoader(componentReqId) {
    let _componentId = `/src/server/components/${componentReqId.toLowerCase()}`;
    let componentLoader = undefined;
    let componentId = _componentId;
    if (components[`${_componentId}.ts`]) {
        componentId = `${_componentId}.ts`;
        componentLoader = components[componentId];
    }
    else if (components[`${componentId}.analog`]) {
        componentId = `${_componentId}.analog`;
        componentLoader = components[componentId];
    }
    else if (components[`${componentId}.ag`]) {
        componentId = `${_componentId}.ag`;
        componentLoader = components[componentId];
    }
    return { componentLoader, componentId };
}
function retrieveTransferredState(html, appId) {
    const regex = new RegExp(`<script id="${appId}-state" type="application/json">(.*?)<\/script>`);
    const match = html.match(regex);
    if (match) {
        const scriptContent = match[1];
        if (scriptContent) {
            try {
                const parsedContent = JSON.parse(scriptContent);
                return parsedContent._analog_output || {};
            }
            catch (e) {
                console.warn('Exception while parsing static outputs for ' + appId, e);
            }
        }
        return {};
    }
    else {
        return {};
    }
}
if (import.meta.env.PROD) {
    enableProdMode();
}
/**
 * Returns a function that accepts the navigation URL,
 * the root HTML, and server context.
 *
 * @param rootComponent
 * @param config
 * @param platformProviders
 * @returns Promise<string | Reponse>
 */
function render(rootComponent, config, platformProviders = []) {
    function bootstrap(context) {
        return bootstrapApplication(rootComponent, config, context);
    }
    return async function render(url, document, serverContext) {
        if (serverComponentRequest(serverContext)) {
            return await renderServerComponent(url, serverContext);
        }
        const html = await renderApplication(bootstrap, {
            document,
            url,
            platformProviders: [
                provideServerContext(serverContext),
                platformProviders,
            ],
        });
        return html;
    };
}
/**
 * Generated bundle index. Do not edit.
 */
export { injectStaticOutputs, injectStaticProps, provideServerContext, render, renderServerComponent, serverComponentRequest };
//# sourceMappingURL=analogjs-router-server.mjs.map