UNPKG

@esmx/core

Version:

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

328 lines (303 loc) 9.61 kB
import path from 'node:path'; import type { BuildSsrTarget } from './core'; /** * Core configuration interface for the module system. * Defines module linking, import mapping, and export configurations. */ export interface ModuleConfig { /** * Module linking configuration. * Key is remote module name, value is module build output directory path. * * @example * ```typescript * links: { * 'shared-lib': '../shared-lib/dist', * 'api-utils': '/var/www/api-utils/dist' * } * ``` */ links?: Record<string, string>; /** * Module import mapping configuration. * Key is local module identifier, value is remote module path. * Mainly used for standard imports of third-party libraries. * * @example * ```typescript * imports: { * 'axios': 'shared-lib/axios', * 'lodash': 'shared-lib/lodash' * } * ``` */ imports?: Record<string, string>; /** * Module export configuration. * Supports multiple configuration forms: mixed array and object. * * @example * ```typescript * // Array form * exports: ['npm:axios', 'root:src/utils/format.ts'] * * // Object form * exports: { * 'axios': 'axios', * 'utils': './src/utils/index.ts' * } * ``` */ exports?: ModuleConfigExportExports; } /** * Union type for export configuration. * Supports mixed array and object forms to provide flexibility * for different configuration scenarios. */ export type ModuleConfigExportExports = | Array<string | Record<string, string | ModuleConfigExportObject>> | Record<string, string | ModuleConfigExportObject>; /** * Configuration object for individual module exports. * Provides fine-grained control over module export behavior. */ export type ModuleConfigExportObject = { /** * Input file path, relative to project root directory. * * @example './src/utils/format' */ input?: string; /** * Environment-specific input file configuration. * Supports client and server differentiated builds. * Set to `false` to disable builds for specific environments. * * @example * ```typescript * inputTarget: { * client: './src/storage/indexedDB.ts', * server: './src/storage/filesystem.ts' * } * ``` */ inputTarget?: Record<BuildSsrTarget, string | false>; /** * Whether to rewrite import paths within modules. * * @default true * @remarks Only needs to be false when exporting npm packages */ rewrite?: boolean; }; /** * Parsed and normalized module configuration. * Contains resolved paths and processed configuration data. */ export interface ParsedModuleConfig { /** Module name */ name: string; /** Module root directory path */ root: string; /** * Resolved link information for connected modules. * Contains absolute paths to client/server directories and manifest files. */ links: Record< string, { /** Module name */ name: string; /** Original root path (relative or absolute) */ root: string; /** Absolute path to client build directory */ client: string; /** Absolute path to client manifest.json */ clientManifestJson: string; /** Absolute path to server build directory */ server: string; /** Absolute path to server manifest.json */ serverManifestJson: string; } >; /** Import mapping configuration (passed through as-is) */ imports: Record<string, string>; /** Processed export configuration */ exports: ParsedModuleConfigExports; } /** * Processed export configuration mapping. * Maps export names to their resolved configuration objects. */ export type ParsedModuleConfigExports = Record< string, ParsedModuleConfigExport >; /** * Processed export configuration for a single module. * Contains resolved input targets and processing flags. */ export interface ParsedModuleConfigExport { /** Export name/identifier */ name: string; /** Resolved input targets for different build environments */ inputTarget: Record<BuildSsrTarget, string | false>; /** Whether to rewrite import paths within this module */ rewrite: boolean; } /** * Parse and normalize module configuration. * Resolves paths, processes exports, and creates a standardized configuration object. * * @param name - Module name * @param root - Module root directory path * @param config - Raw module configuration (optional) * @returns Parsed and normalized module configuration * * @example * ```typescript * const parsed = parseModuleConfig('my-app', '/path/to/app', { * links: { 'shared-lib': '../shared-lib/dist' }, * imports: { 'axios': 'shared-lib/axios' }, * exports: ['npm:axios', 'root:src/utils/format.ts'] * }); * ``` */ export function parseModuleConfig( name: string, root: string, config: ModuleConfig = {} ): ParsedModuleConfig { return { name, root, links: getLinks(name, root, config), imports: config.imports ?? {}, exports: getExports(config) }; } /** * Prefix constants for export configuration syntactic sugar. * Used to identify and process npm: and root: prefixes in export strings. */ const PREFIX = { /** Prefix for npm package exports */ npm: 'npm:', /** Prefix for source file exports */ root: 'root:' } as const; /** * Process and resolve module linking configuration. * Creates absolute paths for client/server directories and manifest files. * Automatically includes the current module as a self-link. * * @param name - Module name * @param root - Module root directory path * @param config - Module configuration * @returns Resolved links configuration with absolute paths * * @internal */ function getLinks(name: string, root: string, config: ModuleConfig) { const result: ParsedModuleConfig['links'] = {}; Object.entries({ [name]: path.resolve(root, 'dist'), ...config.links }).forEach(([name, value]) => { const serverRoot = path.isAbsolute(value) ? value : path.resolve(root, value); result[name] = { name: name, root: value, client: path.resolve(serverRoot, 'client'), clientManifestJson: path.resolve( serverRoot, 'client/manifest.json' ), server: path.resolve(serverRoot, 'server'), serverManifestJson: path.resolve(serverRoot, 'server/manifest.json') }; }); return result; } /** * Process and normalize module exports configuration. * Handles different export formats (array, object, object array) and * processes prefix syntactic sugar (npm:, root:). * Automatically adds default entry exports for client and server. * * @param config - Module configuration (optional) * @returns Processed exports configuration * * @internal */ function getExports(config: ModuleConfig = {}) { const result: ParsedModuleConfig['exports'] = {}; const exports: Record<string, ModuleConfigExportObject | string> = { 'src/entry.client': { inputTarget: { client: './src/entry.client', server: false } }, 'src/entry.server': { inputTarget: { client: false, server: './src/entry.server' } } }; if (Array.isArray(config.exports)) { const FILE_EXT_REGEX = /\.(js|mjs|cjs|jsx|mjsx|cjsx|ts|mts|cts|tsx|mtsx|ctsx)$/i; config.exports.forEach((item) => { if (typeof item === 'string') { if (item.startsWith(PREFIX.npm)) { // npm: prefix - export npm package, maintain original import paths item = item.substring(PREFIX.npm.length); exports[item] = { rewrite: false, input: item }; } else if (item.startsWith(PREFIX.root)) { // root: prefix - export source file, rewrite import paths item = item .substring(PREFIX.root.length) .replace(FILE_EXT_REGEX, ''); exports[item] = { input: './' + item }; } else { console.error(`Invalid module export: ${item}`); } } else { // Object configuration - merge directly Object.assign(exports, item); } }); } else if (config.exports) { // Object configuration - merge directly Object.assign(exports, config.exports); } for (const [name, value] of Object.entries(exports)) { const opts = typeof value === 'string' ? { input: value } : value; const client = opts.inputTarget?.client ?? opts.input ?? name; const server = opts.inputTarget?.server ?? opts.input ?? name; result[name] = { name, rewrite: opts.rewrite ?? true, inputTarget: { client, server } }; } return result; }