denoify
Version:
For NPM module authors that would like to support Deno but do not want to write and maintain a port.
133 lines (108 loc) • 5.67 kB
text/typescript
import * as path from "path";
import { assert, is } from "tsafe";
import * as fs from "fs";
import type { resolveNodeModuleToDenoModuleFactory } from "./resolveNodeModuleToDenoModule";
import type { getInstalledVersionPackageJsonFactory } from "./getInstalledVersionPackageJson";
import { ParsedImportExportStatement } from "./types/ParsedImportExportStatement";
import { consumeExecutableReplacerFactory } from "./replacer";
import { getProjectRoot } from "../tools/getProjectRoot";
/**
* examples:
* import { Evt } from "evt" -> import { Evt } from "https://deno.land/x/evt@.../mod.ts"
* import { id } "evt/dist/tools/typeSafety" -> "https://deno.land/x/evt@.../deno_dist/tools/typeSafety/index.ts"
* import * as interfaces from "./interfaces" -> import * as interfaces from "./interfaces/index.ts"
*/
export function denoifyImportExportStatementFactory(
params: {
userProvidedReplacerPath: string | undefined;
getDestDirPath(params: { dirPath: string }): string;
} & ReturnType<typeof resolveNodeModuleToDenoModuleFactory> &
ReturnType<typeof getInstalledVersionPackageJsonFactory>
) {
const { userProvidedReplacerPath, getDestDirPath, resolveNodeModuleToDenoModule, getInstalledVersionPackageJson } = params;
const { consumeExecutableReplacer: consumeExecutableBuiltinsReplacer } = consumeExecutableReplacerFactory({
"executableFilePath": path.join(
getProjectRoot(),
fs.existsSync(path.join(getProjectRoot(), "dist")) ? "dist" : "",
"bin",
"replacer",
"index.js"
)
});
const { consumeExecutableReplacer: consumeExecutableUserProvidedReplacer } =
userProvidedReplacerPath === undefined
? { "consumeExecutableReplacer": undefined }
: consumeExecutableReplacerFactory({
"executableFilePath": userProvidedReplacerPath
});
async function denoifyImportExportStatement(params: {
/** Path of the file in which the import was */
dirPath: string;
/** e.g:
* import { Evt } from "evt"
* import { id } "evt/dist/tools/typeSafety"
* import * as interfaces from "./interfaces"
* ...
*/
importExportStatement: string;
}): Promise<string> {
const { dirPath, importExportStatement } = params;
const parsedImportExportStatement = ParsedImportExportStatement.parse(params.importExportStatement);
if (parsedImportExportStatement.parsedArgument.type === "URL") {
//There is no reason to have url import in the source
//file as url import are not supported by node it does not
//hurt to leave the import unchanged. See #7
return importExportStatement;
}
const stringify = (argument: string) =>
ParsedImportExportStatement.stringify({
...parsedImportExportStatement,
"parsedArgument": ParsedImportExportStatement.ParsedArgument.parse(argument)
});
if (parsedImportExportStatement.parsedArgument.type === "PROJECT LOCAL FILE") {
const { relativePath } = parsedImportExportStatement.parsedArgument;
if (/\.json$/i.test(relativePath)) {
return stringify(relativePath);
}
for (const ext of ["ts", "tsx"]) {
if (fs.existsSync(path.join(dirPath, `${relativePath}.${ext}`))) {
return stringify(`${relativePath}.${ext}`);
}
if (relativePath.endsWith(`.${ext}`) && fs.existsSync(path.join(dirPath, relativePath))) {
return stringify(relativePath);
}
}
// relative esm module import
const esmReg = /\.m?js$/;
if (esmReg.test(path.extname(relativePath)) && fs.existsSync(path.join(dirPath, relativePath.replace(esmReg, ".ts")))) {
return stringify(relativePath.replace(esmReg, ".ts"));
}
const out = path.posix.join(relativePath, "index.ts");
return stringify(out.startsWith(".") ? out : `./${out}`);
}
//NOTE: Should be inferable...
assert(is<import("./replacer").ParsedImportExportStatement<"DEPENDENCY">>(parsedImportExportStatement));
const { nodeModuleName, specificImportPath } = parsedImportExportStatement.parsedArgument;
const { version } = await getInstalledVersionPackageJson({ nodeModuleName }).catch(() => ({ "version": "0.0.0" }));
for (const consumeExecutableReplacer of [consumeExecutableUserProvidedReplacer, consumeExecutableBuiltinsReplacer]) {
const result = await consumeExecutableReplacer?.({
parsedImportExportStatement,
version,
"destDirPath": getDestDirPath({ dirPath })
});
if (result === undefined) {
continue;
}
return result;
}
const nodeToDenoModuleResolutionResult = await resolveNodeModuleToDenoModule({ nodeModuleName });
if (nodeToDenoModuleResolutionResult.result === "UNKNOWN BUILTIN") {
return stringify(`node:${ParsedImportExportStatement.ParsedArgument.stringify(parsedImportExportStatement.parsedArgument)}`);
}
const { getValidImportUrl } = nodeToDenoModuleResolutionResult;
return stringify(
await getValidImportUrl(!specificImportPath ? { "target": "DEFAULT EXPORT" } : { "target": "SPECIFIC FILE", specificImportPath })
);
}
return { denoifyImportExportStatement };
}