Skip to content

Commit 03bdd2e

Browse files
authored
Centralized browser tsconfig (#32087)
1 parent f2c56f7 commit 03bdd2e

File tree

12 files changed

+269
-41
lines changed

12 files changed

+269
-41
lines changed

common/config/rush/pnpm-lock.yaml

+66-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

common/tools/dev-tool/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"env-paths": "^2.2.1",
6060
"express": "^4.19.2",
6161
"fs-extra": "^11.2.0",
62+
"memfs": "^4.14.1",
6263
"minimist": "^1.2.8",
6364
"prettier": "^3.3.3",
6465
"rollup": "^4.22.0",

common/tools/dev-tool/src/commands/run/build-test.ts

+10-14
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,11 @@
22
// Licensed under the MIT License.
33

44
import path from "node:path";
5-
import {
6-
cpSync,
7-
existsSync,
8-
mkdirSync,
9-
readFileSync,
10-
readdirSync,
11-
statSync,
12-
writeFileSync,
13-
} from "node:fs";
5+
import { cpSync, existsSync, mkdirSync, readdirSync, statSync, writeFileSync } from "node:fs";
146
import { leafCommand, makeCommandInfo } from "../../framework/command";
157
import { createPrinter } from "../../util/printer";
168
import { resolveProject } from "../../util/resolveProject";
9+
import { resolveConfig } from "../../util/resolveTsConfig";
1710
import { spawnSync } from "node:child_process";
1811

1912
const log = createPrinter("build-test");
@@ -107,15 +100,18 @@ function compileForEnvironment(
107100
overrideMap: Map<string, string>,
108101
): boolean {
109102
const tsconfigPath = path.join(process.cwd(), tsConfig);
110-
const tsConfigFile = readFileSync(tsconfigPath, "utf8");
111-
const tsConfigJSON = JSON.parse(tsConfigFile);
103+
const tsConfigJSON = resolveConfig(tsconfigPath);
112104
const outputPath = tsConfigJSON.compilerOptions.outDir;
113105
if (!existsSync(tsconfigPath)) {
114106
log.error(`TypeScript config ${tsConfig} does not exist`);
115107
return false;
116108
}
117109

118-
const browserTestPath = path.join(process.cwd(), outputPath);
110+
const browserTestPath = outputPath;
111+
if (!browserTestPath) {
112+
log.error(`Output path not defined in ${tsConfig}`);
113+
return false;
114+
}
119115
if (!existsSync(browserTestPath)) {
120116
mkdirSync(browserTestPath, { recursive: true });
121117
}
@@ -191,8 +187,8 @@ function overrideFile(
191187
sourceFile: string,
192188
destinationFile: string,
193189
): void {
194-
const sourceFileType = path.join(process.cwd(), rootDir, relativeDir, sourceFile);
195-
const destFileType = path.join(process.cwd(), rootDir, relativeDir, destinationFile);
190+
const sourceFileType = path.join(rootDir, relativeDir, sourceFile);
191+
const destFileType = path.join(rootDir, relativeDir, destinationFile);
196192

197193
cpSync(sourceFileType, destFileType, { force: true });
198194
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
import * as path from "path";
5+
import * as fs from "fs";
6+
import ts from "typescript";
7+
8+
type Config = { compilerOptions: ts.CompilerOptions };
9+
10+
function resolveConfigDir(configPath: string, unresolvedPath: string) {
11+
return unresolvedPath.replace(/\$\{configDir\}/g, path.dirname(configPath));
12+
}
13+
14+
function mergeConfig(config1: Config, config2: Config) {
15+
return {
16+
...config1,
17+
...config2,
18+
compilerOptions: {
19+
...config1.compilerOptions,
20+
...config2.compilerOptions,
21+
},
22+
};
23+
}
24+
25+
/**
26+
* Generates a complete TypeScript configuration by reading and parsing a given tsconfig file.
27+
*
28+
* @param configPath - The path to the tsconfig file.
29+
* @returns A record containing the raw parsed configuration.
30+
*/
31+
export function resolveConfig(configPath: string) {
32+
function helper(configPath: string) {
33+
const absolutePath = path.resolve(configPath);
34+
const configDir = path.dirname(absolutePath);
35+
const rawConfig = JSON.parse(
36+
fs.readFileSync(
37+
!absolutePath.toLowerCase().endsWith(".json") ? `${absolutePath}.json` : absolutePath,
38+
"utf8",
39+
),
40+
);
41+
42+
let resolvedConfig = {} as Config;
43+
const { extends: parents, ...rest } = rawConfig;
44+
if (parents) {
45+
const baseConfigs = Array.isArray(parents) ? parents : [parents];
46+
for (const baseConfigPath of baseConfigs) {
47+
const baseConfigAbsolutePath = path.resolve(configDir, baseConfigPath);
48+
const baseConfig = helper(baseConfigAbsolutePath);
49+
resolvedConfig = mergeConfig(resolvedConfig, baseConfig);
50+
}
51+
}
52+
return mergeConfig(resolvedConfig, rest);
53+
}
54+
const resolvedConfig = helper(configPath);
55+
const outDir = resolvedConfig.compilerOptions.outDir;
56+
if (outDir) {
57+
resolvedConfig.compilerOptions.outDir = resolveConfigDir(configPath, outDir);
58+
}
59+
return resolvedConfig;
60+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License
3+
4+
import { describe, it, beforeEach, expect, vi } from "vitest";
5+
import { vol } from "memfs";
6+
import { resolveConfig } from "../src/util/resolveTsConfig";
7+
8+
vi.mock("fs", async () => {
9+
const memfs = await import("memfs");
10+
return {
11+
...memfs.fs,
12+
};
13+
});
14+
15+
describe("resolveConfig", () => {
16+
beforeEach(() => {
17+
vol.reset();
18+
});
19+
20+
it("should resolve a simple tsconfig.json", () => {
21+
const def = {
22+
compilerOptions: {
23+
target: "ES6",
24+
},
25+
};
26+
vol.fromJSON({
27+
"/project/tsconfig.json": JSON.stringify(def),
28+
});
29+
30+
const result = resolveConfig("/project/tsconfig.json");
31+
expect(result).toEqual(def);
32+
});
33+
34+
it("should resolve tsconfig.json with single extend", () => {
35+
vol.fromJSON({
36+
"/project/tsconfig.base.json": JSON.stringify({
37+
compilerOptions: {
38+
target: "ES5",
39+
module: "CommonJS",
40+
},
41+
}),
42+
"/project/tsconfig.json": JSON.stringify({
43+
extends: "./tsconfig.base.json",
44+
compilerOptions: {
45+
strict: true,
46+
},
47+
}),
48+
});
49+
50+
const result = resolveConfig("/project/tsconfig.json");
51+
expect(result).toEqual({
52+
compilerOptions: {
53+
target: "ES5",
54+
module: "CommonJS",
55+
strict: true,
56+
},
57+
});
58+
});
59+
60+
it("should resolve tsconfig.json with single extend and conflicting option", () => {
61+
vol.fromJSON({
62+
"/project/tsconfig.base.json": JSON.stringify({
63+
compilerOptions: {
64+
target: "ES5",
65+
outDir: "path1",
66+
},
67+
}),
68+
"/project/tsconfig.json": JSON.stringify({
69+
extends: "./tsconfig.base.json",
70+
compilerOptions: {
71+
strict: true,
72+
outDir: "path2",
73+
},
74+
}),
75+
});
76+
77+
const result = resolveConfig("/project/tsconfig.json");
78+
expect(result).toEqual({
79+
compilerOptions: {
80+
target: "ES5",
81+
strict: true,
82+
outDir: "path2",
83+
},
84+
});
85+
});
86+
87+
it("should resolve tsconfig.json with multiple extends with conflicting options in parents", () => {
88+
vol.fromJSON({
89+
"/project/tsconfig.base1.json": JSON.stringify({
90+
compilerOptions: {
91+
target: "ES5",
92+
outDir: "path1",
93+
},
94+
}),
95+
"/project/tsconfig.base2.json": JSON.stringify({
96+
compilerOptions: {
97+
outDir: "path2",
98+
},
99+
}),
100+
"/project/tsconfig.json": JSON.stringify({
101+
extends: ["./tsconfig.base1.json", "./tsconfig.base2.json"],
102+
compilerOptions: {
103+
strict: true,
104+
},
105+
}),
106+
});
107+
108+
const result = resolveConfig("/project/tsconfig.json");
109+
expect(result).toEqual({
110+
compilerOptions: {
111+
target: "ES5",
112+
strict: true,
113+
outDir: "path2",
114+
},
115+
});
116+
});
117+
});
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
11
{
2-
"extends": "./tsconfig.test.json",
3-
"exclude": ["./test/**/node", "./test/stress"],
4-
"compilerOptions": {
5-
"outDir": "./dist-test/browser",
6-
"noEmit": false
7-
}
2+
"extends": ["./tsconfig.test.json", "../../../tsconfig.browser.base.json"],
3+
"exclude": ["./test/**/node", "./test/stress"]
84
}
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
11
{
2-
"extends": "./tsconfig.test.json",
3-
"exclude": ["./test/**/node"],
4-
"compilerOptions": {
5-
"outDir": "./dist-test/browser",
6-
"noEmit": false
7-
}
2+
"extends": ["./tsconfig.test.json", "../../../tsconfig.browser.base.json"]
83
}
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
11
{
2-
"extends": "./tsconfig.test.json",
3-
"exclude": ["./test/**/node", "./test/manual*"],
4-
"compilerOptions": {
5-
"outDir": "./dist-test/browser",
6-
"noEmit": false
7-
}
2+
"extends": ["./tsconfig.test.json", "../../../tsconfig.browser.base.json"],
3+
"exclude": ["./test/**/node", "./test/manual*"]
84
}
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
11
{
2-
"extends": "./tsconfig.test.json",
3-
"exclude": ["./test/**/node"],
4-
"compilerOptions": {
5-
"outDir": "./dist-test/browser",
6-
"noEmit": false
7-
}
2+
"extends": ["./tsconfig.test.json", "../../../tsconfig.browser.base.json"]
83
}

0 commit comments

Comments
 (0)