|
4 | 4 | import fs from "node:fs/promises";
|
5 | 5 | import type { LoadHook, ModuleFormat, ResolveHook } from "node:module";
|
6 | 6 | import { builtinModules, createRequire } from "node:module";
|
7 |
| -import { dirname, extname, join, resolve as resolvePath } from "node:path"; |
| 7 | +import { dirname, join, resolve as resolvePath } from "node:path"; |
8 | 8 | import { fileURLToPath, pathToFileURL } from "node:url";
|
9 | 9 | import { ResolverFactory } from "oxc-resolver";
|
10 | 10 | import { debugLog } from "../SyncWorker.cjs";
|
@@ -166,7 +166,7 @@ export const load: LoadHook = async function load(url, context, nextLoad) {
|
166 | 166 | return await nextLoad(url, context);
|
167 | 167 | }
|
168 | 168 |
|
169 |
| - const format: ModuleFormat = context.format ?? (await getPackageType(url)) ?? "commonjs"; |
| 169 | + const format: ModuleFormat = context.format ?? (await getPackageType(fileURLToPath(url))) ?? "commonjs"; |
170 | 170 | if (format == "commonjs") {
|
171 | 171 | // if the package is a commonjs package and we return the source contents explicitly, this loader will process the inner requires, but with a broken/different version of \`require\` internally.
|
172 | 172 | // if we return a nullish source, node falls back to the old, mainline require chain, which has require.cache set properly and whatnot.
|
@@ -197,30 +197,43 @@ export const load: LoadHook = async function load(url, context, nextLoad) {
|
197 | 197 | };
|
198 | 198 | };
|
199 | 199 |
|
200 |
| -async function getPackageType(url: string): Promise<ModuleFormat | undefined> { |
201 |
| - // `url` is only a file path during the first iteration when passed the resolved url from the load() hook |
202 |
| - // an actual file path from load() will contain a file extension as it's required by the spec |
203 |
| - // this simple truthy check for whether `url` contains a file extension will work for most projects but does not cover some edge-cases (such as extensionless files or a url ending in a trailing space) extensionless files or a url ending in a trailing space) |
204 |
| - const isFilePath = !!extname(url); |
| 200 | +async function getPackageType(path: string, isFilePath?: boolean): Promise<ModuleFormat | undefined> { |
| 201 | + try { |
| 202 | + isFilePath ??= await fs.readdir(path).then(() => false); |
| 203 | + } catch (err: any) { |
| 204 | + if (err?.code !== "ENOTDIR") { |
| 205 | + throw err; |
| 206 | + } |
| 207 | + isFilePath = true; |
| 208 | + } |
| 209 | + |
205 | 210 | // If it is a file path, get the directory it's in
|
206 |
| - const dir = isFilePath ? dirname(fileURLToPath(url)) : url; |
| 211 | + const dir = isFilePath ? dirname(path) : path; |
207 | 212 | // Compose a file path to a package.json in the same directory,
|
208 | 213 | // which may or may not exist
|
209 | 214 | const packagePath = resolvePath(dir, "package.json");
|
210 |
| - debugLog?.("getPackageType", { url, packagePath }); |
| 215 | + debugLog?.("getPackageType", { path, packagePath }); |
211 | 216 |
|
212 | 217 | // Try to read the possibly nonexistent package.json
|
213 | 218 | const type = await fs
|
214 | 219 | .readFile(packagePath, { encoding: "utf8" })
|
215 |
| - .then((filestring) => JSON.parse(filestring).type) |
| 220 | + .then((filestring) => { |
| 221 | + // As per node's docs, we use the nearest package.json to figure out the package type (see https://nodejs.org/api/packages.html#type) |
| 222 | + // If it lacks a "type" key, we assume "commonjs". If we fail to parse, we also choose to assume "commonjs". |
| 223 | + try { |
| 224 | + return JSON.parse(filestring).type || "commonjs"; |
| 225 | + } catch (_err) { |
| 226 | + return "commonjs"; |
| 227 | + } |
| 228 | + }) |
216 | 229 | .catch((err) => {
|
217 | 230 | if (err?.code !== "ENOENT") console.error(err);
|
218 | 231 | });
|
219 |
| - // If package.json existed and contained a `type` field with a value, voilà |
| 232 | + // If package.json existed, we guarantee a type and return it. |
220 | 233 | if (type) return type;
|
221 | 234 | // Otherwise, (if not at the root) continue checking the next directory up
|
222 | 235 | // If at the root, stop and return false
|
223 |
| - if (dir.length > 1) return await getPackageType(resolvePath(dir, "..")); |
| 236 | + if (dir.length > 1) return await getPackageType(resolvePath(dir, ".."), false); |
224 | 237 |
|
225 | 238 | return undefined;
|
226 | 239 | }
|
0 commit comments