UNPKG

@esmx/core

Version:

A high-performance microfrontend framework supporting Vue, React, Preact, Solid, and Svelte with SSR and Module Federation capabilities.

700 lines (699 loc) 25.2 kB
import type { ImportMap, ScopesMap, SpecifierMap } from '@esmx/import'; import { type App } from './app'; import { type ManifestJson } from './manifest-json'; import { type ModuleConfig, type ParsedModuleConfig } from './module-config'; import { type PackConfig, type ParsedPackConfig } from './pack-config'; import type { ImportmapMode } from './render-context'; import type { RenderContext, RenderContextOptions } from './render-context'; import type { Middleware } from './utils/middleware'; import { type ProjectPath } from './utils/resolve-path'; /** * Core configuration options interface for the Esmx framework */ export interface EsmxOptions { /** * Project root directory path * - Can be absolute or relative path * - Defaults to current working directory (process.cwd()) */ root?: string; /** * Whether it is production environment * - true: Production environment * - false: Development environment * - Defaults to process.env.NODE_ENV === 'production' */ isProd?: boolean; /** * Base path placeholder configuration * - string: Custom placeholder * - false: Disable placeholder * - Default value is '[[[___ESMX_DYNAMIC_BASE___]]]' * - Used for dynamically replacing the base path of assets at runtime */ basePathPlaceholder?: string | false; /** * Module configuration options * - Used to configure module resolution rules for the project * - Includes module aliases, external dependencies, etc. */ modules?: ModuleConfig; /** * Package configuration options * - Used to package build artifacts into standard npm .tgz format packages * - Includes output path, package.json handling, packaging hooks, etc. */ packs?: PackConfig; /** * Development environment application creation function * - Only used in development environment * - Used to create application instance for development server * @param esmx Esmx instance */ devApp?: (esmx: Esmx) => Promise<App>; /** * Server startup configuration function * - Used to configure and start HTTP server * - Can be used in both development and production environments * @param esmx Esmx instance */ server?: (esmx: Esmx) => Promise<void>; /** * Post-build processing function * - Executed after project build is completed * - Can be used to perform additional resource processing, deployment, etc. * @param esmx Esmx instance */ postBuild?: (esmx: Esmx) => Promise<void>; } /** * Application build target types. * - client: Client build target, used to generate code that runs in the browser * - server: Server build target, used to generate code that runs in Node.js environment */ export type BuildEnvironment = 'client' | 'server'; /** * Command enumeration for the Esmx framework. * Used to control the runtime mode and lifecycle of the framework. */ export declare enum COMMAND { /** * Development mode * Starts development server with hot reload support */ dev = "dev", /** * Build mode * Generates production build artifacts */ build = "build", /** * Preview mode * Preview build artifacts */ preview = "preview", /** * Start mode * Starts production environment server */ start = "start" } export type { ImportMap, SpecifierMap, ScopesMap }; export declare class Esmx { private readonly _options; private _readied; private _importmapHash; private get readied(); /** * Get module name * @returns {string} The name of the current module, sourced from module configuration * @throws {NotReadyError} Throws error when framework instance is not initialized */ get name(): string; /** * Get module variable name * @returns {string} A valid JavaScript variable name generated based on the module name * @throws {NotReadyError} Throws error when framework instance is not initialized */ get varName(): string; /** * Get the absolute path of the project root directory * @returns {string} The absolute path of the project root directory * If the configured root is a relative path, it is resolved to an absolute path based on the current working directory */ get root(): string; /** * Determine if currently in production environment * @returns {boolean} Environment flag * Prioritizes the isProd in configuration, if not configured, judges based on process.env.NODE_ENV */ get isProd(): boolean; /** * Get the base path of the module * @returns {string} The base path of the module starting and ending with a slash * Used to construct the access path for module assets */ get basePath(): string; /** * Get the base path placeholder * @returns {string} Base path placeholder or empty string * Used for dynamically replacing the base path of the module at runtime, can be disabled through configuration */ get basePathPlaceholder(): string; /** * Get the currently executing command * @returns {COMMAND} The command enumeration value currently being executed * @throws {NotReadyError} Throws error when calling this method if the framework instance is not initialized */ get command(): COMMAND; /** * Get the command enumeration type * @returns {typeof COMMAND} Command enumeration type definition */ get COMMAND(): typeof COMMAND; /** * Get module configuration information * @returns {ParsedModuleConfig} Complete configuration information of the current module */ get moduleConfig(): ParsedModuleConfig; /** * Get package configuration information * @returns {ParsedPackConfig} Package-related configuration of the current module */ get packConfig(): ParsedPackConfig; /** * Get the static asset processing middleware for the application. * * This middleware is responsible for handling static asset requests for the application, * providing different implementations based on the runtime environment: * - Development environment: Supports real-time compilation and hot reloading of source code, uses no-cache strategy * - Production environment: Handles built static assets, supports long-term caching for immutable files * * @returns {Middleware} Returns the static asset processing middleware function * @throws {NotReadyError} Throws error when calling this method if the framework instance is not initialized * * @example * ```ts * const server = http.createServer((req, res) => { * // Use middleware to handle static asset requests * esmx.middleware(req, res, async () => { * const rc = await esmx.render({ url: req.url }); * res.end(rc.html); * }); * }); * ``` */ get middleware(): Middleware; /** * Get the server-side rendering function for the application. * * This function is responsible for executing server-side rendering, * providing different implementations based on the runtime environment: * - Development environment: Loads server entry file from source code, supports hot reloading and real-time preview * - Production environment: Loads built server entry file, provides optimized rendering performance * * @returns {(options?: RenderContextOptions) => Promise<RenderContext>} Returns the server-side rendering function * @throws {NotReadyError} Throws error when calling this method if the framework instance is not initialized * * @example * ```ts * // Basic usage * const rc = await esmx.render({ * params: { url: req.url } * }); * res.end(rc.html); * * // Advanced configuration * const rc = await esmx.render({ * base: '', // Set base path * importmapMode: 'inline', // Set import map mode * entryName: 'default', // Specify render entry * params: { * url: req.url, * state: { user: 'admin' } * } * }); * ``` */ get render(): (options?: RenderContextOptions) => Promise<RenderContext>; constructor(options?: EsmxOptions); /** * Initialize the Esmx framework instance. * * This method executes the following core initialization process: * 1. Parse project configuration (package.json, module configuration, package configuration, etc.) * 2. Create application instance (development or production environment) * 3. Execute corresponding lifecycle methods based on the command * * @param command - Framework running command * - dev: Start development server with hot reload support * - build: Build production artifacts * - preview: Preview build artifacts * - start: Start production environment server * * @returns Returns true for successful initialization * @throws {Error} Throws error when initializing repeatedly * * @example * ```ts * // entry.node.ts * import type { EsmxOptions } from '@esmx/core'; * * export default { * // Development environment configuration * async devApp(esmx) { * return import('@esmx/rspack').then((m) => * m.createRspackHtmlApp(esmx, { * config(context) { * // Custom Rspack configuration * } * }) * ); * }, * * // HTTP server configuration * async server(esmx) { * const server = http.createServer((req, res) => { * // Static file handling * esmx.middleware(req, res, async () => { * // Pass rendering parameters * const render = await esmx.render({ * params: { url: req.url } * }); * // Respond with HTML content * res.end(render.html); * }); * }); * * // Listen to port * server.listen(3000, () => { * console.log('http://localhost:3000'); * }); * } * } satisfies EsmxOptions; * ``` */ init(command: COMMAND): Promise<boolean>; /** * Destroy the Esmx framework instance, performing resource cleanup and connection closing operations. * * This method is mainly used for resource cleanup in development environment, including: * - Closing development servers (such as Rspack Dev Server) * - Cleaning up temporary files and cache * - Releasing system resources * * Note: In general, the framework automatically handles resource release, users do not need to manually call this method. * Only use it when custom resource cleanup logic is needed. * * @returns Returns a Promise that resolves to a boolean value * - true: Cleanup successful or no cleanup needed * - false: Cleanup failed * * @example * ```ts * // Use when custom cleanup logic is needed * process.once('SIGTERM', async () => { * await esmx.destroy(); // Clean up resources * process.exit(0); * }); * ``` */ destroy(): Promise<boolean>; /** * Execute the application's build process. * * This method is responsible for executing the entire application build process, including: * - Compiling source code * - Generating production build artifacts * - Optimizing and compressing code * - Generating asset manifests * * The build process prints start and end times, as well as total duration and other information. * * @returns Returns a Promise that resolves to a boolean value * - true: Build successful or build method not implemented * - false: Build failed * * @throws {NotReadyError} Throws error when calling this method if the framework instance is not initialized * * @example * ```ts * // entry.node.ts * import type { EsmxOptions } from '@esmx/core'; * * export default { * // Development environment configuration * async devApp(esmx) { * return import('@esmx/rspack').then((m) => * m.createRspackHtmlApp(esmx, { * config(context) { * // Custom Rspack configuration * } * }) * ); * }, * * // Post-build processing * async postBuild(esmx) { * // Generate static HTML after build completion * const render = await esmx.render({ * params: { url: '/' } * }); * esmx.writeSync( * esmx.resolvePath('dist/client', 'index.html'), * render.html * ); * } * } satisfies EsmxOptions; * ``` */ build(): Promise<boolean>; /** * Start HTTP server and configure server instance. * * This method is called in the following lifecycle of the framework: * - Development environment (dev): Start development server, providing features like hot reload * - Production environment (start): Start production server, providing production-grade performance * * The specific implementation of the server is provided by the user through the server configuration function in EsmxOptions. * This function is responsible for: * - Creating HTTP server instance * - Configuring middleware and routes * - Handling requests and responses * - Starting server listening * * @returns Returns a Promise that resolves when the server startup is complete * @throws {NotReadyError} Throws error when calling this method if the framework instance is not initialized * * @example * ```ts * // entry.node.ts * import http from 'node:http'; * import type { EsmxOptions } from '@esmx/core'; * * export default { * // Server configuration * async server(esmx) { * const server = http.createServer((req, res) => { * // Handle static assets * esmx.middleware(req, res, async () => { * // Server-side rendering * const render = await esmx.render({ * params: { url: req.url } * }); * res.end(render.html); * }); * }); * * // Start server * server.listen(3000, () => { * console.log('Server running at http://localhost:3000'); * }); * } * } satisfies EsmxOptions; * ``` */ server(): Promise<void>; /** * Execute post-build processing logic. * * This method is called after the application build is completed, used to perform additional resource processing, such as: * - Generating static HTML files * - Processing build artifacts * - Executing deployment tasks * - Sending build notifications * * The method automatically captures and handles exceptions during execution, ensuring it does not affect the main build process. * * @returns Returns a Promise that resolves to a boolean value * - true: Post-processing successful or no processing needed * - false: Post-processing failed * * @example * ```ts * // entry.node.ts * import type { EsmxOptions } from '@esmx/core'; * * export default { * // Post-build processing * async postBuild(esmx) { * // Generate static HTML for multiple pages * const pages = ['/', '/about', '/404']; * * for (const url of pages) { * const render = await esmx.render({ * params: { url } * }); * * // Write static HTML file * esmx.writeSync( * esmx.resolvePath('dist/client', url.substring(1), 'index.html'), * render.html * ); * } * } * } satisfies EsmxOptions; * ``` */ postBuild(): Promise<boolean>; /** * Resolve project relative path to absolute path * * @param projectPath - Project path type, such as 'dist/client', 'dist/server', etc. * @param args - Path segments to be concatenated * @returns Resolved absolute path * * @example * ```ts * // Used in entry.node.ts * async postBuild(esmx) { * const outputPath = esmx.resolvePath('dist/client', 'index.html'); * // Output: /project/root/dist/client/index.html * } * ``` */ resolvePath(projectPath: ProjectPath, ...args: string[]): string; /** * Write file content synchronously * * @param filepath - Absolute path of the file * @param data - Data to be written, can be string, Buffer or object * @returns Whether the write was successful * * @example * ```ts * // Used in entry.node.ts * async postBuild(esmx) { * const htmlPath = esmx.resolvePath('dist/client', 'index.html'); * const success = esmx.writeSync(htmlPath, '<html>...</html>'); * } * ``` */ writeSync(filepath: string, data: any): boolean; /** * Write file content asynchronously * * @param filepath - Absolute path of the file * @param data - Data to be written, can be string, Buffer or object * @returns Promise<boolean> Whether the write was successful * * @example * ```ts * // Used in entry.node.ts * async postBuild(esmx) { * const htmlPath = esmx.resolvePath('dist/client', 'index.html'); * const success = await esmx.write(htmlPath, '<html>...</html>'); * } * ``` */ write(filepath: string, data: any): Promise<boolean>; /** * Read and parse JSON file synchronously * * @template T - Expected JSON object type to return * @param filename - Absolute path of the JSON file * @returns {T} Parsed JSON object * @throws Throws exception when file does not exist or JSON format is incorrect * * @example * ```ts * // Used in entry.node.ts * async server(esmx) { * const manifest = esmx.readJsonSync<Manifest>(esmx.resolvePath('dist/client', 'manifest.json')); * // Use manifest object * } * ``` */ readJsonSync<T = any>(filename: string): T; /** * Read and parse JSON file asynchronously * * @template T - Expected JSON object type to return * @param filename - Absolute path of the JSON file * @returns {Promise<T>} Parsed JSON object * @throws Throws exception when file does not exist or JSON format is incorrect * * @example * ```ts * // Used in entry.node.ts * async server(esmx) { * const manifest = await esmx.readJson<Manifest>(esmx.resolvePath('dist/client', 'manifest.json')); * // Use manifest object * } * ``` */ readJson<T = any>(filename: string): Promise<T>; /** * Get build manifest list * * @description * This method is used to get the build manifest list for the specified target environment, including the following features: * 1. **Cache Management** * - Uses internal caching mechanism to avoid repeated loading * - Returns immutable manifest list * * 2. **Environment Adaptation** * - Supports both client and server environments * - Returns corresponding manifest information based on the target environment * * 3. **Module Mapping** * - Contains module export information * - Records resource dependency relationships * * @param env - Target environment type * - 'client': Client environment * - 'server': Server environment * @returns Returns read-only build manifest list * @throws {NotReadyError} Throws error when calling this method if the framework instance is not initialized * * @example * ```ts * // Used in entry.node.ts * async server(esmx) { * // Get client build manifest * const manifests = await esmx.getManifestList('client'); * * // Find build information for a specific module * const appModule = manifests.find(m => m.name === 'my-app'); * if (appModule) { * console.log('App exports:', appModule.exports); * console.log('App chunks:', appModule.chunks); * } * } * ``` */ getManifestList(env: BuildEnvironment): Promise<readonly ManifestJson[]>; /** * Get import map object * * @description * This method is used to generate ES module import maps with the following features: * 1. **Module Resolution** * - Generate module mappings based on build manifests * - Support both client and server environments * - Automatically handle module path resolution * * 2. **Cache Optimization** * - Use internal caching mechanism * - Return immutable mapping objects * * 3. **Path Handling** * - Automatically handle module paths * - Support dynamic base paths * * @param env - Target environment type * - 'client': Generate import map for browser environment * - 'server': Generate import map for server environment * @returns Returns read-only import map object * @throws {NotReadyError} Throws error when calling this method if the framework instance is not initialized * * @example * ```ts * // Used in entry.node.ts * async server(esmx) { * // Get client import map * const importmap = await esmx.getImportMap('client'); * * // Custom HTML template * const html = ` * <!DOCTYPE html> * <html> * <head> * <script type="importmap"> * ${JSON.stringify(importmap)} * </script> * </head> * <body> * <!-- Page content --> * </body> * </html> * `; * } * ``` */ getImportMap(env: BuildEnvironment): Promise<Readonly<ImportMap>>; /** * Get client import map information * * @description * This method is used to generate import map code for client environment, supporting two modes: * 1. **Inline Mode (inline)** * - Inline import map directly into HTML * - Reduce additional network requests * - Suitable for scenarios with smaller import maps * * 2. **JS File Mode (js)** * - Generate standalone JS file * - Support browser caching * - Suitable for scenarios with larger import maps * * Core Features: * - Automatically handle dynamic base paths * - Support module path runtime replacement * - Optimize caching strategy * - Ensure module loading order * * @param mode - Import map mode * - 'inline': Inline mode, returns HTML script tag * - 'js': JS file mode, returns information with file path * @returns Returns import map related information * - src: URL of the JS file (only in js mode) * - filepath: Local path of the JS file (only in js mode) * - code: HTML script tag content * @throws {NotReadyError} Throws error when calling this method if the framework instance is not initialized * * @example * ```ts * // Used in entry.node.ts * async server(esmx) { * const server = express(); * server.use(esmx.middleware); * * server.get('*', async (req, res) => { * // Use JS file mode * const result = await esmx.render({ * importmapMode: 'js', * params: { url: req.url } * }); * res.send(result.html); * }); * * // Or use inline mode * server.get('/inline', async (req, res) => { * const result = await esmx.render({ * importmapMode: 'inline', * params: { url: req.url } * }); * res.send(result.html); * }); * } * ``` */ getImportMapClientInfo<T extends ImportmapMode>(mode: T): Promise<T extends 'js' ? { src: string; filepath: string; code: string; } : { src: null; filepath: null; code: string; }>; /** * Get the list of static import paths for a module. * * @param env - Build target ('client' | 'server') * @param specifier - Module specifier * @returns Returns the list of static import paths, returns null if not found * @throws {NotReadyError} Throws error when calling this method if the framework instance is not initialized * * @example * ```ts * // Get static import paths for client entry module * const paths = await esmx.getStaticImportPaths( * 'client', * `your-app-name/src/entry.client` * ); * ``` */ getStaticImportPaths(env: BuildEnvironment, specifier: string): Promise<readonly string[] | null>; }