ttsc 0.28.6 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/launcher/internal/commonJsExportMetadata.d.ts +7 -0
- package/lib/launcher/internal/commonJsExportMetadata.js +89 -0
- package/lib/launcher/internal/commonJsExportMetadata.js.map +1 -0
- package/lib/launcher/internal/prepareExecution.js +3 -2
- package/lib/launcher/internal/prepareExecution.js.map +1 -1
- package/lib/launcher/internal/runtimeCompilerArgs.d.ts +9 -0
- package/lib/launcher/internal/runtimeCompilerArgs.js +70 -0
- package/lib/launcher/internal/runtimeCompilerArgs.js.map +1 -0
- package/lib/launcher/internal/runtimeHooks.js +112 -268
- package/lib/launcher/internal/runtimeHooks.js.map +1 -1
- package/package.json +12 -8
- package/shim/compiler/resolution.go +16 -0
- package/src/launcher/internal/commonJsExportMetadata.ts +91 -0
- package/src/launcher/internal/prepareExecution.ts +11 -2
- package/src/launcher/internal/runtimeCompilerArgs.ts +77 -0
- package/src/launcher/internal/runtimeHooks.ts +128 -292
|
@@ -31,7 +31,25 @@ const runBuild_1 = require("../../compiler/internal/runBuild");
|
|
|
31
31
|
const spawnNative_1 = require("../../compiler/internal/spawnNative");
|
|
32
32
|
const createCanonicalTempDirectory_1 = require("../../internal/createCanonicalTempDirectory");
|
|
33
33
|
const projectInputPathIdentity_1 = require("../../internal/projectInputPathIdentity");
|
|
34
|
+
const commonJsExportMetadata_1 = require("./commonJsExportMetadata");
|
|
35
|
+
const runtimeCompilerArgs_1 = require("./runtimeCompilerArgs");
|
|
34
36
|
const servedSourceMap_1 = require("./servedSourceMap");
|
|
37
|
+
/**
|
|
38
|
+
* One emit policy for orphan execution and CommonJS export discovery. The
|
|
39
|
+
* compiler ignores the consumer's config, lowers proposal syntax, and checks no
|
|
40
|
+
* types because the entry build owns diagnostics. Isolation prevents imported
|
|
41
|
+
* const-enum inlining and secondary emits, and keeps const enums as runtime
|
|
42
|
+
* exports that the name scanner must also observe.
|
|
43
|
+
*/
|
|
44
|
+
const ISOLATED_EMIT_ARGS = [
|
|
45
|
+
"--ignoreConfig",
|
|
46
|
+
"--target",
|
|
47
|
+
"es2022",
|
|
48
|
+
"--noCheck",
|
|
49
|
+
"--skipLibCheck",
|
|
50
|
+
"--noResolve",
|
|
51
|
+
"--isolatedModules",
|
|
52
|
+
];
|
|
35
53
|
/**
|
|
36
54
|
* Synchronous Node module hooks installed (via `module.registerHooks`) in the
|
|
37
55
|
* child process `ttsx` spawns to run a TypeScript entry _from source_.
|
|
@@ -50,11 +68,9 @@ const servedSourceMap_1 = require("./servedSourceMap");
|
|
|
50
68
|
* type-stripping cannot do cross-file type-only elision — e.g. a
|
|
51
69
|
* value-shaped import of a type+namespace merge survives stripping and
|
|
52
70
|
* dangles at runtime.
|
|
53
|
-
* 3. No owning tsconfig
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* `export` syntax becomes `module.exports`; any other (ESM) file keeps the
|
|
57
|
-
* fast in-process `mode: "transform"` type-strip.
|
|
71
|
+
* 3. No owning tsconfig: use an isolated tsgo emit in the runtime module format,
|
|
72
|
+
* lowering standard decorators and CommonJS imports/exports. Type stripping
|
|
73
|
+
* remains recovery when no compiler emit is available.
|
|
58
74
|
*
|
|
59
75
|
* The hooks are synchronous and run on the main thread (not a loader worker):
|
|
60
76
|
* that is what lets a CommonJS `require("./x")` chain reach them and what makes
|
|
@@ -793,7 +809,7 @@ function owningModuleOptions(filename) {
|
|
|
793
809
|
// The owning project cannot be read, so nothing is known about the format
|
|
794
810
|
// it would have emitted. That is the same state as having no project at
|
|
795
811
|
// all, and it is what the dependency lane will conclude too when its build
|
|
796
|
-
// fails and the file falls through to the orphan
|
|
812
|
+
// fails and the file falls through to the isolated orphan emit.
|
|
797
813
|
moduleOptionsCache.set(tsconfig, null);
|
|
798
814
|
return null;
|
|
799
815
|
}
|
|
@@ -916,9 +932,8 @@ function projectModuleOptions(compilerOptions) {
|
|
|
916
932
|
/**
|
|
917
933
|
* Resolve the JavaScript to run for a TypeScript source file, in priority
|
|
918
934
|
* order: the entry project's pre-built emit (transform plugins applied), a
|
|
919
|
-
* built raw `.ts` dependency, or
|
|
920
|
-
*
|
|
921
|
-
* `require` handler.
|
|
935
|
+
* built raw `.ts` dependency, or an isolated emit when no tsconfig owns it.
|
|
936
|
+
* Shared by the ESM `load` hook and the CommonJS `require` handler.
|
|
922
937
|
*/
|
|
923
938
|
function resolveServedSource(filename, url = (0, node_url_1.pathToFileURL)(filename).href, prepareAsEntry = false) {
|
|
924
939
|
const real = realPath(filename);
|
|
@@ -960,7 +975,8 @@ function resolveServedSource(filename, url = (0, node_url_1.pathToFileURL)(filen
|
|
|
960
975
|
* map's `sources`, so the JavaScript executed under the `.ts` source URL stays
|
|
961
976
|
* self-describing after the per-run emit directory is deleted. Applied to both
|
|
962
977
|
* the entry lane (`serveEntryEmit`) and the dependency lane
|
|
963
|
-
* (`serveBuiltDependency`); the orphan
|
|
978
|
+
* (`serveBuiltDependency`); the orphan lane inlines its own map before
|
|
979
|
+
* caching.
|
|
964
980
|
*/
|
|
965
981
|
function withInlineSourceMap(served) {
|
|
966
982
|
const source = (0, servedSourceMap_1.inlineServedSourceMap)(served.source, served.emittedFile, served.sourceFile);
|
|
@@ -971,23 +987,16 @@ function withInlineSourceMap(served) {
|
|
|
971
987
|
* vendored package that ships raw `.ts`/`.cts`/`.mts` straight under
|
|
972
988
|
* `node_modules`), choosing the lowering by the format the file resolves to.
|
|
973
989
|
*
|
|
974
|
-
*
|
|
975
|
-
* rewrites
|
|
976
|
-
* file
|
|
977
|
-
*
|
|
978
|
-
* with module syntax (`export const`, `export namespace`, `export function`).
|
|
979
|
-
* Stripping leaves the `export` in place and Node's CommonJS loader dies with
|
|
980
|
-
* `SyntaxError: Unexpected token 'export'`. So a CommonJS-format orphan is
|
|
981
|
-
* lowered through a real tsgo `--module commonjs` single-file emit (which also
|
|
982
|
-
* handles `export =`), exactly the format decision tsgo would have made for an
|
|
983
|
-
* owning project; an ESM-format orphan keeps the fast in-process strip.
|
|
990
|
+
* Both module formats need the compiler: Node's type stripping neither lowers
|
|
991
|
+
* standard decorators nor rewrites ESM exports for CommonJS. Use the existing
|
|
992
|
+
* single-file emit with the file's own module format and a standard target.
|
|
993
|
+
* Retain stripping as recovery when no compiler emit is available.
|
|
984
994
|
*/
|
|
985
995
|
function transformOrphanSource(filename, url) {
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
}
|
|
996
|
+
const format = moduleFormat(filename, null) === "commonjs" ? "commonjs" : "module";
|
|
997
|
+
const lowered = emitOrphanSource(filename, format);
|
|
998
|
+
if (lowered !== null) {
|
|
999
|
+
return lowered;
|
|
991
1000
|
}
|
|
992
1001
|
return (0, node_module_1.stripTypeScriptTypes)(node_fs_1.default.readFileSync(filename, "utf8"), {
|
|
993
1002
|
mode: "transform",
|
|
@@ -995,13 +1004,12 @@ function transformOrphanSource(filename, url) {
|
|
|
995
1004
|
});
|
|
996
1005
|
}
|
|
997
1006
|
/**
|
|
998
|
-
* Lower a single
|
|
999
|
-
*
|
|
1000
|
-
*
|
|
1001
|
-
*
|
|
1002
|
-
* output, so the caller can fall back to the in-process strip.
|
|
1007
|
+
* Lower a single source file to its runtime module format. Emit-only, no
|
|
1008
|
+
* diagnostic gate (the entry project's up-front check is the type gate),
|
|
1009
|
+
* matching `buildDependency`. Returns `null` when tsgo is unavailable or
|
|
1010
|
+
* produced no output, so the caller can fall back to the in-process strip.
|
|
1003
1011
|
*/
|
|
1004
|
-
function
|
|
1012
|
+
function emitOrphanSource(filename, format) {
|
|
1005
1013
|
let tsgo;
|
|
1006
1014
|
try {
|
|
1007
1015
|
tsgo = (0, resolveTsgo_1.resolveTsgo)({ cwd: node_path_1.default.dirname(filename) }).binary;
|
|
@@ -1009,12 +1017,12 @@ function emitOrphanAsCommonJs(filename) {
|
|
|
1009
1017
|
catch {
|
|
1010
1018
|
return null;
|
|
1011
1019
|
}
|
|
1012
|
-
// Content-hash cache:
|
|
1020
|
+
// Content-hash cache: an orphan's tsgo single-file emit is lowered
|
|
1013
1021
|
// once and reused by every other process in the run, and across runs. Without
|
|
1014
1022
|
// it a program that fans out into many processes (the automated test corpus
|
|
1015
1023
|
// imports the same vendored `.ts` deps from thousands of generated files) would
|
|
1016
1024
|
// re-spawn tsgo per file per process and crawl.
|
|
1017
|
-
const cacheFile = orphanCacheFile(filename, tsgo);
|
|
1025
|
+
const cacheFile = orphanCacheFile(filename, tsgo, format);
|
|
1018
1026
|
if (cacheFile !== null) {
|
|
1019
1027
|
const hit = readFileOrNull(cacheFile);
|
|
1020
1028
|
if (hit !== null) {
|
|
@@ -1025,29 +1033,20 @@ function emitOrphanAsCommonJs(filename) {
|
|
|
1025
1033
|
try {
|
|
1026
1034
|
const res = (0, spawnNative_1.spawnNative)(tsgo, [
|
|
1027
1035
|
filename,
|
|
1028
|
-
// The file is named on the command line, so any tsconfig tsgo would
|
|
1029
|
-
// discover by walking up (the consumer's own) must be ignored — both
|
|
1030
|
-
// because it is not this file's project and because tsgo errors out
|
|
1031
|
-
// ("tsconfig.json is present but will not be loaded") otherwise.
|
|
1032
|
-
"--ignoreConfig",
|
|
1033
1036
|
"--module",
|
|
1034
|
-
"commonjs",
|
|
1035
|
-
|
|
1036
|
-
"
|
|
1037
|
-
|
|
1038
|
-
// the type gate, so the single-file pass does not need to type-check.
|
|
1039
|
-
// Skipping the check (and the lib check it implies) cuts the per-file
|
|
1040
|
-
// cost several-fold, which matters when a program generates and imports
|
|
1041
|
-
// thousands of raw `.ts` files at runtime (a fanned-out test corpus) and
|
|
1042
|
-
// each one would otherwise pay a full single-file check.
|
|
1043
|
-
"--noCheck",
|
|
1044
|
-
"--skipLibCheck",
|
|
1037
|
+
format === "commonjs" ? "commonjs" : "esnext",
|
|
1038
|
+
...ISOLATED_EMIT_ARGS,
|
|
1039
|
+
"--sourceMap",
|
|
1040
|
+
"--inlineSources",
|
|
1045
1041
|
"--outDir",
|
|
1046
1042
|
outDir,
|
|
1047
1043
|
"--listEmittedFiles",
|
|
1048
1044
|
], { cwd: node_path_1.default.dirname(filename), encoding: "utf8" });
|
|
1049
|
-
const emitted =
|
|
1050
|
-
const
|
|
1045
|
+
const emitted = pickEmittedJavaScript(filename, parseEmittedFiles((0, spawnNative_1.outputText)(res.stdout)));
|
|
1046
|
+
const source = emitted === null ? null : readFileOrNull(emitted);
|
|
1047
|
+
const lowered = source === null
|
|
1048
|
+
? null
|
|
1049
|
+
: (0, servedSourceMap_1.inlineServedSourceMap)(source, emitted, filename);
|
|
1051
1050
|
if (lowered !== null && cacheFile !== null) {
|
|
1052
1051
|
writeOrphanCache(cacheFile, lowered);
|
|
1053
1052
|
}
|
|
@@ -1061,7 +1060,10 @@ function emitOrphanAsCommonJs(filename) {
|
|
|
1061
1060
|
}
|
|
1062
1061
|
}
|
|
1063
1062
|
/**
|
|
1064
|
-
*
|
|
1063
|
+
* Read actual owned output for CommonJS export-name discovery, falling back to
|
|
1064
|
+
* isolated emission only when no project emitted this source. Project
|
|
1065
|
+
* transforms and const-enum settings decide which names exist at runtime. An
|
|
1066
|
+
* ESM emit is lowered as JavaScript solely to scan its CommonJS names.
|
|
1065
1067
|
*
|
|
1066
1068
|
* This intentionally does not read or write the runtime orphan cache. Name
|
|
1067
1069
|
* discovery may inspect a source dependency without executing it, so sharing
|
|
@@ -1074,6 +1076,12 @@ function emitCommonJsForNameScan(filename) {
|
|
|
1074
1076
|
if (cached !== undefined) {
|
|
1075
1077
|
return cached;
|
|
1076
1078
|
}
|
|
1079
|
+
const served = serveEntryEmit(real) ?? serveDependencyEmit(real);
|
|
1080
|
+
if (served !== null &&
|
|
1081
|
+
moduleFormat(real, served.moduleOptions) === "commonjs") {
|
|
1082
|
+
commonJsNameScanSources.set(real, served.source);
|
|
1083
|
+
return served.source;
|
|
1084
|
+
}
|
|
1077
1085
|
let tsgo;
|
|
1078
1086
|
try {
|
|
1079
1087
|
tsgo = (0, resolveTsgo_1.resolveTsgo)({ cwd: node_path_1.default.dirname(real) }).binary;
|
|
@@ -1084,20 +1092,21 @@ function emitCommonJsForNameScan(filename) {
|
|
|
1084
1092
|
}
|
|
1085
1093
|
const outDir = (0, createCanonicalTempDirectory_1.createCanonicalTempDirectory)("ttsx-export-scan-");
|
|
1086
1094
|
try {
|
|
1095
|
+
// Compile the owned JavaScript, not the original TypeScript whose project
|
|
1096
|
+
// may already have erased or transformed declarations. No source executes.
|
|
1097
|
+
const input = served === null ? real : node_path_1.default.join(outDir, "source.cts");
|
|
1098
|
+
if (served !== null)
|
|
1099
|
+
node_fs_1.default.writeFileSync(input, served.source);
|
|
1087
1100
|
const res = (0, spawnNative_1.spawnNative)(tsgo, [
|
|
1088
|
-
|
|
1089
|
-
"--ignoreConfig",
|
|
1101
|
+
input,
|
|
1090
1102
|
"--module",
|
|
1091
1103
|
"commonjs",
|
|
1092
|
-
|
|
1093
|
-
"es2022",
|
|
1094
|
-
"--noCheck",
|
|
1095
|
-
"--skipLibCheck",
|
|
1104
|
+
...ISOLATED_EMIT_ARGS,
|
|
1096
1105
|
"--outDir",
|
|
1097
1106
|
outDir,
|
|
1098
1107
|
"--listEmittedFiles",
|
|
1099
1108
|
], { cwd: node_path_1.default.dirname(real), encoding: "utf8" });
|
|
1100
|
-
const emitted = pickEmittedJavaScript(
|
|
1109
|
+
const emitted = pickEmittedJavaScript(input, parseEmittedFiles((0, spawnNative_1.outputText)(res.stdout)));
|
|
1101
1110
|
const lowered = emitted === null ? null : readFileOrNull(emitted);
|
|
1102
1111
|
commonJsNameScanSources.set(real, lowered);
|
|
1103
1112
|
return lowered;
|
|
@@ -1118,14 +1127,14 @@ function orphanCacheRoot() {
|
|
|
1118
1127
|
const base = process.env.TTSC_CACHE_DIR && process.env.TTSC_CACHE_DIR.length !== 0
|
|
1119
1128
|
? process.env.TTSC_CACHE_DIR
|
|
1120
1129
|
: node_path_1.default.join(node_os_1.default.tmpdir(), "ttsc-orphan");
|
|
1121
|
-
return node_path_1.default.join(base, "ttsx-orphan
|
|
1130
|
+
return node_path_1.default.join(base, "ttsx-orphan");
|
|
1122
1131
|
}
|
|
1123
1132
|
/**
|
|
1124
|
-
* Content-addressed cache path for
|
|
1125
|
-
*
|
|
1126
|
-
*
|
|
1133
|
+
* Content-addressed cache path for isolated orphan lowering, including the
|
|
1134
|
+
* source path because the inlined map identifies that path. `null` when the
|
|
1135
|
+
* source cannot be read.
|
|
1127
1136
|
*/
|
|
1128
|
-
function orphanCacheFile(filename, tsgo) {
|
|
1137
|
+
function orphanCacheFile(filename, tsgo, format) {
|
|
1129
1138
|
let source;
|
|
1130
1139
|
try {
|
|
1131
1140
|
source = node_fs_1.default.readFileSync(filename);
|
|
@@ -1136,6 +1145,9 @@ function orphanCacheFile(filename, tsgo) {
|
|
|
1136
1145
|
const key = node_crypto_1.default
|
|
1137
1146
|
.createHash("sha256")
|
|
1138
1147
|
.update(tsgo)
|
|
1148
|
+
.update("\0isolated-source-map-v1\0")
|
|
1149
|
+
.update(filename)
|
|
1150
|
+
.update("\0" + format)
|
|
1139
1151
|
.update("\0")
|
|
1140
1152
|
.update(source)
|
|
1141
1153
|
.digest("hex")
|
|
@@ -1158,16 +1170,6 @@ function writeOrphanCache(cacheFile, lowered) {
|
|
|
1158
1170
|
// ignore — caching is an optimization, correctness does not depend on it
|
|
1159
1171
|
}
|
|
1160
1172
|
}
|
|
1161
|
-
/** First `TSFILE:` path tsgo printed under `--listEmittedFiles`, or `null`. */
|
|
1162
|
-
function parseFirstEmittedFile(stdout) {
|
|
1163
|
-
for (const line of stdout.split(/\r?\n/)) {
|
|
1164
|
-
const match = line.match(/^TSFILE:\s*(.+)$/);
|
|
1165
|
-
if (match?.[1]) {
|
|
1166
|
-
return match[1].trim();
|
|
1167
|
-
}
|
|
1168
|
-
}
|
|
1169
|
-
return null;
|
|
1170
|
-
}
|
|
1171
1173
|
/** `TSFILE:` paths tsgo printed under `--listEmittedFiles`. */
|
|
1172
1174
|
function parseEmittedFiles(stdout) {
|
|
1173
1175
|
const files = [];
|
|
@@ -1202,35 +1204,30 @@ function pickEmittedJavaScript(filename, emittedFiles) {
|
|
|
1202
1204
|
* Runtime CommonJS consumers see the getters that helper installs, but Node's
|
|
1203
1205
|
* ESM linker only exposes named imports it can statically identify from
|
|
1204
1206
|
* `exports.name = ...` assignments. For relative star re-exports whose emitted
|
|
1205
|
-
* target is available,
|
|
1206
|
-
*
|
|
1207
|
+
* target is available, advertise its names through inert assignments that
|
|
1208
|
+
* Node's static lexer recognizes. The original helper still owns every runtime
|
|
1209
|
+
* binding, including values static discovery cannot enumerate.
|
|
1207
1210
|
*/
|
|
1208
1211
|
function exposeCommonJsStarExports(source, emittedFile, sourceFile) {
|
|
1209
|
-
|
|
1210
|
-
|
|
1212
|
+
const parsed = (0, commonJsExportMetadata_1.parseCommonJsExports)(source);
|
|
1213
|
+
const reserved = new Set(parsed.exports);
|
|
1214
|
+
const names = new Set();
|
|
1215
|
+
for (const specifier of parsed.reexports) {
|
|
1216
|
+
for (const name of collectStarExportNames(emittedFile, sourceFile, specifier)) {
|
|
1217
|
+
if (name !== "default" && name !== "__esModule" && !reserved.has(name)) {
|
|
1218
|
+
names.add(name);
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1211
1221
|
}
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
if (names.length === 0) {
|
|
1222
|
-
return statement;
|
|
1223
|
-
}
|
|
1224
|
-
for (const name of names) {
|
|
1225
|
-
reserved.add(name);
|
|
1226
|
-
}
|
|
1227
|
-
const receiver = `__ttsx_export_star_${index++}`;
|
|
1228
|
-
return [
|
|
1229
|
-
...names.map((name) => `${indent}exports.${name} = void 0;`),
|
|
1230
|
-
`${indent}var ${receiver} = require(${JSON.stringify(specifier)});`,
|
|
1231
|
-
...names.map((name) => `${indent}__createBinding(exports, ${receiver}, ${JSON.stringify(name)});`),
|
|
1232
|
-
].join("\n");
|
|
1233
|
-
});
|
|
1222
|
+
if (names.size === 0)
|
|
1223
|
+
return source;
|
|
1224
|
+
// Node's lexer reads these assignments statically. Nothing executes, and no
|
|
1225
|
+
// existing statement, source position, control-flow body or runtime export
|
|
1226
|
+
// is changed. Keep all specifier/literal parsing in the same lexer Node uses.
|
|
1227
|
+
const hints = [...names]
|
|
1228
|
+
.map((name) => `exports[${JSON.stringify(name)}] = void 0;`)
|
|
1229
|
+
.join(" ");
|
|
1230
|
+
return source + `\nif (false) { ${hints} }\n`;
|
|
1234
1231
|
}
|
|
1235
1232
|
function collectStarExportNames(emittedFile, sourceFile, specifier) {
|
|
1236
1233
|
if (emittedFile !== undefined) {
|
|
@@ -1257,8 +1254,9 @@ function collectCommonJsExportNames(emittedFile, seen) {
|
|
|
1257
1254
|
if (source === null) {
|
|
1258
1255
|
return new Set();
|
|
1259
1256
|
}
|
|
1260
|
-
const
|
|
1261
|
-
|
|
1257
|
+
const parsed = (0, commonJsExportMetadata_1.parseCommonJsExports)(source);
|
|
1258
|
+
const names = new Set(parsed.exports);
|
|
1259
|
+
for (const specifier of parsed.reexports) {
|
|
1262
1260
|
const target = resolveEmittedRequire(real, specifier);
|
|
1263
1261
|
if (target === null) {
|
|
1264
1262
|
continue;
|
|
@@ -1271,153 +1269,6 @@ function collectCommonJsExportNames(emittedFile, seen) {
|
|
|
1271
1269
|
}
|
|
1272
1270
|
return names;
|
|
1273
1271
|
}
|
|
1274
|
-
function collectStaticCommonJsExportNames(source) {
|
|
1275
|
-
// Scan executable syntax only. Text that merely resembles an assignment —
|
|
1276
|
-
// `exports.x =` inside a comment, string, or template-literal text — must not
|
|
1277
|
-
// become an ESM-visible export name, or a named import of it would link to
|
|
1278
|
-
// `undefined` for a property the CommonJS module never defines. Masking the
|
|
1279
|
-
// inert lexical spans before matching keeps genuine top-level assignments
|
|
1280
|
-
// (and executable `${ ... }` template substitutions) while dropping the
|
|
1281
|
-
// decoys.
|
|
1282
|
-
const scannable = maskCommentsAndStrings(source);
|
|
1283
|
-
const names = new Set();
|
|
1284
|
-
const pattern = /(?:^|[^\w$])(?:exports|module\.exports)\.([A-Za-z_$][\w$]*)\s*=/g;
|
|
1285
|
-
let match;
|
|
1286
|
-
while ((match = pattern.exec(scannable)) !== null) {
|
|
1287
|
-
names.add(match[1]);
|
|
1288
|
-
}
|
|
1289
|
-
return names;
|
|
1290
|
-
}
|
|
1291
|
-
/**
|
|
1292
|
-
* Blank out the interior of line comments, block comments, string literals, and
|
|
1293
|
-
* template-literal text in emitted JavaScript, replacing each masked character
|
|
1294
|
-
* with a space while preserving newlines, code, and template `${ ... }`
|
|
1295
|
-
* substitutions verbatim. Positions and length are preserved so an offset in
|
|
1296
|
-
* the masked text maps back to the same offset in the source.
|
|
1297
|
-
*
|
|
1298
|
-
* The input is tsgo's CommonJS emit (well-formed JavaScript), so a character
|
|
1299
|
-
* scanner that tracks the standard comment/string/template states is sufficient
|
|
1300
|
-
* to separate executable tokens from inert text. Regular-expression literals
|
|
1301
|
-
* are intentionally not masked: distinguishing `/`-division from a regex
|
|
1302
|
-
* literal needs full tokenization, and tsgo's CommonJS emit never wraps an
|
|
1303
|
-
* `exports.<name> =` assignment inside a regex literal.
|
|
1304
|
-
*/
|
|
1305
|
-
function maskCommentsAndStrings(source) {
|
|
1306
|
-
const out = source.split("");
|
|
1307
|
-
const n = out.length;
|
|
1308
|
-
const blank = (index) => {
|
|
1309
|
-
const ch = out[index];
|
|
1310
|
-
if (ch !== "\n" && ch !== "\r") {
|
|
1311
|
-
out[index] = " ";
|
|
1312
|
-
}
|
|
1313
|
-
};
|
|
1314
|
-
const stack = [{ kind: "code", braceDepth: 0 }];
|
|
1315
|
-
let i = 0;
|
|
1316
|
-
while (i < n) {
|
|
1317
|
-
const top = stack[stack.length - 1];
|
|
1318
|
-
const ch = out[i];
|
|
1319
|
-
if (top.kind === "template") {
|
|
1320
|
-
if (ch === "\\") {
|
|
1321
|
-
blank(i);
|
|
1322
|
-
blank(i + 1);
|
|
1323
|
-
i += 2;
|
|
1324
|
-
continue;
|
|
1325
|
-
}
|
|
1326
|
-
if (ch === "`") {
|
|
1327
|
-
blank(i);
|
|
1328
|
-
stack.pop();
|
|
1329
|
-
i += 1;
|
|
1330
|
-
continue;
|
|
1331
|
-
}
|
|
1332
|
-
if (ch === "$" && out[i + 1] === "{") {
|
|
1333
|
-
// Enter a code substitution: `${` and its contents stay executable.
|
|
1334
|
-
stack.push({ kind: "code", braceDepth: 0 });
|
|
1335
|
-
i += 2;
|
|
1336
|
-
continue;
|
|
1337
|
-
}
|
|
1338
|
-
blank(i);
|
|
1339
|
-
i += 1;
|
|
1340
|
-
continue;
|
|
1341
|
-
}
|
|
1342
|
-
// Code context.
|
|
1343
|
-
if (ch === "/" && out[i + 1] === "/") {
|
|
1344
|
-
blank(i);
|
|
1345
|
-
blank(i + 1);
|
|
1346
|
-
i += 2;
|
|
1347
|
-
while (i < n && out[i] !== "\n") {
|
|
1348
|
-
blank(i);
|
|
1349
|
-
i += 1;
|
|
1350
|
-
}
|
|
1351
|
-
continue;
|
|
1352
|
-
}
|
|
1353
|
-
if (ch === "/" && out[i + 1] === "*") {
|
|
1354
|
-
blank(i);
|
|
1355
|
-
blank(i + 1);
|
|
1356
|
-
i += 2;
|
|
1357
|
-
while (i < n && !(out[i] === "*" && out[i + 1] === "/")) {
|
|
1358
|
-
blank(i);
|
|
1359
|
-
i += 1;
|
|
1360
|
-
}
|
|
1361
|
-
if (i < n) {
|
|
1362
|
-
blank(i);
|
|
1363
|
-
blank(i + 1);
|
|
1364
|
-
i += 2;
|
|
1365
|
-
}
|
|
1366
|
-
continue;
|
|
1367
|
-
}
|
|
1368
|
-
if (ch === '"' || ch === "'") {
|
|
1369
|
-
blank(i);
|
|
1370
|
-
i += 1;
|
|
1371
|
-
while (i < n && out[i] !== ch) {
|
|
1372
|
-
if (out[i] === "\\") {
|
|
1373
|
-
blank(i);
|
|
1374
|
-
blank(i + 1);
|
|
1375
|
-
i += 2;
|
|
1376
|
-
continue;
|
|
1377
|
-
}
|
|
1378
|
-
// A bare newline ends an unterminated string; stop masking so the rest
|
|
1379
|
-
// of the line is still scanned as code (defensive — tsgo never emits
|
|
1380
|
-
// one).
|
|
1381
|
-
if (out[i] === "\n") {
|
|
1382
|
-
break;
|
|
1383
|
-
}
|
|
1384
|
-
blank(i);
|
|
1385
|
-
i += 1;
|
|
1386
|
-
}
|
|
1387
|
-
if (i < n && out[i] === ch) {
|
|
1388
|
-
blank(i);
|
|
1389
|
-
i += 1;
|
|
1390
|
-
}
|
|
1391
|
-
continue;
|
|
1392
|
-
}
|
|
1393
|
-
if (ch === "`") {
|
|
1394
|
-
blank(i);
|
|
1395
|
-
stack.push({ kind: "template", braceDepth: 0 });
|
|
1396
|
-
i += 1;
|
|
1397
|
-
continue;
|
|
1398
|
-
}
|
|
1399
|
-
if (ch === "{") {
|
|
1400
|
-
top.braceDepth += 1;
|
|
1401
|
-
i += 1;
|
|
1402
|
-
continue;
|
|
1403
|
-
}
|
|
1404
|
-
if (ch === "}") {
|
|
1405
|
-
if (top.braceDepth === 0 && stack.length > 1) {
|
|
1406
|
-
// Close the enclosing template `${ ... }` and resume template text.
|
|
1407
|
-
stack.pop();
|
|
1408
|
-
i += 1;
|
|
1409
|
-
continue;
|
|
1410
|
-
}
|
|
1411
|
-
if (top.braceDepth > 0) {
|
|
1412
|
-
top.braceDepth -= 1;
|
|
1413
|
-
}
|
|
1414
|
-
i += 1;
|
|
1415
|
-
continue;
|
|
1416
|
-
}
|
|
1417
|
-
i += 1;
|
|
1418
|
-
}
|
|
1419
|
-
return out.join("");
|
|
1420
|
-
}
|
|
1421
1272
|
function collectSourceCommonJsExportNames(sourceFile, seen) {
|
|
1422
1273
|
const real = realPath(sourceFile);
|
|
1423
1274
|
if (seen.has(real)) {
|
|
@@ -1428,8 +1279,9 @@ function collectSourceCommonJsExportNames(sourceFile, seen) {
|
|
|
1428
1279
|
if (source === null) {
|
|
1429
1280
|
return new Set();
|
|
1430
1281
|
}
|
|
1431
|
-
const
|
|
1432
|
-
|
|
1282
|
+
const parsed = (0, commonJsExportMetadata_1.parseCommonJsExports)(source);
|
|
1283
|
+
const names = new Set(parsed.exports);
|
|
1284
|
+
for (const specifier of parsed.reexports) {
|
|
1433
1285
|
const target = resolveSourceSpecifier(real, specifier);
|
|
1434
1286
|
if (target === null) {
|
|
1435
1287
|
continue;
|
|
@@ -1442,15 +1294,6 @@ function collectSourceCommonJsExportNames(sourceFile, seen) {
|
|
|
1442
1294
|
}
|
|
1443
1295
|
return names;
|
|
1444
1296
|
}
|
|
1445
|
-
function collectExportStarSpecifiers(source) {
|
|
1446
|
-
const specifiers = [];
|
|
1447
|
-
const pattern = /^(\s*)__exportStar\(\s*require\((["'])([^"']+)\2\)\s*,\s*exports\s*\);/gm;
|
|
1448
|
-
let match;
|
|
1449
|
-
while ((match = pattern.exec(source)) !== null) {
|
|
1450
|
-
specifiers.push(match[3]);
|
|
1451
|
-
}
|
|
1452
|
-
return specifiers;
|
|
1453
|
-
}
|
|
1454
1297
|
function resolveEmittedRequire(emittedFile, specifier) {
|
|
1455
1298
|
if (!isRelativeSpecifier(specifier)) {
|
|
1456
1299
|
return null;
|
|
@@ -1479,7 +1322,9 @@ function resolveSourceSpecifier(sourceFile, specifier) {
|
|
|
1479
1322
|
}
|
|
1480
1323
|
const base = node_path_1.default.resolve(node_path_1.default.dirname(sourceFile), specifier);
|
|
1481
1324
|
if (node_path_1.default.extname(base).length !== 0) {
|
|
1482
|
-
|
|
1325
|
+
if (isFile(base))
|
|
1326
|
+
return base;
|
|
1327
|
+
return typescriptSourcesForJavaScriptSpecifier(base).find(isFile) ?? null;
|
|
1483
1328
|
}
|
|
1484
1329
|
for (const extension of TYPESCRIPT_EXTENSIONS) {
|
|
1485
1330
|
const candidate = base + extension;
|
|
@@ -1495,9 +1340,6 @@ function resolveSourceSpecifier(sourceFile, specifier) {
|
|
|
1495
1340
|
}
|
|
1496
1341
|
return null;
|
|
1497
1342
|
}
|
|
1498
|
-
function isIdentifierName(name) {
|
|
1499
|
-
return /^[A-Za-z_$][\w$]*$/.test(name);
|
|
1500
|
-
}
|
|
1501
1343
|
/**
|
|
1502
1344
|
* Serve the entry project's pre-built JavaScript for a source file the build
|
|
1503
1345
|
* emitted, or `null` when the file is outside the build or its emit is
|
|
@@ -1629,7 +1471,7 @@ function serveDependencyEmit(real) {
|
|
|
1629
1471
|
built = ensureProjectBuilt(tsconfig);
|
|
1630
1472
|
}
|
|
1631
1473
|
catch {
|
|
1632
|
-
// The owning project produced no emit at all; fall back to
|
|
1474
|
+
// The owning project produced no emit at all; fall back to isolated emit of
|
|
1633
1475
|
// this single file rather than failing the whole run.
|
|
1634
1476
|
return null;
|
|
1635
1477
|
}
|
|
@@ -1703,6 +1545,7 @@ function dependencyCacheKey(tsconfig, options = {}) {
|
|
|
1703
1545
|
return (node_crypto_1.default
|
|
1704
1546
|
.createHash("sha256")
|
|
1705
1547
|
.update(tsconfig)
|
|
1548
|
+
.update("\0runtime-es2025")
|
|
1706
1549
|
// Descriptor evaluation promises a result bound to this process's exact
|
|
1707
1550
|
// input observations. Reusing an emit another evaluator built can pair
|
|
1708
1551
|
// that process's old source/config bytes with this process's later hashes.
|
|
@@ -1850,6 +1693,7 @@ function buildDependency(tsconfig, cacheDir, metaPath) {
|
|
|
1850
1693
|
node_fs_1.default.mkdirSync(emitDir, { recursive: true });
|
|
1851
1694
|
const result = (0, runBuild_1.runBuild)({
|
|
1852
1695
|
cwd: project.root,
|
|
1696
|
+
passthrough: (0, runtimeCompilerArgs_1.runtimeCompilerArgs)(project),
|
|
1853
1697
|
emit: true,
|
|
1854
1698
|
forceListEmittedFiles: true,
|
|
1855
1699
|
outDir: emitDir,
|
|
@@ -1891,7 +1735,7 @@ function buildDependency(tsconfig, cacheDir, metaPath) {
|
|
|
1891
1735
|
// list": a native transform host (typia, @ttsc/banner, …) emits without
|
|
1892
1736
|
// printing the `--listEmittedFiles` lines, so `result.emittedFiles` is empty
|
|
1893
1737
|
// even on a clean build. A genuinely empty output directory is the real
|
|
1894
|
-
// failure; the caller then falls back to
|
|
1738
|
+
// failure; the caller then falls back to isolated emit of the one file.
|
|
1895
1739
|
if (!emittedAnything(emitDir)) {
|
|
1896
1740
|
// Drop the failed generation so its partial directory can never be mistaken
|
|
1897
1741
|
// for a reusable build.
|
|
@@ -2231,8 +2075,8 @@ const moduleOptionsCache = new Map();
|
|
|
2231
2075
|
* walk stops at a `node_modules` boundary: a tsconfig above `node_modules`
|
|
2232
2076
|
* belongs to the consumer, not to the published dependency inside it, so a
|
|
2233
2077
|
* dependency that ships no tsconfig of its own has no owning project and is
|
|
2234
|
-
*
|
|
2235
|
-
* because `file` is already its real path (outside `node_modules`).
|
|
2078
|
+
* compiled in isolation instead. A pnpm-symlinked workspace package is
|
|
2079
|
+
* unaffected because `file` is already its real path (outside `node_modules`).
|
|
2236
2080
|
*
|
|
2237
2081
|
* The walk is memoised per directory (the whole walked chain shares one
|
|
2238
2082
|
* answer), so the thousands of files a fanned-out test corpus imports from the
|