Skip to content

Commit

Permalink
feat: added options to resolve absolute path in tsconfig paths
Browse files Browse the repository at this point in the history
  • Loading branch information
prisis committed Nov 19, 2024
1 parent b41cae5 commit 9bbb1b5
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 110 deletions.
14 changes: 10 additions & 4 deletions packages/packem/__tests__/intigration/typescript.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,18 +260,18 @@ describe("packem typescript", () => {
expect(mjs).toMatchSnapshot("mjs code output");
});

it.each(["@", "#", "~"])("should resolve tsconfig paths with a '%s'", async (namespace) => {
it.each([["@/", "@/*", false], ["#/", "#/*", false], ["~/", "~/*", false], ["/", "/*", true]])("should resolve tsconfig paths with a '%s'", async (namespace, patchKey, resolveAbsolutePath) => {
expect.assertions(5);

writeFileSync(`${temporaryDirectoryPath}/src/index.ts`, `import "${namespace}/Test";`);
writeFileSync(`${temporaryDirectoryPath}/src/components/Test.ts`, "console.log(1);");
writeFileSync(`${temporaryDirectoryPath}/src/index.ts`, `import "${namespace as string}Test";`);

await installPackage(temporaryDirectoryPath, "typescript");
await createTsConfig(temporaryDirectoryPath, {
compilerOptions: {
baseUrl: "src",
paths: {
[namespace + "/*"]: ["components/*.ts"],
[patchKey as string]: ["components/*.ts"],
},
},
});
Expand All @@ -282,7 +282,13 @@ describe("packem typescript", () => {
main: "./dist/index.cjs",
module: "./dist/index.mjs",
});
await createPackemConfig(temporaryDirectoryPath);
await createPackemConfig(temporaryDirectoryPath, {
config: {
rollup: {
tsconfigPaths: { resolveAbsolutePath },
}
}
});

const binProcess = await execPackemSync("build", [], {
cwd: temporaryDirectoryPath,
Expand Down
4 changes: 4 additions & 0 deletions packages/packem/src/packem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,10 @@ const generateOptions = (
preset: "recommended",
propertyReadSideEffects: true,
},
tsconfigPaths: {
// Default is false to avoid performance issues
resolveAbsolutePath: false,
},
url: {
emitFiles: true,
fileName: "[hash][extname]",
Expand Down
14 changes: 12 additions & 2 deletions packages/packem/src/rollup/get-rollup-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,12 @@ export const getRollupOptions = async (context: BuildContext, fileCache: FileCac
cachingPlugin(resolveTypescriptMjsCtsPlugin(), fileCache),

context.tsconfig && cachingPlugin(resolveTsconfigRootDirectoriesPlugin(context.options.rootDir, context.logger, context.tsconfig), fileCache),
context.tsconfig && cachingPlugin(resolveTsconfigPathsPlugin(context.options.rootDir, context.tsconfig, context.logger), fileCache),
context.tsconfig &&
context.options.rollup.tsconfigPaths &&
cachingPlugin(
resolveTsconfigPathsPlugin(context.options.rootDir, context.tsconfig, context.logger, context.options.rollup.tsconfigPaths),
fileCache,
),

cachingPlugin(
resolveExternalsPlugin(context.pkg, context.tsconfig, context.options, context.logger, context.options.rollup.resolveExternals ?? {}),
Expand Down Expand Up @@ -687,7 +692,12 @@ export const getRollupDtsOptions = async (context: BuildContext, fileCache: File
},

context.tsconfig && cachingPlugin(resolveTsconfigRootDirectoriesPlugin(context.options.rootDir, context.logger, context.tsconfig), fileCache),
context.tsconfig && cachingPlugin(resolveTsconfigPathsPlugin(context.options.rootDir, context.tsconfig, context.logger), fileCache),
context.tsconfig &&
context.options.rollup.tsconfigPaths &&
cachingPlugin(
resolveTsconfigPathsPlugin(context.options.rootDir, context.tsconfig, context.logger, context.options.rollup.tsconfigPaths),
fileCache,
),

cachingPlugin(
resolveExternalsPlugin(context.pkg, context.tsconfig, context.options, context.logger, context.options.rollup.resolveExternals ?? {}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ const matchedText = (pattern: Pattern, candidate: string): string => candidate.s

const patternText = ({ prefix, suffix }: Pattern): string => `${prefix}*${suffix}`;

export const getTsconfigPaths = (
const getTsconfigPaths = (
rootDirectory: string,
tsconfig: TsConfigResult,
logger?: Pail,
Expand Down Expand Up @@ -139,128 +139,106 @@ export const getTsconfigPaths = (
};
};

export const resolvePathsToIds = async (
paths: Record<string, string[]>,
pathsKeys: string[],
resolvedBaseUrl: string,
id: string,
matcher: (candidate: string) => Promise<ResolvedId | null>,
logger?: Pail,
// eslint-disable-next-line sonarjs/cognitive-complexity
): Promise<ResolvedId | null> => {
if (pathsKeys.length === 0) {
return null;
}

if (id.includes("\0")) {
logger?.debug({
message: `Skipping resolution of ${id} as it is a virtual module`,
prefix: "plugin:packem:resolve-tsconfig-paths",
});
export type TsconfigPathsPluginOptions = {
resolveAbsolutePath?: boolean;
}

return null;
}
// eslint-disable-next-line no-secrets/no-secrets
/**
* Handles tsconfig.json or jsconfig.js "paths" option for webpack
* Largely based on how the TypeScript compiler handles it:
* https://github.com/microsoft/TypeScript/blob/1a9c8197fffe3dace5f8dca6633d450a88cba66d/src/compiler/moduleNameResolver.ts#L1362
*/
export const resolveTsconfigPathsPlugin = (rootDirectory: string, tsconfig: TsConfigResult, logger: Pail, pluginOptions: TsconfigPathsPluginOptions): Plugin => {
const { paths, resolvedBaseUrl } = getTsconfigPaths(rootDirectory, tsconfig, logger);
const pathsKeys = Object.keys(paths);

// Exclude node_modules from paths support (speeds up resolving)
if (id.includes("node_modules")) {
logger?.debug({
message: `Skipping request as it is inside node_modules ${id}`,
prefix: "plugin:packem:resolve-tsconfig-paths",
});
return {
name: "packem:resolve-tsconfig-paths",
// eslint-disable-next-line sonarjs/cognitive-complexity
async resolveId(id, importer, options) {
if (pathsKeys.length === 0) {
return null;
}

return null;
}
if (id.includes("\0")) {
logger.debug({
message: `Skipping resolution of ${id} as it is a virtual module`,
prefix: "plugin:packem:resolve-tsconfig-paths",
});

if (isAbsolute(id)) {
logger?.debug({
message: `Skipping request as it is an absolute path ${id}`,
prefix: "plugin:packem:resolve-tsconfig-paths",
});
return null;
}

return null;
}
// Exclude node_modules from paths support (speeds up resolving)
if (id.includes("node_modules")) {
logger.debug({
message: `Skipping request as it is inside node_modules ${id}`,
prefix: "plugin:packem:resolve-tsconfig-paths",
});

if (isRelative(id)) {
logger?.debug({
message: `Skipping request as it is a relative path ${id}`,
prefix: "plugin:packem:resolve-tsconfig-paths",
});
return null;
}

return null;
}
// If the module name does not match any of the patterns in `paths` we hand off resolving to webpack
const matchedPattern = matchPatternOrExact(pathsKeys, id);
if (!pluginOptions.resolveAbsolutePath && isAbsolute(id)) {
logger.debug({
message: `Skipping request as it is an absolute path ${id}`,
prefix: "plugin:packem:resolve-tsconfig-paths",
});

if (!matchedPattern) {
logger?.debug({
message: `moduleName did not match any paths pattern ${id}`,
prefix: "plugin:packem:resolve-tsconfig-paths",
});
return null;
}

return null;
}
if (isRelative(id)) {
logger.debug({
message: `Skipping request as it is a relative path ${id}`,
prefix: "plugin:packem:resolve-tsconfig-paths",
});

const matchedStar = typeof matchedPattern === "string" ? undefined : matchedText(matchedPattern, id);
const matchedPatternText = typeof matchedPattern === "string" ? matchedPattern : patternText(matchedPattern);
return null;
}
// If the module name does not match any of the patterns in `paths` we hand off resolving to webpack
const matchedPattern = matchPatternOrExact(pathsKeys, id);

for await (const tsPath of paths[matchedPatternText] as string[]) {
const currentPath = matchedStar ? tsPath.replace("*", matchedStar) : tsPath;
if (!matchedPattern) {
logger.debug({
message: `moduleName did not match any paths pattern ${id}`,
prefix: "plugin:packem:resolve-tsconfig-paths",
});

// Ensure .d.ts is not matched
if (currentPath.endsWith(".d.ts") || currentPath.endsWith(".d.cts") || currentPath.endsWith(".d.mts")) {
// eslint-disable-next-line no-continue
continue;
}
return null;
}

const candidate = join(resolvedBaseUrl, currentPath);
const matchedStar = typeof matchedPattern === "string" ? undefined : matchedText(matchedPattern, id);
const matchedPatternText = typeof matchedPattern === "string" ? matchedPattern : patternText(matchedPattern);

const matched = await matcher(candidate);
for await (const tsPath of paths[matchedPatternText] as string[]) {
const currentPath = matchedStar ? tsPath.replace("*", matchedStar) : tsPath;

if (matched) {
return matched;
}
}
// Ensure .d.ts is not matched
if (currentPath.endsWith(".d.ts") || currentPath.endsWith(".d.cts") || currentPath.endsWith(".d.mts")) {
// eslint-disable-next-line no-continue
continue;
}

return null;
};
const candidate = join(resolvedBaseUrl, currentPath);

// eslint-disable-next-line no-secrets/no-secrets
/**
* Handles tsconfig.json or jsconfig.js "paths" option for webpack
* Largely based on how the TypeScript compiler handles it:
* https://github.com/microsoft/TypeScript/blob/1a9c8197fffe3dace5f8dca6633d450a88cba66d/src/compiler/moduleNameResolver.ts#L1362
*/
export const resolveTsconfigPathsPlugin = (rootDirectory: string, tsconfig: TsConfigResult, logger: Pail): Plugin => {
const { paths, resolvedBaseUrl } = getTsconfigPaths(rootDirectory, tsconfig, logger);
const pathsKeys = Object.keys(paths);
try {
const resolved = await this.resolve(candidate, importer, { skipSelf: true, ...options });

return {
name: "packem:resolve-tsconfig-paths",
async resolveId(id, importer, options) {
return await resolvePathsToIds(
paths,
pathsKeys,
resolvedBaseUrl,
id,
async (candidate) => {
try {
const resolved = await this.resolve(candidate, importer, { skipSelf: true, ...options });

if (resolved) {
return resolved;
}
} catch (error) {
logger.debug({
context: error,
message: `Failed to resolve ${candidate} from ${id as string}`,
prefix: "plugin:packem:resolve-tsconfig-paths",
});
if (resolved) {
return resolved;
}
} catch (error) {
logger.debug({
context: error,
message: `Failed to resolve ${candidate} from ${id as string}`,
prefix: "plugin:packem:resolve-tsconfig-paths",
});
}
}

return null;
},
logger,
);
return null;
},
};
};
2 changes: 1 addition & 1 deletion packages/packem/src/rollup/watch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ const watch = async (
...context.options.rollup.watch,
};

rollupOptions.watch.include = [join(context.options.sourceDir, "**", "*"), "package.json", "packem.config.*"];
rollupOptions.watch.include = [join(context.options.sourceDir, "**", "*"), "package.json", "packem.config.*", "tsconfig.json", "tsconfig.*.json"];

if (Array.isArray(context.options.rollup.watch.include)) {
rollupOptions.watch.include = [...rollupOptions.watch.include, ...context.options.rollup.watch.include];
Expand Down
2 changes: 2 additions & 0 deletions packages/packem/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import type FileCache from "./utils/file-cache";
import type { UrlOptions } from "./rollup/plugins/url";
import type { SourcemapsPluginOptions } from "./rollup/plugins/source-maps";
import type { ResolveExternalsPluginOptions } from "./rollup/plugins/resolve-externals-plugin";
import type { TsconfigPathsPluginOptions } from "./rollup/plugins/typescript/resolve-tsconfig-paths-plugin";

type DeepPartial<T> = { [P in keyof T]?: DeepPartial<T[P]> };

Expand Down Expand Up @@ -99,6 +100,7 @@ export interface RollupBuildOptions {
exclude?: FilterPattern;
include?: FilterPattern;
};
tsconfigPaths?: TsconfigPathsPluginOptions | false;
preserveDynamicImports?: boolean;
raw?: RawLoaderOptions | false;
replace: RollupReplaceOptions | false;
Expand Down

0 comments on commit 9bbb1b5

Please sign in to comment.