diff --git a/README.md b/README.md index 3c39b17..53c89ac 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,25 @@ If `process.env.TS_NODE_BASEURL` is set it will override the value of `baseUrl` If `process.env.TS_NODE_PROJECT` is set it will be used to resolved tsconfig.json +## With jest + +You can plug in tsconfig-paths as a before hook transformer for jest, just following the example below: + +```json +"transform": { + "^.+\\.(t|j)s$": [ + "ts-jest", + { + "astTransformers": { + "before": [ + "node_modules/tsconfig-paths/plugin" + ] + } + } + ] + }, +``` + ### With webpack For webpack please use the [tsconfig-paths-webpack-plugin](https://github.com/dividab/tsconfig-paths-webpack-plugin). diff --git a/package.json b/package.json index e08f4fa..4204c47 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,11 @@ "author": "Jonas Kello", "license": "MIT", "repository": "https://github.com/dividab/tsconfig-paths", + "files": [ + "lib", + "plugin.js", + "register.js" + ], "engines": { "node": ">=6" }, diff --git a/plugin.js b/plugin.js new file mode 100644 index 0000000..4b2a329 --- /dev/null +++ b/plugin.js @@ -0,0 +1,3 @@ +// eslint-disable-next-line @typescript-eslint/no-require-imports +module.exports = require('./lib/build-plugin/factory'); +module.exports.default = module.exports; \ No newline at end of file diff --git a/src/build-plugin/before-hook.ts b/src/build-plugin/before-hook.ts new file mode 100644 index 0000000..a4df5bd --- /dev/null +++ b/src/build-plugin/before-hook.ts @@ -0,0 +1,111 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import * as os from "os"; +import { dirname, posix } from "path"; +import * as ts from "typescript"; +import { TypeScriptBinaryLoader } from "./typescript-loader"; +import { createMatchPath, MatchPath } from "../match-path-sync"; +import { tsConfigLoader } from "../tsconfig-loader"; + +function getNotAliasedPath( + sf: ts.SourceFile, + matcher: MatchPath, + text: string +): string | undefined { + let result = matcher(text, undefined, undefined, [ + ".ts", + ".tsx", + ".js", + ".jsx", + ]); + if (!result) { + return; + } + if (os.platform() === "win32") { + result = result.replace(/\\/g, "/"); + } + try { + // Installed packages (node modules) should take precedence over root files with the same name. + // Ref: https://github.com/nestjs/nest-cli/issues/838 + const packagePath = require.resolve(text); + if (packagePath) { + return text; + } + } catch { + // ignore + } + + const resolvedPath = posix.relative(dirname(sf.fileName), result) || "./"; + return resolvedPath.startsWith(".") ? resolvedPath : "./" + resolvedPath; +} + +const compilerOptions = tsConfigLoader({ + cwd: process.cwd(), + getEnv: (key: string) => process.env[key], +}); + +export function tsconfigPathsBeforeHookFactory(): ( + ctx: ts.TransformationContext +) => ts.Transformer { + const tsBinary = new TypeScriptBinaryLoader().load(); + const { paths = {}, baseUrl = "./" } = compilerOptions; + const matcher = createMatchPath(baseUrl, paths, ["main"]); + + return (ctx: ts.TransformationContext): ts.Transformer => { + return (sf: ts.SourceFile) => { + const visitNode = (node: ts.Node): ts.Node => { + if ( + tsBinary.isImportDeclaration(node) || + (tsBinary.isExportDeclaration(node) && node.moduleSpecifier) + ) { + try { + const importPathWithQuotes = node.moduleSpecifier?.getText(); + + if (!importPathWithQuotes) { + return node; + } + const text = importPathWithQuotes.substring( + 1, + importPathWithQuotes.length - 1 + ); + const result = getNotAliasedPath(sf, matcher, text); + if (!result) { + return node; + } + const moduleSpecifier = + tsBinary.factory.createStringLiteral(result); + (moduleSpecifier as any).parent = ( + node as any + ).moduleSpecifier.parent; + + if (tsBinary.isImportDeclaration(node)) { + const updatedNode = tsBinary.factory.updateImportDeclaration( + node, + node.modifiers, + node.importClause, + moduleSpecifier, + node.assertClause + ); + (updatedNode as any).flags = node.flags; + return updatedNode; + } else { + const updatedNode = tsBinary.factory.updateExportDeclaration( + node, + node.modifiers, + node.isTypeOnly, + node.exportClause, + moduleSpecifier, + node.assertClause + ); + (updatedNode as any).flags = node.flags; + return updatedNode; + } + } catch { + return node; + } + } + return tsBinary.visitEachChild(node, visitNode, ctx); + }; + return tsBinary.visitNode(sf, visitNode); + }; + }; +} diff --git a/src/build-plugin/factory.ts b/src/build-plugin/factory.ts new file mode 100644 index 0000000..e585772 --- /dev/null +++ b/src/build-plugin/factory.ts @@ -0,0 +1,5 @@ +import { tsconfigPathsBeforeHookFactory } from "./before-hook"; + +export const name = "tsconfig-paths"; +export const version = 1; +export const factory = tsconfigPathsBeforeHookFactory; diff --git a/src/build-plugin/typescript-loader.ts b/src/build-plugin/typescript-loader.ts new file mode 100644 index 0000000..454ebdb --- /dev/null +++ b/src/build-plugin/typescript-loader.ts @@ -0,0 +1,24 @@ +/* eslint-disable no-magic-numbers */ +import * as ts from "typescript"; + +export class TypeScriptBinaryLoader { + private tsBinary?: typeof ts; + + public load(): typeof ts { + if (this.tsBinary) { + return this.tsBinary; + } + + try { + const tsBinaryPath = require.resolve("typescript"); + // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports + const tsBinary = require(tsBinaryPath); + this.tsBinary = tsBinary; + return tsBinary; + } catch { + throw new Error( + 'TypeScript could not be found! Please, install "typescript" package.' + ); + } + } +}