|
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