UNPKG

@bleed-believer/path-alias

Version:
126 lines (125 loc) 5.68 kB
import { fileURLToPath, pathToFileURL } from "url"; import { isBuiltin } from "module"; import { transform } from "@swc/core"; import { resolve } from "path"; import { access } from "node:fs/promises"; import { isPackageInstalled } from "./tool/is-package-installed/index.js"; import { ExtParser } from "./tool/ext-parser/ext-parser.js"; import { TsConfig } from "./tool/ts-config/index.js"; export class HookManager { #resolveCache = new Map(); #loadCache = new Map(); #tsConfig = TsConfig.load(); #swcrc; #rootDir = resolve(this.#tsConfig.cwd, this.#tsConfig.rootDir); #outDir = resolve(this.#tsConfig.cwd, this.#tsConfig.outDir); async #fileExist(path) { try { await access(path); return true; } catch { return false; } } #isURL(input) { try { new URL(input); return true; } catch { return false; } } async resolve(specifier, context, defaultResolve) { if (typeof context.parentURL === 'string') { const cache = this.#resolveCache.get(specifier); if (typeof cache === 'string') { // Get the value stored in cache specifier = cache; } else if (isPackageInstalled(specifier, context.parentURL)) { // Store the module specifier in cache this.#resolveCache.set(specifier, specifier); } else { const path = resolve(fileURLToPath(context.parentURL), '..', specifier); if (!await this.#fileExist(path)) { // Recalculate using outDir and rootDir // const tsPath = new ExtParser(path).toTs().replace(this.#outDir, this.#rootDir); // const jsPath = new ExtParser(path).toJs().replace(this.#rootDir, this.#outDir); const tsPath = typeof this.#outDir === 'string' && typeof this.#rootDir === 'string' ? new ExtParser(path).toTs().replace(this.#outDir, this.#rootDir) : new ExtParser(path).toTs(); const jsPath = typeof this.#outDir === 'string' && typeof this.#rootDir === 'string' ? new ExtParser(path).toJs().replace(this.#rootDir, this.#outDir) : new ExtParser(path).toJs(); if (await this.#fileExist(tsPath)) { specifier = new ExtParser(specifier).toTs(); } else if (await this.#fileExist(jsPath)) { specifier = new ExtParser(specifier).toJs(); } else if (jsPath.startsWith(this.#outDir)) { // Resolve using tsconfig path alias const promisePaths = this.#tsConfig.resolveAll(specifier, true).map(async (x)=>[ x, await this.#fileExist(x) ]); const resolvedPath = (await Promise.all(promisePaths))?.find(([_, exist])=>exist)?.[0]; if (typeof resolvedPath === 'string') { const href = pathToFileURL(resolvedPath).href; this.#resolveCache.set(specifier, href); specifier = href; } } } } } return defaultResolve(specifier, context); } async load(url, context, defaultLoad) { let out = this.#loadCache.get(url); if (out) { return out; } if (this.#isURL(url) && !isBuiltin(url)) { const path = fileURLToPath(url); const rootDir = resolve(this.#tsConfig.cwd, this.#tsConfig.rootDir); if (path.startsWith(rootDir)) { if (!this.#swcrc) { this.#swcrc = this.#tsConfig.toSwcConfig(); this.#swcrc.inlineSourcesContent = true; this.#swcrc.outputPath = resolve(this.#tsConfig.cwd, this.#tsConfig.outDir); this.#swcrc.sourceMaps = 'inline'; } const localSwcConfig = structuredClone(this.#swcrc); localSwcConfig.sourceFileName = path; localSwcConfig.filename = localSwcConfig.sourceFileName; delete localSwcConfig.exclude; const original = await defaultLoad(url, { ...context, format: 'module' }); const rawText = original.source.toString('utf-8'); const result = await transform(rawText, localSwcConfig); out = { format: 'module', source: result.code, shortCircuit: true }; this.#loadCache.set(url, out); return out; } } if (context.format === 'json') { // Added JSON compatibility if (this.#tsConfig.config.compilerOptions?.resolveJsonModule) { if (!context.importAttributes) { context.importAttributes = {}; } context.importAttributes = { type: 'json' }; } else { throw new Error([ 'To import a JSON file, first sets in your tsconfig.json configuration', 'file the property ["compilerOptions"]["resolveJsonModule"] into `true`' ].join(` `)); } } out = await defaultLoad(url, context); this.#loadCache.set(url, out); return out; } }