Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dts): expose ts compiler errors #278

Merged
merged 1 commit into from
Dec 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading