From c1fb7325bdca83f9ee9b95c2b3d5572ac800436d Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 1 Oct 2024 16:26:50 +0200 Subject: [PATCH] add esbuild transpiler for fast CI --- build/lib/tsb/index.js | 4 +-- build/lib/tsb/index.ts | 4 +-- build/lib/tsb/transpiler.js | 49 ++++++++++++++++++++++++++++++++- build/lib/tsb/transpiler.ts | 55 +++++++++++++++++++++++++++++++++++++ 4 files changed, 107 insertions(+), 5 deletions(-) diff --git a/build/lib/tsb/index.js b/build/lib/tsb/index.js index 8b8116d5a49..204c06e80ac 100644 --- a/build/lib/tsb/index.js +++ b/build/lib/tsb/index.js @@ -14,8 +14,8 @@ const path_1 = require("path"); const utils_1 = require("./utils"); const fs_1 = require("fs"); const log = require("fancy-log"); -const colors = require("ansi-colors"); const transpiler_1 = require("./transpiler"); +const colors = require("ansi-colors"); class EmptyDuplex extends stream_1.Duplex { _write(_chunk, _encoding, callback) { callback(); } _read() { this.push(null); } @@ -97,7 +97,7 @@ function create(projectPath, existingOptions, config, onError = _defaultOnError) if (config.transpileOnly) { const transpiler = !config.transpileWithSwc ? new transpiler_1.TscTranspiler(logFn, printDiagnostic, projectPath, cmdLine) - : new transpiler_1.SwcTranspiler(logFn, printDiagnostic, projectPath, cmdLine); + : new transpiler_1.ESBuildTranspiler(logFn, printDiagnostic, projectPath, cmdLine); result = (() => createTranspileStream(transpiler)); } else { diff --git a/build/lib/tsb/index.ts b/build/lib/tsb/index.ts index 670ac6542e7..53c752d2655 100644 --- a/build/lib/tsb/index.ts +++ b/build/lib/tsb/index.ts @@ -12,8 +12,8 @@ import { dirname } from 'path'; import { strings } from './utils'; import { readFileSync, statSync } from 'fs'; import * as log from 'fancy-log'; +import { ESBuildTranspiler, ITranspiler, TscTranspiler } from './transpiler'; import colors = require('ansi-colors'); -import { ITranspiler, SwcTranspiler, TscTranspiler } from './transpiler'; export interface IncrementalCompiler { (token?: any): Readable & Writable; @@ -130,7 +130,7 @@ export function create( if (config.transpileOnly) { const transpiler = !config.transpileWithSwc ? new TscTranspiler(logFn, printDiagnostic, projectPath, cmdLine) - : new SwcTranspiler(logFn, printDiagnostic, projectPath, cmdLine); + : new ESBuildTranspiler(logFn, printDiagnostic, projectPath, cmdLine); result = (() => createTranspileStream(transpiler)); } else { const _builder = builder.createTypeScriptBuilder({ logFn }, projectPath, cmdLine); diff --git a/build/lib/tsb/transpiler.js b/build/lib/tsb/transpiler.js index 100b1bac972..7b8ffab888d 100644 --- a/build/lib/tsb/transpiler.js +++ b/build/lib/tsb/transpiler.js @@ -4,8 +4,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.SwcTranspiler = exports.TscTranspiler = void 0; +exports.SwcTranspiler = exports.ESBuildTranspiler = exports.TscTranspiler = void 0; const swc = require("@swc/core"); +const esbuild = require("esbuild"); const ts = require("typescript"); const threads = require("node:worker_threads"); const Vinyl = require("vinyl"); @@ -224,6 +225,52 @@ class TscTranspiler { } } exports.TscTranspiler = TscTranspiler; +class ESBuildTranspiler { + _logFn; + _onError; + _cmdLine; + _outputFileNames; + _jobs = []; + onOutfile; + constructor(_logFn, _onError, configFilePath, _cmdLine) { + this._logFn = _logFn; + this._onError = _onError; + this._cmdLine = _cmdLine; + _logFn('Transpile', `will use ESBuild to transpile source files`); + this._outputFileNames = new OutputFileNameOracle(_cmdLine, configFilePath); + } + async join() { + const jobs = this._jobs.slice(); + this._jobs.length = 0; + await Promise.allSettled(jobs); + } + transpile(file) { + if (!(file.contents instanceof Buffer)) { + throw Error('file.contents must be a Buffer'); + } + const t1 = Date.now(); + this._jobs.push(esbuild.transform(file.contents, { + target: ['es2022'], + loader: 'ts', + tsconfigRaw: JSON.stringify({ compilerOptions: this._cmdLine.options }), + supported: { + 'class-static-blocks': false // SEE https://github.com/evanw/esbuild/issues/3823 + } + }).then(result => { + const outBase = this._cmdLine.options.outDir ?? file.base; + const outPath = this._outputFileNames.getOutputFileName(file.path); + this.onOutfile(new Vinyl({ + path: outPath, + base: outBase, + contents: Buffer.from(result.code), + })); + this._logFn('Transpile', `esbuild took ${Date.now() - t1}ms for ${file.path}`); + }).catch(err => { + this._onError(err); + })); + } +} +exports.ESBuildTranspiler = ESBuildTranspiler; function _isDefaultEmpty(src) { return src .replace('"use strict";', '') diff --git a/build/lib/tsb/transpiler.ts b/build/lib/tsb/transpiler.ts index b6a9deeb3c7..20eb915d67a 100644 --- a/build/lib/tsb/transpiler.ts +++ b/build/lib/tsb/transpiler.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as swc from '@swc/core'; +import * as esbuild from 'esbuild'; import * as ts from 'typescript'; import * as threads from 'node:worker_threads'; import * as Vinyl from 'vinyl'; @@ -291,6 +292,60 @@ export class TscTranspiler implements ITranspiler { } } +export class ESBuildTranspiler implements ITranspiler { + + private readonly _outputFileNames: OutputFileNameOracle; + private _jobs: Promise[] = []; + + onOutfile?: ((file: Vinyl) => void) | undefined; + + constructor( + private readonly _logFn: (topic: string, message: string) => void, + private readonly _onError: (err: any) => void, + configFilePath: string, + private readonly _cmdLine: ts.ParsedCommandLine + ) { + _logFn('Transpile', `will use ESBuild to transpile source files`); + this._outputFileNames = new OutputFileNameOracle(_cmdLine, configFilePath); + } + + async join(): Promise { + const jobs = this._jobs.slice(); + this._jobs.length = 0; + await Promise.allSettled(jobs); + } + + transpile(file: Vinyl): void { + if (!(file.contents instanceof Buffer)) { + throw Error('file.contents must be a Buffer'); + } + const t1 = Date.now(); + this._jobs.push(esbuild.transform(file.contents, { + target: ['es2022'], + loader: 'ts', + tsconfigRaw: JSON.stringify({ compilerOptions: this._cmdLine.options }), + supported: { + 'class-static-blocks': false // SEE https://github.com/evanw/esbuild/issues/3823 + } + }).then(result => { + + const outBase = this._cmdLine.options.outDir ?? file.base; + const outPath = this._outputFileNames.getOutputFileName(file.path); + + this.onOutfile!(new Vinyl({ + path: outPath, + base: outBase, + contents: Buffer.from(result.code), + })); + + this._logFn('Transpile', `esbuild took ${Date.now() - t1}ms for ${file.path}`); + + }).catch(err => { + this._onError(err); + })); + } +} + function _isDefaultEmpty(src: string): boolean { return src .replace('"use strict";', '')