UNPKG

rwsdk

Version:

Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime

258 lines (257 loc) 12.5 kB
import createDebug from "debug"; import resolve from "enhanced-resolve"; import fs from "fs"; import path from "path"; import { normalizeModulePath } from "../lib/normalizeModulePath.mjs"; const debug = createDebug("rwsdk:vite:enhanced-resolve-plugin"); // Enhanced-resolve plugin that wraps Vite plugin resolution class VitePluginResolverPlugin { constructor(environment, source = "resolve", target = "resolved") { this.environment = environment; this.source = source; this.target = target; // Create an enhanced-resolve instance for the plugin context const baseOptions = mapViteResolveToEnhancedResolveOptions(this.environment.config, this.environment.name); this.enhancedResolver = resolve.create(baseOptions); } apply(resolver) { const target = resolver.ensureHook(this.target); resolver .getHook(this.source) .tapAsync("VitePluginResolverPlugin", (request, resolveContext, callback) => { const plugins = this.environment?.plugins; if (!plugins) { return callback(); } // Create a plugin context with enhanced-resolve-based resolve method const pluginContext = { environment: this.environment, resolve: async (id, importer) => { return new Promise((resolve) => { this.enhancedResolver({}, importer || this.environment.config.root, id, {}, (err, result) => { if (!err && result) { debug("Context resolve: %s -> %s", id, result); resolve({ id: result }); } else { debug("Context resolve failed for %s: %s", id, err?.message || "not found"); resolve(null); } }); }); }, }; debug("Trying to resolve %s from %s", request.request, request.path); // This function encapsulates the logic to process Vite plugins for a given request. const runPluginProcessing = async (currentRequest) => { debug("Available plugins:", plugins.map((p) => p.name)); for (const plugin of plugins) { const resolveIdHandler = plugin.resolveId; if (!resolveIdHandler) continue; let handlerFn; let shouldApplyFilter = false; let filter = null; if (typeof resolveIdHandler === "function") { handlerFn = resolveIdHandler; } else if (typeof resolveIdHandler === "object" && typeof resolveIdHandler.handler === "function") { handlerFn = resolveIdHandler.handler; shouldApplyFilter = true; filter = resolveIdHandler.filter; } if (!handlerFn) continue; if (shouldApplyFilter && filter?.id) { const idFilter = filter.id; let shouldSkip = false; if (idFilter instanceof RegExp) { shouldSkip = !idFilter.test(currentRequest.request); } else if (Array.isArray(idFilter)) { // Handle array of filters - matches if ANY filter matches shouldSkip = !idFilter.some((f) => f instanceof RegExp ? f.test(currentRequest.request) : f === currentRequest.request); } else if (typeof idFilter === "string") { shouldSkip = idFilter !== currentRequest.request; } else if (typeof idFilter === "object" && idFilter !== null) { // Handle include/exclude object pattern const { include, exclude } = idFilter; let matches = true; // Check include patterns (if any) if (include) { const includePatterns = Array.isArray(include) ? include : [include]; matches = includePatterns.some((pattern) => pattern instanceof RegExp ? pattern.test(currentRequest.request) : pattern === currentRequest.request); } // Check exclude patterns (if any) - exclude overrides include if (matches && exclude) { const excludePatterns = Array.isArray(exclude) ? exclude : [exclude]; const isExcluded = excludePatterns.some((pattern) => pattern instanceof RegExp ? pattern.test(currentRequest.request) : pattern === currentRequest.request); matches = !isExcluded; } shouldSkip = !matches; } if (shouldSkip) { debug("Skipping plugin '%s' due to filter mismatch for '%s'", plugin.name, currentRequest.request); continue; } } try { debug("Calling plugin '%s' for '%s'", plugin.name, currentRequest.request); const result = await handlerFn.call(pluginContext, currentRequest.request, currentRequest.path, { scan: true }); debug("Plugin '%s' returned:", plugin.name, result); if (!result) continue; const resolvedId = typeof result === "string" ? result : result.id; if (resolvedId && resolvedId !== currentRequest.request) { debug("Plugin '%s' resolved '%s' -> '%s'", plugin.name, currentRequest.request, resolvedId); return callback(null, { ...currentRequest, path: resolvedId, }); } else if (resolvedId === currentRequest.request) { debug("Plugin '%s' returned unchanged ID, continuing to next plugin", plugin.name); } } catch (e) { debug("Plugin '%s' failed while resolving '%s': %s", plugin.name, currentRequest.request, e.message); } } // If no plugin resolves, fall back to enhanced-resolve's default behavior debug("No Vite plugin resolved '%s', falling back.", currentRequest.request); // For absolute paths, check if the file exists if (path.isAbsolute(currentRequest.request)) { try { if (fs.existsSync(currentRequest.request)) { debug("File exists, resolving to: %s", currentRequest.request); return callback(null, { ...currentRequest, path: currentRequest.request, }); } } catch (e) { debug("Error checking file existence: %s", e.message); } } callback(); }; // For relative imports, normalize them to absolute paths first if (request.request.startsWith("../") || request.request.startsWith("./")) { try { // Use path.dirname to get the directory of the importer file const importerDir = path.dirname(request.path); const absolutePath = normalizeModulePath(request.request, importerDir, { absolute: true }); debug("Absolutified %s -> %s", request.request, absolutePath); const absoluteRequest = { ...request, request: absolutePath }; runPluginProcessing(absoluteRequest).catch((e) => { debug("Error in plugin processing: %s", e.message); callback(); }); } catch (e) { debug("Failed to absolutify %s: %s", request.request, e.message); callback(); } } else { // For non-relative imports, process them directly runPluginProcessing(request).catch((e) => { debug("Error in plugin processing: %s", e.message); callback(); }); } }); } } const mapAlias = (alias) => { const mappedAlias = {}; if (Array.isArray(alias)) { // Handle array format: { find: string | RegExp, replacement: string } for (const { find, replacement } of alias) { if (find instanceof RegExp) { // For RegExp, use the source as-is mappedAlias[find.source] = replacement; } else { // For string aliases, use them as-is without modification mappedAlias[find] = replacement; } } } else { // Handle object format: { [find: string]: replacement } for (const [find, replacement] of Object.entries(alias)) { // Use the alias key as-is without modification mappedAlias[find] = replacement; } } return mappedAlias; }; export const mapViteResolveToEnhancedResolveOptions = (viteConfig, envName) => { const env = viteConfig.environments[envName]; if (!env) { throw new Error(`Could not find environment configuration for "${envName}".`); } const envResolveOptions = (env.resolve || {}); // Merge root config aliases with environment-specific aliases const mergedAlias = { ...(viteConfig.resolve?.alias ? mapAlias(viteConfig.resolve.alias) : {}), ...(envResolveOptions.alias ? mapAlias(envResolveOptions.alias) : {}), }; // Use comprehensive extensions list similar to Vite's defaults const extensions = envResolveOptions.extensions || [ ".mjs", ".js", ".mts", ".ts", ".jsx", ".tsx", ".json", ]; const baseOptions = { // File system is required by enhanced-resolve. fileSystem: fs, // Map Vite's resolve options to enhanced-resolve's options. alias: Object.keys(mergedAlias).length > 0 ? mergedAlias : undefined, conditionNames: envResolveOptions.conditions, mainFields: envResolveOptions.mainFields, extensions, symlinks: envResolveOptions.preserveSymlinks, // Add default node modules resolution. modules: ["node_modules"], roots: [viteConfig.root], }; return baseOptions; }; export const createViteAwareResolver = (viteConfig, environment) => { const baseOptions = mapViteResolveToEnhancedResolveOptions(viteConfig, environment.name); // Add Vite plugin resolver if environment is provided const plugins = environment ? [new VitePluginResolverPlugin(environment)] : []; const enhancedResolveOptions = { ...baseOptions, plugins, }; debug("Creating enhanced-resolve with options:", { extensions: enhancedResolveOptions.extensions, alias: enhancedResolveOptions.alias, roots: enhancedResolveOptions.roots, }); return resolve.create(enhancedResolveOptions); };