Use ts-go for building our extensions

Also reverts to use experimental decorators due to stage 3 decorators not being supported yet https://github.com/microsoft/typescript-go/issues/2354

The skipLib check is needed to work around some jsdom types issues
This commit is contained in:
Matt Bierner
2026-02-02 20:02:55 -08:00
parent 424ef28d9b
commit cca17c1b7f
9 changed files with 168 additions and 48 deletions

View File

@@ -7,21 +7,22 @@
import { EventEmitter } from 'events';
EventEmitter.defaultMaxListeners = 100;
import es from 'event-stream';
import glob from 'glob';
import gulp from 'gulp';
import filter from 'gulp-filter';
import plumber from 'gulp-plumber';
import sourcemaps from 'gulp-sourcemaps';
import * as path from 'path';
import * as nodeUtil from 'util';
import es from 'event-stream';
import filter from 'gulp-filter';
import * as util from './lib/util.ts';
import { getVersion } from './lib/getVersion.ts';
import * as task from './lib/task.ts';
import watcher from './lib/watch/index.ts';
import { createReporter } from './lib/reporter.ts';
import glob from 'glob';
import plumber from 'gulp-plumber';
import * as ext from './lib/extensions.ts';
import { getVersion } from './lib/getVersion.ts';
import { createReporter } from './lib/reporter.ts';
import * as task from './lib/task.ts';
import * as tsb from './lib/tsb/index.ts';
import sourcemaps from 'gulp-sourcemaps';
import { createTsgoStream, spawnTsgo } from './lib/tsgo.ts';
import * as util from './lib/util.ts';
import watcher from './lib/watch/index.ts';
const root = path.dirname(import.meta.dirname);
const commit = getVersion(root);
@@ -150,25 +151,22 @@ const tasks = compilations.map(function (tsconfigFile) {
.pipe(gulp.dest(out));
}));
const compileTask = task.define(`compile-extension:${name}`, task.series(cleanTask, () => {
const pipeline = createPipeline(false, true);
const nonts = gulp.src(src, srcOpts).pipe(filter(['**', '!**/*.ts']));
const input = es.merge(nonts, pipeline.tsProjectSrc());
const compileTask = task.define(`compile-extension:${name}`, task.series(cleanTask, async () => {
const nonts = gulp.src(src, srcOpts).pipe(filter(['**', '!**/*.ts'], { dot: true }));
const copyNonTs = util.streamToPromise(nonts.pipe(gulp.dest(out)));
const tsgo = spawnTsgo(absolutePath);
return input
.pipe(pipeline())
.pipe(gulp.dest(out));
await Promise.all([copyNonTs, tsgo]);
}));
const watchTask = task.define(`watch-extension:${name}`, task.series(cleanTask, () => {
const pipeline = createPipeline(false);
const nonts = gulp.src(src, srcOpts).pipe(filter(['**', '!**/*.ts']));
const input = es.merge(nonts, pipeline.tsProjectSrc());
const nonts = gulp.src(src, srcOpts).pipe(filter(['**', '!**/*.ts'], { dot: true }));
const watchInput = watcher(src, { ...srcOpts, ...{ readDelay: 200 } });
const watchNonTs = watchInput.pipe(filter(['**', '!**/*.ts'], { dot: true })).pipe(gulp.dest(out));
const tsgoStream = watchInput.pipe(util.debounce(() => createTsgoStream(absolutePath), 200));
const watchStream = es.merge(nonts.pipe(gulp.dest(out)), watchNonTs, tsgoStream);
return watchInput
.pipe(util.incremental(pipeline, input))
.pipe(gulp.dest(out));
return watchStream;
}));
// Tasks

115
build/lib/tsgo.ts Normal file
View File

@@ -0,0 +1,115 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as cp from 'child_process';
import es from 'event-stream';
import * as path from 'path';
import { createReporter } from './reporter.ts';
const root = path.dirname(path.dirname(import.meta.dirname));
const tsgoPath = path.join(root, 'node_modules', '.bin', 'tsgo');
const ansiRegex = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
export function spawnTsgo(projectPath: string): Promise<void> {
const reporter = createReporter('extensions');
let report: NodeJS.ReadWriteStream | undefined;
const beginReport = (emitError: boolean) => {
if (report) {
report.end();
}
report = reporter.end(emitError);
};
const endReport = () => {
if (!report) {
return;
}
report.end();
report = undefined;
};
const args = [tsgoPath, '--project', projectPath, '--pretty', 'false'];
beginReport(false);
const child = cp.spawn(process.execPath, args, {
cwd: root,
stdio: ['ignore', 'pipe', 'pipe']
});
let buffer = '';
const handleLine = (line: string) => {
const trimmed = line.replace(ansiRegex, '').trim();
if (!trimmed) {
return;
}
if (/Starting compilation|File change detected/i.test(trimmed)) {
beginReport(false);
return;
}
if (/Compilation complete/i.test(trimmed)) {
endReport();
return;
}
const match = /(.*\(\d+,\d+\): )(.*: )(.*)/.exec(trimmed);
if (match) {
const fullpath = path.isAbsolute(match[1]) ? match[1] : path.join(root, match[1]);
const message = match[3];
reporter(fullpath + message);
} else {
reporter(trimmed);
}
};
const handleData = (data: Buffer) => {
buffer += data.toString('utf8');
const lines = buffer.split(/\r?\n/);
buffer = lines.pop() ?? '';
for (const line of lines) {
handleLine(line);
}
};
child.stdout?.on('data', handleData);
child.stderr?.on('data', handleData);
const done = new Promise<void>((resolve, reject) => {
child.on('exit', code => {
if (buffer.trim()) {
handleLine(buffer);
buffer = '';
}
endReport();
if (code === 0) {
resolve();
return;
}
reject(new Error(`tsgo exited with code ${code ?? 'unknown'}`));
});
child.on('error', err => {
endReport();
reject(err);
});
});
return done;
}
export function createTsgoStream(projectPath: string): NodeJS.ReadWriteStream {
const stream = es.through();
spawnTsgo(projectPath).then(() => {
stream.emit('end');
}).catch(() => {
// Errors are already reported by spawnTsgo via the reporter.
// Don't emit 'error' on the stream as that would exit the watch process.
stream.emit('end');
});
return stream;
}