Skip to content

Commit

Permalink
feat(dts): expose ts compiler errors (#278)
Browse files Browse the repository at this point in the history
  • Loading branch information
danielroe authored Dec 27, 2024
1 parent eddef8e commit d7819d9
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 53 deletions.
1 change: 1 addition & 0 deletions src/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface OutputFile {
extension?: string;
contents?: string;
declaration?: boolean;
errors?: Error[];
raw?: boolean;
skip?: boolean;
}
Expand Down
26 changes: 20 additions & 6 deletions src/make.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ import {
OutputFile,
Loader,
} from "./loader";
import { getDeclarations, normalizeCompilerOptions } from "./utils/dts";
import {
DeclarationOutput,
getDeclarations,
normalizeCompilerOptions,
} from "./utils/dts";
import { getVueDeclarations } from "./utils/vue-dts";
import { LoaderName } from "./loaders";
import { glob, type GlobOptions } from "tinyglobby";
Expand Down Expand Up @@ -83,17 +87,17 @@ export async function mkdist(
);
}
options.typescript.compilerOptions = defu(
{ noEmit: false },
{ noEmit: false } satisfies TSConfig["compilerOptions"],
options.typescript.compilerOptions,
{
allowJs: true,
declaration: true,
incremental: true,
skipLibCheck: true,
strictNullChecks: true,
emitDeclarationOnly: true,
allowImportingTsExtensions: true,
allowNonTsExtensions: true,
},
} satisfies TSConfig["compilerOptions"],
);

// Create loader
Expand All @@ -120,12 +124,16 @@ export async function mkdist(
const dtsOutputs = outputs.filter((o) => o.declaration && !o.skip);
if (dtsOutputs.length > 0) {
const vfs = new Map(dtsOutputs.map((o) => [o.srcPath, o.contents || ""]));
const declarations = Object.create(null);
const declarations: DeclarationOutput = Object.create(null);
for (const loader of [getVueDeclarations, getDeclarations]) {
Object.assign(declarations, await loader(vfs, options));
}
for (const output of dtsOutputs) {
output.contents = declarations[output.srcPath] || "";
const result = declarations[output.srcPath];
output.contents = result?.contents || "";
if (result.errors) {
output.errors = result.errors;
}
}
}

Expand Down Expand Up @@ -187,6 +195,7 @@ export async function mkdist(

// Write outputs
const writtenFiles: string[] = [];
const errors: Array<{ filename: string; errors: TypeError[] }> = [];
await Promise.all(
outputs
.filter((o) => !o.skip)
Expand All @@ -197,10 +206,15 @@ export async function mkdist(
? copyFileWithStream(output.srcPath, outFile)
: fsp.writeFile(outFile, output.contents, "utf8"));
writtenFiles.push(outFile);

if (output.errors) {
errors.push({ filename: outFile, errors: output.errors });
}
}),
);

return {
errors,
writtenFiles,
};
}
43 changes: 35 additions & 8 deletions src/utils/dts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { findStaticImports, findExports, findTypeExports } from "mlly";
import { resolve } from "pathe";
import type { TSConfig } from "pkg-types";
import type { MkdistOptions } from "../make";
import type { CompilerHost, EmitResult } from "typescript";

export async function normalizeCompilerOptions(
_options: TSConfig["compilerOptions"],
Expand All @@ -11,10 +12,15 @@ export async function normalizeCompilerOptions(
return ts.convertCompilerOptionsFromJson(_options, process.cwd()).options;
}

export type DeclarationOutput = Record<
string,
{ contents: string; errors?: Error[] }
>;

export async function getDeclarations(
vfs: Map<string, string>,
opts?: MkdistOptions,
) {
): Promise<DeclarationOutput> {
const ts = await import("typescript").then((r) => r.default || r);

const inputFiles = [...vfs.keys()];
Expand All @@ -38,11 +44,10 @@ export async function getDeclarations(
tsHost,
);
const result = program.emit();
if (result.diagnostics?.length) {
console.error(ts.formatDiagnostics(result.diagnostics, tsHost));
}
const output = extractDeclarations(vfs, inputFiles, opts);
augmentWithDiagnostics(result, output, tsHost, ts);

return extractDeclarations(vfs, inputFiles, opts);
return output;
}

const JS_EXT_RE = /\.(m|c)?(ts|js)$/;
Expand All @@ -53,8 +58,8 @@ export function extractDeclarations(
vfs: Map<string, string>,
inputFiles: string[],
opts?: MkdistOptions,
) {
const output: Record<string, string> = {};
): DeclarationOutput {
const output: DeclarationOutput = {};

for (const filename of inputFiles) {
const dtsFilename = filename.replace(JSX_EXT_RE, ".d.$1ts");
Expand Down Expand Up @@ -88,10 +93,32 @@ export function extractDeclarations(
);
}
}
output[filename] = contents;
output[filename] = { contents };

vfs.delete(filename);
}

return output;
}

export function augmentWithDiagnostics(
result: EmitResult,
output: DeclarationOutput,
tsHost: CompilerHost,
ts: typeof import("typescript"),
) {
if (result.diagnostics?.length) {
for (const diagnostic of result.diagnostics) {
const filename = diagnostic.file?.fileName;
if (filename in output) {
output[filename].errors = output[filename].errors || [];
output[filename].errors.push(
new TypeError(ts.formatDiagnostics([diagnostic], tsHost), {
cause: diagnostic,
}),
);
}
}
console.error(ts.formatDiagnostics(result.diagnostics, tsHost));
}
}
87 changes: 49 additions & 38 deletions src/utils/vue-dts.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import { createRequire } from "node:module";
import { CompilerOptions, CreateProgramOptions } from "typescript";
import { CreateProgramOptions } from "typescript";
import { readPackageJSON } from "pkg-types";
import { satisfies } from "semver";
import { normalize } from "pathe";
import { MkdistOptions } from "../make";
import { extractDeclarations } from "./dts";
import {
augmentWithDiagnostics,
DeclarationOutput,
extractDeclarations,
} from "./dts";

const require = createRequire(import.meta.url);

export async function getVueDeclarations(
vfs: Map<string, string>,
opts?: MkdistOptions,
) {
): Promise<DeclarationOutput> {
const fileMapping = getFileMapping(vfs);
const srcFiles = Object.keys(fileMapping);
const originFiles = Object.values(fileMapping);
Expand All @@ -28,26 +32,22 @@ export async function getVueDeclarations(
}

const { version } = pkgInfo;
let output: DeclarationOutput;
switch (true) {
case satisfies(version, "^1.8.27"): {
await emitVueTscV1(vfs, opts.typescript.compilerOptions, srcFiles);
output = await emitVueTscV1(vfs, srcFiles, originFiles, opts);
break;
}
case satisfies(version, "~v2.0.0"): {
await emitVueTscV2(vfs, opts.typescript.compilerOptions, srcFiles);
output = await emitVueTscV2(vfs, srcFiles, originFiles, opts);
break;
}
default: {
await emitVueTscLatest(
vfs,
opts.typescript.compilerOptions,
srcFiles,
opts.rootDir!,
);
output = await emitVueTscLatest(vfs, srcFiles, originFiles, opts);
}
}

return extractDeclarations(vfs, originFiles, opts);
return output;
}

const SFC_EXT_RE = /\.vue\.[cm]?[jt]s$/;
Expand All @@ -64,8 +64,9 @@ function getFileMapping(vfs: Map<string, string>): Record<string, string> {

async function emitVueTscV1(
vfs: Map<string, string>,
compilerOptions: CompilerOptions,
srcFiles: string[],
inputFiles: string[],
originFiles: string[],
opts?: MkdistOptions,
) {
const vueTsc: typeof import("vue-tsc1") = await import("vue-tsc")
.then((r) => r.default || r)
Expand All @@ -75,7 +76,7 @@ async function emitVueTscV1(
const ts =
require("typescript") as typeof import("typescript/lib/tsserverlibrary");

const tsHost = ts.createCompilerHost(compilerOptions);
const tsHost = ts.createCompilerHost(opts.typescript.compilerOptions);

const _tsSysWriteFile = ts.sys.writeFile;
ts.sys.writeFile = (filename, content) => {
Expand All @@ -91,15 +92,17 @@ async function emitVueTscV1(

try {
const program = vueTsc.createProgram({
rootNames: srcFiles,
options: compilerOptions,
rootNames: inputFiles,
options: opts.typescript.compilerOptions,
host: tsHost,
});

const result = program.emit();
if (result.diagnostics?.length) {
console.error(ts.formatDiagnostics(result.diagnostics, tsHost));
}
const output = extractDeclarations(vfs, originFiles, opts);

augmentWithDiagnostics(result, output, tsHost, ts);

return output;
} finally {
ts.sys.writeFile = _tsSysWriteFile;
ts.sys.readFile = _tsSysReadFile;
Expand All @@ -108,8 +111,9 @@ async function emitVueTscV1(

async function emitVueTscV2(
vfs: Map<string, string>,
compilerOptions: CompilerOptions,
srcFiles: string[],
inputFiles: string[],
originFiles: string[],
opts?: MkdistOptions,
) {
const { resolve: resolveModule } = await import("mlly");
const ts: typeof import("typescript") = await import("typescript").then(
Expand All @@ -124,7 +128,7 @@ async function emitVueTscV2(
const volarTs: typeof import("@volar/typescript") =
requireFromVueTsc("@volar/typescript");

const tsHost = ts.createCompilerHost(compilerOptions);
const tsHost = ts.createCompilerHost(opts.typescript.compilerOptions);
tsHost.writeFile = (filename, content) => {
vfs.set(filename, vueTsc.removeEmitGlobalTypes(content));
};
Expand All @@ -140,8 +144,8 @@ async function emitVueTscV2(
return vfs.has(filename) || _tsFileExist(filename);
};
const programOptions: CreateProgramOptions = {
rootNames: srcFiles,
options: compilerOptions,
rootNames: inputFiles,
options: opts.typescript.compilerOptions,
host: tsHost,
};
const createProgram = volarTs.proxyCreateProgram(
Expand All @@ -167,18 +171,22 @@ async function emitVueTscV2(
return [vueLanguagePlugin];
},
);

const program = createProgram(programOptions);

const result = program.emit();
if (result.diagnostics?.length) {
console.error(ts.formatDiagnostics(result.diagnostics, tsHost));
}
const output = extractDeclarations(vfs, originFiles, opts);

augmentWithDiagnostics(result, output, tsHost, ts);

return output;
}

async function emitVueTscLatest(
vfs: Map<string, string>,
compilerOptions: CompilerOptions,
srcFiles: string[],
rootDir: string,
inputFiles: string[],
originFiles: string[],
opts?: MkdistOptions,
) {
const { resolve: resolveModule } = await import("mlly");
const ts: typeof import("typescript") = await import("typescript").then(
Expand All @@ -190,7 +198,7 @@ async function emitVueTscLatest(
const volarTs: typeof import("@volar/typescript") =
requireFromVueTsc("@volar/typescript");

const tsHost = ts.createCompilerHost(compilerOptions);
const tsHost = ts.createCompilerHost(opts.typescript.compilerOptions);
tsHost.writeFile = (filename, content) => {
vfs.set(filename, content);
};
Expand All @@ -207,8 +215,8 @@ async function emitVueTscLatest(
};

const programOptions: CreateProgramOptions = {
rootNames: srcFiles,
options: compilerOptions,
rootNames: inputFiles,
options: opts.typescript.compilerOptions,
host: tsHost,
};

Expand All @@ -222,7 +230,7 @@ async function emitVueTscLatest(
vueLanguageCore.createParsedCommandLineByJson(
ts,
ts.sys,
rootDir,
opts.rootDir,
{},
undefined,
true,
Expand All @@ -234,8 +242,11 @@ async function emitVueTscLatest(
);

const program = createProgram(programOptions);

const result = program.emit();
if (result.diagnostics?.length) {
console.error(ts.formatDiagnostics(result.diagnostics, tsHost));
}
const output = extractDeclarations(vfs, originFiles, opts);

augmentWithDiagnostics(result, output, tsHost, ts);

return output;
}
Loading

0 comments on commit d7819d9

Please sign in to comment.