resolve-tspaths
Version:
Transform path mappings in your compiled Typescript code
177 lines (176 loc) • 7.57 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MATCHERS = void 0;
exports.generateChanges = generateChanges;
exports.replaceAliasPathsInFile = replaceAliasPathsInFile;
exports.aliasToRelativePath = aliasToRelativePath;
const fs_1 = require("fs");
const path_1 = require("path");
const errors_1 = require("../utils/errors");
const path_2 = require("../utils/path");
exports.MATCHERS = [
// require("package"), require.resolve("package"), import("package")
/((?:require|require\.resolve|import)\((?:"([^"]+)")\))/g,
/((?:require|require\.resolve|import)\((?:'([^']+)')\))/g,
// import "package"
/((?:import|export) (?:"([^"]+)"))/g,
/((?:import|export) (?:'([^']+)'))/g,
// import {} from "package", import * as name from "package"
/((?:import|export) .+ from (?:"([^"]+)"))/g,
/((?:import|export) .+ from (?:'([^']+)'))/g,
// multiline import/exports with {}
/((?:import|export) {\n[^}]+} from (?:"([^"]+)"))/g,
/((?:import|export) {\n[^}]+} from (?:'([^']+)'))/g,
];
const MODULE_EXTS = [
".js",
".jsx",
".ts",
".tsx",
".cjs",
".mjs",
".mdx",
".d.ts",
".json",
];
/**
* Generate the alias path mapping changes to apply to the provide files.
*
* @param files The list of files to replace alias paths in.
* @param aliases The path mapping configuration from tsconfig.
* @param programPaths Program options.
*/
function generateChanges(files, aliases, programPaths) {
const changeList = [];
for (const file of files) {
const { changed, text, changes } = replaceAliasPathsInFile(file, aliases, programPaths);
if (!changed)
continue;
changeList.push({ file, text, changes });
}
return changeList;
}
/**
* Read the file at the given path and return the text with aliased paths replaced.
*
* @param filePath The path to the file.
* @param aliases The path mapping configuration from tsconfig.
* @param programPaths Program options.
*/
function replaceAliasPathsInFile(filePath, aliases, programPaths) {
if (!(0, fs_1.existsSync)(filePath))
throw new errors_1.FileNotFoundError(replaceAliasPathsInFile.name, filePath);
const originalText = (0, fs_1.readFileSync)(filePath, "utf-8");
const changes = [];
const newText = exports.MATCHERS.reduce((text, regex) => text.replace(regex, (original, importStatement, importSpecifier) => {
// The import is an esm import if it is inside a typescript (definition) file or if it uses `import` or `export`
const esmImport = !filePath.endsWith(".ts") &&
(importStatement.includes("import") ||
importStatement.includes("export"));
const result = aliasToRelativePath(importSpecifier, filePath, aliases, programPaths, esmImport);
if (!result.replacement)
return original;
const index = original.lastIndexOf(importSpecifier);
changes.push({
original: (0, path_2.normalizePath)(result.original),
modified: (0, path_2.normalizePath)(result.replacement),
});
return (original.substring(0, index) +
result.replacement +
original.substring(index + importSpecifier.length));
}), originalText);
return { changed: originalText !== newText, text: newText, changes };
}
/**
* Convert an aliased path to a relative path.
*
* @param importSpecifier A import specifier as used in the source file
* @param outputFile The location of the file that the aliased path was from.
* @param aliases The path mapping configuration from tsconfig.
* @param programPaths Program options.
* @param esModule Whether the import will be resolved with ES module semantics or commonjs semantics
*/
function aliasToRelativePath(importSpecifier, outputFile, aliases, { srcPath, outPath }, esModule) {
const sourceFile = (0, path_1.resolve)(srcPath, (0, path_1.relative)(outPath, outputFile));
const sourceFileDirectory = (0, path_1.dirname)(sourceFile);
const outputFileDirectory = (0, path_1.dirname)(outputFile);
const importPathIsRelative = importSpecifier.startsWith("./") || importSpecifier.startsWith("../");
const matchingAliases = aliases.filter(({ prefix }) => importSpecifier.startsWith(prefix));
const absoluteImportPaths = importPathIsRelative
? [(0, path_1.resolve)(sourceFileDirectory, importSpecifier)]
: matchingAliases.flatMap(({ prefix, aliasPaths }) => aliasPaths.map((aliasPath) => (0, path_1.resolve)(aliasPath, importSpecifier.replace(prefix, ""))));
const absoluteImport = absoluteImportPaths.reduce((acc, path) => acc || resolveImportPath(path), null);
if (!absoluteImport) {
return {
file: outputFile,
original: importSpecifier,
};
}
const absoluteImportPath = esModule
? absoluteImport.file
: absoluteImport.imported;
const relativeImportPath = absoluteImport.type === "file"
? (0, path_1.join)((0, path_1.relative)(sourceFileDirectory, (0, path_1.dirname)(absoluteImportPath)), (0, path_1.basename)(absoluteImportPath))
: (0, path_1.relative)(sourceFileDirectory, absoluteImportPath);
const prefixedRelativePath = relativeImportPath.replace(/^(?!\.+\/)/, (m) => "./" + m);
const relativePathJsExtension = prefixedRelativePath.replace(/\.[^/.]*ts[^/.]*$/, (match) => match
.replace(/\.ts$/, ".js")
.replace(/\.tsx$/, ".jsx")
.replace(/\.mts$/, ".mjs")
.replace(/\.cts$/, ".cjs"));
const jsxFileExists = isFile((0, path_1.resolve)(outputFileDirectory, relativePathJsExtension));
const relativePathJsxExtension = jsxFileExists
? relativePathJsExtension
: relativePathJsExtension.replace(/\.jsx$/, ".js");
return Object.assign({ file: (0, path_2.normalizePath)(outputFile), original: (0, path_2.normalizePath)(importSpecifier) }, (importSpecifier !== relativePathJsxExtension && {
replacement: (0, path_2.normalizePath)(relativePathJsxExtension),
}));
}
/**
* Find the file that will be imported by the given import path.
*
* @param importPath An non-relative import path
*/
function resolveImportPath(importPath) {
const importPathTs = importPath.replace(/\.[^/.]*js[^/.]*$/, (match) => match.replace("js", "ts"));
const importPathWithoutFileExtension = importPath.replace(/\.[^/.]*(js|json)[^/.]*$/, "");
const importPathWithExtensions = MODULE_EXTS.map((ext) => `${importPathWithoutFileExtension}${ext}`);
const possiblePaths = [importPath, importPathTs, ...importPathWithExtensions];
const existingPath = possiblePaths.find((path) => isFile(path));
if (existingPath) {
return {
imported: importPath,
file: existingPath,
type: "file",
};
}
// Try index files if the path is a directory
const possiblePathsAsDirectory = isDirectory(importPath)
? MODULE_EXTS.map((ext) => `${importPath}/index${ext}`)
: [];
const existingIndexPath = possiblePathsAsDirectory.find((path) => isFile(path));
if (existingIndexPath) {
return {
imported: importPath,
file: existingIndexPath,
type: "directory",
};
}
return null;
}
function isFile(path) {
try {
return (0, fs_1.statSync)(path).isFile();
}
catch (e) {
return false;
}
}
function isDirectory(path) {
try {
return (0, fs_1.statSync)(path).isDirectory();
}
catch (e) {
return false;
}
}