UNPKG

resolve-tspaths

Version:

Transform path mappings in your compiled Typescript code

177 lines (176 loc) 7.57 kB
"use strict"; 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; } }