TypeScript Plug'n'Play Resolver
ts-pnp provides a low-level module resolver specifically designed to integrate Yarn's Plug'n'Play (PnP) resolution strategy with the TypeScript compiler API. It is primarily intended for developers building custom TypeScript compiler hosts, tools, or plugins, rather than for direct application usage. The current stable version is 1.2.0, indicating a mature and stable API, and its release cadence is generally tied to updates in Yarn PnP or TypeScript APIs. Its key differentiator is enabling seamless PnP support for TypeScript's module resolution, allowing TypeScript projects to leverage the benefits of PnP without complex manual configuration, particularly when used in conjunction with companion plugins for build tools like Webpack, Jest, and Rollup.
Common errors
-
Cannot find module 'ts-pnp'
cause The `ts-pnp` package has not been installed or is not accessible in the current environment.fixRun `yarn add -D ts-pnp` or `npm install --save-dev ts-pnp` in your project directory. -
TypeError: Cannot read properties of undefined (reading 'resolvedModule')
cause This typically occurs if the `resolveModuleName` function returns `undefined` (or a similar falsy value) when it was expected to resolve a module, indicating a failure in the resolution process, possibly due to an incorrect path or a missing package in the PnP cache.fixVerify that the module being resolved actually exists and is properly installed via Yarn PnP. Double-check the `moduleName` and `containingFile` arguments passed to `resolvePnpModuleName` and ensure the `ts.resolveModuleName` fallback is correctly provided. -
TS2307: Cannot find module '...' or its corresponding type declarations.
cause Despite `ts-pnp` being integrated, TypeScript is still unable to locate a module or its type definitions. This could mean PnP resolution failed, or a module is missing from the Yarn cache/`.pnp.cjs` file.fixEnsure the module is correctly listed in `package.json` and `yarn install` has been run. Verify that `ts-pnp`'s `resolveModuleName` is correctly wired into the `CompilerHost` and that the fallback to `ts.resolveModuleName` is present for non-PnP resolved modules or edge cases.
Warnings
- gotcha This package is a low-level utility for integrating Yarn PnP with the TypeScript Compiler API. It is not intended for direct application usage. Most users should look for higher-level integrations like `pnp-webpack-plugin`, `jest-pnp-resolver`, or `rollup-plugin-pnp-resolve` instead.
- gotcha Incorrectly providing the `ts.resolveModuleName` or `ts.resolveTypeReferenceDirective` fallback function as the fifth argument to `ts-pnp`'s `resolveModuleName` will lead to incorrect module resolution behavior, potentially causing modules to not be found or resolved in unexpected ways.
- breaking Significant changes to Yarn's Plug'n'Play specification or the TypeScript Compiler API's module resolution hooks might require updates to `ts-pnp`. While the library strives for compatibility, fundamental shifts could impact its functionality.
- gotcha While `ts-pnp` enables PnP resolution, it does not manage the installation or activation of PnP itself. Your project must be configured to use Yarn PnP for `ts-pnp` to function effectively.
Install
-
npm install ts-pnp -
yarn add ts-pnp -
pnpm add ts-pnp
Imports
- resolveModuleName
import resolveModuleName from 'ts-pnp'; // Incorrect, it's a named export. const { resolveModuleName } = require('ts-pnp'); // CommonJS is typically not preferred for this library's use case with TypeScript.import { resolveModuleName } from 'ts-pnp'; - resolveTypeReferenceDirectives
import { resolveModuleName } from 'ts-pnp'; // Used indirectly via resolveModuleName for type directives. - CompilerHost integration
import * as ts from 'typescript'; import { resolveModuleName } from 'ts-pnp';
Quickstart
import { resolveModuleName as resolvePnpModuleName } from 'ts-pnp';
import * as ts from 'typescript';
interface CustomCompilerHost extends ts.CompilerHost {
resolveModuleNames: (moduleNames: string[], containingFile: string) => (ts.ResolvedModule | undefined)[];
resolveTypeReferenceDirectives: (typeDirectiveNames: string[], containingFile: string) => (ts.ResolvedTypeReferenceDirective | undefined)[];
}
function createPnPCompilerHost(
compilerOptions: ts.CompilerOptions,
host?: ts.CompilerHost, // Optional base host for fallback
): CustomCompilerHost {
// Provide a default host if none is given
const baseHost = host || ts.createCompilerHost(compilerOptions, true);
const customHost: CustomCompilerHost = {
...baseHost,
resolveModuleNames,
resolveTypeReferenceDirectives,
};
return customHost;
function resolveModuleNames(moduleNames: string[], containingFile: string) {
return moduleNames.map(moduleName => {
// ts-pnp resolver with a fallback to TypeScript's default resolver
const resolved = resolvePnpModuleName(
moduleName,
containingFile,
compilerOptions,
customHost,
ts.resolveModuleName // Essential fallback to TypeScript's internal resolver
);
return resolved.resolvedModule;
});
}
function resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string) {
return typeDirectiveNames.map(typeDirectiveName => {
// ts-pnp resolver for type directives, with fallback
const resolved = resolvePnpModuleName(
typeDirectiveName,
containingFile,
compilerOptions,
customHost,
ts.resolveTypeReferenceDirective // Essential fallback for type reference directives
);
return resolved.resolvedTypeReferenceDirective;
});
}
}
// Example usage (conceptual, actual compilation would involve more setup):
const compilerOptions: ts.CompilerOptions = {
target: ts.ScriptTarget.ESNext,
module: ts.ModuleKind.ESNext,
strict: true,
// ... other options
};
const pnpHost = createPnPCompilerHost(compilerOptions);
console.log('PnP-enabled TypeScript CompilerHost created.');
// To compile, you would typically use ts.createProgram with this host:
// const program = ts.createProgram(['./src/index.ts'], compilerOptions, pnpHost);
// program.emit();