diff --git a/examples/vite-react-ts/tsconfig.json b/examples/vite-react-ts/tsconfig.json index 288a0e2..e4e5c6a 100644 --- a/examples/vite-react-ts/tsconfig.json +++ b/examples/vite-react-ts/tsconfig.json @@ -14,5 +14,5 @@ "strict": true, "types": ["vite/client", "node"] }, - "include": ["./src/**/*"] + "include": ["./src/**/*", "vite.config.ts"] } diff --git a/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/build/index.ts b/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/build/index.ts index 52ab029..3693180 100644 --- a/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/build/index.ts +++ b/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/build/index.ts @@ -1,4 +1,5 @@ import type { Module, ModuleLinker } from "node:vm"; +import { loadModule } from "../loadModule"; import { loadBuiltinModule } from "../loaders/loadBuiltinModule"; import { loadNodeModule } from "../loaders/loadNodeModule"; import type { TransformContext } from "../types"; @@ -29,43 +30,31 @@ export function createLinker({ const basePath = referencingPath === "*target*" ? modulePath : referencingPath; - const loadedModule = - (await loadBuiltinModule({ - modulesCache, - referencingModule, - specifier, - })) ?? - (await loadNodeModule({ modulesCache, referencingModule, specifier })) ?? - (await loadViteModule({ - ctx, - modulesCache, - referencingModule, - specifier, - importMeta, - })) ?? - (await loadAbsoluteModule({ - ctx, - modulesCache, - referencingModule, - specifier, - importMeta, - })) ?? - (await loadRelativeModule({ - basePath, - ctx, - modulesCache, - referencingModule, - specifier, - importMeta, - })); + const loadModuleArgs = { + basePath, + ctx, + modulesCache, + referencingModule, + specifier, + importMeta, + }; - if (loadedModule) { - return loadedModule; + const { error, module } = await loadModule( + [ + loadBuiltinModule, + loadNodeModule, + loadViteModule, + loadAbsoluteModule, + loadRelativeModule, + ], + loadModuleArgs, + ); + + if (module) { + return module; } - throw new Error( - `Failed to load "${specifier}" from "${referencingModule.identifier}"`, - ); + throw error; }; return { linker }; } diff --git a/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/build/loaders/loadAbsoluteModule.ts b/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/build/loaders/loadAbsoluteModule.ts index ddc1a29..93aec10 100644 --- a/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/build/loaders/loadAbsoluteModule.ts +++ b/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/build/loaders/loadAbsoluteModule.ts @@ -2,7 +2,7 @@ import vm, { type Module } from "node:vm"; import { isVirtualCssModuleId } from "#@/vite/virtualCssModuleId"; import { isVirtualGlobalStyleId } from "#@/vite/virtualGlobalStyleId"; import { buildInitializeImportMeta } from "../../initializeImportMeta"; -import type { TransformContext } from "../../types"; +import type { LoadModuleReturn, TransformContext } from "../../types"; type LoadAbsoluteModuleArgs = { modulesCache: Map; @@ -18,7 +18,7 @@ export async function loadAbsoluteModule({ referencingModule, ctx, importMeta, -}: LoadAbsoluteModuleArgs): Promise { +}: LoadAbsoluteModuleArgs): Promise { // casablanca modules must be resolved by casablanca plugin. const skipSelf = !isVirtualCssModuleId(specifier) && !isVirtualGlobalStyleId(specifier); @@ -27,20 +27,31 @@ export async function loadAbsoluteModule({ skipSelf, }); if (!resolvedAbsolutePath) { - return null; + return { + error: new Error(`Failed to resolve "${specifier}"`), + module: null, + }; } const cached = modulesCache.get(resolvedAbsolutePath.id); if (cached) { - return cached; + return { error: null, module: cached }; } const loaded = await ctx .load({ id: resolvedAbsolutePath.id, resolveDependencies: true, }) - .catch(() => null); - if (!loaded?.code) { - return null; + .then((r) => + typeof r.code === "string" + ? { code: r.code, error: null } + : { + code: null, + error: new Error(`Failed to load "${resolvedAbsolutePath.id}"`), + }, + ) + .catch((e) => ({ code: null, error: e })); + if (typeof loaded.code !== "string") { + return { error: loaded.error, module: null }; } const m = new vm.SourceTextModule(loaded.code, { context: referencingModule.context, @@ -51,5 +62,5 @@ export async function loadAbsoluteModule({ }), }); modulesCache.set(resolvedAbsolutePath.id, m); - return m; + return { error: null, module: m }; } diff --git a/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/build/loaders/loadRelativeModule.ts b/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/build/loaders/loadRelativeModule.ts index eaa03fc..fa372d1 100644 --- a/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/build/loaders/loadRelativeModule.ts +++ b/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/build/loaders/loadRelativeModule.ts @@ -1,6 +1,6 @@ import vm, { type Module } from "node:vm"; import { buildInitializeImportMeta } from "../../initializeImportMeta"; -import type { TransformContext } from "../../types"; +import type { LoadModuleReturn, TransformContext } from "../../types"; type LoadRelativeModuleArgs = { modulesCache: Map; @@ -18,24 +18,35 @@ export async function loadRelativeModule({ ctx, basePath, importMeta, -}: LoadRelativeModuleArgs): Promise { +}: LoadRelativeModuleArgs): Promise { // resolve id as relative path const resolvedRelativePath = await ctx.resolve(specifier, basePath); if (!resolvedRelativePath) { - return null; + return { + error: new Error(`Failed to resolve "${specifier}"`), + module: null, + }; } const cached = modulesCache.get(resolvedRelativePath.id); if (cached) { - return cached; + return { error: null, module: cached }; } const loaded = await ctx .load({ id: resolvedRelativePath.id, resolveDependencies: true, }) - .catch(() => null); - if (typeof loaded?.code !== "string") { - return null; + .then((r) => + typeof r.code === "string" + ? { code: r.code, error: null } + : { + code: null, + error: new Error(`Failed to load "${resolvedRelativePath.id}"`), + }, + ) + .catch((e) => ({ code: null, error: e })); + if (typeof loaded.code !== "string") { + return { error: loaded.error, module: null }; } const m = new vm.SourceTextModule(loaded.code, { @@ -47,5 +58,5 @@ export async function loadRelativeModule({ }), }); modulesCache.set(resolvedRelativePath.id, m); - return m; + return { error: null, module: m }; } diff --git a/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/build/loaders/loadViteModule.ts b/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/build/loaders/loadViteModule.ts index 27cc80d..71b46e3 100644 --- a/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/build/loaders/loadViteModule.ts +++ b/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/build/loaders/loadViteModule.ts @@ -1,6 +1,6 @@ import vm, { type Module } from "node:vm"; import { buildInitializeImportMeta } from "../../initializeImportMeta"; -import type { TransformContext } from "../../types"; +import type { LoadModuleReturn, TransformContext } from "../../types"; type LoadViteModuleArgs = { modulesCache: Map; @@ -16,10 +16,10 @@ export async function loadViteModule({ referencingModule, ctx, importMeta, -}: LoadViteModuleArgs): Promise { +}: LoadViteModuleArgs): Promise { const cached = modulesCache.get(specifier); if (cached) { - return cached; + return { error: null, module: cached }; } // resolve id as vite-specific module const loaded = await ctx @@ -27,9 +27,17 @@ export async function loadViteModule({ id: specifier, resolveDependencies: true, }) - .catch(() => null); - if (!loaded?.code) { - return null; + .then((r) => + r.code + ? { code: r.code, error: null } + : { + code: null, + error: new Error(`Failed to load "${specifier}"`), + }, + ) + .catch((e) => ({ code: null, error: e })); + if (!loaded.code) { + return { error: loaded.error, module: null }; } const m = new vm.SourceTextModule(loaded.code, { @@ -41,5 +49,5 @@ export async function loadViteModule({ }), }); modulesCache.set(specifier, m); - return m; + return { error: null, module: m }; } diff --git a/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/loadModule.ts b/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/loadModule.ts new file mode 100644 index 0000000..c48cd64 --- /dev/null +++ b/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/loadModule.ts @@ -0,0 +1,36 @@ +import type { LoadModuleReturn } from "./types"; + +export async function loadModule( + loaders: ((a: T) => Promise)[], + args: T, +): Promise { + const tailLoader = loaders.pop(); + if (!tailLoader) { + throw new Error("Loaders are empty."); + } + const errors: Error[] = []; + for (const loader of loaders) { + const result = await loader(args); + if (result.module) { + return result; + } + errors.push(result.error); + } + const result = await tailLoader(args); + if (result.module) { + return result; + } + errors.push(result.error); + const errorMsg = `\ +Failed to load "${args.specifier}". + +Captured errors while loading: +------ +${errors.map((e) => `${e}`).join("\n------\n")} +------ +`; + return { + error: new Error(errorMsg), + module: null, + }; +} diff --git a/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/loaders/loadBuiltinModule.ts b/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/loaders/loadBuiltinModule.ts index 6fa4f05..3a3cd46 100644 --- a/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/loaders/loadBuiltinModule.ts +++ b/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/loaders/loadBuiltinModule.ts @@ -1,5 +1,6 @@ import { isBuiltin } from "node:module"; import vm, { type Module } from "node:vm"; +import type { LoadModuleReturn } from "../types"; export async function loadBuiltinModule({ modulesCache, @@ -9,20 +10,26 @@ export async function loadBuiltinModule({ modulesCache: Map; specifier: string; referencingModule: Module; -}): Promise { +}): Promise { const cached = modulesCache.get(specifier); if (cached) { - return cached; + return { error: null, module: cached }; } if (!isBuiltin(specifier)) { - return null; + return { + error: new Error(`"${specifier}" is not a builtin module.`), + module: null, + }; } const normalizedSpecifier = specifier.startsWith("node:") ? specifier : `node:${specifier}`; const imported = await import(normalizedSpecifier).catch(() => null); if (!imported) { - return null; + return { + error: new Error(`Module "${specifier}" was not found in builtins.`), + module: null, + }; } const exportNames = Object.keys(imported); const m = new vm.SyntheticModule( @@ -41,5 +48,5 @@ export async function loadBuiltinModule({ modulesCache.set(normalizedSpecifier, m); isBuiltin(shortSpecifier) ? modulesCache.set(shortSpecifier, m) : void 0; - return m; + return { error: null, module: m }; } diff --git a/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/loaders/loadNodeModule.ts b/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/loaders/loadNodeModule.ts index c6ffeb2..411d06c 100644 --- a/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/loaders/loadNodeModule.ts +++ b/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/loaders/loadNodeModule.ts @@ -1,4 +1,5 @@ import vm, { type Module } from "node:vm"; +import type { LoadModuleReturn } from "../types"; export async function loadNodeModule({ modulesCache, @@ -8,14 +9,17 @@ export async function loadNodeModule({ modulesCache: Map; specifier: string; referencingModule: Module; -}): Promise { +}): Promise { const cached = modulesCache.get(specifier); if (cached) { - return cached; + return { error: null, module: cached }; } const imported = await import(specifier).catch(() => null); if (!imported) { - return null; + return { + error: new Error(`Module "${specifier}" was not found in node_modules.`), + module: null, + }; } const exportNames = Object.keys(imported); const m = new vm.SyntheticModule( @@ -31,5 +35,5 @@ export async function loadNodeModule({ }, ); modulesCache.set(specifier, m); - return m; + return { error: null, module: m }; } diff --git a/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/serve/index.ts b/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/serve/index.ts index 8f165ef..fcccaca 100644 --- a/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/serve/index.ts +++ b/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/serve/index.ts @@ -1,5 +1,6 @@ import type { Module, ModuleLinker } from "node:vm"; import type { ViteDevServer } from "vite"; +import { loadModule } from "../loadModule"; import { loadBuiltinModule } from "../loaders/loadBuiltinModule"; import { loadNodeModule } from "../loaders/loadNodeModule"; import { loadRelativeModule } from "./loaders/loadRelativeModule"; @@ -25,36 +26,24 @@ export function createLinker({ referencingPath === "*target*" ? modulePath : referencingPath; const serverSpecifier = normalizeSpecifier(specifier); - const loadedModule = - (await loadBuiltinModule({ - modulesCache, - referencingModule, - specifier: serverSpecifier, - })) ?? - (await loadNodeModule({ - modulesCache, - referencingModule, - specifier: serverSpecifier, - })) ?? - (await loadViteModule({ - modulesCache, - referencingModule, - server, - specifier: serverSpecifier, - })) ?? - (await loadRelativeModule({ - basePath, - modulesCache, - referencingModule, - server, - specifier: serverSpecifier, - })); + const loadModuleArgs = { + basePath, + modulesCache, + referencingModule, + server, + specifier: serverSpecifier, + }; - if (loadedModule) { - return loadedModule; + const { error, module } = await loadModule( + [loadBuiltinModule, loadNodeModule, loadViteModule, loadRelativeModule], + loadModuleArgs, + ); + + if (module) { + return module; } - throw new Error(`Failed to load "${serverSpecifier}" from "${modulePath}"`); + throw error; }; return { linker }; } diff --git a/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/serve/loaders/loadRelativeModule.ts b/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/serve/loaders/loadRelativeModule.ts index 7bd3fbc..57f71bf 100644 --- a/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/serve/loaders/loadRelativeModule.ts +++ b/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/serve/loaders/loadRelativeModule.ts @@ -1,5 +1,6 @@ import vm, { type Module } from "node:vm"; import type { ViteDevServer } from "vite"; +import type { LoadModuleReturn } from "../../types"; export async function loadRelativeModule({ modulesCache, @@ -13,7 +14,7 @@ export async function loadRelativeModule({ referencingModule: Module; server: ViteDevServer; basePath: string; -}): Promise { +}): Promise { // resolve id as relative path const resolvedRelativePath = await server.pluginContainer.resolveId( specifier, @@ -21,11 +22,14 @@ export async function loadRelativeModule({ ); if (!resolvedRelativePath) { - return null; + return { + error: new Error(`Failed to resolve "${specifier}"`), + module: null, + }; } const cached = modulesCache.get(resolvedRelativePath.id); if (cached) { - return cached; + return { error: null, module: cached }; } const resolvedModule = server.moduleGraph.getModuleById( @@ -34,9 +38,12 @@ export async function loadRelativeModule({ const url = resolvedModule?.url ?? resolvedRelativePath.id; - const loaded = await server.ssrLoadModule(url).catch(() => null); + const { error, loaded } = await server + .ssrLoadModule(url) + .then((r) => ({ loaded: r, error: null })) + .catch((e) => ({ loaded: null, error: e })); if (!loaded) { - return null; + return { error, module: null }; } const exportNames = Object.keys(loaded); @@ -53,5 +60,5 @@ export async function loadRelativeModule({ }, ); modulesCache.set(specifier, m); - return m; + return { error: null, module: m }; } diff --git a/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/serve/loaders/loadViteModule.ts b/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/serve/loaders/loadViteModule.ts index 9d27a3c..bf8c3a3 100644 --- a/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/serve/loaders/loadViteModule.ts +++ b/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/serve/loaders/loadViteModule.ts @@ -1,5 +1,6 @@ import vm, { type Module } from "node:vm"; import type { ViteDevServer } from "vite"; +import type { LoadModuleReturn } from "../../types"; export async function loadViteModule({ modulesCache, @@ -11,20 +12,26 @@ export async function loadViteModule({ specifier: string; referencingModule: Module; server: ViteDevServer; -}): Promise { +}): Promise { const cached = modulesCache.get(specifier); if (cached) { - return cached; + return { error: null, module: cached }; } // resolve id as vite-specific module const resolvedModule = server.moduleGraph.getModuleById(specifier); if (!resolvedModule) { - return null; + return { + error: new Error(`Failed to resolve "${specifier}"`), + module: null, + }; } const { url } = resolvedModule; - const loaded = await server.ssrLoadModule(url).catch(() => null); + const { loaded, error } = await server + .ssrLoadModule(url) + .then((r) => ({ loaded: r, error: null })) + .catch((e) => ({ loaded: null, error: e })); if (!loaded) { - return null; + return { error, module: null }; } const exportNames = Object.keys(loaded); @@ -41,5 +48,5 @@ export async function loadViteModule({ }, ); modulesCache.set(specifier, m); - return m; + return { error: null, module: m }; } diff --git a/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/types.ts b/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/types.ts index b88ded4..44fc923 100644 --- a/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/types.ts +++ b/packages/core/src/vite/hooks/transform/stages/3.evaluate-module/types.ts @@ -1,3 +1,4 @@ +import type { Module } from "node:vm"; import type { types } from "@babel/core"; import type { Plugin } from "vite"; import type { CapturedVariableNames } from "../1.capture-tagged-styles/types"; @@ -32,3 +33,8 @@ export type Evaluator = (args: { export type TransformContext = ThisParameterType< Exclude, { order?: unknown }> >; + +export type LoadModuleFailed = { error: Error; module: null }; +export type LoadModuleOk = { error: null; module: Module }; + +export type LoadModuleReturn = LoadModuleFailed | LoadModuleOk;