vite-esbuild-typescript-checker
Version:
Checker typescript vite and esbuild
218 lines (175 loc) • 7.07 kB
text/typescript
import { IssueCustom } from "./worker";
import { ErrorPayload, WebSocketServer } from "vite";
import { IssueWebpackError } from "fork-ts-checker-webpack-plugin/lib/issue/IssueWebpackError";
import chalk from "chalk";
import moment from "moment";
import process from "process";
import path from "path";
import fs from "fs";
import os from "os";
import { Worker } from "worker_threads";
import { BabelCodeFrameOptions } from "fork-ts-checker-webpack-plugin/lib/formatter";
import { codeFrameColumns } from "@babel/code-frame";
import { Issue } from "fork-ts-checker-webpack-plugin/lib/issue";
import { TypeScriptReporterOptions } from "fork-ts-checker-webpack-plugin/lib/typescript-reporter/TypeScriptReporterOptions";
import { EsLintReporterOptions } from "fork-ts-checker-webpack-plugin/lib/eslint-reporter/EsLintReporterOptions";
export type WorkerCallback = (err: any, result?: any) => any;
export type Formatter = (issue: Issue, options?: BabelCodeFrameOptions) => string;
export class Helper {
public changedFiles: Array<string> = [];
public deletedFiles: Array<string> = [];
public timeout: NodeJS.Timeout | undefined = undefined;
public worker: Worker | undefined;
public config: PluginConfig;
constructor(config: PluginConfig) {
this.config = config;
this.timeout = undefined;
this.worker = undefined;
}
runWorker(path: string, cb: WorkerCallback, workerData: object | null = null) {
const worker = new Worker(path, { workerData });
worker.on('message', cb.bind(null, null));
worker.on('error', cb);
worker.on('exit', (exitCode) => {
if (exitCode === 0) {
return null;
}
return cb(new Error(`Worker has stopped with code ${exitCode}`));
});
return worker;
}
workerStart(ws?: WebSocketServer, watch: boolean = false) {
this.config.checker.async = watch;
this.worker = this.runWorker(path.resolve(__dirname, 'worker.js'), (err, message) => {
if (err) {
console.log(err);
process.exit();
return null;
}
this.handleMessage(message, ws)
}, this.config.checker);
this.worker.postMessage({
type: 'changedFiles',
files: { changedFiles: this.changedFiles, deletedFiles: this.deletedFiles }
});
}
clearFiles() {
this.changedFiles = [];
this.deletedFiles = [];
if (this.timeout) clearTimeout(this.timeout);
}
addFile = (path: string) => {
if (!path.endsWith('.vue') && !path.endsWith('.ts') && !path.endsWith('.js')) return;
if (this.changedFiles.indexOf(path) === -1) {
this.changedFiles.push(path);
if (this.timeout) clearTimeout(this.timeout);
this.timeout = setTimeout(this.changeFiles.bind(this), 300);
}
}
deleteFile = (path: string) => {
if (!path.endsWith('.vue') && !path.endsWith('.ts') && !path.endsWith('.js') && !path.endsWith('vite.config.ts')) return;
if (this.deletedFiles.indexOf(path) === -1) {
this.deletedFiles.push(path);
if (this.timeout) clearTimeout(this.timeout);
this.timeout = setTimeout(this.changeFiles.bind(this), 300);
}
}
changeFiles() {
this.worker?.postMessage({
type: 'changedFiles',
files: { changedFiles: this.changedFiles, deletedFiles: this.deletedFiles }
});
this.clearFiles();
}
getPayloadError(issues: IssueCustom): ErrorPayload {
return {
type: 'error',
err: issueToViteError(issues)
};
}
handleMessage(message: any, ws?: WebSocketServer) {
if (message.type === 'issueList') {
const issues = message.issues.map((issue: IssueCustom) => {
issue.formatted = createCodeFrameFormatter({
highlightCode: false,
forceColor: false
})(issue);
issue.formattedColor = createCodeFrameFormatter({
highlightCode: true
})(issue);
const error = new IssueWebpackError(issue.formattedColor, issue);
console.log([
chalk.red(`ERROR in ${error.file}`),
`${error.message}`
].join("\n"));
return issue;
});
if (ws && issues[0] && this.config.vite.overlay) {
ws.send(this.getPayloadError(issues[0]));
}
if (issues.length) console.log(chalk.red(`[${moment().format('Y-MM-DD H:mm:ss')}] Found ${issues.length} errors.`));
console.log(chalk.green(`[${moment().format('Y-MM-DD H:mm:ss')}] Types check done in ${message.time}ms.`));
}
}
}
export function issueToViteError(
issue: IssueCustom
): ErrorPayload['err'] {
let loc: ErrorPayload['err']['loc']
if (issue.location) {
loc = {
file: issue.file,
line: issue.location.start.line,
column: issue.location.start.column ?? 0,
}
}
return {
message: issue.formatted ?? '',
stack: "",
id: issue.file,
frame: 'issue.stripedCodeFrame',
plugin: `fork-ts-checker-vite-plugin`,
loc,
}
}
export function createCodeFrameFormatter(options?: BabelCodeFrameOptions): Formatter {
const basicFormatter = createBasicFormatter();
return function codeFrameFormatter(issue) {
const source = issue.file && fs.existsSync(issue.file) && fs.readFileSync(issue.file, 'utf-8');
let frame = '';
if (source && issue.location) {
frame = codeFrameColumns(source, issue.location, {
highlightCode: true,
...(options || {}),
})
.split('\n')
.map((line: string) => ' ' + line)
.join(os.EOL);
}
const lines = [basicFormatter(issue, {
highlightCode: true,
...(options || {}),
})];
if (frame) {
lines.push(frame);
}
return lines.join(os.EOL);
};
}
export function createBasicFormatter(): Formatter {
return function basicFormatter(issue, options?: BabelCodeFrameOptions) {
return (options?.highlightCode ? chalk.grey(issue.code + ': ') : issue.code + ': ') + issue.message;
};
}
export interface PluginConfig {
vite: {
overlay: boolean
},
checker: ForkTsCheckerWebpackPluginOptions
}
export interface ForkTsCheckerWebpackPluginOptions {
async?: boolean;
typescript?: TypeScriptReporterOptions;
eslint?: EsLintReporterOptions;
formatter?: 'codeframe' | 'basic';
}