Skip to content

Commit 5b91122

Browse files
committed
Add dts bundling
1 parent 3ee116d commit 5b91122

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+7781
-7833
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ scripts/configurePrerelease.js
4747
scripts/configureLanguageServiceBuild.js
4848
scripts/open-user-pr.js
4949
scripts/open-cherry-pick-pr.js
50+
scripts/dtsBundler.js
5051
scripts/processDiagnosticMessages.d.ts
5152
scripts/processDiagnosticMessages.js
5253
scripts/produceLKG.js

Gulpfile.js

+31-7
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,15 @@ const buildSrc = () => buildProject("src");
127127
// But, if we are bundling, we are running only d.ts emit, so maybe this is fast?
128128
task("build-src", series(preSrc, buildSrc));
129129

130+
/**
131+
* @param {string} projectPath
132+
* @param {string} entrypoint
133+
* @param {string} output
134+
*/
135+
async function runDtsBundler(projectPath, entrypoint, output) {
136+
await exec(process.execPath, ["./scripts/dtsBundler.js", projectPath, entrypoint, output]);
137+
}
138+
130139
/** @type {string | undefined} */
131140
let copyrightHeader;
132141
function getCopyrightHeader() {
@@ -143,6 +152,7 @@ function getCopyrightHeader() {
143152
* @param {boolean} performanceMatters True if this is a bundle where performance matters, so should be optimized at the cost of build time.
144153
*/
145154
function esbuildTask(entrypoint, outfile, exportIsTsObject = false, performanceMatters = false) {
155+
performanceMatters = false;
146156
const preBabel = `${outfile}.tmp.js`;
147157

148158
/** @type {esbuild.BuildOptions} */
@@ -251,10 +261,13 @@ const preBuild = cmdLineOptions.lkg ? lkgPreBuild : localPreBuild;
251261
const esbuildServices = esbuildTask("./src/typescript/typescript.ts", "./built/local/typescript.js", /* exportIsTsObject */ true, /* performanceMatters */ true);
252262

253263
// TODO(jakebailey): rename this; no longer "services".
264+
265+
const buildServicesProject = () => buildProject("src/typescript");
266+
254267
const buildServices = () => {
255268
if (cmdLineOptions.bundle) return esbuildServices.build();
256269
writeCJSReexport("./built/local/typescript/typescript.js", "./built/local/typescript.js");
257-
return buildProject("src/typescript");
270+
return buildServicesProject();
258271
};
259272

260273
task("services", series(preBuild, buildServices));
@@ -277,6 +290,9 @@ task("watch-services").flags = {
277290
" --built": "Compile using the built version of the compiler."
278291
};
279292

293+
const dtsServices = () => runDtsBundler("./src/typescript/tsconfig.json", "./built/local/typescript/typescript.d.ts", "./built/local/typescript.d.ts");
294+
task("dts-services", series(preBuild, buildServicesProject, dtsServices));
295+
task("dts-services").description = "Builds typescript.d.ts";
280296

281297
const esbuildServer = esbuildTask("./src/tsserver/server.ts", "./built/local/tsserver.js", /* exportIsTsObject */ true, /* performanceMatters */ true);
282298

@@ -323,10 +339,11 @@ task("watch-min").flags = {
323339

324340
const esbuildLssl = esbuildTask("./src/tsserverlibrary/tsserverlibrary.ts", "./built/local/tsserverlibrary.js", /* exportIsTsObject */ true, /* performanceMatters */ true);
325341

342+
const buildLsslProject = () => buildProject("src/tsserverlibrary");
326343
const buildLssl = () => {
327344
if (cmdLineOptions.bundle) return esbuildLssl.build();
328345
writeCJSReexport("./built/local/tsserverlibrary/tsserverlibrary.js", "./built/local/tsserverlibrary.js");
329-
return buildProject("src/tsserverlibrary");
346+
return buildLsslProject();
330347
};
331348
task("lssl", series(preBuild, buildLssl));
332349
task("lssl").description = "Builds language service server library";
@@ -348,6 +365,14 @@ task("watch-lssl").flags = {
348365
" --built": "Compile using the built version of the compiler."
349366
};
350367

368+
const dtsLssl = () => runDtsBundler("./src/tsserverlibrary/tsconfig.json", "./built/local/tsserverlibrary/tsserverlibrary.d.ts", "./built/local/tsserverlibrary.d.ts");
369+
task("dts-lssl", series(preBuild, buildLsslProject, dtsLssl));
370+
task("dts-lssl").description = "Builds tsserverlibrary.d.ts";
371+
372+
// TODO(jakebailey): this is probably not efficient, but, gulp.
373+
const dts = series(preBuild, parallel(dtsServices, dtsLssl));
374+
task("dts", dts);
375+
351376
const testRunner = "./built/local/run.js";
352377
const esbuildTests = esbuildTask("./src/testRunner/_namespaces/Harness.ts", testRunner);
353378

@@ -458,7 +483,7 @@ const buildOtherOutputs = parallel(buildCancellationToken, buildTypingsInstaller
458483
task("other-outputs", series(preBuild, buildOtherOutputs));
459484
task("other-outputs").description = "Builds miscelaneous scripts and documents distributed with the LKG";
460485

461-
task("local", series(preBuild, parallel(localize, buildTsc, buildServer, buildServices, buildLssl, buildOtherOutputs)));
486+
task("local", series(preBuild, parallel(localize, buildTsc, buildServer, buildServices, buildLssl, buildOtherOutputs, dts)));
462487
task("local").description = "Builds the full compiler and services";
463488
task("local").flags = {
464489
" --built": "Compile using the built version of the compiler."
@@ -474,7 +499,7 @@ const preTest = parallel(buildTsc, buildTests, buildServices, buildLssl);
474499
preTest.displayName = "preTest";
475500

476501
const runTests = () => runConsoleTests(testRunner, "mocha-fivemat-progress-reporter", /*runInParallel*/ false, /*watchMode*/ false);
477-
task("runtests", series(preBuild, preTest, runTests));
502+
task("runtests", series(preBuild, preTest, dts, runTests));
478503
task("runtests").description = "Runs the tests using the built run.js file.";
479504
task("runtests").flags = {
480505
"-t --tests=<regex>": "Pattern for tests to run.",
@@ -493,7 +518,7 @@ task("runtests").flags = {
493518
};
494519

495520
const runTestsParallel = () => runConsoleTests(testRunner, "min", /*runInParallel*/ cmdLineOptions.workers > 1, /*watchMode*/ false);
496-
task("runtests-parallel", series(preBuild, preTest, runTestsParallel));
521+
task("runtests-parallel", series(preBuild, preTest, dts, runTestsParallel));
497522
task("runtests-parallel").description = "Runs all the tests in parallel using the built run.js file.";
498523
task("runtests-parallel").flags = {
499524
" --light": "Run tests in light mode (fewer verifications, but tests run faster).",
@@ -597,8 +622,7 @@ const produceLKG = async () => {
597622
}
598623
};
599624

600-
// TODO(jakebailey): dependencies on dts
601-
task("LKG", series(lkgPreBuild, parallel(localize, buildTsc, buildServer, buildServices, buildLssl, buildOtherOutputs), produceLKG));
625+
task("LKG", series(lkgPreBuild, parallel(localize, buildTsc, buildServer, buildServices, buildLssl, buildOtherOutputs, dts), produceLKG));
602626
task("LKG").description = "Makes a new LKG out of the built js files";
603627
task("LKG").flags = {
604628
" --built": "Compile using the built version of the compiler.",

scripts/dtsBundler.ts

+214
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
/**
2+
* WARNING: this is a very, very rudimentary d.ts bundler; it only works
3+
* in the TS project thanks to our history using namespaces, which has
4+
* prevented us from duplicating names across files, and allows us to
5+
* bundle as namespaces again, even though the project is modules.
6+
*/
7+
8+
import * as assert from "assert";
9+
import * as fs from "fs";
10+
import * as path from "path";
11+
import * as ts from "../lib/typescript";
12+
13+
const [
14+
projectPath,
15+
entrypoint,
16+
output,
17+
] = process.argv.slice(2);
18+
19+
assert(projectPath);
20+
assert(entrypoint);
21+
assert(output);
22+
23+
console.log(`Bundling ${entrypoint} to ${output}, using project ${projectPath}`);
24+
25+
const newLineKind = ts.NewLineKind.LineFeed;
26+
const newLine = newLineKind === ts.NewLineKind.LineFeed ? "\n" : "\r\n";
27+
28+
function isDeclarationStatement(node: ts.Node): node is ts.DeclarationStatement {
29+
return (ts as any).isDeclarationStatement(node);
30+
}
31+
32+
function isInternalDeclaration(node: ts.Node): boolean {
33+
return (ts as any).isInternalDeclaration(node, node.getSourceFile());
34+
}
35+
36+
function getParentVariableStatement(node: ts.VariableDeclaration): ts.VariableStatement {
37+
const declarationList = node.parent as ts.VariableDeclarationList;
38+
assert(ts.isVariableDeclarationList(declarationList), `expected VariableDeclarationList at ${nodeToLocation(node)}`);
39+
assert(declarationList.declarations.length === 1, `expected VariableDeclarationList of length 1 at ${nodeToLocation(node)}`);
40+
const variableStatement = declarationList.parent;
41+
assert(ts.isVariableStatement(variableStatement), `expected VariableStatement at ${nodeToLocation(node)}`);
42+
return variableStatement;
43+
}
44+
45+
function getDeclarationStatement(node: ts.Declaration): ts.Statement | undefined {
46+
if (ts.isVariableDeclaration(node)) {
47+
return getParentVariableStatement(node);
48+
}
49+
else if (isDeclarationStatement(node)) {
50+
return node;
51+
}
52+
return undefined;
53+
}
54+
55+
const nullTransformationContext: ts.TransformationContext = (ts as any).nullTransformationContext;
56+
57+
const tsconfigContents = ts.readJsonConfigFile(path.join(projectPath, "tsconfig.json"), ts.sys.readFile);
58+
const tsconfig = ts.parseJsonConfigFileContent(tsconfigContents, ts.sys, projectPath);
59+
const program = ts.createProgram([entrypoint], tsconfig.options);
60+
61+
const typeChecker = program.getTypeChecker();
62+
63+
const sourceFile = program.getSourceFile(entrypoint);
64+
assert(sourceFile);
65+
const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile);
66+
assert(moduleSymbol);
67+
68+
const printer = ts.createPrinter({ newLine: newLineKind });
69+
70+
71+
const lines: string[] = [];
72+
const indent = " ";
73+
let currentIndent = "";
74+
75+
function increaseIndent() {
76+
currentIndent += indent;
77+
}
78+
79+
function decreaseIndent() {
80+
currentIndent = currentIndent.slice(indent.length);
81+
}
82+
83+
function write(s: string) {
84+
if (!s) {
85+
lines.push("");
86+
}
87+
else {
88+
lines.push(...s.split(/\r?\n/).filter(line => line).map(line => (currentIndent + line).trimEnd()));
89+
}
90+
}
91+
92+
const containsPublicAPICache = new Map<ts.Symbol, boolean>();
93+
94+
function containsPublicAPI(symbol: ts.Symbol): boolean {
95+
const cached = containsPublicAPICache.get(symbol);
96+
if (cached !== undefined) {
97+
return cached;
98+
}
99+
100+
const result = containsPublicAPIWorker();
101+
containsPublicAPICache.set(symbol, result);
102+
return result;
103+
104+
function containsPublicAPIWorker(): boolean {
105+
if (!symbol.declarations?.length) {
106+
return false;
107+
}
108+
109+
if (symbol.flags & ts.SymbolFlags.Alias) {
110+
const resolved = typeChecker.getAliasedSymbol(symbol);
111+
return containsPublicAPI(resolved);
112+
}
113+
114+
if (symbol.flags & ts.SymbolFlags.ValueModule) {
115+
for (const me of typeChecker.getExportsOfModule(symbol)) {
116+
if (containsPublicAPI(me)) {
117+
return true;
118+
}
119+
}
120+
return false;
121+
}
122+
123+
for (const decl of symbol.declarations) {
124+
const statement = getDeclarationStatement(decl);
125+
if (statement && !isInternalDeclaration(statement)) {
126+
return true;
127+
}
128+
}
129+
130+
return false;
131+
}
132+
}
133+
134+
function nodeToLocation(decl: ts.Node): string {
135+
const sourceFile = decl.getSourceFile();
136+
const lc = sourceFile.getLineAndCharacterOfPosition(decl.pos);
137+
return `${sourceFile.fileName}:${lc.line}:${lc.character}`;
138+
}
139+
140+
function emitAsNamespace(name: string, moduleSymbol: ts.Symbol) {
141+
assert(moduleSymbol.flags & ts.SymbolFlags.ValueModule);
142+
143+
if (name === "ts") {
144+
// We will write `export = ts` at the end.
145+
write(`declare namespace ${name} {`);
146+
}
147+
else {
148+
// No export modifier; we are already in the namespace.
149+
write(`namespace ${name} {`);
150+
}
151+
increaseIndent();
152+
153+
const moduleExports = typeChecker.getExportsOfModule(moduleSymbol);
154+
for (const me of moduleExports) {
155+
if (!containsPublicAPI(me)) {
156+
continue;
157+
}
158+
159+
assert(me.declarations?.length);
160+
161+
if (me.flags & ts.SymbolFlags.Alias) {
162+
const resolved = typeChecker.getAliasedSymbol(me);
163+
emitAsNamespace(me.name, resolved);
164+
continue;
165+
}
166+
167+
for (const decl of me.declarations) {
168+
let statement = getDeclarationStatement(decl);
169+
170+
if (!statement) {
171+
throw new Error(`Unhandled declaration for ${me.name} at ${nodeToLocation(decl)}`);
172+
}
173+
174+
// Ignore an internal declaration.
175+
if (isInternalDeclaration(statement)) {
176+
continue;
177+
}
178+
179+
// Remove internal components and declare/const keywords.
180+
statement = ts.visitEachChild(statement, (node) => {
181+
if (isInternalDeclaration(node)) {
182+
return undefined;
183+
}
184+
185+
switch (node.kind) {
186+
case ts.SyntaxKind.DeclareKeyword: // No need to emit this in d.ts files.
187+
case ts.SyntaxKind.ConstKeyword: // Remove const from const enums.
188+
case ts.SyntaxKind.ExportKeyword: // No export modifier; we are already in the namespace.
189+
return undefined;
190+
}
191+
192+
return node;
193+
}, nullTransformationContext);
194+
195+
write(printer.printNode(ts.EmitHint.Unspecified, statement, decl.getSourceFile()));
196+
}
197+
}
198+
199+
decreaseIndent();
200+
write(`}`);
201+
}
202+
203+
emitAsNamespace("ts", moduleSymbol);
204+
205+
write("export = ts;");
206+
207+
const copyrightNotice = fs.readFileSync(path.join(__dirname, "..", "CopyrightNotice.txt"), "utf-8");
208+
const outputContents = copyrightNotice + lines.join(newLine);
209+
210+
if (outputContents.includes("@internal")) {
211+
console.error("Output includes untrimmed @internal nodes!");
212+
}
213+
214+
fs.writeFileSync(output, outputContents);

src/compiler/commandLineParser.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2081,7 +2081,7 @@ function getTsconfigRootOptionsMap() {
20812081
}
20822082

20832083
/** @internal */
2084-
interface JsonConversionNotifier {
2084+
export interface JsonConversionNotifier {
20852085
/**
20862086
* Notifies parent option object is being set with the optionKey and a valid optionValue
20872087
* Currently it notifies only if there is element with type object (parentOption) and

src/compiler/factory/utilities.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ import {
22
AccessorDeclaration, addEmitFlags, AdditiveOperator, AdditiveOperatorOrHigher, AssertionLevel,
33
AssignmentOperatorOrHigher, BinaryExpression, BinaryOperator, BinaryOperatorToken, BindingOrAssignmentElement,
44
BindingOrAssignmentElementRestIndicator, BindingOrAssignmentElementTarget, BindingOrAssignmentPattern,
5-
BitwiseOperator, BitwiseOperatorOrHigher, BooleanLiteral, CharacterCodes, CommaListExpression,
5+
BitwiseOperator, BitwiseOperatorOrHigher, Block, BooleanLiteral, CharacterCodes, CommaListExpression,
66
compareStringsCaseSensitive, CompilerOptions, Debug, Declaration, EmitFlags, EmitHelperFactory, EmitHost,
77
EmitResolver, EntityName, EqualityOperator, EqualityOperatorOrHigher, ExclamationToken, ExponentiationOperator,
88
ExportDeclaration, Expression, ExpressionStatement, externalHelpersModuleNameText, first, firstOrUndefined,
99
ForInitializer, GeneratedIdentifier, GeneratedIdentifierFlags, GeneratedNamePart, GeneratedPrivateIdentifier,
10+
GetAccessorDeclaration,
1011
getAllAccessorDeclarations, getEmitFlags, getEmitHelpers, getEmitModuleKind, getESModuleInterop,
1112
getExternalModuleName, getExternalModuleNameFromPath, getJSDocType, getJSDocTypeTag, getModifiers,
1213
getNamespaceDeclarationNode, getOrCreateEmitNode, getOriginalNode, getParseTreeNode,
@@ -26,7 +27,7 @@ import {
2627
NumericLiteral, ObjectLiteralElementLike, ObjectLiteralExpression, or, OuterExpression, OuterExpressionKinds,
2728
outFile, parseNodeFactory, PlusToken, PostfixUnaryExpression, PrefixUnaryExpression, PrivateIdentifier,
2829
PropertyAssignment, PropertyDeclaration, PropertyName, pushIfUnique, QuestionToken, ReadonlyKeyword,
29-
RelationalOperator, RelationalOperatorOrHigher, setOriginalNode, setParent, setStartsOnNewLine, setTextRange,
30+
RelationalOperator, RelationalOperatorOrHigher, SetAccessorDeclaration, setOriginalNode, setParent, setStartsOnNewLine, setTextRange,
3031
ShiftOperator, ShiftOperatorOrHigher, ShorthandPropertyAssignment, some, SourceFile, Statement, StringLiteral,
3132
SyntaxKind, TextRange, ThisTypeNode, Token, TypeNode, TypeParameterDeclaration,
3233
} from "../_namespaces/ts";
@@ -185,7 +186,7 @@ export function createForOfBindingStatement(factory: NodeFactory, node: ForIniti
185186
}
186187

187188
/** @internal */
188-
export function insertLeadingStatement(factory: NodeFactory, dest: Statement, source: Statement) {
189+
export function insertLeadingStatement(factory: NodeFactory, dest: Statement, source: Statement): Block {
189190
if (isBlock(dest)) {
190191
return factory.updateBlock(dest, setTextRange(factory.createNodeArray([source, ...dest.statements]), dest.statements));
191192
}
@@ -1456,7 +1457,7 @@ export function createAccessorPropertyBackingField(factory: NodeFactory, node: P
14561457
*
14571458
* @internal
14581459
*/
1459-
export function createAccessorPropertyGetRedirector(factory: NodeFactory, node: PropertyDeclaration, modifiers: ModifiersArray | undefined, name: PropertyName) {
1460+
export function createAccessorPropertyGetRedirector(factory: NodeFactory, node: PropertyDeclaration, modifiers: ModifiersArray | undefined, name: PropertyName): GetAccessorDeclaration {
14601461
return factory.createGetAccessorDeclaration(
14611462
modifiers,
14621463
name,
@@ -1478,7 +1479,7 @@ export function createAccessorPropertyGetRedirector(factory: NodeFactory, node:
14781479
*
14791480
* @internal
14801481
*/
1481-
export function createAccessorPropertySetRedirector(factory: NodeFactory, node: PropertyDeclaration, modifiers: ModifiersArray | undefined, name: PropertyName) {
1482+
export function createAccessorPropertySetRedirector(factory: NodeFactory, node: PropertyDeclaration, modifiers: ModifiersArray | undefined, name: PropertyName): SetAccessorDeclaration {
14821483
return factory.createSetAccessorDeclaration(
14831484
modifiers,
14841485
name,

0 commit comments

Comments
 (0)