@podium/layout
Version:
Module for composing full page layouts out of page fragments in a micro frontend architecture.
593 lines (587 loc) • 23.8 kB
TypeScript
declare global {
namespace Express {
export interface PodiumHttpIncomingParameters {
[key: string]: unknown;
}
/**
* In a layout setting the values on the context are serialized to HTTP headers
* and mainly intended as an "outbox" for information that is sent to the podlet.
*/
export interface PodiumHttpIncomingContext {
/**
* @see https://podium-lib.io/docs/guides/context#default-context-variables
*/
'podium-debug'?: string;
/**
* Does user agent sniffing to try to guess the visitor's device type.
* @see https://podium-lib.io/docs/guides/context#default-context-variables
*/
'podium-device-type'?: string;
/**
* Locale information from the layout.
* @see https://podium-lib.io/docs/guides/context#default-context-variables
*/
'podium-locale'?: string;
/**
* Used to calculate the podlet's public URL when proxied behind a layout.
* @see https://podium-lib.io/docs/guides/context#construct-public-urls
*/
'podium-mount-origin'?: string;
/**
* Used to calculate the podlet's public URL when proxied behind a layout.
* @see https://podium-lib.io/docs/guides/context#construct-public-urls
*/
'podium-mount-pathname'?: string;
/**
* Used to calculate the podlet's public URL when proxied behind a layout.
* @see https://podium-lib.io/docs/guides/context#construct-public-urls
*/
'podium-public-pathname'?: string;
/**
* Name of the caller.
*/
'podium-requested-by'?: string;
[key: string]: unknown;
}
export interface PodiumHttpIncomingViewParameters {
[key: string]: unknown;
}
export interface Locals {
podium: HttpIncoming<
PodiumHttpIncomingParameters,
PodiumHttpIncomingContext,
PodiumHttpIncomingViewParameters
>;
}
export interface Response {
/**
* This method wraps the provided fragment in a default HTML document before dispatching.
*
* @param markup The HTML contents of the document
* @param args Parameters sent to the template function
*
* @see https://podium-lib.io/docs/api/layout#respodiumsendfragment
*/
podiumSend(fragment: string | TemplateResult, ...args: unknown[]): Response;
}
}
}
/**
* @typedef {(...args: any) => void} LogFunction
*
* @typedef {Object} LayoutOptions
* @property {string} name - (required) layout name
* @property {string} pathname - (required) layout pathname
* @property {import('abslog').AbstractLoggerOptions} [logger] - A logger to use when provided. Can be the console object if console logging is desired but can also be any Log4j compatible logging object as well. Nothing is logged if no logger is provided. (default null)
* @property {LayoutContext} [context] - Options to be passed on to the internal `@podium/context` constructor. See that module for details. (default null)
* @property {LayoutClientOptions} [client] - Options to be passed on to the internal `@podium/client` constructor. See that module for details. (default null)
* @property {import('@podium/proxy').PodiumProxyOptions} [proxy] - Options to be passed on to the internal `@podium/proxy` constructor. See that module for details. (default null)
*
*
* @typedef {Object} LayoutContext
* @property {{enabled: boolean}} [debug] - Config object passed on to the debug parser. (default { enabled: false })
* @property {Object} [locale] - Config object passed on to the locale parser.
* @property {Object} [deviceType] - Config object passed on to the device type parser.
* @property {Object} [mountOrigin] - Config object passed on to the mount origin parser.
* @property {Object} [mountPathname] - Config object passed on to the mount pathname parser.
* @property {Object} [publicPathname] - Config object passed on to the public pathname parser.
*
* @typedef {Object} LayoutClientOptions
* @property {number} [retries=4] - Number of times the client should retry settling a version number conflict before terminating. (default 4)
* @property {number} [timeout=1000] - Default value, in milliseconds, for how long a request should wait before the connection is terminated. (default 1000)
* @property {number} [maxAge=Infinity] - Default value, in milliseconds, for how long manifests should be cached. (default Infinity)
*
* @typedef {{ as?: string | false | null, crossorigin?: string | null | boolean, disabled?: boolean | '' | null, hreflang?: string | false | null, title?: string | false | null, media?: string | false | null, rel?: string | false | null, type?: string | false | null, value: string | false | null, data?: Array<{ key: string; value: string }>, strategy?: "beforeInteractive" | "afterInteractive" | "lazy", scope?: "content" | "fallback" | "all", [key: string]: any }} AssetCssLike
* @typedef {{ value: string | null, crossorigin?: string | null | boolean, type?: string | null | false, integrity?: string | null | false, referrerpolicy?: string | null | false, nomodule?: boolean | null | '', async?: boolean | null | '', defer?: boolean | null | '', data?: Array<{ key: string; value: string }>, strategy?: "beforeInteractive" | "afterInteractive" | "lazy", scope?: "content" | "fallback" | "all", [key: string]: any }} AssetJsLike
*/
export default class PodiumLayout {
constructor({ name, pathname, logger, context, client, proxy, }: {
name?: string;
pathname?: string;
logger?: any;
context?: {};
client?: {};
proxy?: {};
});
/**
* Name that the layout identifies itself by (set in the constructor).
* This value must be in camelCase.
*
* @see https://podium-lib.io/docs/api/layout/#name
*
* @example ```js
* const layout = new Layout({
* name: 'myLayoutName',
* pathname: '/foo',
* });
* ```
*/
name: string;
/**
* A logger. The abstract logger "Abslog" is used to make it possible to provide different kinds of loggers.
* The logger can be provided via the 'logger' constructor argument.
*
* @see https://www.npmjs.com/package/abslog
*
* @example ```js
* const layout = new Layout({ logger: console, ... });
* layout.log.trace('trace log to the console')
* layout.log.debug('debug log to the console')
* layout.log.info('info log to the console')
* layout.log.warn('warn log to the console')
* layout.log.error('error log to the console')
* layout.log.fatal('fatal log to the console')
* ```
*
* @type {import('abslog').ValidLogger}
*/
log: import("abslog").ValidLogger;
/**
* An instance of the `@podium/proxy` module
* @see https://github.com/podium-lib/proxy
*
* @type{import("@podium/proxy").default}
*/
httpProxy: import("@podium/proxy").default;
/**
* Options to be passed on to the context parsers. See the `@podium/context` module for details.
*
* @example ```js
* const layout = new Layout({
* name: 'myLayout',
* pathname: '/foo',
* context: {
* debug: {
* enabled: true,
* },
* },
* });
* ```
*
* @see https://podium-lib.io/docs/api/layout/#context
* @see https://podium-lib.io/docs/layout/context
* @see https://podium-lib.io/docs/podlet/context
*
* @type {Context}
*/
context: Context;
/**
* Property that holds information about a Cascading Style Sheet related to a layout.
*
* @see https://podium-lib.io/docs/api/assets#assetcss
* @see https://podium-lib.io/docs/api/assets
*
* @example ```js
* const podlet = layout.client.register({
* name: 'myPodlet',
* uri: 'http://localhost:7100/manifest.json',
* });
*
* app.get(layout.pathname(), async (req, res, next) => {
* const incoming = res.locals.podium;
*
* const response = await podlet.fetch(incoming);
*
* console.log(incoming.css) // array with the layouts and podlets AssetCSS objects *
*
* [ ... ]
* });
* ```
*
* @type {AssetCss[]}
*/
cssRoute: AssetCss[];
/**
* Property that holds information about a layout's Javascript client side assets.
*
* @see https://podium-lib.io/docs/api/assets#assetjs
* @see https://podium-lib.io/docs/api/assets
*
* @example ```js
* const podlet = layout.client.register({
* name: 'myPodlet',
* uri: 'http://localhost:7100/manifest.json',
* });
*
* app.get(layout.pathname(), async (req, res, next) => {
* const incoming = res.locals.podium;
*
* const response = await podlet.fetch(incoming);
*
* console.log(response.js) // array with the podlets AssetJS objects
*
* [ ... ]
* });
* ```
*
* @type {AssetJs[]}
*/
jsRoute: AssetJs[];
/**
* Metrics client stream object that can be used to consume metrics out of a Podium layout.
* @see https://www.npmjs.com/package/@metrics/client for detailed documentation
*
* @example
* ```js
* const layout = new Layout(...);
* layout.metrics.pipe(...);
* // or
* layout.metrics.on('data', chunk => { ... });
* ```
*
* @type {import("@metrics/client")}
*/
metrics: import("@metrics/client");
client: Client;
/**
* Set relative or absolute URLs to Cascading Style Sheets (CSS) assets for the layout.
* When set the values will be internally kept and made available for the document template to include.
* This method can be called multiple times with a single options object to set multiple assets or one can provide an array of options objects to set multiple assets.
* @see https://podium-lib.io/docs/api/layout/#cssoptionsoptions
* @see https://podium-lib.io/docs/api/assets#assetcss
* @see https://podium-lib.io/docs/api/assets
*
* @example ```js
* const app = express();
* const layout = new Layout({
* name: 'myLayout',
* pathname: '/',
* });
*
* app.get('/assets.css', (req, res) => {
* res.status(200).sendFile('./src/js/main.css', err => {});
* });
*
* layout.css({ value: '/assets.css' });
* ```
*
* @param { AssetCss | AssetCss[] | AssetCssLike | AssetCssLike[] } options
* @returns {void}
*/
css(options: AssetCss | AssetCss[] | AssetCssLike | AssetCssLike[]): void;
/**
* Set relative or absolute URLs to JavaScript assets for the layout.
* When set, the values will be internally kept and made available for the document template to include.
* This method can be called multiple times with a single options object to set multiple assets or one can provide an array of options objects to set multiple assets.
*
* @see https://podium-lib.io/docs/api/layout/#jsoptionsoptions
* @see https://podium-lib.io/docs/api/assets#assetjs
* @see https://podium-lib.io/docs/api/assets
*
* @example ```js
* const app = express();
* const layout = new Layout({
* name: 'myLayout',
* pathname: '/',
* });
*
* app.get('/assets.js', (req, res) => {
* res.status(200).sendFile('./src/js/main.js', err => {});
* });
*
* layout.js({ value: '/assets.js' });
* ```
*
* @param {AssetJs | AssetJs[] | AssetJsLike | AssetJsLike[] } [options]
* @returns {void}
*/
js(options?: AssetJs | AssetJs[] | AssetJsLike | AssetJsLike[]): void;
/**
* Sets the default document template.
* Takes a template function that accepts an instance of HttpIncoming, a content string as well as any additional markup for the document's head section:
* ```js
* (incoming, body, head) => `Return an HTML string here`;
* ```
*
* @see https://podium-lib.io/docs/api/layout/#viewtemplate
* @see https://podium-lib.io/docs/api/document
*
* @example
* A document template can be provided using the layout.view method
* ```js
* layout.view((incoming, body, head) => `<!doctype html>
* <html lang="${incoming.context.locale}">
* <head>
* <meta charset="${incoming.view.encoding}">
* <title>${incoming.view.title}</title>
* ${head}
* </head>
* <body>
* ${body}
* </body>
* </html>`;
* );
* ```
*
* @template {{ [key: string]: unknown }} T
* @param {( incoming: HttpIncoming<T>, fragment: string, ...args: unknown[]) => string} fn
* @returns {void}
*/
view<T extends {
[key: string]: unknown;
}>(fn: (incoming: HttpIncoming<T>, fragment: string, ...args: unknown[]) => string): void;
/**
* Method to render the document template. By default this will render a default document template provided by Podium unless a custom one is set by using the .view method.
* In most HTTP frameworks this method can be ignored in favour of res.podiumSend(). If present, res.podiumSend() has the advantage that it's not necessary to pass in HttpIncoming as the first argument.
* @see https://podium-lib.io/docs/api/layout#renderhttpincoming-fragment-args
*
* @example
* ```js
* layout.view = (incoming, body, head) => {
* return `
* <html>
* <head>${head}</head>
* <body>${body}</body>
* </html>
* `;
* };
*
* app.get(layout.pathname(), (req, res) => {
* const incoming = res.locals.podium;
*
* const head = `<meta ..... />`;
* const body = `<section>my content</section>`;
*
* const document = layout.render(incoming, body, head);
*
* res.send(document);
* });
* ```
*
* @template {{ [key: string]: unknown }} T
* @param {HttpIncoming<T>} incoming - Instance of Podium HttpIncoming object
* @param {string | TemplateResult} data - the content as an HTML markup string
* @param {...any} args - additional args depending on the template and what values it accepts
* @returns {string}
*/
render<T extends {
[key: string]: unknown;
}>(incoming: HttpIncoming<T>, data: string | TemplateResult, ...args: any[]): string;
/**
* Method for processing an incoming HTTP request. This method is intended to be used to implement support for multiple HTTP frameworks and in most cases it won't be necessary for layout developers to use this method directly when creating a layout server.
* What it does:
* * Runs context parsers on the incoming request and sets an object with the context at HttpIncoming.context which can be passed on to the client when requesting content from podlets.
* * Mounts a proxy so that each podlet can do transparent proxy requests as needed.
* * Returns a Promise which will resolve with the HttpIncoming object that was passed in.
*
* If the inbound request matches a proxy endpoint the returned Promise will resolve with a HttpIncoming object where the .proxy property is set to true.
*
* @see https://podium-lib.io/docs/api/layout#processhttpincoming
* @see https://podium-lib.io/docs/api/incoming
*
* @example
* ```js
* import { HttpIncoming } from '@podium/utils';
* import Layout from '@podium/layout';
*
* const layout = new Layout({
* name: 'myLayout',
* pathname: '/',
* });
*
* const server = http.createServer(async (req, res) => {
* const incoming = new HttpIncoming(req, res);
*
* try {
* const result = await layout.process(incoming);
* if (result.proxy) return;
*
* res.statusCode = 200;
* res.setHeader('Content-Type', 'application/json');
* res.end(JSON.stringify(result));
* } catch (error) {
* res.statusCode = 500;
* res.setHeader('Content-Type', 'text/plain');
* res.end('Internal server error');
* }
* });
* ```
*
* @param {HttpIncoming} incoming
* @param {{ proxy?: boolean, context?: boolean }} [options]
* @returns {Promise<HttpIncoming>}
*/
process(incoming: HttpIncoming, { proxy, context }?: {
proxy?: boolean;
context?: boolean;
}): Promise<HttpIncoming>;
/**
* A Connect/Express compatible middleware function which takes care of the various operations needed for a layout to operate correctly. This function is more or less just a wrapper for the .process() method.
* The middleware will create an HttpIncoming object for each request and place it on the response at res.locals.podium.
*
* **Important:** *This middleware must be mounted before defining any routes.*
*
* @see https://podium-lib.io/docs/api/layout#middleware
*
* @example
* ```js
* const app = express();
* app.use(layout.middleware());
* ```
*
* @returns {(req: any, res: any, next: function) => Promise<void>}
*/
middleware(): (req: any, res: any, next: Function) => Promise<void>;
/**
* A helper method used to retrieve the pathname value that was set in the constructor. This can be handy when defining routes since the pathname set in the constructor must also be the base path for the layout's main content route
* (set in the constructor)
*
* @see https://podium-lib.io/docs/api/layout/#pathname
*
* @example
* The method returns the value of `pathname` as defined in the layout constructor
* ```js
* const layout = new Layout({ pathname: '/foo', ... });
* layout.pathname() // /foo
* ```
*
* @example
* This method is typically used when defining routes to ensure the pathname is prepended to any routes
* ```js
* const layout = new Layout({
* name: 'myLayout',
* pathname: '/foo',
* });
*
* app.get(layout.pathname(), (req, res, next) => {
* [ ... ]
* });
*
* app.get(`${layout.pathname()}/bar`, (req, res, next) => {
* [ ... ]
* });
*
* app.get(`${layout.pathname()}/bar/:id`, (req, res, next) => {
* [ ... ]
* });
* ```
*
* @returns {string}
*/
pathname(): string;
get [Symbol.toStringTag](): string;
#private;
}
export type PodiumClientResource = import("@podium/client").PodiumClientResource;
export type PodiumClientResourceOptions = import("@podium/client").PodiumClientResourceOptions;
export type PodiumClientResponse = import("@podium/client").PodiumClientResponse;
export type PodiumRedirect = import("@podium/client").PodiumRedirect;
export type PodletManifest = import("@podium/client").PodletManifest;
export type RegisterOptions = import("@podium/client").RegisterOptions;
export type AssetCss = import("@podium/utils").AssetCss;
export type AssetJs = import("@podium/utils").AssetJs;
export type PodiumHttpIncoming = import("@podium/utils").PodiumHttpIncoming;
export type PodiumCssAsset = import("@podium/utils").PodiumCssAsset;
export type PodiumJavaScriptAsset = import("@podium/utils").PodiumJavaScriptAsset;
export type HttpIncoming<T extends Record<string, unknown> = Record<string, unknown>, U extends Record<string, unknown> = Record<string, unknown>, V extends Record<string, unknown> = Record<string, unknown>> = import("@podium/utils").HttpIncoming<T, U, V>;
export type LogFunction = (...args: any) => void;
export type LayoutOptions = {
/**
* - (required) layout name
*/
name: string;
/**
* - (required) layout pathname
*/
pathname: string;
/**
* - A logger to use when provided. Can be the console object if console logging is desired but can also be any Log4j compatible logging object as well. Nothing is logged if no logger is provided. (default null)
*/
logger?: import("abslog").AbstractLoggerOptions;
/**
* - Options to be passed on to the internal `@podium/context` constructor. See that module for details. (default null)
*/
context?: LayoutContext;
/**
* - Options to be passed on to the internal `@podium/client` constructor. See that module for details. (default null)
*/
client?: LayoutClientOptions;
/**
* - Options to be passed on to the internal `@podium/proxy` constructor. See that module for details. (default null)
*/
proxy?: import("@podium/proxy").PodiumProxyOptions;
};
export type LayoutContext = {
/**
* - Config object passed on to the debug parser. (default { enabled: false })
*/
debug?: {
enabled: boolean;
};
/**
* - Config object passed on to the locale parser.
*/
locale?: any;
/**
* - Config object passed on to the device type parser.
*/
deviceType?: any;
/**
* - Config object passed on to the mount origin parser.
*/
mountOrigin?: any;
/**
* - Config object passed on to the mount pathname parser.
*/
mountPathname?: any;
/**
* - Config object passed on to the public pathname parser.
*/
publicPathname?: any;
};
export type LayoutClientOptions = {
/**
* - Number of times the client should retry settling a version number conflict before terminating. (default 4)
*/
retries?: number;
/**
* - Default value, in milliseconds, for how long a request should wait before the connection is terminated. (default 1000)
*/
timeout?: number;
/**
* - Default value, in milliseconds, for how long manifests should be cached. (default Infinity)
*/
maxAge?: number;
};
export type AssetCssLike = {
as?: string | false | null;
crossorigin?: string | null | boolean;
disabled?: boolean | "" | null;
hreflang?: string | false | null;
title?: string | false | null;
media?: string | false | null;
rel?: string | false | null;
type?: string | false | null;
value: string | false | null;
data?: Array<{
key: string;
value: string;
}>;
strategy?: "beforeInteractive" | "afterInteractive" | "lazy";
scope?: "content" | "fallback" | "all";
[key: string]: any;
};
export type AssetJsLike = {
value: string | null;
crossorigin?: string | null | boolean;
type?: string | null | false;
integrity?: string | null | false;
referrerpolicy?: string | null | false;
nomodule?: boolean | null | "";
async?: boolean | null | "";
defer?: boolean | null | "";
data?: Array<{
key: string;
value: string;
}>;
strategy?: "beforeInteractive" | "afterInteractive" | "lazy";
scope?: "content" | "fallback" | "all";
[key: string]: any;
};
import { html } from '@podium/utils';
import { escape } from '@podium/utils';
import { DangerouslyIncludeUnescapedHTML } from '@podium/utils';
import { TemplateResult } from '@podium/utils';
import Context from '@podium/context';
import Client from '@podium/client';
export { html, escape, DangerouslyIncludeUnescapedHTML, TemplateResult };