UNPKG

wunphile

Version:

Simple, imperative JavaScript-based static site generator

442 lines (441 loc) 17.9 kB
/** * Template string function that takes in HTML and interpolates it with the provided values. * The values can be any type, but all values except render fragments or arrays of render fragments will be sanitized. * * @example * ```js * const greeting = html`<h1>Hello, ${name}!</h1>` * ``` * * @param {string|TemplateStringsArray} strs The string(s) to interpolate * @param {any[]} vals The values to interpolate * @returns {RenderFragments} The resulting render fragments */ export function html(strs: string | TemplateStringsArray, ...vals: any[]): RenderFragments; /** * Generates a plain text render fragment from a raw string. * The string's escape sequences will not be processed. * * @example * ```js * const greeting = text`Hello, ${name}!` * ``` * * @param {string|TemplateStringsArray} strs The string(s) to interpolate * @param {any[]} vals The values to interpolate * @returns {RenderFragments} The resulting plain text render fragment */ export function text(strs: string | TemplateStringsArray, ...vals: any[]): RenderFragments; /** * Renders the provided render fragments to HTML. * @param {RenderFragments} fragments The render fragments to render * @returns {string} The rendered HTML */ export function renderToHtml(fragments: RenderFragments): string; /** * Copies a directory recursively. * @param {string} source The source directory * @param {string} target The target directory * @returns {Promise<void>} */ export function copyRecursive(source: string, target: string): Promise<void>; /** * Sanitizes the provided HTML by escaping HTML entities * @param {string} html The HTML to sanitize * @returns {string} The sanitized HTML * @since 1.0.0 */ export function sanitizeHtml(html: string): string; /** * The name of the environment variable that specifies the root path. * @type {string} */ export const ROOT_PATH_ENV_VAR: string; /** * The name of the environment variable that specifies the output path. * @type {string} */ export const OUTPUT_PATH_ENV_VAR: string; /** * The name of the environment variable that specifies the path to use for the not found page. * @type {string} */ export const NOT_FOUND_PATH_ENV_VAR: string; /** * The name of the environment variable that specifies the development server port. * @type {string} */ export const DEV_PORT_ENV_VAR: string; /** * The name of the environment variable that specifies the development server host. * @type {string} */ export const DEV_HOST_ENV_VAR: string; /** * The name of the environment variable that specifies whether hot reloading is enabled in development mode. * @type {string} */ export const DEV_NO_HOT_RELOAD_ENV_VAR: string; /** * The name of the environment variable that specifies whether hot reloading is enabled in development mode. * @type {string} */ export const DEV_NO_INJECT_ENV_VAR: string; /** * The main class for the Wunphile library. * * @example * ```js * import { Wunphile } from 'wunphile' * import { IndexPage } from './components/Greeting.js' * * const ssg = new Wunphile(import.meta.url) * * ssg.page('/index.html', IndexPage) * * await ssg.cli() * ``` */ export class Wunphile { /** * Creates a new Wunphile instance. * * @example * ```js * import { Wunphile } from 'wunphile' * import { IndexPage } from './components/Greeting.js' * * const ssg = new Wunphile(import.meta.url) * * ssg.page('/index.html', IndexPage) * * await ssg.cli() * ``` * * @param {string} importMetaUrl The `import.meta.url` of the program main module. * @param {string} [clientDir='./src/client'] The path to the client directory. Client behaviors must be within this directory. * * Used to resolve the root path and for identifying the main module for hot reloading. */ constructor(importMetaUrl: string, clientDir?: string | undefined); /** * The path of the output directory to write the generated site to. * This is normally specified by the `SSG_OUTPUT_PATH` environment variable, or defaults to `dist` in the process's current working directory if not specified. * @returns {string} */ get outputPath(): string; /** * Preloads a behavior module. * The loading is done asynchronously, similarly to using an HTML `<link rel="modulepreload">` tag. * Calling this function will cause a behavior module to be included in the generated site, regardless of whether it is used in any pages. * * @example * ```js * ssg.preloadBehaviorModule(import('./client/behavior/Greeting.js')) * ``` * @param {Promise<{ default: BehaviorModule }>} promise The promise from the `import()` call for the behavior module */ preloadBehaviorModule(promise: Promise<{ default: BehaviorModule; }>): void; /** * Overrides the root path. * The root path is normally specified by the `SSG_ROOT_PATH` environment variable, or defaults to the directory of the program main module if not specified. * @param {string} path The root path * @returns {Wunphile} This */ overrideRootPath(path: string): Wunphile; /** * Overrides the output path. * The output path is normally specified by the `SSG_OUTPUT_PATH` environment variable, or defaults to `dist` relative to the root path if not specified. * @param {string} path The output path * @returns {Wunphile} This */ overrideOutputPath(path: string): Wunphile; /** * Overrides the not found path. * The not found path is normally specified by the `SSG_NOT_FOUND_PATH` environment variable, or defaults to `/404.html` if not specified. * @param {string} path The not found path * @returns {Wunphile} This */ overrideNotFoundPath(path: string): Wunphile; /** * Registers a component to a page path. * @param {string} path The page's path * @param {Component<void, void>} component The page's component */ page(path: string, component: Component<void, void>): void; /** * Registers raw content to a page path. * @param {string} path The page's path * @param {string} content The page's content */ pageRaw(path: string, content: string): void; /** * Registers a redirect from one path to another. * The redirect is done with an HTML file using a meta refresh tag and fallback JavaScript. * @param {string} path The path to redirect from * @param {string} url The URL/path to redirect to */ redirect(path: string, url: string): void; /** * Registers a component to be used as the not found page. * The not found page is configured via the `SSG_NOT_FOUND_PATH` environment variable, and defaults to `/404.html`. * @param {Component<void, void>} component The component to use for the not found page */ notFoundPage(component: Component<void, void>): void; /** * Registers a static directory to a path. * For example, if you have a directory named `site-assets` in your project and you want to serve it at the path `/assets` in the generated site, * you would call this method with `staticDir('/assets', './site-assets')`. * * @param {string} path The path to the static directory * @param {string} dir The directory to copy */ staticDir(path: string, dir: string): void; /** * Registers a static file to a path. * For example, if you have a file named `site-icon.ico` in your project and you want to serve it at the path `/favicon.ico` in the generated site, * you would call this method with `staticFile('/favicon.ico', './site-icon.ico')`. * * @param {string} path The path to the static file * @param {string} file The file to copy */ staticFile(path: string, file: string): void; /** * Returns the resolved path of the specified path relative to the project root path. * @param {string} path The path to resolve * @returns {string} The resolved project path */ toProjectPath(path: string): string; /** * Returns the resolved path of the specified path relative to the output path. * @param {string} path The path to resolve * @returns {string} The resolved output path */ toOutputPath(path: string): string; /** * Builds the site only. * This method will clear the output directory before building. * * The order in which paths are processed is as follows: * 1. Static directories * 2. Static files * 3. Pages * * If a path is specified multiple times, the last one will ultimately be used. * * Instead of calling this method directly, you can also call the {@link Wunphile#cli} method, which will determine whether * to build or watch the site depending on the environment and arguments, acting as a CLI. * * @returns {Promise<void>} */ build(): Promise<void>; /** * Serves the site on a local HTTP server. * * The site will be served without being built or touching the file system. * * This is not meant for production, only for local development. * Production sites should be built ahead of time, and their output should be * served on a web server such as Nginx or Caddy. * * The order in which paths are resolved is as follows: * 1. Pages * 2. Static files * 3. Static directories * * If a path is specified multiple times, the first one will ultimately be used. * This is the reverse of the order in which paths are processed when building the site, * which serves to mimic how the built version would be served statically. * * Instead of calling this method directly, you can also call the {@link Wunphile#cli} method, which will determine whether * to build or watch the site depending on the environment and arguments, acting as a CLI. * * @param {number} port The port to listen on * @param {string} [host='127.0.0.1'] The host to listen on (optional, defaults to `127.0.0.1`) * @param {EventEmitter<{ reload: [] }>|null} [hotReloadEventEmitter=null] The event emitter to emit hot reload events on (optional, defaults to `null`). * If null, no hot reload code will be injected into pages. * To trigger a hot reload, emit the `reload` event on the event emitter. * @returns {Promise<void>} */ serve(port: number, host?: string | undefined, hotReloadEventEmitter?: EventEmitter<{ reload: []; }> | null | undefined): Promise<void>; /** * Runs the site generator CLI. * The CLI can build the site or serve it on a local HTTP server for development. * * Call this only after all pages and static files have been registered. * It should be at the end of your main module. * * @example * ```js * import { Wunphile } from 'wunphile' * * const ssg = new Wunphile(import.meta.url) * * // Register pages and static files. * // ... * * await ssg.cli() * ``` * * @returns {Promise<void>} */ cli(): Promise<void>; #private; } /** * A render fragment is a string that can be rendered to HTML. * How it will be rendered depends on the fragment type. */ export class RenderFragment { /** * Creates render fragments from the provided value. * * If the value is a render fragment, an array with it as the only element will be returned. * If the value is an array, each fragment will be processed with this function and returned in an array. * If the value is anything else, it will be turned into a string with the {@link String} function and returned as a text fragment. * * @param {any} val The value to convert * @returns {RenderFragments} The resulting render fragments */ static from(val: any): RenderFragments; /** * Instantiates a new RenderFragment with the specified type and value. * @param {RenderFragmentType} type The fragment type * @param {string} value The fragment value * @param {Promise<any> | null} behaviorModulePromise The attached behavior module promise (defaults to null) */ constructor(type: RenderFragmentType, value: string, behaviorModulePromise?: Promise<any> | null); /** * The fragment type. * @type {RenderFragmentType} * @readonly */ readonly type: RenderFragmentType; /** * The fragment's value. * How it will be rendered depends on the fragment type. * @type {string} * @readonly */ readonly value: string; /** * The attached behavior module promise. * This is null if the fragment is not a behavior module import. * @type {Promise<any> | null} * @readonly */ readonly behaviorModulePromise: Promise<any> | null; /** * Renders the fragment to HTML. * @returns {string} */ toHtml(): string; } /** * Component that renders the behavior module loader. * This is required for any pages' behavior modules to be loaded on the browser. * You can use this component at the end of your page's body. * * @type {Component<void, void>} */ export const BehaviorLoader: Component<void, void>; /** * @typedef BehaviorModuleProps * The props type for a behavior module. * * @property {Promise<{ default: BehaviorModule }>} module The promise from the `import()` call for the behavior module. For example, `import('./client/behavior/Greeting.js')`. */ /** * Wraps a component in a behavior. * The first element in the children will be the element passed to the behavior. * * You must use {@link BehaviorLoader} at the end of your page's body, otherwise the behavior module will never be loaded on the browser. * @param {BehaviorModuleProps} props The component's props * @param {RenderFragments} children The component's children (should contain a single parent element) * @returns {RenderFragments} The rendered behavior module * * @type {Component<BehaviorModuleProps, RenderFragments>} */ export const BehaviorComponent: Component<BehaviorModuleProps, RenderFragments>; /** * An array of render fragments. */ export type RenderFragments = RenderFragment[]; /** * A component is a function that returns render fragments. * It can take in props and optionally children. */ export type Component<TProps, TChildren> = (props: TProps, children: TChildren) => RenderFragments; /** * A behavior is a function that will run on the browser. * Its first argument is the containing element of the component the behavior is attached to. */ export type Behavior = (element: HTMLElement) => void; /** * A behavior module is a module that will run on the browser. * It can be used in tandem with components to add interactivity. * For example, an image component could be augmented with a behavior that allows it to be enlarged. * * Behavior modules can also include common functions that other modules can use. * * The benefit of using behavior modules is that your JavaScript engine's TypeScript support can be used to automatically convert behaviors to plain JS for the browser. * * Important: top-level code (other than imports), as well as extra properties on the module, will not be included in the browser bundle. * If you need a stateful module, write a plain JS module and import it. You must not use TypeScript for such modules, as the builder will not be able to convert them to plain JS. * * All imports must be relative within the client directory. * Additionally, all imports to behavior modules must be top-level, otherwise the builder will not be able to resolve and convert them. * Do not write any import statements after your export statement! The builder is not smart enough to find them. * * EXTREMELY IMPORTANT: Do not import any modules with side effects. * Modules are actually imported in the Node.js environment, so modules with side effects can wreck the build by modifying global state or calling browser APIs. * So long as modules don't have side effects, you can safely import them. Libraries such as jQuery do not have side effects, so you can safely import them. */ export type BehaviorModule = { /** * The behavior module's URL. Use `import.meta.url` for this value. Do not access this property; its runtime value is undefined behavior. */ behaviorModuleUrl: string; /** * The behavior to run on the browser. Can be omitted if this module only provides functions for other modules to use. */ behavior?: Behavior | undefined; /** * A map of functions to export. Can be omitted if this module only provides a behavior. */ functions?: Record<string, Function> | undefined; }; /** * How to load behaviors for a page. * * If set to "import", the loader will be imported as a file. This will result in slightly slower loading, but is compatible with content security policies that disallow inline scripts. * If set to "inline", the loader will be inlined in the page. This is the default and fastest option. If your content security policy disallows inline scripts, use "import" instead. */ export type PageBehaviorLoaderType = "file" | "inline"; /** * The props type for a behavior module. */ export type BehaviorModuleProps = { /** * The promise from the `import()` call for the behavior module. For example, `import('./client/behavior/Greeting.js')`. */ module: Promise<{ default: BehaviorModule; }>; }; import { EventEmitter } from 'node:events'; /** * Render fragment types. */ type RenderFragmentType = number; declare namespace RenderFragmentType { let TEXT: number; let HTML: number; let BEHAVIOR_MODULE: number; let BEHAVIOR_MODULE_LOADER: number; } export {};