Skip to content

Commit

Permalink
fix: added correct resolution for isolated declarations
Browse files Browse the repository at this point in the history
  • Loading branch information
prisis committed Nov 18, 2024
1 parent 9df80ed commit eb3e6b6
Show file tree
Hide file tree
Showing 15 changed files with 225 additions and 39 deletions.
93 changes: 90 additions & 3 deletions packages/packem/__tests__/intigration/typescript.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { existsSync, symlinkSync } from "node:fs";
import { rm } from "node:fs/promises";

import { readFileSync, writeFileSync, writeJsonSync } from "@visulima/fs";
import { isAccessibleSync, readFileSync, writeFileSync, writeJsonSync } from "@visulima/fs";
import { join } from "@visulima/path";
import { temporaryDirectory } from "tempy";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
Expand Down Expand Up @@ -220,7 +220,10 @@ describe("packem typescript", () => {

await installPackage(temporaryDirectoryPath, "typescript");

writeFileSync(`${temporaryDirectoryPath}/src/index.ts`, 'import "components:Test";\n import { test2 } from "components:Test2";\n\nconsole.log(test2);');
writeFileSync(
`${temporaryDirectoryPath}/src/index.ts`,
'import "components:Test";\n import { test2 } from "components:Test2";\n\nconsole.log(test2);',
);
writeFileSync(`${temporaryDirectoryPath}/src/components/Test.ts`, "console.log(1);");
writeFileSync(`${temporaryDirectoryPath}/src/components/Test2.ts`, "export const test2 = 'test'");

Expand Down Expand Up @@ -1189,7 +1192,7 @@ export type Num2 = number`,
cwd: temporaryDirectoryPath,
reject: false,
});
console.log(binProcess.stdout)

expect(binProcess.stderr).toBe("");
expect(binProcess.exitCode).toBe(0);
expect(binProcess.stdout).toContain("Using isolated declaration transformer to generate declaration files...");
Expand Down Expand Up @@ -1412,6 +1415,90 @@ export default test;
`);
},
);

it.each(["typescript", "oxc", "swc"])("should resolve aliases with '%s' isolated declarations transformer", async (isolatedDeclarationTransformer) => {
expect.assertions(9);

writeFileSync(`${temporaryDirectoryPath}/src/index.ts`, 'import { a } from "utils/a";\nexport default a;');
writeFileSync(`${temporaryDirectoryPath}/src/utils/a.ts`, "export const a: number = 1;");

await installPackage(temporaryDirectoryPath, "typescript");

if (isolatedDeclarationTransformer === "oxc") {
await installPackage(temporaryDirectoryPath, "oxc-transform");
}

if (isolatedDeclarationTransformer === "swc") {
await installPackage(temporaryDirectoryPath, "@swc/core");
}

await createPackageJson(temporaryDirectoryPath, {
devDependencies: {
typescript: "*",
},
exports: {
".": {
import: {
default: "./dist/index.mjs",
types: "./dist/index.d.mts",
},
require: {
default: "./dist/index.cjs",
types: "./dist/index.d.cts",
},
},
},
});
await createPackemConfig(temporaryDirectoryPath, {
config: {
cjsInterop: true,
},
isolatedDeclarationTransformer: isolatedDeclarationTransformer as "swc" | "typescript" | "oxc" | undefined,
transformer: "esbuild",
});
await createTsConfig(temporaryDirectoryPath, {
compilerOptions: {
baseUrl: "src",
isolatedDeclarations: true,
noErrorTruncation: true,
paths: {
"utils/*": ["utils/*.ts"],
},
},
});

const binProcess = await execPackemSync("build", [], {
cwd: temporaryDirectoryPath,
reject: false,
});

expect(binProcess.stderr).toBe("");
expect(binProcess.exitCode).toBe(0);
expect(binProcess.stdout).toContain("Using isolated declaration transformer to generate declaration files...");

const dCtsContent = readFileSync(`${temporaryDirectoryPath}/dist/index.d.cts`);

expect(dCtsContent).toBe(`import { a } from "./utils/a.d.cts";
export = a;`);
expect(isAccessibleSync(`${temporaryDirectoryPath}/dist/utils/a.d.cts`)).toBeTruthy();

const dtsContent = readFileSync(`${temporaryDirectoryPath}/dist/index.d.ts`);

expect(dtsContent).toBe(`import { a } from "./utils/a.d.ts";
export = a;`);
expect(isAccessibleSync(`${temporaryDirectoryPath}/dist/utils/a.d.ts`)).toBeTruthy();

const dCtsTypesContent = readFileSync(`${temporaryDirectoryPath}/dist/index.d.mts`);

expect(dCtsTypesContent).toBe(`import { a } from "./utils/a.d.mts";
export default a;
`);
expect(isAccessibleSync(`${temporaryDirectoryPath}/dist/utils/a.d.mts`)).toBeTruthy();
});
});

it("should use the outDir option from tsconfig if present", async () => {
Expand Down
6 changes: 1 addition & 5 deletions packages/packem/__tests__/intigration/url.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,11 +277,7 @@ module.exports = png;
limit: 10,
sourceDir: join(temporaryDirectoryPath, ".."),
},
[
join(temporaryDirectoryPath, "/dist/png.cjs"),
join(temporaryDirectoryPath, "/dist/png.mjs"),
join(temporaryDirectoryPath, "/dist/", pngPath),
],
[join(temporaryDirectoryPath, "/dist/png.cjs"), join(temporaryDirectoryPath, "/dist/png.mjs"), join(temporaryDirectoryPath, "/dist/", pngPath)],
false,
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ describe("extendString", () => {
["../../types.mts", "types.ts", "../../types.ts"],
["../../types.js", "types.ts", "../../types.ts"],
["./types.d.ts", "types.d.cts", "./types.d.cts"],
["utils:a", "utils/a.ts", "./utils/a.ts"],
])(
"should append the additional parts of referenceString to baseString '%s' when they have common leading parts",
(baseString, referenceString, expectedString) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { describe, expect, it } from "vitest";

import splitTsconfigPathKey from "../../../../../src/rollup/plugins/isolated-declarations/split-tsconfig-path-key";

describe("splitTsconfigPathKey", () => {
it("should split a simple path", () => {
expect.assertions(1);

const result = splitTsconfigPathKey("src/utils/*");

expect(result).toEqual(["src", "utils", "*"]);
});

it("should split a namespaced path", () => {
expect.assertions(1);

const result = splitTsconfigPathKey("@namespace:module/utils/*");

expect(result).toEqual(["@namespace:module", "utils", "*"]);
});

it("should handle a wildcard-only path", () => {
expect.assertions(1);

const result = splitTsconfigPathKey("*");

expect(result).toEqual(["*"]);
});

it("should handle a namespaced path without wildcard", () => {
expect.assertions(1);

const result = splitTsconfigPathKey("@namespace:module/utils");

expect(result).toEqual(["@namespace:module", "utils"]);
});

it("should throw an error for invalid input", () => {
expect.assertions(3);

expect(() => splitTsconfigPathKey("")).toThrow("Invalid key: Key must be a non-empty string.");
expect(() => splitTsconfigPathKey(null as any)).toThrow("Invalid key: Key must be a non-empty string.");
expect(() => splitTsconfigPathKey(undefined as any)).toThrow("Invalid key: Key must be a non-empty string.");
});
});
2 changes: 1 addition & 1 deletion packages/packem/src/commands/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ const createAddCommand = (cli: Cli): void => {
const packagesToInstall: string[] = [];

for (const loader of cssLoaders) {
packagesToInstall.push(...cssLoaderDependencies[loader as keyof typeof cssLoaderDependencies] as string[]);
packagesToInstall.push(...(cssLoaderDependencies[loader as keyof typeof cssLoaderDependencies] as string[]));
}

cssLoaders.push("sourceMap");
Expand Down
2 changes: 1 addition & 1 deletion packages/packem/src/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ const createInitCommand = (cli: Cli): void => {

if (shouldInstall) {
for (const loader of cssLoaders) {
packagesToInstall.push(...cssLoaderDependencies[loader as keyof typeof cssLoaderDependencies] as string[]);
packagesToInstall.push(...(cssLoaderDependencies[loader as keyof typeof cssLoaderDependencies] as string[]));
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/packem/src/jit/create-stub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const createStub = async (context: BuildContext): Promise<void> => {
{
...context.options.jiti,
alias: {
...resolveAliases(context, "jit"),
...resolveAliases(context.pkg, context.options),
...context.options.jiti.alias,
},
transformOptions: {
Expand Down
18 changes: 11 additions & 7 deletions packages/packem/src/rollup/get-rollup-options.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { readdirSync } from "node:fs";
import { versions } from "node:process";

import type { ResolverObject } from "@rollup/plugin-alias";
Expand Down Expand Up @@ -38,6 +37,7 @@ import cachingPlugin from "./plugins/plugin-cache";
import prependDirectivePlugin from "./plugins/prepend-directives";
import preserveDirectivesPlugin from "./plugins/preserve-directives";
import { rawPlugin } from "./plugins/raw";
import { resolveExternalsPlugin } from "./plugins/resolve-externals-plugin";
import resolveFileUrlPlugin from "./plugins/resolve-file-url";
import type { ShebangOptions } from "./plugins/shebang";
import { removeShebangPlugin, shebangPlugin } from "./plugins/shebang";
Expand All @@ -53,7 +53,6 @@ import createSplitChunks from "./utils/chunks/create-split-chunks";
import getChunkFilename from "./utils/get-chunk-filename";
import getEntryFileNames from "./utils/get-entry-file-names";
import resolveAliases from "./utils/resolve-aliases";
import { resolveExternalsPlugin } from "./plugins/resolve-externals-plugin";

const sortUserPlugins = (plugins: RollupPlugins | undefined, type: "build" | "dts"): [Plugin[], Plugin[], Plugin[]] => {
const prePlugins: Plugin[] = [];
Expand Down Expand Up @@ -219,8 +218,8 @@ const sharedOnWarn = (warning: RollupLog, context: BuildContext): boolean => {
};

// eslint-disable-next-line sonarjs/cognitive-complexity
const baseRollupOptions = (context: BuildContext, type: "build" | "dts"): RollupOptions => {
return <RollupOptions>{
const baseRollupOptions = (context: BuildContext, type: "build" | "dts"): RollupOptions =>
<RollupOptions>{
input: Object.fromEntries(context.options.entries.map((entry) => [entry.name, resolve(context.options.rootDir, entry.input)])),

logLevel: context.options.debug ? "debug" : "info",
Expand Down Expand Up @@ -276,7 +275,6 @@ const baseRollupOptions = (context: BuildContext, type: "build" | "dts"): Rollup

watch: context.mode === "watch" ? context.options.rollup.watch : false,
};
};

// eslint-disable-next-line sonarjs/cognitive-complexity,import/exports-last
export const getRollupOptions = async (context: BuildContext, fileCache: FileCache): Promise<RollupOptions> => {
Expand Down Expand Up @@ -380,7 +378,10 @@ export const getRollupOptions = async (context: BuildContext, fileCache: FileCac
context.tsconfig && cachingPlugin(resolveTsconfigRootDirectoriesPlugin(context.options.rootDir, context.logger, context.tsconfig), fileCache),
context.tsconfig && cachingPlugin(resolveTsconfigPathsPlugin(context.options.rootDir, context.tsconfig, context.logger), fileCache),

cachingPlugin(resolveExternalsPlugin(context.pkg, context.tsconfig, context.options, context.logger, context.options.rollup.resolveExternals ?? {}), fileCache),
cachingPlugin(
resolveExternalsPlugin(context.pkg, context.tsconfig, context.options, context.logger, context.options.rollup.resolveExternals ?? {}),
fileCache,
),

context.options.rollup.replace &&
replacePlugin({
Expand Down Expand Up @@ -688,7 +689,10 @@ 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),

cachingPlugin(resolveExternalsPlugin(context.pkg, context.tsconfig, context.options, context.logger, context.options.rollup.resolveExternals ?? {}), fileCache),
cachingPlugin(
resolveExternalsPlugin(context.pkg, context.tsconfig, context.options, context.logger, context.options.rollup.resolveExternals ?? {}),
fileCache,
),

context.options.rollup.replace &&
replacePlugin({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import splitTsconfigPathKey from "./split-tsconfig-path-key";

const extendString = (baseString: string, referenceString: string): string => {
// Remove leading './' or '../' for accurate comparison
const baseNormalized = baseString.replace(/^\.\//, "");
Expand All @@ -13,8 +15,8 @@ const extendString = (baseString: string, referenceString: string): string => {
}

// Split both strings into components
const baseParts = baseString.split("/");
const referenceParts = referenceString.split("/");
const baseParts = splitTsconfigPathKey(baseString);
const referenceParts = splitTsconfigPathKey(referenceString);

// Find the first differing index, starting from the end
let baseIndex = baseParts.length - 1;
Expand All @@ -28,9 +30,13 @@ const extendString = (baseString: string, referenceString: string): string => {
referenceIndex--;
}

const commonBase = baseParts.slice(0, baseIndex).join("/");
let commonBase = baseParts.slice(0, baseIndex).join("/");
const missingPart = referenceParts.slice(referenceIndex).join("/");

if (!commonBase.startsWith(".") || commonBase === "") {
commonBase = "./" + commonBase;
}

return commonBase + (missingPart ? "/" + missingPart : "");
};

Expand Down
34 changes: 27 additions & 7 deletions packages/packem/src/rollup/plugins/isolated-declarations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { FilterPattern } from "@rollup/pluginutils";
import { createFilter } from "@rollup/pluginutils";
import { readFile } from "@visulima/fs";
import { basename, relative } from "@visulima/path";
import type { TsConfigResult } from "@visulima/tsconfig";
import { parseAsync } from "oxc-parser";
import type { NormalizedInputOptions, NormalizedOutputOptions, Plugin, PluginContext, PreRenderedChunk } from "rollup";

Expand All @@ -29,7 +30,9 @@ export const isolatedDeclarationsPlugin = (
transformer: (code: string, id: string) => Promise<IsolatedDeclarationsResult>,
declaration: boolean | "compatible" | "node16" | undefined,
cjsInterop: boolean,
options: IsolatedDeclarationsOptions = {},
options: IsolatedDeclarationsOptions,
tsconfig?: TsConfigResult,
// eslint-disable-next-line sonarjs/cognitive-complexity
): Plugin => {
const filter = createFilter(options.include, options.exclude);

Expand All @@ -39,7 +42,15 @@ export const isolatedDeclarationsPlugin = (
outputFiles[filename.replace(ENDING_RE, "")] = source;
};

// eslint-disable-next-line func-style,sonarjs/cognitive-complexity
let tsconfigPathPatterns: RegExp[] = [];

if (tsconfig?.config.compilerOptions) {
tsconfigPathPatterns = Object.entries(tsconfig.config.compilerOptions.paths ?? {}).map(([key]) =>
(key.endsWith("*") ? new RegExp(`^${key.replace("*", "(.*)")}$`) : new RegExp(`^${key}$`)),

Check failure

Code scanning / CodeQL

Incomplete string escaping or encoding High

This replaces only the first occurrence of "*".
);
}

// eslint-disable-next-line func-style
async function transform(this: PluginContext, code: string, id: string): Promise<undefined> {
if (!filter(id)) {
return;
Expand All @@ -61,13 +72,12 @@ export const isolatedDeclarationsPlugin = (
(node) => node.type === "ImportDeclaration" || node.type === "ExportAllDeclaration" || node.type === "ExportNamedDeclaration",
);

for (const node of imports) {
for await (const node of imports) {
if (!node.source || basename(node.source.value).includes(".")) {
// eslint-disable-next-line no-continue
continue;
}

// eslint-disable-next-line no-await-in-loop
const resolved = await this.resolve(node.source.value, id);

if (!resolved || resolved.external) {
Expand All @@ -85,8 +95,19 @@ export const isolatedDeclarationsPlugin = (
) {
const resolvedId = resolved.id.replace(sourceDirectory + "/", "");

// eslint-disable-next-line no-param-reassign,@typescript-eslint/restrict-plus-operands
code = code.replaceAll('from "' + node.source.value + '"', 'from "' + extendString(node.source.value, resolvedId) + '"');
let extendedSourceValue = node.source.value;

if (tsconfigPathPatterns.some((pattern) => pattern.test(node.source.value)) && !node.source.value.startsWith(".")) {
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
extendedSourceValue = "./" + node.source.value;
}

// eslint-disable-next-line no-param-reassign
code = code.replaceAll(
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
'from "' + node.source.value + '"',
'from "' + extendString(extendedSourceValue, resolvedId) + '"',
);
}
}
}
Expand Down Expand Up @@ -167,7 +188,6 @@ export const isolatedDeclarationsPlugin = (
return <Plugin>{
name: "packem:isolated-declarations",

// eslint-disable-next-line sonarjs/cognitive-complexity
async renderStart(outputOptions: NormalizedOutputOptions, { input }: NormalizedInputOptions): Promise<void> {
const outBase = lowestCommonAncestor(...(Array.isArray(input) ? input : Object.values(input)));

Expand Down
Loading

0 comments on commit eb3e6b6

Please sign in to comment.