From d7fafc7ada23e11f87abfa70c993ed1ff05d4404 Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 7 Sep 2022 10:40:45 +0200 Subject: [PATCH 1/3] remove swc as executable task --- build/lib/swc/.swcrc-amd | 22 ---------- build/lib/swc/.swcrc-no-mod | 22 ---------- build/lib/swc/index.js | 74 ---------------------------------- build/lib/swc/index.ts | 80 ------------------------------------- 4 files changed, 198 deletions(-) delete mode 100644 build/lib/swc/.swcrc-amd delete mode 100644 build/lib/swc/.swcrc-no-mod delete mode 100644 build/lib/swc/index.js delete mode 100644 build/lib/swc/index.ts diff --git a/build/lib/swc/.swcrc-amd b/build/lib/swc/.swcrc-amd deleted file mode 100644 index 3d0fdde1c85de..0000000000000 --- a/build/lib/swc/.swcrc-amd +++ /dev/null @@ -1,22 +0,0 @@ -{ - "$schema": "http://json.schemastore.org/swcrc", - "exclude": "\\.js$", - "jsc": { - "parser": { - "syntax": "typescript", - "tsx": false, - "decorators": true - }, - "target": "es2020", - "loose": false, - "minify": { - "compress": false, - "mangle": false - } - }, - "module": { - "type": "amd", - "noInterop": true - }, - "minify": false -} diff --git a/build/lib/swc/.swcrc-no-mod b/build/lib/swc/.swcrc-no-mod deleted file mode 100644 index 0a19eb7d2fdce..0000000000000 --- a/build/lib/swc/.swcrc-no-mod +++ /dev/null @@ -1,22 +0,0 @@ -{ - "$schema": "http://json.schemastore.org/swcrc", - "exclude": "\\.js$", - "jsc": { - "parser": { - "syntax": "typescript", - "tsx": false, - "decorators": true - }, - "target": "es2020", - "loose": false, - "minify": { - "compress": false, - "mangle": false - } - }, - "isModule": false, - "module": { - "type": "es6" - }, - "minify": false -} diff --git a/build/lib/swc/index.js b/build/lib/swc/index.js deleted file mode 100644 index 3052c2ffbba3a..0000000000000 --- a/build/lib/swc/index.js +++ /dev/null @@ -1,74 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -exports.createSwcClientStream = void 0; -const child_process_1 = require("child_process"); -const stream_1 = require("stream"); -const path_1 = require("path"); -const util = require("util"); -const gulp = require("gulp"); -/** - * SWC transpile stream. Can be used as stream but `exec` is the prefered way because under the - * hood this simply shells out to swc-cli. There is room for improvement but this already works. - * Ideas - * * use API, not swc-cli - * * invoke binaries directly, don't go through swc-cli - * * understand how to configure both setups in one (https://github.com/swc-project/swc/issues/4989) - */ -function createSwcClientStream() { - const execAsync = util.promisify(child_process_1.exec); - const cwd = (0, path_1.join)(__dirname, '../../../'); - const srcDir = (0, path_1.join)(__dirname, '../../../src'); - const outDir = (0, path_1.join)(__dirname, '../../../out'); - const pathConfigAmd = (0, path_1.join)(__dirname, '.swcrc-amd'); - const pathConfigNoModule = (0, path_1.join)(__dirname, '.swcrc-no-mod'); - return new class extends stream_1.Readable { - constructor() { - super({ objectMode: true, highWaterMark: Number.MAX_SAFE_INTEGER }); - this._isStarted = false; - } - async exec(print) { - const t1 = Date.now(); - const errors = []; - try { - const data1 = await execAsync(`npx swc --config-file ${pathConfigAmd} ${srcDir}/ --out-dir ${outDir}`, { encoding: 'utf-8', cwd }); - errors.push(data1.stderr); - const data2 = await execAsync(`npx swc --config-file ${pathConfigNoModule} ${srcDir}/vs/base/worker/workerMain.ts --out-dir ${outDir}`, { encoding: 'utf-8', cwd }); - errors.push(data2.stderr); - return true; - } - catch (error) { - console.error(errors); - console.error(error); - this.destroy(error); - return false; - } - finally { - if (print) { - console.log(`DONE with SWC after ${Date.now() - t1}ms`); - } - } - } - async _read(_size) { - if (this._isStarted) { - return; - } - this._isStarted = true; - if (!this.exec()) { - this.push(null); - return; - } - for await (const file of gulp.src(`${outDir}/**/*.js`, { base: outDir })) { - this.push(file); - } - this.push(null); - } - }; -} -exports.createSwcClientStream = createSwcClientStream; -if (process.argv[1] === __filename) { - createSwcClientStream().exec(true); -} diff --git a/build/lib/swc/index.ts b/build/lib/swc/index.ts deleted file mode 100644 index 3b211322fd1ea..0000000000000 --- a/build/lib/swc/index.ts +++ /dev/null @@ -1,80 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { exec } from 'child_process'; -import { Readable } from 'stream'; -import { join } from 'path'; -import * as util from 'util'; -import * as gulp from 'gulp'; - -/** - * SWC transpile stream. Can be used as stream but `exec` is the prefered way because under the - * hood this simply shells out to swc-cli. There is room for improvement but this already works. - * Ideas - * * use API, not swc-cli - * * invoke binaries directly, don't go through swc-cli - * * understand how to configure both setups in one (https://github.com/swc-project/swc/issues/4989) - */ -export function createSwcClientStream(): Readable & { exec(print?: boolean): Promise } { - - const execAsync = util.promisify(exec); - - const cwd = join(__dirname, '../../../'); - const srcDir = join(__dirname, '../../../src'); - const outDir = join(__dirname, '../../../out'); - - const pathConfigAmd = join(__dirname, '.swcrc-amd'); - const pathConfigNoModule = join(__dirname, '.swcrc-no-mod'); - - return new class extends Readable { - - private _isStarted = false; - - constructor() { - super({ objectMode: true, highWaterMark: Number.MAX_SAFE_INTEGER }); - } - - async exec(print?: boolean) { - const t1 = Date.now(); - const errors: string[] = []; - try { - const data1 = await execAsync(`npx swc --config-file ${pathConfigAmd} ${srcDir}/ --out-dir ${outDir}`, { encoding: 'utf-8', cwd }); - errors.push(data1.stderr); - - const data2 = await execAsync(`npx swc --config-file ${pathConfigNoModule} ${srcDir}/vs/base/worker/workerMain.ts --out-dir ${outDir}`, { encoding: 'utf-8', cwd }); - errors.push(data2.stderr); - return true; - } catch (error) { - console.error(errors); - console.error(error); - this.destroy(error); - return false; - } finally { - if (print) { - console.log(`DONE with SWC after ${Date.now() - t1}ms`); - } - } - } - - async _read(_size: number): Promise { - if (this._isStarted) { - return; - } - this._isStarted = true; - if (!this.exec()) { - this.push(null); - return; - } - for await (const file of gulp.src(`${outDir}/**/*.js`, { base: outDir })) { - this.push(file); - } - this.push(null); - } - }; -} - -if (process.argv[1] === __filename) { - createSwcClientStream().exec(true); -} From 87e54a07598eccb1c605c7440678f54e01c3447e Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 7 Sep 2022 10:46:27 +0200 Subject: [PATCH 2/3] consume swc as libaray and wire up into the existing transpiler logic --- build/gulpfile.js | 2 +- build/lib/compilation.js | 33 ++----- build/lib/compilation.ts | 41 ++------ build/lib/tsb/index.js | 4 +- build/lib/tsb/index.ts | 10 +- build/lib/tsb/transpiler.js | 141 +++++++++++++++++++++------- build/lib/tsb/transpiler.ts | 182 +++++++++++++++++++++++++++--------- 7 files changed, 273 insertions(+), 140 deletions(-) diff --git a/build/gulpfile.js b/build/gulpfile.js index ec94b2dcfe9d4..e945c06eed45d 100644 --- a/build/gulpfile.js +++ b/build/gulpfile.js @@ -20,7 +20,7 @@ gulp.task(compileApiProposalNamesTask); gulp.task(watchApiProposalNamesTask); // SWC Client Transpile -const transpileClientSWCTask = task.define('transpile-client-swc', task.series(util.rimraf('out'), util.buildWebNodePaths('out'), transpileClientSWC('src', 'out'))); +const transpileClientSWCTask = task.define('transpile-client-swc', task.series(util.rimraf('out'), util.buildWebNodePaths('out'), transpileTask('src', 'out', true))); gulp.task(transpileClientSWCTask); // Transpile only diff --git a/build/lib/compilation.js b/build/lib/compilation.js index 3adc651bef741..f5d46d7b20cad 100644 --- a/build/lib/compilation.js +++ b/build/lib/compilation.js @@ -4,7 +4,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.watchApiProposalNamesTask = exports.compileApiProposalNamesTask = exports.watchTask = exports.compileTask = exports.transpileTask = exports.transpileClientSWC = void 0; +exports.watchApiProposalNamesTask = exports.compileApiProposalNamesTask = exports.watchTask = exports.compileTask = exports.transpileTask = void 0; const es = require("event-stream"); const fs = require("fs"); const gulp = require("gulp"); @@ -18,28 +18,7 @@ const ansiColors = require("ansi-colors"); const os = require("os"); const File = require("vinyl"); const task = require("./task"); -const swc_1 = require("./swc"); const watch = require('./watch'); -// --- SWC: transpile ------------------------------------- -function transpileClientSWC(src, out) { - return function () { - // run SWC sync and put files straight onto the disk - const swcPromise = (0, swc_1.createSwcClientStream)().exec(); - // copy none TS resources, like CSS, images, onto the disk - const bom = require('gulp-bom'); - const utf8Filter = util.filter(data => /(\/|\\)test(\/|\\).*utf8/.test(data.path)); - const tsFilter = util.filter(data => !/\.ts$/.test(data.path)); - const srcStream = gulp.src(`${src}/**`, { base: `${src}` }); - const copyStream = srcStream - .pipe(utf8Filter) - .pipe(bom()) // this is required to preserve BOM in test files that loose it otherwise - .pipe(utf8Filter.restore) - .pipe(tsFilter); - const copyPromise = util.streamToPromise(copyStream.pipe(gulp.dest(out))); - return Promise.all([swcPromise, copyPromise]); - }; -} -exports.transpileClientSWC = transpileClientSWC; // --- gulp-tsb: compile and transpile -------------------------------- const reporter = (0, reporter_1.createReporter)(); function getTypeScriptCompilerOptions(src) { @@ -64,7 +43,11 @@ function createCompile(src, build, emitError, transpileOnly) { if (!build) { overrideOptions.inlineSourceMap = true; } - const compilation = tsb.create(projectPath, overrideOptions, { verbose: false, transpileOnly }, err => reporter(err)); + const compilation = tsb.create(projectPath, overrideOptions, { + verbose: false, + transpileOnly: Boolean(transpileOnly), + transpileWithSwc: typeof transpileOnly !== 'boolean' && transpileOnly.swc + }, err => reporter(err)); function pipeline(token) { const bom = require('gulp-bom'); const utf8Filter = util.filter(data => /(\/|\\)test(\/|\\).*utf8/.test(data.path)); @@ -95,9 +78,9 @@ function createCompile(src, build, emitError, transpileOnly) { }; return pipeline; } -function transpileTask(src, out) { +function transpileTask(src, out, swc) { return function () { - const transpile = createCompile(src, false, true, true); + const transpile = createCompile(src, false, true, { swc }); const srcPipe = gulp.src(`${src}/**`, { base: `${src}` }); return srcPipe .pipe(transpile()) diff --git a/build/lib/compilation.ts b/build/lib/compilation.ts index 65185152bd832..b9769822d73f1 100644 --- a/build/lib/compilation.ts +++ b/build/lib/compilation.ts @@ -17,38 +17,9 @@ import * as os from 'os'; import ts = require('typescript'); import * as File from 'vinyl'; import * as task from './task'; -import { createSwcClientStream } from './swc'; const watch = require('./watch'); -// --- SWC: transpile ------------------------------------- - -export function transpileClientSWC(src: string, out: string) { - - return function () { - - // run SWC sync and put files straight onto the disk - const swcPromise = createSwcClientStream().exec(); - - // copy none TS resources, like CSS, images, onto the disk - const bom = require('gulp-bom') as typeof import('gulp-bom'); - const utf8Filter = util.filter(data => /(\/|\\)test(\/|\\).*utf8/.test(data.path)); - const tsFilter = util.filter(data => !/\.ts$/.test(data.path)); - const srcStream = gulp.src(`${src}/**`, { base: `${src}` }); - - const copyStream = srcStream - .pipe(utf8Filter) - .pipe(bom()) // this is required to preserve BOM in test files that loose it otherwise - .pipe(utf8Filter.restore) - .pipe(tsFilter); - - const copyPromise = util.streamToPromise(copyStream.pipe(gulp.dest(out))); - - return Promise.all([swcPromise, copyPromise]); - }; - -} - // --- gulp-tsb: compile and transpile -------------------------------- const reporter = createReporter(); @@ -68,7 +39,7 @@ function getTypeScriptCompilerOptions(src: string): ts.CompilerOptions { return options; } -function createCompile(src: string, build: boolean, emitError: boolean, transpileOnly: boolean) { +function createCompile(src: string, build: boolean, emitError: boolean, transpileOnly: boolean | { swc: boolean }) { const tsb = require('./tsb') as typeof import('./tsb'); const sourcemaps = require('gulp-sourcemaps') as typeof import('gulp-sourcemaps'); @@ -79,7 +50,11 @@ function createCompile(src: string, build: boolean, emitError: boolean, transpil overrideOptions.inlineSourceMap = true; } - const compilation = tsb.create(projectPath, overrideOptions, { verbose: false, transpileOnly }, err => reporter(err)); + const compilation = tsb.create(projectPath, overrideOptions, { + verbose: false, + transpileOnly: Boolean(transpileOnly), + transpileWithSwc: typeof transpileOnly !== 'boolean' && transpileOnly.swc + }, err => reporter(err)); function pipeline(token?: util.ICancellationToken) { const bom = require('gulp-bom') as typeof import('gulp-bom'); @@ -115,11 +90,11 @@ function createCompile(src: string, build: boolean, emitError: boolean, transpil return pipeline; } -export function transpileTask(src: string, out: string): () => NodeJS.ReadWriteStream { +export function transpileTask(src: string, out: string, swc: boolean): () => NodeJS.ReadWriteStream { return function () { - const transpile = createCompile(src, false, true, true); + const transpile = createCompile(src, false, true, { swc }); const srcPipe = gulp.src(`${src}/**`, { base: `${src}` }); return srcPipe diff --git a/build/lib/tsb/index.js b/build/lib/tsb/index.js index a0b6423bb61a0..51806a479eac7 100644 --- a/build/lib/tsb/index.js +++ b/build/lib/tsb/index.js @@ -92,7 +92,9 @@ function create(projectPath, existingOptions, config, onError = _defaultOnError) } let result; if (config.transpileOnly) { - const transpiler = new transpiler_1.Transpiler(logFn, printDiagnostic, projectPath, cmdLine); + const transpiler = !config.transpileWithSwc + ? new transpiler_1.TscTranspiler(logFn, printDiagnostic, projectPath, cmdLine) + : new transpiler_1.SwcTranspiler(logFn, printDiagnostic, projectPath, cmdLine); result = (() => createTranspileStream(transpiler)); } else { diff --git a/build/lib/tsb/index.ts b/build/lib/tsb/index.ts index 384a6beeb932d..0c5952cec342b 100644 --- a/build/lib/tsb/index.ts +++ b/build/lib/tsb/index.ts @@ -13,7 +13,7 @@ import { strings } from './utils'; import { readFileSync, statSync } from 'fs'; import * as log from 'fancy-log'; import colors = require('ansi-colors'); -import { Transpiler } from './transpiler'; +import { ITranspiler, SwcTranspiler, TscTranspiler } from './transpiler'; export interface IncrementalCompiler { (token?: any): Readable & Writable; @@ -36,7 +36,7 @@ const _defaultOnError = (err: string) => console.log(JSON.stringify(err, null, 4 export function create( projectPath: string, existingOptions: Partial, - config: { verbose?: boolean; transpileOnly?: boolean; transpileOnlyIncludesDts?: boolean }, + config: { verbose?: boolean; transpileOnly?: boolean; transpileOnlyIncludesDts?: boolean; transpileWithSwc?: boolean }, onError: (message: string) => void = _defaultOnError ): IncrementalCompiler { @@ -95,7 +95,7 @@ export function create( } // TRANSPILE ONLY stream doing just TS to JS conversion - function createTranspileStream(transpiler: Transpiler): Readable & Writable { + function createTranspileStream(transpiler: ITranspiler): Readable & Writable { return through(function (this: through.ThroughStream & { queue(a: any): void }, file: Vinyl) { // give the file to the compiler if (file.isStream()) { @@ -126,7 +126,9 @@ export function create( let result: IncrementalCompiler; if (config.transpileOnly) { - const transpiler = new Transpiler(logFn, printDiagnostic, projectPath, cmdLine); + const transpiler = !config.transpileWithSwc + ? new TscTranspiler(logFn, printDiagnostic, projectPath, cmdLine) + : new SwcTranspiler(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 38e6b5c00e4da..0609105e8696a 100644 --- a/build/lib/tsb/transpiler.js +++ b/build/lib/tsb/transpiler.js @@ -3,8 +3,10 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +var _a; Object.defineProperty(exports, "__esModule", { value: true }); -exports.Transpiler = void 0; +exports.SwcTranspiler = exports.TscTranspiler = void 0; +const swc = require("@swc/core"); const ts = require("typescript"); const threads = require("node:worker_threads"); const Vinyl = require("vinyl"); @@ -36,6 +38,35 @@ if (!threads.isMainThread) { threads.parentPort.postMessage(res); }); } +class OutputFileNameOracle { + constructor(cmdLine, configFilePath) { + this.getOutputFileName = (file) => { + try { + // windows: path-sep normalizing + file = ts.normalizePath(file); + if (!cmdLine.options.configFilePath) { + // this is needed for the INTERNAL getOutputFileNames-call below... + cmdLine.options.configFilePath = configFilePath; + } + const isDts = file.endsWith('.d.ts'); + if (isDts) { + file = file.slice(0, -5) + '.ts'; + cmdLine.fileNames.push(file); + } + const outfile = ts.getOutputFileNames(cmdLine, file, true)[0]; + if (isDts) { + cmdLine.fileNames.pop(); + } + return outfile; + } + catch (err) { + console.error(file, cmdLine.fileNames); + console.error(err); + throw new err; + } + }; + } +} class TranspileWorker { constructor(outFileFn) { this.id = TranspileWorker.pool++; @@ -112,39 +143,15 @@ class TranspileWorker { } } TranspileWorker.pool = 1; -class Transpiler { +class TscTranspiler { constructor(logFn, _onError, configFilePath, _cmdLine) { this._onError = _onError; this._cmdLine = _cmdLine; this._workerPool = []; this._queue = []; this._allJobs = []; - logFn('Transpile', `will use ${Transpiler.P} transpile worker`); - this._getOutputFileName = (file) => { - try { - // windows: path-sep normalizing - file = ts.normalizePath(file); - if (!_cmdLine.options.configFilePath) { - // this is needed for the INTERNAL getOutputFileNames-call below... - _cmdLine.options.configFilePath = configFilePath; - } - const isDts = file.endsWith('.d.ts'); - if (isDts) { - file = file.slice(0, -5) + '.ts'; - _cmdLine.fileNames.push(file); - } - const outfile = ts.getOutputFileNames(_cmdLine, file, true)[0]; - if (isDts) { - _cmdLine.fileNames.pop(); - } - return outfile; - } - catch (err) { - console.error(file, _cmdLine.fileNames); - console.error(err); - throw new err; - } - }; + logFn('Transpile', `will use ${TscTranspiler.P} transpile worker`); + this._outputFileNames = new OutputFileNameOracle(_cmdLine, configFilePath); } async join() { // wait for all penindg jobs @@ -161,7 +168,7 @@ class Transpiler { return; } const newLen = this._queue.push(file); - if (newLen > Transpiler.P ** 2) { + if (newLen > TscTranspiler.P ** 2) { this._consumeQueue(); } } @@ -172,8 +179,8 @@ class Transpiler { } // kinda LAZYily create workers if (this._workerPool.length === 0) { - for (let i = 0; i < Transpiler.P; i++) { - this._workerPool.push(new TranspileWorker(file => this._getOutputFileName(file))); + for (let i = 0; i < TscTranspiler.P; i++) { + this._workerPool.push(new TranspileWorker(file => this._outputFileNames.getOutputFileName(file))); } } const freeWorker = this._workerPool.filter(w => !w.isBusy); @@ -187,7 +194,7 @@ class Transpiler { } const job = new Promise(resolve => { const consume = () => { - const files = this._queue.splice(0, Transpiler.P); + const files = this._queue.splice(0, TscTranspiler.P); if (files.length === 0) { // DONE resolve(undefined); @@ -210,11 +217,77 @@ class Transpiler { } } } -exports.Transpiler = Transpiler; -Transpiler.P = Math.floor((0, node_os_1.cpus)().length * .5); +exports.TscTranspiler = TscTranspiler; +TscTranspiler.P = Math.floor((0, node_os_1.cpus)().length * .5); function _isDefaultEmpty(src) { return src .replace('"use strict";', '') .replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '$1') .trim().length === 0; } +class SwcTranspiler { + constructor(_logFn, _onError, configFilePath, _cmdLine) { + this._logFn = _logFn; + this._onError = _onError; + this._cmdLine = _cmdLine; + this._jobs = []; + _logFn('Transpile', `will use SWC 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 (this._cmdLine.options.noEmit) { + // not doing ANYTHING here + return; + } + const tsSrc = String(file.contents); + const isAmd = /\n(import|export)/m.test(tsSrc); + const t1 = Date.now(); + this._jobs.push(swc.transform(tsSrc, isAmd ? SwcTranspiler._swcrcAmd : SwcTranspiler._swcrcEsm).then(output => { + 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(output.code), + })); + this._logFn('Transpile', `swc took ${Date.now() - t1}ms for ${file.path}`); + }).catch(err => { + this._onError(err); + })); + } +} +exports.SwcTranspiler = SwcTranspiler; +_a = SwcTranspiler; +// --- .swcrc +SwcTranspiler._swcrcAmd = { + exclude: '\.js$', + jsc: { + parser: { + syntax: 'typescript', + tsx: false, + decorators: true + }, + target: 'es2020', + loose: false, + minify: { + compress: false, + mangle: false + } + }, + module: { + type: 'amd', + noInterop: true + }, + minify: false, +}; +SwcTranspiler._swcrcEsm = { + ..._a._swcrcAmd, + module: { + type: 'es6' + } +}; diff --git a/build/lib/tsb/transpiler.ts b/build/lib/tsb/transpiler.ts index 7bd789ef6eb98..062c2b1a17939 100644 --- a/build/lib/tsb/transpiler.ts +++ b/build/lib/tsb/transpiler.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as swc from '@swc/core'; import * as ts from 'typescript'; import * as threads from 'node:worker_threads'; import * as Vinyl from 'vinyl'; @@ -48,6 +49,47 @@ if (!threads.isMainThread) { }); } +class OutputFileNameOracle { + + readonly getOutputFileName: (name: string) => string; + + constructor(cmdLine: ts.ParsedCommandLine, configFilePath: string) { + // very complicated logic to re-use TS internal functions to know the output path + // given a TS input path and its config + type InternalTsApi = typeof ts & { + normalizePath(path: string): string; + getOutputFileNames(commandLine: ts.ParsedCommandLine, inputFileName: string, ignoreCase: boolean): readonly string[]; + }; + this.getOutputFileName = (file) => { + try { + + // windows: path-sep normalizing + file = (ts).normalizePath(file); + + if (!cmdLine.options.configFilePath) { + // this is needed for the INTERNAL getOutputFileNames-call below... + cmdLine.options.configFilePath = configFilePath; + } + const isDts = file.endsWith('.d.ts'); + if (isDts) { + file = file.slice(0, -5) + '.ts'; + cmdLine.fileNames.push(file); + } + const outfile = (ts).getOutputFileNames(cmdLine, file, true)[0]; + if (isDts) { + cmdLine.fileNames.pop(); + } + return outfile; + + } catch (err) { + console.error(file, cmdLine.fileNames); + console.error(err); + throw new err; + } + }; + } +} + class TranspileWorker { private static pool = 1; @@ -141,12 +183,18 @@ class TranspileWorker { } } +export interface ITranspiler { + onOutfile?: (file: Vinyl) => void; + join(): Promise; + transpile(file: Vinyl): void; +} -export class Transpiler { +export class TscTranspiler implements ITranspiler { static P = Math.floor(cpus().length * .5); - private readonly _getOutputFileName: (name: string) => string; + private readonly _outputFileNames: OutputFileNameOracle; + public onOutfile?: (file: Vinyl) => void; @@ -160,42 +208,8 @@ export class Transpiler { configFilePath: string, private readonly _cmdLine: ts.ParsedCommandLine ) { - logFn('Transpile', `will use ${Transpiler.P} transpile worker`); - - - // very complicated logic to re-use TS internal functions to know the output path - // given a TS input path and its config - type InternalTsApi = typeof ts & { - normalizePath(path: string): string; - getOutputFileNames(commandLine: ts.ParsedCommandLine, inputFileName: string, ignoreCase: boolean): readonly string[]; - }; - this._getOutputFileName = (file) => { - try { - - // windows: path-sep normalizing - file = (ts).normalizePath(file); - - if (!_cmdLine.options.configFilePath) { - // this is needed for the INTERNAL getOutputFileNames-call below... - _cmdLine.options.configFilePath = configFilePath; - } - const isDts = file.endsWith('.d.ts'); - if (isDts) { - file = file.slice(0, -5) + '.ts'; - _cmdLine.fileNames.push(file); - } - const outfile = (ts).getOutputFileNames(_cmdLine, file, true)[0]; - if (isDts) { - _cmdLine.fileNames.pop(); - } - return outfile; - - } catch (err) { - console.error(file, _cmdLine.fileNames); - console.error(err); - throw new err; - } - }; + logFn('Transpile', `will use ${TscTranspiler.P} transpile worker`); + this._outputFileNames = new OutputFileNameOracle(_cmdLine, configFilePath); } async join() { @@ -218,7 +232,7 @@ export class Transpiler { } const newLen = this._queue.push(file); - if (newLen > Transpiler.P ** 2) { + if (newLen > TscTranspiler.P ** 2) { this._consumeQueue(); } } @@ -232,8 +246,8 @@ export class Transpiler { // kinda LAZYily create workers if (this._workerPool.length === 0) { - for (let i = 0; i < Transpiler.P; i++) { - this._workerPool.push(new TranspileWorker(file => this._getOutputFileName(file))); + for (let i = 0; i < TscTranspiler.P; i++) { + this._workerPool.push(new TranspileWorker(file => this._outputFileNames.getOutputFileName(file))); } } @@ -251,7 +265,7 @@ export class Transpiler { const job = new Promise(resolve => { const consume = () => { - const files = this._queue.splice(0, Transpiler.P); + const files = this._queue.splice(0, TscTranspiler.P); if (files.length === 0) { // DONE resolve(undefined); @@ -283,3 +297,87 @@ function _isDefaultEmpty(src: string): boolean { .replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '$1') .trim().length === 0; } + + +export class SwcTranspiler implements ITranspiler { + + onOutfile?: ((file: Vinyl) => void) | undefined; + + private readonly _outputFileNames: OutputFileNameOracle; + private _jobs: Promise[] = []; + + 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 SWC 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 (this._cmdLine.options.noEmit) { + // not doing ANYTHING here + return; + } + + const tsSrc = String(file.contents); + const isAmd = /\n(import|export)/m.test(tsSrc); + const t1 = Date.now(); + this._jobs.push(swc.transform(tsSrc, isAmd ? SwcTranspiler._swcrcAmd : SwcTranspiler._swcrcEsm).then(output => { + + 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(output.code), + })); + + this._logFn('Transpile', `swc took ${Date.now() - t1}ms for ${file.path}`); + + }).catch(err => { + this._onError(err); + })); + } + + // --- .swcrc + + + private static readonly _swcrcAmd: swc.Options = { + exclude: '\.js$', + jsc: { + parser: { + syntax: 'typescript', + tsx: false, + decorators: true + }, + target: 'es2020', + loose: false, + minify: { + compress: false, + mangle: false + } + }, + module: { + type: 'amd', + noInterop: true + }, + minify: false, + }; + + private static readonly _swcrcEsm: swc.Options = { + ...this._swcrcAmd, + module: { + type: 'es6' + } + }; +} From d6f572702129b782ae55061cbb92a043d7ea975a Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 7 Sep 2022 11:18:53 +0200 Subject: [PATCH 3/3] also use SWC for extensions transpile --- build/gulpfile.extensions.js | 2 +- build/lib/tsb/transpiler.js | 25 +++++++++++++++++++++++-- build/lib/tsb/transpiler.ts | 28 ++++++++++++++++++++++++++-- 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/build/gulpfile.extensions.js b/build/gulpfile.extensions.js index 04132cd4400cb..727b14eb26759 100644 --- a/build/gulpfile.extensions.js +++ b/build/gulpfile.extensions.js @@ -110,7 +110,7 @@ const tasks = compilations.map(function (tsconfigFile) { overrideOptions.inlineSources = Boolean(build); overrideOptions.base = path.dirname(absolutePath); - const compilation = tsb.create(absolutePath, overrideOptions, { verbose: false, transpileOnly, transpileOnlyIncludesDts: transpileOnly }, err => reporter(err.toString())); + const compilation = tsb.create(absolutePath, overrideOptions, { verbose: false, transpileOnly, transpileOnlyIncludesDts: transpileOnly, transpileWithSwc: true }, err => reporter(err.toString())); const pipeline = function () { const input = es.through(); diff --git a/build/lib/tsb/transpiler.js b/build/lib/tsb/transpiler.js index 0609105e8696a..e5e90128d09f5 100644 --- a/build/lib/tsb/transpiler.js +++ b/build/lib/tsb/transpiler.js @@ -245,9 +245,23 @@ class SwcTranspiler { return; } const tsSrc = String(file.contents); - const isAmd = /\n(import|export)/m.test(tsSrc); const t1 = Date.now(); - this._jobs.push(swc.transform(tsSrc, isAmd ? SwcTranspiler._swcrcAmd : SwcTranspiler._swcrcEsm).then(output => { + let options = SwcTranspiler._swcrcEsm; + if (this._cmdLine.options.module === ts.ModuleKind.AMD) { + const isAmd = /\n(import|export)/m.test(tsSrc); + if (isAmd) { + options = SwcTranspiler._swcrcAmd; + } + } + else if (this._cmdLine.options.module === ts.ModuleKind.CommonJS) { + options = SwcTranspiler._swcrcCommonJS; + } + this._jobs.push(swc.transform(tsSrc, options).then(output => { + // check if output of a DTS-files isn't just "empty" and iff so + // skip this file + if (file.path.endsWith('.d.ts') && _isDefaultEmpty(output.code)) { + return; + } const outBase = this._cmdLine.options.outDir ?? file.base; const outPath = this._outputFileNames.getOutputFileName(file.path); this.onOutfile(new Vinyl({ @@ -285,6 +299,13 @@ SwcTranspiler._swcrcAmd = { }, minify: false, }; +SwcTranspiler._swcrcCommonJS = { + ..._a._swcrcAmd, + module: { + type: 'commonjs', + importInterop: 'none' + } +}; SwcTranspiler._swcrcEsm = { ..._a._swcrcAmd, module: { diff --git a/build/lib/tsb/transpiler.ts b/build/lib/tsb/transpiler.ts index 062c2b1a17939..a82cbaef8905b 100644 --- a/build/lib/tsb/transpiler.ts +++ b/build/lib/tsb/transpiler.ts @@ -329,9 +329,25 @@ export class SwcTranspiler implements ITranspiler { } const tsSrc = String(file.contents); - const isAmd = /\n(import|export)/m.test(tsSrc); const t1 = Date.now(); - this._jobs.push(swc.transform(tsSrc, isAmd ? SwcTranspiler._swcrcAmd : SwcTranspiler._swcrcEsm).then(output => { + + let options: swc.Options = SwcTranspiler._swcrcEsm; + if (this._cmdLine.options.module === ts.ModuleKind.AMD) { + const isAmd = /\n(import|export)/m.test(tsSrc); + if (isAmd) { + options = SwcTranspiler._swcrcAmd; + } + } else if (this._cmdLine.options.module === ts.ModuleKind.CommonJS) { + options = SwcTranspiler._swcrcCommonJS; + } + + this._jobs.push(swc.transform(tsSrc, options).then(output => { + + // check if output of a DTS-files isn't just "empty" and iff so + // skip this file + if (file.path.endsWith('.d.ts') && _isDefaultEmpty(output.code)) { + return; + } const outBase = this._cmdLine.options.outDir ?? file.base; const outPath = this._outputFileNames.getOutputFileName(file.path); @@ -374,6 +390,14 @@ export class SwcTranspiler implements ITranspiler { minify: false, }; + private static readonly _swcrcCommonJS: swc.Options = { + ...this._swcrcAmd, + module: { + type: 'commonjs', + importInterop: 'none' + } + }; + private static readonly _swcrcEsm: swc.Options = { ...this._swcrcAmd, module: {