Skip to content

Commit a58f021

Browse files
authored
Normalise imports in manually written declaration files (#559)
1 parent 0a4853c commit a58f021

File tree

6 files changed

+140
-52
lines changed

6 files changed

+140
-52
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@preconstruct/cli": patch
3+
---
4+
5+
Extend import path normalisation in generated declaration files with the `importsConditions` and `onlyEmitUsedTypeScriptDeclarations` experimental flags to manually written declaration files as well

packages/cli/src/build/__tests__/declarations.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,3 +519,67 @@ test("replaces package.json#imports in declaration files without importCondition
519519
520520
`);
521521
});
522+
523+
test("normalises imports in manually authored .d.ts files", async () => {
524+
let dir = await testdir({
525+
"package.json": JSON.stringify({
526+
name: "@imports-replacing/repo",
527+
preconstruct: {
528+
packages: ["packages/pkg-a"],
529+
___experimentalFlags_WILL_CHANGE_IN_PATCH: {
530+
onlyEmitUsedTypeScriptDeclarations: true,
531+
},
532+
},
533+
}),
534+
"packages/pkg-a/package.json": JSON.stringify({
535+
name: "pkg-a",
536+
main: "dist/pkg-a.cjs.js",
537+
module: "dist/pkg-a.esm.js",
538+
imports: {
539+
"#hidden": "./src/hidden_stuff.ts",
540+
},
541+
}),
542+
"packages/pkg-a/src/index.js": ts`
543+
export { gem } from "#hidden";
544+
`,
545+
"packages/pkg-a/src/index.d.ts": ts`
546+
export { gem } from "#hidden";
547+
export type A = typeof import(/** comment */ "#hidden").gem;
548+
export type B = typeof import(/* non-jsdoc comment */ "./hidden_stuff").gem;
549+
export type C = typeof import("./hidden_stuff.ts").gem;
550+
`,
551+
"packages/pkg-a/src/hidden_stuff.ts": ts`
552+
export const gem = "🎁";
553+
`,
554+
node_modules: typescriptFixture.node_modules,
555+
"tsconfig.json": JSON.stringify({
556+
compilerOptions: {
557+
module: "ESNext",
558+
moduleResolution: "node16",
559+
allowImportingTsExtensions: true,
560+
strict: true,
561+
declaration: true,
562+
},
563+
}),
564+
});
565+
await build(dir);
566+
567+
expect(await getFiles(dir, ["packages/*/dist/**/*.d.*"]))
568+
.toMatchInlineSnapshot(`
569+
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ packages/pkg-a/dist/declarations/src/hidden_stuff.d.ts ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
570+
export declare const gem = "\\uD83C\\uDF81";
571+
572+
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ packages/pkg-a/dist/declarations/src/index.d.ts ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
573+
export { gem } from "./hidden_stuff.js";
574+
export type A = typeof import(/** comment */ "./hidden_stuff.js").gem;
575+
export type B = typeof import(/* non-jsdoc comment */ "./hidden_stuff.js").gem;
576+
export type C = typeof import("./hidden_stuff.js").gem;
577+
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ packages/pkg-a/dist/pkg-a.cjs.d.ts ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
578+
export * from "./declarations/src/index";
579+
//# sourceMappingURL=pkg-a.cjs.d.ts.map
580+
581+
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ packages/pkg-a/dist/pkg-a.cjs.d.ts.map ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
582+
{"version":3,"file":"pkg-a.cjs.d.ts","sourceRoot":"","sources":["./declarations/src/index.d.ts"],"names":[],"mappings":"AAAA"}
583+
584+
`);
585+
});

packages/cli/src/rollup-plugins/typescript-declarations/common.ts

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import * as fs from "fs-extra";
55
import path from "path";
66
import normalizePath from "normalize-path";
77
import { getModuleSpecifier } from "./get-module-specifier";
8+
import MagicString from "magic-string";
89

910
export type DeclarationFile = {
1011
name: string;
@@ -131,7 +132,7 @@ export async function getProgram(dirname: string, pkgName: string, ts: TS) {
131132
: memoizedGetProgram(ts)(configFileName);
132133
}
133134

134-
export const getDeclarationsForFile = async (
135+
export function getDeclarationsForFile(
135136
filename: string,
136137
typescript: TS,
137138
program: import("typescript").Program,
@@ -140,29 +141,51 @@ export const getDeclarationsForFile = async (
140141
diagnosticsHost: import("typescript").FormatDiagnosticsHost,
141142
/** This will only be called once per unique module specifier in a file */
142143
visitModuleSpecifier?: (moduleSpecifier: string) => string
143-
): Promise<EmittedDeclarationOutput> => {
144+
): EmittedDeclarationOutput {
145+
const cachedVisitModuleSpecifier = memoize(
146+
visitModuleSpecifier ?? ((x) => x)
147+
);
148+
const sourceFile = program.getSourceFile(
149+
typescript.sys.useCaseSensitiveFileNames ? filename : filename.toLowerCase()
150+
);
151+
if (!sourceFile) {
152+
throw new Error(
153+
`Could not find source file at ${filename} in TypeScript declaration generation, this is likely a bug in Preconstruct`
154+
);
155+
}
144156
if (filename.endsWith(".d.ts")) {
157+
let content = sourceFile.text;
158+
if (visitModuleSpecifier) {
159+
const magicString = new MagicString(content);
160+
const visitor = (node: import("typescript").Node): void => {
161+
const moduleSpecifier = getModuleSpecifier(node, typescript);
162+
if (moduleSpecifier) {
163+
const replaced = cachedVisitModuleSpecifier(moduleSpecifier.text);
164+
if (replaced !== moduleSpecifier.text) {
165+
magicString.update(
166+
moduleSpecifier.getStart(sourceFile, false),
167+
moduleSpecifier.getEnd(),
168+
JSON.stringify(replaced)
169+
);
170+
}
171+
}
172+
typescript.forEachChild(node, visitor);
173+
};
174+
typescript.forEachChild(sourceFile, visitor);
175+
content = magicString.toString();
176+
}
145177
return {
146178
types: {
147179
name: filename.replace(
148180
normalizedPkgDir,
149181
normalizePath(path.join(normalizedPkgDir, "dist", "declarations"))
150182
),
151-
content: await fs.readFile(filename, "utf8"),
183+
content,
152184
},
153185
filename,
154186
};
155187
}
156188

157-
const sourceFile = program.getSourceFile(
158-
typescript.sys.useCaseSensitiveFileNames ? filename : filename.toLowerCase()
159-
);
160-
if (!sourceFile) {
161-
throw new Error(
162-
`Could not find source file at ${filename} in TypeScript declaration generation, this is likely a bug in Preconstruct`
163-
);
164-
}
165-
166189
const emitted: Partial<EmittedDeclarationOutput> = {};
167190
const otherEmitted: { name: string; text: string }[] = [];
168191
const { diagnostics } = program.emit(
@@ -198,7 +221,6 @@ export const getDeclarationsForFile = async (
198221
if (!visitModuleSpecifier) {
199222
return node;
200223
}
201-
const cachedVisitModuleSpecifier = memoize(visitModuleSpecifier);
202224

203225
const replacedNodes = new Map<
204226
import("typescript").StringLiteral,
@@ -247,7 +269,7 @@ export const getDeclarationsForFile = async (
247269
);
248270
}
249271
return { types: emitted.types, map: emitted.map, filename };
250-
};
272+
}
251273

252274
export function overwriteDeclarationMapSourceRoot(
253275
content: string,

packages/cli/src/rollup-plugins/typescript-declarations/get-declarations-with-imported-module-specifiers-replacing.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ function replaceExt(filename: string) {
1616
});
1717
}
1818

19-
export async function getDeclarationsWithImportedModuleSpecifiersReplacing(
19+
export function getDeclarationsWithImportedModuleSpecifiersReplacing(
2020
typescript: TS,
2121
program: Program,
2222
normalizedPkgDir: string,
@@ -26,7 +26,7 @@ export async function getDeclarationsWithImportedModuleSpecifiersReplacing(
2626
containingFile: string
2727
) => ResolvedModuleFull | undefined,
2828
resolvedEntrypointSources: string[]
29-
): Promise<EmittedDeclarationOutput[]> {
29+
): EmittedDeclarationOutput[] {
3030
const depQueue = new Set(resolvedEntrypointSources);
3131
const diagnosticsHost = getDiagnosticsHost(typescript, projectDir);
3232
const normalizedPkgDirNodeModules = normalizePath(
@@ -57,8 +57,7 @@ export async function getDeclarationsWithImportedModuleSpecifiersReplacing(
5757
}
5858
return forImport;
5959
};
60-
// this is mostly sync except for one bit so running this concurrently wouldn't really help
61-
const output = await getDeclarationsForFile(
60+
const output = getDeclarationsForFile(
6261
filename,
6362
typescript,
6463
program,

packages/cli/src/rollup-plugins/typescript-declarations/get-declarations.ts

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
} from "./common";
88
import { Program, ResolvedModuleFull } from "typescript";
99

10-
export async function getDeclarations(
10+
export function getDeclarations(
1111
typescript: TS,
1212
program: Program,
1313
normalizedPkgDir: string,
@@ -18,7 +18,7 @@ export async function getDeclarations(
1818
containingFile: string
1919
) => ResolvedModuleFull | undefined,
2020
resolvedEntrypointSources: string[]
21-
): Promise<EmittedDeclarationOutput[]> {
21+
): EmittedDeclarationOutput[] {
2222
let allDeps = new Set(resolvedEntrypointSources);
2323

2424
for (let dep of allDeps) {
@@ -49,16 +49,14 @@ export async function getDeclarations(
4949

5050
const diagnosticsHost = getDiagnosticsHost(typescript, projectDir);
5151

52-
return Promise.all(
53-
[...allDeps].map((filename) => {
54-
return getDeclarationsForFile(
55-
filename,
56-
typescript,
57-
program,
58-
normalizedPkgDir,
59-
projectDir,
60-
diagnosticsHost
61-
);
62-
})
63-
);
52+
return [...allDeps].map((filename) => {
53+
return getDeclarationsForFile(
54+
filename,
55+
typescript,
56+
program,
57+
normalizedPkgDir,
58+
projectDir,
59+
diagnosticsHost
60+
);
61+
});
6462
}

packages/cli/src/rollup-plugins/typescript-declarations/index.ts

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -100,26 +100,26 @@ export default function typescriptDeclarations(pkg: Package): Plugin {
100100
})
101101
);
102102

103-
const declarations = await (pkg.project.experimentalFlags
104-
.importsConditions ||
105-
pkg.project.experimentalFlags.onlyEmitUsedTypeScriptDeclarations
106-
? getDeclarationsWithImportedModuleSpecifiersReplacing(
107-
typescript,
108-
program,
109-
normalizedDirname,
110-
pkg.project.directory,
111-
resolveModule,
112-
[...entrypointSourceToTypeScriptSource.values()]
113-
)
114-
: getDeclarations(
115-
typescript,
116-
program,
117-
normalizedDirname,
118-
pkg.project.directory,
119-
pkg.name,
120-
resolveModule,
121-
[...entrypointSourceToTypeScriptSource.values()]
122-
));
103+
const declarations =
104+
pkg.project.experimentalFlags.importsConditions ||
105+
pkg.project.experimentalFlags.onlyEmitUsedTypeScriptDeclarations
106+
? getDeclarationsWithImportedModuleSpecifiersReplacing(
107+
typescript,
108+
program,
109+
normalizedDirname,
110+
pkg.project.directory,
111+
resolveModule,
112+
[...entrypointSourceToTypeScriptSource.values()]
113+
)
114+
: getDeclarations(
115+
typescript,
116+
program,
117+
normalizedDirname,
118+
pkg.project.directory,
119+
pkg.name,
120+
resolveModule,
121+
[...entrypointSourceToTypeScriptSource.values()]
122+
);
123123

124124
let srcFilenameToDtsFilenameMap = new Map<string, string>();
125125

0 commit comments

Comments
 (0)