@opentui/core
Version:
OpenTUI is a TypeScript library on a native Zig core for building terminal user interfaces (TUIs)
473 lines • 20.7 kB
JavaScript
/*
* Exposes runtime-only modules (for example `@opentui/core`, `@opentui/solid`,
* `solid-js`) to externally loaded plugins by rewriting matching imports to
* virtual `opentui:runtime-module:*` ids.
*
* Why this is exact-path + prescan instead of one broad `onLoad`:
* - Bun can break CJS/UMD interop if a file is routed through plugin `onLoad`
* (real repro: `jsonc-parser` resolving to `lib/umd/main.js`;
* https://github.com/oven-sh/bun/issues/19279,
* https://github.com/oven-sh/bun/issues/21369), so arbitrary `node_modules`
* JS cannot be blanket-rewritten.
* - runtime `onResolve` is sync-only, so package/type/source discovery here is
* synchronous and cached.
* - a matched `onLoad` cannot safely fall through, so loaders must be narrow.
* - Bun may canonicalize paths before `onLoad`, so loaders are registered for
* both the resolved path spelling and its realpath, then canonical-checked.
* - Bun may native-load `node_modules` ESM without firing `onResolve` for
* nested package imports, so `node_modules` ESM is recursively prescanned and
* only files that actually need runtime rewriting get exact-path loaders.
*
* Behavior:
* - non-`node_modules` source files get a dedicated rewrite loader immediately.
* - `node_modules` files are rewritten only if they are ESM (`.mjs`, `.mts`,
* `.ts`, `.tsx`, `.jsx`, or `.js` under `package.json#type="module"`) and
* directly or transitively need runtime-module rewriting; unrelated CJS stays
* untouched.
* - optional bare-specifier rewriting is preserved for sibling files in
* packages already marked for runtime rewriting.
*
* Notes:
* - import scanning is regex-based, not a full parser.
* - CJS helper libraries that themselves import runtime modules are still not
* supported.
* - `package.json#type` caching is per plugin setup, not module-global, so a
* later plugin instance in the same process can observe filesystem changes.
*/
import { existsSync, readFileSync, realpathSync } from "node:fs";
import { basename, dirname, isAbsolute, join } from "node:path";
import { fileURLToPath } from "node:url";
import * as coreRuntime from "@opentui/core";
const CORE_RUNTIME_SPECIFIER = "@opentui/core";
const CORE_TESTING_RUNTIME_SPECIFIER = "@opentui/core/testing";
const RUNTIME_MODULE_PREFIX = "opentui:runtime-module:";
const MAX_RUNTIME_RESOLVE_PARENTS = 64;
const DEFAULT_RUNTIME_PLUGIN_REWRITE_OPTIONS = {
nodeModulesRuntimeSpecifiers: true,
nodeModulesBareSpecifiers: false,
};
const DEFAULT_CORE_RUNTIME_MODULE_SPECIFIERS = [CORE_RUNTIME_SPECIFIER, CORE_TESTING_RUNTIME_SPECIFIER];
const DEFAULT_CORE_RUNTIME_MODULE_SPECIFIER_SET = new Set(DEFAULT_CORE_RUNTIME_MODULE_SPECIFIERS);
export const isCoreRuntimeModuleSpecifier = (specifier) => {
return DEFAULT_CORE_RUNTIME_MODULE_SPECIFIER_SET.has(specifier);
};
const loadCoreTestingRuntimeModule = async () => {
return (await import("@opentui/core/testing"));
};
const escapeRegExp = (value) => {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
};
const exactSpecifierFilter = (specifier) => {
return new RegExp(`^${escapeRegExp(specifier)}$`);
};
const exactPathFilter = (paths) => {
const candidates = [...new Set(paths.map(sourcePath))];
return new RegExp(`^(?:${candidates.map(escapeRegExp).join("|")})(?:[?#].*)?$`);
};
export const runtimeModuleIdForSpecifier = (specifier) => {
return `${RUNTIME_MODULE_PREFIX}${encodeURIComponent(specifier)}`;
};
const resolveRuntimeModuleExports = async (moduleEntry) => {
if (typeof moduleEntry === "function") {
return await moduleEntry();
}
return moduleEntry;
};
const sourcePath = (path) => {
const searchIndex = path.indexOf("?");
const hashIndex = path.indexOf("#");
const end = [searchIndex, hashIndex].filter((index) => index >= 0).sort((a, b) => a - b)[0];
return end === undefined ? path : path.slice(0, end);
};
const normalizedSourcePathByPath = new Map();
const normalizeSourcePath = (path) => {
const cleanPath = sourcePath(path);
const cachedPath = normalizedSourcePathByPath.get(cleanPath);
if (cachedPath !== undefined) {
return cachedPath;
}
let normalizedPath = cleanPath;
try {
normalizedPath = realpathSync(cleanPath);
}
catch {
normalizedPath = cleanPath;
}
normalizedSourcePathByPath.set(cleanPath, normalizedPath);
return normalizedPath;
};
const isNodeModulesPath = (path) => {
return /(?:^|[/\\])node_modules(?:[/\\])/.test(path);
};
const packageTypeForPath = (path, packageTypeByPackageJsonPath) => {
let currentDir = dirname(path);
while (true) {
const packageJsonPath = join(currentDir, "package.json");
if (existsSync(packageJsonPath)) {
const cachedPackageType = packageTypeByPackageJsonPath.get(packageJsonPath);
if (cachedPackageType) {
return cachedPackageType;
}
let packageType = "commonjs";
try {
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
if (packageJson.type === "module") {
packageType = "module";
}
}
catch {
packageType = "commonjs";
}
packageTypeByPackageJsonPath.set(packageJsonPath, packageType);
return packageType;
}
const parentDir = dirname(currentDir);
if (parentDir === currentDir) {
return "commonjs";
}
currentDir = parentDir;
}
};
const isNodeModulesEsmPath = (path, packageTypeByPackageJsonPath) => {
const normalizedPath = normalizeSourcePath(path);
if (!isNodeModulesPath(normalizedPath)) {
return false;
}
if (normalizedPath.endsWith(".mjs") ||
normalizedPath.endsWith(".mts") ||
normalizedPath.endsWith(".ts") ||
normalizedPath.endsWith(".tsx") ||
normalizedPath.endsWith(".jsx")) {
return true;
}
if (normalizedPath.endsWith(".cjs") || normalizedPath.endsWith(".cts") || !normalizedPath.endsWith(".js")) {
return false;
}
return packageTypeForPath(normalizedPath, packageTypeByPackageJsonPath) === "module";
};
const nodeModulesPackageRootForPath = (path) => {
let currentDir = dirname(path);
while (true) {
const parentDir = dirname(currentDir);
if (parentDir === currentDir) {
return null;
}
if (basename(parentDir) === "node_modules") {
return currentDir;
}
if (basename(dirname(parentDir)) === "node_modules" && basename(parentDir).startsWith("@")) {
return currentDir;
}
currentDir = parentDir;
}
};
const resolveRuntimePluginRewriteOptions = (options) => {
return {
nodeModulesRuntimeSpecifiers: options?.nodeModulesRuntimeSpecifiers ?? DEFAULT_RUNTIME_PLUGIN_REWRITE_OPTIONS.nodeModulesRuntimeSpecifiers,
nodeModulesBareSpecifiers: options?.nodeModulesBareSpecifiers ?? DEFAULT_RUNTIME_PLUGIN_REWRITE_OPTIONS.nodeModulesBareSpecifiers,
};
};
const runtimeLoaderForPath = (path) => {
const cleanPath = sourcePath(path);
if (cleanPath.endsWith(".tsx")) {
return "tsx";
}
if (cleanPath.endsWith(".jsx")) {
return "jsx";
}
if (cleanPath.endsWith(".ts") || cleanPath.endsWith(".mts") || cleanPath.endsWith(".cts")) {
return "ts";
}
if (cleanPath.endsWith(".js") || cleanPath.endsWith(".mjs") || cleanPath.endsWith(".cjs")) {
return "js";
}
return null;
};
const resolveImportSpecifierPatterns = [
/(from\s+["'])([^"']+)(["'])/g,
/(import\s+["'])([^"']+)(["'])/g,
/(import\s*\(\s*["'])([^"']+)(["']\s*\))/g,
/(require\s*\(\s*["'])([^"']+)(["']\s*\))/g,
];
const isBareSpecifier = (specifier) => {
if (specifier.startsWith(".") || specifier.startsWith("/") || specifier.startsWith("\\")) {
return false;
}
if (specifier.startsWith("node:") ||
specifier.startsWith("bun:") ||
specifier.startsWith("http:") ||
specifier.startsWith("https:") ||
specifier.startsWith("file:") ||
specifier.startsWith("data:")) {
return false;
}
if (specifier.startsWith(RUNTIME_MODULE_PREFIX)) {
return false;
}
return true;
};
const registerResolveParent = (resolveParentsByRecency, resolveParent) => {
const existingIndex = resolveParentsByRecency.indexOf(resolveParent);
if (existingIndex >= 0) {
resolveParentsByRecency.splice(existingIndex, 1);
}
resolveParentsByRecency.push(resolveParent);
if (resolveParentsByRecency.length > MAX_RUNTIME_RESOLVE_PARENTS) {
resolveParentsByRecency.shift();
}
};
const rewriteImportSpecifiers = (code, resolveReplacement) => {
let transformedCode = code;
for (const pattern of resolveImportSpecifierPatterns) {
transformedCode = transformedCode.replace(pattern, (fullMatch, prefix, specifier, suffix) => {
const replacement = resolveReplacement(specifier);
if (!replacement || replacement === specifier) {
return fullMatch;
}
return `${prefix}${replacement}${suffix}`;
});
}
return transformedCode;
};
const collectImportSpecifiers = (code) => {
const specifiers = new Set();
for (const pattern of resolveImportSpecifierPatterns) {
code.replace(pattern, (_fullMatch, _prefix, specifier) => {
specifiers.add(specifier);
return _fullMatch;
});
}
return [...specifiers];
};
const resolveFromParent = (specifier, parent) => {
try {
const resolvedSpecifier = import.meta.resolve(specifier, parent);
if (resolvedSpecifier === specifier ||
resolvedSpecifier.startsWith("node:") ||
resolvedSpecifier.startsWith("bun:")) {
return null;
}
return resolvedSpecifier;
}
catch {
return null;
}
};
const resolveSourcePathFromSpecifier = (specifier, importer) => {
if (specifier.startsWith("node:") ||
specifier.startsWith("bun:") ||
specifier.startsWith("http:") ||
specifier.startsWith("https:") ||
specifier.startsWith("data:") ||
specifier.startsWith(RUNTIME_MODULE_PREFIX)) {
return null;
}
if (specifier.startsWith("file:")) {
return sourcePath(fileURLToPath(specifier));
}
if (isAbsolute(specifier)) {
return sourcePath(specifier);
}
const resolvedSpecifier = resolveFromParent(specifier, importer);
if (!resolvedSpecifier) {
return null;
}
if (resolvedSpecifier.startsWith("file:")) {
return sourcePath(fileURLToPath(resolvedSpecifier));
}
if (isAbsolute(resolvedSpecifier)) {
return sourcePath(resolvedSpecifier);
}
return null;
};
const rewriteImportsFromResolveParents = (code, resolveParentsByRecency) => {
if (resolveParentsByRecency.length === 0) {
return code;
}
const resolveFromParents = (specifier) => {
if (!isBareSpecifier(specifier)) {
return null;
}
for (let index = resolveParentsByRecency.length - 1; index >= 0; index -= 1) {
const resolveParent = resolveParentsByRecency[index];
const resolvedSpecifier = resolveFromParent(specifier, resolveParent);
if (resolvedSpecifier) {
return resolvedSpecifier;
}
}
return null;
};
return rewriteImportSpecifiers(code, resolveFromParents);
};
const rewriteRuntimeSpecifiers = (code, runtimeModuleIdsBySpecifier) => {
return rewriteImportSpecifiers(code, (specifier) => {
const runtimeModuleId = runtimeModuleIdsBySpecifier.get(specifier);
return runtimeModuleId ?? null;
});
};
export function createRuntimePlugin(input = {}) {
const runtimeModules = new Map();
runtimeModules.set(CORE_RUNTIME_SPECIFIER, input.core ?? coreRuntime);
runtimeModules.set(CORE_TESTING_RUNTIME_SPECIFIER, loadCoreTestingRuntimeModule);
const rewriteOptions = resolveRuntimePluginRewriteOptions(input.rewrite);
for (const [specifier, moduleEntry] of Object.entries(input.additional ?? {})) {
runtimeModules.set(specifier, moduleEntry);
}
const runtimeModuleIdsBySpecifier = new Map();
for (const specifier of runtimeModules.keys()) {
runtimeModuleIdsBySpecifier.set(specifier, runtimeModuleIdForSpecifier(specifier));
}
return {
name: "bun-plugin-opentui-runtime-modules",
setup: (build) => {
const resolveParentsByRecency = [];
const installedRewriteLoaders = new Set();
const nodeModulesBareRewritePackageRoots = new Set();
const packageTypeByPackageJsonPath = new Map();
const sourceAnalysisByPath = new Map();
const nodeModulesRuntimeRewritePathsByPath = new Map();
const installRewriteLoader = (path) => {
const resolvedTargetPath = sourcePath(path);
const canonicalTargetPath = normalizeSourcePath(resolvedTargetPath);
if (installedRewriteLoaders.has(canonicalTargetPath)) {
return;
}
installedRewriteLoaders.add(canonicalTargetPath);
// Register both the resolved path spelling and its canonical realpath so Bun
// can reach the loader even if it reports the same file through a different alias.
build.onLoad({ filter: exactPathFilter([resolvedTargetPath, canonicalTargetPath]) }, async (args) => {
const loadedPath = normalizeSourcePath(args.path);
if (loadedPath !== canonicalTargetPath) {
return undefined;
}
const nodeModulesPath = isNodeModulesPath(loadedPath);
const shouldRewriteRuntimeSpecifiers = !nodeModulesPath || rewriteOptions.nodeModulesRuntimeSpecifiers;
const shouldRewriteBareSpecifiers = !nodeModulesPath || rewriteOptions.nodeModulesBareSpecifiers;
const loader = runtimeLoaderForPath(args.path);
if (!loader) {
throw new Error(`Unable to determine runtime loader for path: ${args.path}`);
}
const contents = await Bun.file(loadedPath).text();
const runtimeRewrittenContents = shouldRewriteRuntimeSpecifiers
? rewriteRuntimeSpecifiers(contents, runtimeModuleIdsBySpecifier)
: contents;
if (runtimeRewrittenContents !== contents && shouldRewriteBareSpecifiers) {
registerResolveParent(resolveParentsByRecency, loadedPath);
}
const transformedContents = shouldRewriteBareSpecifiers
? rewriteImportsFromResolveParents(runtimeRewrittenContents, resolveParentsByRecency)
: runtimeRewrittenContents;
return {
contents: transformedContents,
loader,
};
});
};
const analyzeSourcePath = (path) => {
const normalizedPath = normalizeSourcePath(path);
const cachedAnalysis = sourceAnalysisByPath.get(normalizedPath);
if (cachedAnalysis) {
return cachedAnalysis;
}
let contents;
try {
contents = readFileSync(normalizedPath, "utf8");
}
catch (error) {
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
const analysis = { importSpecifiers: [], needsRuntimeSpecifierRewrite: false };
sourceAnalysisByPath.set(normalizedPath, analysis);
return analysis;
}
throw error;
}
const importSpecifiers = collectImportSpecifiers(contents);
const analysis = {
importSpecifiers,
needsRuntimeSpecifierRewrite: importSpecifiers.some((specifier) => runtimeModuleIdsBySpecifier.has(specifier)),
};
sourceAnalysisByPath.set(normalizedPath, analysis);
return analysis;
};
const collectNodeModulesRuntimeRewritePaths = (path, visiting = new Set()) => {
const normalizedPath = normalizeSourcePath(path);
if (!isNodeModulesEsmPath(normalizedPath, packageTypeByPackageJsonPath)) {
return [];
}
const cachedPaths = nodeModulesRuntimeRewritePathsByPath.get(normalizedPath);
if (cachedPaths) {
return cachedPaths;
}
if (visiting.has(normalizedPath)) {
return [];
}
visiting.add(normalizedPath);
const rewritePaths = new Set();
const analysis = analyzeSourcePath(normalizedPath);
if (analysis.needsRuntimeSpecifierRewrite) {
rewritePaths.add(normalizedPath);
}
for (const specifier of analysis.importSpecifiers) {
const resolvedPath = resolveSourcePathFromSpecifier(specifier, normalizedPath);
if (!resolvedPath || !isNodeModulesEsmPath(resolvedPath, packageTypeByPackageJsonPath)) {
continue;
}
for (const nestedPath of collectNodeModulesRuntimeRewritePaths(resolvedPath, visiting)) {
rewritePaths.add(nestedPath);
}
}
visiting.delete(normalizedPath);
const resolvedRewritePaths = [...rewritePaths];
nodeModulesRuntimeRewritePathsByPath.set(normalizedPath, resolvedRewritePaths);
return resolvedRewritePaths;
};
for (const [specifier, moduleEntry] of runtimeModules.entries()) {
const moduleId = runtimeModuleIdsBySpecifier.get(specifier);
if (!moduleId) {
continue;
}
build.module(moduleId, async () => ({
exports: await resolveRuntimeModuleExports(moduleEntry),
loader: "object",
}));
build.onResolve({ filter: exactSpecifierFilter(specifier) }, () => ({ path: moduleId }));
}
build.onResolve({ filter: /.*/ }, (args) => {
if (runtimeModuleIdsBySpecifier.has(args.path) || args.path.startsWith(RUNTIME_MODULE_PREFIX)) {
return undefined;
}
const path = resolveSourcePathFromSpecifier(args.path, args.importer);
if (!path || !runtimeLoaderForPath(path)) {
return undefined;
}
const nodeModulesPath = isNodeModulesPath(path);
if (!nodeModulesPath) {
installRewriteLoader(path);
return undefined;
}
if (!rewriteOptions.nodeModulesRuntimeSpecifiers && !rewriteOptions.nodeModulesBareSpecifiers) {
return undefined;
}
for (const rewritePath of collectNodeModulesRuntimeRewritePaths(path)) {
installRewriteLoader(rewritePath);
}
const packageRoot = nodeModulesPackageRootForPath(path);
if (rewriteOptions.nodeModulesBareSpecifiers &&
packageRoot &&
nodeModulesBareRewritePackageRoots.has(packageRoot)) {
installRewriteLoader(path);
return undefined;
}
if (!rewriteOptions.nodeModulesRuntimeSpecifiers || !analyzeSourcePath(path).needsRuntimeSpecifierRewrite) {
return undefined;
}
if (rewriteOptions.nodeModulesBareSpecifiers && packageRoot) {
nodeModulesBareRewritePackageRoots.add(packageRoot);
}
installRewriteLoader(path);
return undefined;
});
},
};
}
//# sourceMappingURL=runtime-plugin.js.map