UNPKG

@redocly/openapi-core

Version:

See https://github.com/Redocly/redocly-cli

100 lines 4.3 kB
import module from 'node:module'; import * as url from 'node:url'; import { logger } from '../logger.js'; const pluginsCache = new Map(); const PLUGIN_VERSION_PARAM = 'v'; let cacheVersion = 0; let isEsmCacheBustHookRegistered = false; // Propagates `?v=<cacheVersion>` from a plugin URL down to its nested `file:` // imports, so bumping `cacheVersion` invalidates the whole plugin graph. function registerEsmCacheBustHook() { module.registerHooks({ // `specifier` — raw import string (e.g. './utils.js', 'node:util'). // `context` — { parentURL, conditions, importAttributes }. // `nextResolve` — delegate to the remaining hooks + Node's default resolver. resolve(specifier, context, nextResolve) { const result = nextResolve(specifier, context); if (!result.url.startsWith('file:')) return result; // Top-level entry: no parent URL to inherit a version from. if (!context.parentURL) return result; // Copy `?v=` from the importer (parentURL) onto the resolved URL. const parentVersion = new URL(context.parentURL).searchParams.get(PLUGIN_VERSION_PARAM); if (!parentVersion) return result; const resolvedURL = new URL(result.url); if (resolvedURL.pathname.includes('/node_modules/')) return result; // If the URL already carries `?v=` (e.g. written explicitly in the // specifier), keep it as-is. Otherwise stamp `parentVersion` fresh — no // prior value is reused, the version comes from the importer every time. if (resolvedURL.searchParams.has(PLUGIN_VERSION_PARAM)) return result; resolvedURL.searchParams.set(PLUGIN_VERSION_PARAM, parentVersion); return { ...result, url: resolvedURL.href }; }, }); } function ensureEsmCacheBustHook() { if (isEsmCacheBustHookRegistered) return; isEsmCacheBustHookRegistered = true; if (typeof module.registerHooks !== 'function') { logger.warn(`Redocly plugin reload requires Node.js >= 22.15 (current: ${process.version}). ` + `ESM plugins will not pick up edits until the process restarts.\n`); return; } registerEsmCacheBustHook(); } export function hasCachedPlugin(absolutePluginPath) { return pluginsCache.has(absolutePluginPath); } export function getCachedPlugins(absolutePluginPath) { return pluginsCache.get(absolutePluginPath); } export function setCachedPlugins(absolutePluginPath, plugins) { pluginsCache.set(absolutePluginPath, plugins); } export function getPluginCacheVersion() { return cacheVersion; } function evictPluginFromCjsCache(pluginPath) { const nodeRequire = module.createRequire(pluginPath); const visited = new Set(); const evict = (modulePath) => { if (visited.has(modulePath)) return; visited.add(modulePath); const cached = nodeRequire.cache[modulePath]; if (!cached) return; for (const child of cached.children) { if (/[/\\]node_modules[/\\]/.test(child.id)) continue; evict(child.id); } delete nodeRequire.cache[modulePath]; }; evict(pluginPath); } export const clearPluginsCache = () => { // CJS: evict from `require.cache` (skip node_modules). // ESM: bump version; next load uses a fresh `?v=` so Node re-imports every module in the plugin graph. const paths = [...pluginsCache.keys()]; for (const pluginPath of paths) { evictPluginFromCjsCache(pluginPath); } pluginsCache.clear(); cacheVersion += 1; ensureEsmCacheBustHook(); }; export async function loadPluginModule(absolutePluginPath) { const pluginUrl = url.pathToFileURL(absolutePluginPath); pluginUrl.searchParams.set(PLUGIN_VERSION_PARAM, String(cacheVersion)); // `webpackIgnore` keeps this dynamic import as a runtime `import()` so Node's // ESM loader (with our `?v=` cache-bust hook) handles it instead of webpack // rewriting it during VSCE/Reunite bundling. return import(/* webpackIgnore: true */ pluginUrl.href); } //# sourceMappingURL=plugins-cache.js.map