Skip to content

Commit 99bfe5f

Browse files
authored
feat(typescript): create a better ts_project worker (#2416)
1 parent 0466084 commit 99bfe5f

File tree

9 files changed

+284
-146
lines changed

9 files changed

+284
-146
lines changed

packages/typescript/BUILD.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ pkg_npm(
9696
":npm_version_check",
9797
"//packages/typescript/internal:BUILD",
9898
"//packages/typescript/internal:ts_project_options_validator.js",
99-
"//packages/typescript/internal/worker",
99+
"//packages/typescript/internal/worker:filegroup",
100100
] + select({
101101
# FIXME: fix stardoc on Windows; //packages/typescript:index.md generation fails with:
102102
# ERROR: D:/b/62unjjin/external/npm_bazel_typescript/BUILD.bazel:36:1: Couldn't build file

packages/typescript/internal/ts_project.bzl

Lines changed: 14 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,7 @@ _DEFAULT_TSC = (
1414
"//typescript/bin:tsc"
1515
)
1616

17-
_DEFAULT_TSC_BIN = (
18-
# BEGIN-INTERNAL
19-
"@npm" +
20-
# END-INTERNAL
21-
"//:node_modules/typescript/bin/tsc"
22-
)
23-
24-
_DEFAULT_TYPESCRIPT_MODULE = (
17+
_DEFAULT_TYPESCRIPT_PACKAGE = (
2518
# BEGIN-INTERNAL
2619
"@npm" +
2720
# END-INTERNAL
@@ -49,7 +42,7 @@ _ATTRS = {
4942
# that compiler might allow more sources than tsc does.
5043
"srcs": attr.label_list(allow_files = True, mandatory = True),
5144
"supports_workers": attr.bool(default = False),
52-
"tsc": attr.label(default = Label(_DEFAULT_TSC), executable = True, cfg = "target"),
45+
"tsc": attr.label(default = Label(_DEFAULT_TSC), executable = True, cfg = "host"),
5346
"tsconfig": attr.label(mandatory = True, allow_single_file = [".json"]),
5447
}
5548

@@ -209,6 +202,7 @@ def _ts_project_impl(ctx):
209202
inputs = inputs,
210203
arguments = [arguments],
211204
outputs = outputs,
205+
mnemonic = "TsProject",
212206
executable = "tsc",
213207
execution_requirements = execution_requirements,
214208
progress_message = "%s %s [tsc -p %s]" % (
@@ -337,8 +331,8 @@ def ts_project_macro(
337331
emit_declaration_only = False,
338332
ts_build_info_file = None,
339333
tsc = None,
340-
worker_tsc_bin = _DEFAULT_TSC_BIN,
341-
worker_typescript_module = _DEFAULT_TYPESCRIPT_MODULE,
334+
typescript_package = _DEFAULT_TYPESCRIPT_PACKAGE,
335+
typescript_require_path = "typescript",
342336
validate = True,
343337
supports_workers = False,
344338
declaration_dir = None,
@@ -506,14 +500,13 @@ def ts_project_macro(
506500
For example, `tsc = "@my_deps//typescript/bin:tsc"`
507501
Or you can pass a custom compiler binary instead.
508502
509-
worker_tsc_bin: Label of the TypeScript compiler binary to run when running in worker mode.
503+
typescript_package: Label of the package containing all data deps of tsc.
510504
511-
For example, `tsc = "@my_deps//node_modules/typescript/bin/tsc"`
512-
Or you can pass a custom compiler binary instead.
505+
For example, `typescript_package = "@my_deps//typescript"`
513506
514-
worker_typescript_module: Label of the package containing all data deps of worker_tsc_bin.
507+
typescript_require_path: Module name which resolves to typescript_package when required
515508
516-
For example, `tsc = "@my_deps//typescript"`
509+
For example, `typescript_require_path = "typescript"`
517510
518511
validate: boolean; whether to check that the tsconfig settings match the attributes.
519512
@@ -642,25 +635,18 @@ def ts_project_macro(
642635
# but that's our own code, so we don't.
643636
"@npm//protobufjs",
644637
# END-INTERNAL
645-
Label("//packages/typescript/internal/worker:worker"),
646-
Label(worker_tsc_bin),
647-
Label(worker_typescript_module),
638+
Label(typescript_package),
639+
Label("//packages/typescript/internal/worker:filegroup"),
648640
tsconfig,
649641
],
650642
entry_point = Label("//packages/typescript/internal/worker:worker_adapter"),
651643
templated_args = [
652-
"$(execpath {})".format(Label(worker_tsc_bin)),
653-
"--project",
654-
"$(execpath {})".format(tsconfig),
655-
# FIXME: should take out_dir into account
656-
"--outDir",
657-
"$(RULEDIR)",
658-
# FIXME: what about other settings like declaration_dir, root_dir, etc
644+
"--typescript_require_path",
645+
typescript_require_path,
659646
],
660647
)
661648

662649
tsc = ":" + tsc_worker
663-
664650
typings_out_dir = declaration_dir if declaration_dir else out_dir
665651
tsbuildinfo_path = ts_build_info_file if ts_build_info_file else name + ".tsbuildinfo"
666652
js_outs = []
@@ -701,9 +687,6 @@ Check the srcs attribute to see that some .ts files are present (or .js files wi
701687
buildinfo_out = tsbuildinfo_path if composite or incremental else None,
702688
tsc = tsc,
703689
link_workspace_root = link_workspace_root,
704-
supports_workers = select({
705-
"@bazel_tools//src/conditions:host_windows": False,
706-
"//conditions:default": supports_workers,
707-
}),
690+
supports_workers = supports_workers,
708691
**kwargs
709692
)

packages/typescript/internal/worker/BUILD.bazel

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
11
# BEGIN-INTERNAL
2-
3-
load("//internal/common:copy_to_bin.bzl", "copy_to_bin")
2+
load("//packages/typescript:checked_in_ts_project.bzl", "checked_in_ts_project")
43
load("//third_party/github.com/bazelbuild/bazel-skylib:rules/copy_file.bzl", "copy_file")
54

5+
# To update index.js run:
6+
# bazel run //packages/typescript/internal/worker:worker_adapter_check_compiled.update
7+
8+
checked_in_ts_project(
9+
name = "worker_adapter",
10+
src = "worker_adapter.ts",
11+
checked_in_js = "index.js",
12+
visibility = ["//visibility:public"],
13+
deps = ["@npm//@types/node"],
14+
)
15+
616
# Copy the proto file to a matching third_party/... nested directory
717
# so the runtime require() statements still work
818
_worker_proto_dir = "third_party/github.com/bazelbuild/bazel/src/main/protobuf"
@@ -22,14 +32,6 @@ copy_file(
2232
visibility = ["//visibility:public"],
2333
)
2434

25-
copy_to_bin(
26-
name = "worker_adapter",
27-
srcs = [
28-
"worker_adapter.js",
29-
],
30-
visibility = ["//visibility:public"],
31-
)
32-
3335
filegroup(
3436
name = "package_contents",
3537
srcs = [
@@ -41,12 +43,13 @@ filegroup(
4143
# END-INTERNAL
4244

4345
exports_files([
44-
"worker_adapter.js",
46+
"index.js",
4547
])
4648

4749
filegroup(
48-
name = "worker",
50+
name = "filegroup",
4951
srcs = [
52+
"index.js",
5053
"third_party/github.com/bazelbuild/bazel/src/main/protobuf/worker_protocol.proto",
5154
"worker.js",
5255
"worker_adapter.js",
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/* THIS FILE GENERATED FROM .ts; see BUILD.bazel */ /* clang-format off */"use strict";
2+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4+
return new (P || (P = Promise))(function (resolve, reject) {
5+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8+
step((generator = generator.apply(thisArg, _arguments || [])).next());
9+
});
10+
};
11+
Object.defineProperty(exports, "__esModule", { value: true });
12+
const fs = require("fs");
13+
const ts = require("typescript");
14+
const MNEMONIC = 'TsProject';
15+
const worker = require('./worker');
16+
let createWatchCompilerHost;
17+
const formatHost = {
18+
getCanonicalFileName: (path) => path,
19+
getCurrentDirectory: ts.sys.getCurrentDirectory,
20+
getNewLine: () => ts.sys.newLine,
21+
};
22+
const reportDiagnostic = (diagnostic) => {
23+
worker.log(ts.formatDiagnostic(diagnostic, formatHost));
24+
};
25+
const reportWatchStatusChanged = (diagnostic) => {
26+
worker.debug(ts.formatDiagnostic(diagnostic, formatHost));
27+
};
28+
function createWatchProgram(options, tsconfigPath, setTimeout) {
29+
const host = createWatchCompilerHost(tsconfigPath, options, Object.assign(Object.assign({}, ts.sys), { setTimeout }), ts.createEmitAndSemanticDiagnosticsBuilderProgram, reportDiagnostic, reportWatchStatusChanged);
30+
return ts.createWatchProgram(host);
31+
}
32+
let workerRequestTimestamp;
33+
let cachedWatchedProgram;
34+
let consolidateChangesCallback;
35+
let cachedWatchProgramArgs;
36+
function getWatchProgram(args) {
37+
const newWatchArgs = args.join(' ');
38+
if (cachedWatchedProgram && cachedWatchProgramArgs && cachedWatchProgramArgs !== newWatchArgs) {
39+
cachedWatchedProgram.close();
40+
cachedWatchedProgram = undefined;
41+
cachedWatchProgramArgs = undefined;
42+
}
43+
if (!cachedWatchedProgram) {
44+
const parsedArgs = ts.parseCommandLine(args);
45+
const tsconfigPath = args[args.indexOf('--project') + 1];
46+
cachedWatchProgramArgs = newWatchArgs;
47+
cachedWatchedProgram = createWatchProgram(parsedArgs.options, tsconfigPath, (callback) => {
48+
consolidateChangesCallback = callback;
49+
});
50+
}
51+
return cachedWatchedProgram;
52+
}
53+
function emitOnce(args) {
54+
var _a;
55+
return __awaiter(this, void 0, void 0, function* () {
56+
const watchProgram = getWatchProgram(args);
57+
if (consolidateChangesCallback) {
58+
consolidateChangesCallback();
59+
}
60+
workerRequestTimestamp = Date.now();
61+
const result = yield ((_a = watchProgram) === null || _a === void 0 ? void 0 : _a.getProgram().emit(undefined, undefined, {
62+
isCancellationRequested: function (timestamp) {
63+
return timestamp !== workerRequestTimestamp;
64+
}.bind(null, workerRequestTimestamp),
65+
throwIfCancellationRequested: function (timestamp) {
66+
if (timestamp !== workerRequestTimestamp) {
67+
throw new ts.OperationCanceledException();
68+
}
69+
}.bind(null, workerRequestTimestamp),
70+
}));
71+
return Boolean(result && result.diagnostics.length === 0);
72+
});
73+
}
74+
function main() {
75+
const typescriptRequirePath = process.argv[process.argv.indexOf('--typescript_require_path') + 1];
76+
try {
77+
const customTypescriptModule = require(typescriptRequirePath);
78+
createWatchCompilerHost = customTypescriptModule.createWatchCompilerHost;
79+
}
80+
catch (e) {
81+
worker.log(`typescript_require_path '${typescriptRequirePath}' could not be resolved`);
82+
throw e;
83+
}
84+
if (process.argv.includes('--persistent_worker')) {
85+
worker.log(`Running ${MNEMONIC} as a Bazel worker`);
86+
worker.runWorkerLoop(emitOnce);
87+
}
88+
else {
89+
worker.log(`Running ${MNEMONIC} as a standalone process`);
90+
worker.log(`Started a new process to perform this action. Your build might be misconfigured, try
91+
--strategy=${MNEMONIC}=worker`);
92+
let argsFilePath = process.argv.pop();
93+
if (argsFilePath.startsWith('@')) {
94+
argsFilePath = argsFilePath.slice(1);
95+
}
96+
const args = fs.readFileSync(argsFilePath).toString().split('\n');
97+
emitOnce(args).finally(() => { var _a; return (_a = cachedWatchedProgram) === null || _a === void 0 ? void 0 : _a.close(); });
98+
}
99+
}
100+
if (require.main === module) {
101+
main();
102+
}

packages/typescript/internal/worker/worker_adapter.js

Lines changed: 0 additions & 99 deletions
This file was deleted.

0 commit comments

Comments
 (0)