wunphile
Version:
Simple, imperative JavaScript-based static site generator
442 lines (441 loc) • 17.9 kB
text/typescript
/**
* 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 {};