diff --git a/src/_transformations/specifiers.test.ts b/src/_transformations/specifiers.test.ts index 149ea80..b6f6934 100644 --- a/src/_transformations/specifiers.test.ts +++ b/src/_transformations/specifiers.test.ts @@ -1,6 +1,12 @@ import fc from "https://esm.sh/fast-check@3.10.0"; -import { name, services, transpileSpecifier } from "./specifiers.ts"; +import { + name, + services, + transpileHttpsImport, + transpileSpecifier, +} from "./specifiers.ts"; +const runtime = fc.stringMatching(/^\w+$/); const join = (array: string[]) => array.join(""); const relativePrefix = fc.constantFrom("./", "../"); const extension = fc.constantFrom("js", "ts", "jsx", "tsx"); @@ -20,9 +26,15 @@ Deno.test(function localSpecifiers() { fc.property( relativePath, extension, - (path, ext) => - transpileSpecifier(`${path}.deno.m${ext}`) === `${path}.node.mjs` && - transpileSpecifier(`${path}.deno.${ext}`) === `${path}.node.js`, + runtime, + runtime, + (path, ext, oldRuntime, newRuntime) => { + const fn = transpileSpecifier(oldRuntime, newRuntime); + return ( + fn(`${path}.${oldRuntime}.m${ext}`) === `${path}.${newRuntime}.mjs` && + fn(`${path}.${oldRuntime}.${ext}`) === `${path}.${newRuntime}.js` + ); + }, ), ); }); @@ -31,7 +43,7 @@ Deno.test(function remoteSpecifiers() { fc.assert( fc.property(urlSegments, (segments) => { const [, scopedPackage, , path] = segments; - return transpileSpecifier(join(segments)) === scopedPackage + path; + return transpileHttpsImport(join(segments)) === scopedPackage + path; }), ); }); diff --git a/src/_transformations/specifiers.ts b/src/_transformations/specifiers.ts index a34f350..bfb3324 100644 --- a/src/_transformations/specifiers.ts +++ b/src/_transformations/specifiers.ts @@ -7,30 +7,30 @@ const version = /^@[^/?]+$/; const path = /\/[^?]*/; const url = re.tag()`^${re.union(services)}(${name})${version}?(${path})?.*$`; -const transpileHttpsImport = (specifier: string) => +export const transpileHttpsImport = (specifier: string) => specifier.replace(url, "$1$2"); export const transpileExtension = (specifier: string) => specifier.replace(/[jt]sx?$/i, "js"); -const transpileRelativeImport = (specifier: string) => - transpileExtension(specifier).replace(".deno.", ".node."); - const isRelative = (specifier: string) => /^\.\.?\//.test(specifier); -export const transpileSpecifier = (specifier: string) => { - if (isRelative(specifier)) return transpileRelativeImport(specifier); - return transpileHttpsImport(specifier); -}; +export const transpileSpecifier = + (oldRuntime: string, newRuntime: string) => (specifier: string) => + isRelative(specifier) + ? transpileExtension(specifier).replace( + `.${oldRuntime}.`, + `.${newRuntime}.`, + ) + : transpileHttpsImport(specifier); /** * Replaces all import/export specifiers in `sourceFile` - * according to the specified `transpileSpecifier` function. - * The default one makes specifiers Node-friendly. + * according to the specified `fn`. */ -export function transpileSpecifiers( +export function replaceSpecifiers( sourceFile: SourceFile, - fn = transpileSpecifier, + fn: (specifier: string) => string, ) { for (const statement of sourceFile.getStatements()) { if ( diff --git a/src/context.ts b/src/context.ts index 02cef58..0fb35f7 100644 --- a/src/context.ts +++ b/src/context.ts @@ -1,4 +1,8 @@ import path from "node:path"; +import { + replaceSpecifiers, + transpileSpecifier, +} from "./_transformations/specifiers.ts"; import { type Config, parse } from "./config.ts"; import { Project, ts } from "./deps.deno.ts"; @@ -18,6 +22,7 @@ export class Context { public baseDir: string; public config: Config; readonly project: Project; + private _runtime = "deno"; /** * Synchronously loads `tsconfig.json` and `"files"`. @@ -42,4 +47,20 @@ export class Context { resolve(...pathSegments: string[]) { return path.join(this.baseDir, ...pathSegments); } + + changeRuntimeTo(newRuntime: string) { + const label = `Changing runtime from ${this._runtime} to ${newRuntime}`; + const fn = transpileSpecifier(this._runtime, newRuntime); + + console.time(label); + for (const sourceFile of this.project.getSourceFiles()) { + if (sourceFile.getBaseName().includes(`.${this._runtime}.`)) { + sourceFile.forget(); + } else { + replaceSpecifiers(sourceFile, fn); + } + } + this._runtime = newRuntime; + console.timeEnd(label); + } } diff --git a/src/deno2node.ts b/src/deno2node.ts index 0a54e91..be084a5 100644 --- a/src/deno2node.ts +++ b/src/deno2node.ts @@ -1,11 +1,6 @@ import { shimEverything } from "./_transformations/shim.ts"; -import { transpileSpecifiers } from "./_transformations/specifiers.ts"; import { vendorEverything } from "./_transformations/vendor.ts"; import { type Context } from "./context.ts"; -import { type SourceFile } from "./deps.deno.ts"; - -const isDenoSpecific = (sourceFile: SourceFile) => - sourceFile.getBaseNameWithoutExtension().toLowerCase().endsWith(".deno"); /** * Attempts to transform arbitrary `ctx.project` into a valid Node.js project: @@ -44,16 +39,7 @@ const isDenoSpecific = (sourceFile: SourceFile) => * [shim file]: https://github.com/fromdeno/deno2node/blob/main/src/shim.node.ts */ export function deno2node(ctx: Context): void { - console.time("Basic transformations"); - for (const sourceFile of ctx.project.getSourceFiles()) { - if (isDenoSpecific(sourceFile)) { - ctx.project.removeSourceFile(sourceFile); - continue; - } - transpileSpecifiers(sourceFile); - } - console.timeEnd("Basic transformations"); - + ctx.changeRuntimeTo("node"); vendorEverything(ctx); shimEverything(ctx); }