@analogjs/router
Version:
Filesystem-based routing for Angular
200 lines (193 loc) • 6.78 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];
}
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