@simplysm/sd-cli 12.16.37 → 12.16.39
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/dist/pkg-builders/client/SdNgBundler.js +3 -1
- package/dist/pkg-builders/client/SdPolyfillPlugin.d.ts +2 -0
- package/dist/pkg-builders/client/SdPolyfillPlugin.js +61 -0
- package/package.json +7 -5
- package/src/pkg-builders/client/SdNgBundler.ts +3 -4
- package/src/pkg-builders/client/SdPolyfillPlugin.ts +70 -0
- package/tests/client/sd-polyfill-plugin.spec.ts +87 -0
- package/tests/deps/sd-dependency-analyzer.spec.ts +56 -44
- package/tests/deps/sd-dependency-cache.spec.ts +26 -68
- package/lib/chrome61-polyfills.js +0 -46
|
@@ -22,6 +22,7 @@ import { createSdNgPlugin } from "./createSdNgPlugin";
|
|
|
22
22
|
import { SdCliPerformanceTimer } from "../../utils/SdCliPerformanceTimer";
|
|
23
23
|
import nodeModule from "module";
|
|
24
24
|
import { SdWorkerPathPlugin } from "../commons/SdWorkerPathPlugin";
|
|
25
|
+
import { SdPolyfillPlugin } from "./SdPolyfillPlugin";
|
|
25
26
|
export class SdNgBundler {
|
|
26
27
|
constructor(_opt, _conf) {
|
|
27
28
|
this._opt = _opt;
|
|
@@ -370,7 +371,7 @@ export class SdNgBundler {
|
|
|
370
371
|
mainFields: ["es2020", "es2015", "browser", "module", "main"],
|
|
371
372
|
entryNames: "[dir]/[name]",
|
|
372
373
|
entryPoints: {
|
|
373
|
-
"sd-polyfills":
|
|
374
|
+
"sd-polyfills": "virtual:sd-polyfills",
|
|
374
375
|
main: this._mainFilePath,
|
|
375
376
|
...(FsUtils.exists(path.resolve(this._opt.pkgPath, "src/polyfills.ts"))
|
|
376
377
|
? {
|
|
@@ -433,6 +434,7 @@ export class SdNgBundler {
|
|
|
433
434
|
}),
|
|
434
435
|
plugins: [
|
|
435
436
|
createSourcemapIgnorelistPlugin(),
|
|
437
|
+
SdPolyfillPlugin(["Chrome >= 61"]),
|
|
436
438
|
createSdNgPlugin(this._opt, this._modifiedFileSet, this._ngResultCache),
|
|
437
439
|
...(this._conf.builderType === "electron"
|
|
438
440
|
? []
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import compat from "core-js-compat";
|
|
2
|
+
import { createRequire } from "module";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
import path from "path";
|
|
5
|
+
const _require = createRequire(import.meta.url);
|
|
6
|
+
const _resolveDir = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const SD_POLYFILL_NS = "sd-polyfill";
|
|
8
|
+
const SD_POLYFILL_FILTER = /^virtual:sd-polyfills$/;
|
|
9
|
+
export function SdPolyfillPlugin(browserslistQuery) {
|
|
10
|
+
return {
|
|
11
|
+
name: "sd-polyfill-plugin",
|
|
12
|
+
setup(build) {
|
|
13
|
+
build.onResolve({ filter: SD_POLYFILL_FILTER }, () => ({
|
|
14
|
+
path: "sd-polyfills",
|
|
15
|
+
namespace: SD_POLYFILL_NS,
|
|
16
|
+
}));
|
|
17
|
+
build.onLoad({ filter: /.*/, namespace: SD_POLYFILL_NS }, () => {
|
|
18
|
+
const { list } = compat({
|
|
19
|
+
targets: browserslistQuery,
|
|
20
|
+
});
|
|
21
|
+
const lines = [];
|
|
22
|
+
// core-js 모듈 (bare specifier + resolveDir로 소비 프로젝트의 node_modules 의존 제거)
|
|
23
|
+
for (const mod of list) {
|
|
24
|
+
try {
|
|
25
|
+
_require.resolve(`core-js/modules/${mod}.js`);
|
|
26
|
+
lines.push(`import "core-js/modules/${mod}.js";`);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// 모듈이 존재하지 않으면 skip (WeakRef 등 polyfill 불가 항목)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// AbortController (Chrome 66+)
|
|
33
|
+
try {
|
|
34
|
+
_require.resolve("abortcontroller-polyfill/dist/abortcontroller-polyfill-only");
|
|
35
|
+
lines.push(`import "abortcontroller-polyfill/dist/abortcontroller-polyfill-only";`);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// skip
|
|
39
|
+
}
|
|
40
|
+
// ResizeObserver (Chrome 64+)
|
|
41
|
+
try {
|
|
42
|
+
_require.resolve("resize-observer-polyfill");
|
|
43
|
+
lines.push(`import RO from "resize-observer-polyfill";`);
|
|
44
|
+
lines.push(`if (typeof window !== "undefined" && !("ResizeObserver" in window)) { window.ResizeObserver = RO; }`);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// skip
|
|
48
|
+
}
|
|
49
|
+
// TransformStream / ReadableStream / WritableStream (Chrome 67+)
|
|
50
|
+
try {
|
|
51
|
+
_require.resolve("web-streams-polyfill/polyfill");
|
|
52
|
+
lines.push(`import "web-streams-polyfill/polyfill";`);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// skip
|
|
56
|
+
}
|
|
57
|
+
return { contents: lines.join("\n"), loader: "js", resolveDir: _resolveDir };
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simplysm/sd-cli",
|
|
3
|
-
"version": "12.16.
|
|
3
|
+
"version": "12.16.39",
|
|
4
4
|
"description": "심플리즘 패키지 - CLI",
|
|
5
5
|
"author": "김석래",
|
|
6
6
|
"repository": {
|
|
@@ -17,14 +17,15 @@
|
|
|
17
17
|
"@angular/compiler-cli": "^20.3.18",
|
|
18
18
|
"@anthropic-ai/sdk": "^0.78.0",
|
|
19
19
|
"@electron/rebuild": "^4.0.3",
|
|
20
|
-
"@simplysm/sd-core-common": "12.16.
|
|
21
|
-
"@simplysm/sd-core-node": "12.16.
|
|
22
|
-
"@simplysm/sd-service-server": "12.16.
|
|
23
|
-
"@simplysm/sd-storage": "12.16.
|
|
20
|
+
"@simplysm/sd-core-common": "12.16.39",
|
|
21
|
+
"@simplysm/sd-core-node": "12.16.39",
|
|
22
|
+
"@simplysm/sd-service-server": "12.16.39",
|
|
23
|
+
"@simplysm/sd-storage": "12.16.39",
|
|
24
24
|
"abortcontroller-polyfill": "^1.7.8",
|
|
25
25
|
"browserslist": "^4.28.1",
|
|
26
26
|
"cordova": "^13.0.0",
|
|
27
27
|
"core-js": "^3.49.0",
|
|
28
|
+
"core-js-compat": "^3.49.0",
|
|
28
29
|
"electron": "^33.4.11",
|
|
29
30
|
"electron-builder": "^25.1.8",
|
|
30
31
|
"esbuild": "0.25.9",
|
|
@@ -41,6 +42,7 @@
|
|
|
41
42
|
"ts-morph": "^27.0.2",
|
|
42
43
|
"tslib": "^2.8.1",
|
|
43
44
|
"typescript": "~5.8.3",
|
|
45
|
+
"web-streams-polyfill": "^4.2.0",
|
|
44
46
|
"yargs": "^18.0.0"
|
|
45
47
|
},
|
|
46
48
|
"devDependencies": {
|
|
@@ -41,6 +41,7 @@ import { INpmConfig } from "../../types/common-config/INpmConfig";
|
|
|
41
41
|
import { ISdBuildResult } from "../../types/build/ISdBuildResult";
|
|
42
42
|
import { ISdTsCompilerOptions } from "../../types/build/ISdTsCompilerOptions";
|
|
43
43
|
import { SdWorkerPathPlugin } from "../commons/SdWorkerPathPlugin";
|
|
44
|
+
import { SdPolyfillPlugin } from "./SdPolyfillPlugin";
|
|
44
45
|
|
|
45
46
|
export class SdNgBundler {
|
|
46
47
|
private readonly _logger = SdLogger.get(["simplysm", "sd-cli", "SdNgBundler"]);
|
|
@@ -488,10 +489,7 @@ export class SdNgBundler {
|
|
|
488
489
|
mainFields: ["es2020", "es2015", "browser", "module", "main"],
|
|
489
490
|
entryNames: "[dir]/[name]",
|
|
490
491
|
entryPoints: {
|
|
491
|
-
"sd-polyfills":
|
|
492
|
-
path.dirname(fileURLToPath(import.meta.url)),
|
|
493
|
-
"../../../lib/chrome61-polyfills.js",
|
|
494
|
-
),
|
|
492
|
+
"sd-polyfills": "virtual:sd-polyfills",
|
|
495
493
|
main: this._mainFilePath,
|
|
496
494
|
...(FsUtils.exists(path.resolve(this._opt.pkgPath, "src/polyfills.ts"))
|
|
497
495
|
? {
|
|
@@ -561,6 +559,7 @@ export class SdNgBundler {
|
|
|
561
559
|
}),
|
|
562
560
|
plugins: [
|
|
563
561
|
createSourcemapIgnorelistPlugin(),
|
|
562
|
+
SdPolyfillPlugin(["Chrome >= 61"]),
|
|
564
563
|
createSdNgPlugin(this._opt, this._modifiedFileSet, this._ngResultCache),
|
|
565
564
|
...(this._conf.builderType === "electron"
|
|
566
565
|
? []
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Plugin, PluginBuild } from "esbuild";
|
|
2
|
+
import compat from "core-js-compat";
|
|
3
|
+
import { createRequire } from "module";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import path from "path";
|
|
6
|
+
|
|
7
|
+
const _require = createRequire(import.meta.url);
|
|
8
|
+
const _resolveDir = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
|
|
10
|
+
const SD_POLYFILL_NS = "sd-polyfill";
|
|
11
|
+
const SD_POLYFILL_FILTER = /^virtual:sd-polyfills$/;
|
|
12
|
+
|
|
13
|
+
export function SdPolyfillPlugin(browserslistQuery: string[]): Plugin {
|
|
14
|
+
return {
|
|
15
|
+
name: "sd-polyfill-plugin",
|
|
16
|
+
setup(build: PluginBuild) {
|
|
17
|
+
build.onResolve({ filter: SD_POLYFILL_FILTER }, () => ({
|
|
18
|
+
path: "sd-polyfills",
|
|
19
|
+
namespace: SD_POLYFILL_NS,
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
build.onLoad({ filter: /.*/, namespace: SD_POLYFILL_NS }, () => {
|
|
23
|
+
const { list } = compat({
|
|
24
|
+
targets: browserslistQuery,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const lines: string[] = [];
|
|
28
|
+
|
|
29
|
+
// core-js 모듈 (bare specifier + resolveDir로 소비 프로젝트의 node_modules 의존 제거)
|
|
30
|
+
for (const mod of list) {
|
|
31
|
+
try {
|
|
32
|
+
_require.resolve(`core-js/modules/${mod}.js`);
|
|
33
|
+
lines.push(`import "core-js/modules/${mod}.js";`);
|
|
34
|
+
} catch {
|
|
35
|
+
// 모듈이 존재하지 않으면 skip (WeakRef 등 polyfill 불가 항목)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// AbortController (Chrome 66+)
|
|
40
|
+
try {
|
|
41
|
+
_require.resolve("abortcontroller-polyfill/dist/abortcontroller-polyfill-only");
|
|
42
|
+
lines.push(`import "abortcontroller-polyfill/dist/abortcontroller-polyfill-only";`);
|
|
43
|
+
} catch {
|
|
44
|
+
// skip
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ResizeObserver (Chrome 64+)
|
|
48
|
+
try {
|
|
49
|
+
_require.resolve("resize-observer-polyfill");
|
|
50
|
+
lines.push(`import RO from "resize-observer-polyfill";`);
|
|
51
|
+
lines.push(
|
|
52
|
+
`if (typeof window !== "undefined" && !("ResizeObserver" in window)) { window.ResizeObserver = RO; }`,
|
|
53
|
+
);
|
|
54
|
+
} catch {
|
|
55
|
+
// skip
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// TransformStream / ReadableStream / WritableStream (Chrome 67+)
|
|
59
|
+
try {
|
|
60
|
+
_require.resolve("web-streams-polyfill/polyfill");
|
|
61
|
+
lines.push(`import "web-streams-polyfill/polyfill";`);
|
|
62
|
+
} catch {
|
|
63
|
+
// skip
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return { contents: lines.join("\n"), loader: "js", resolveDir: _resolveDir };
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { SdPolyfillPlugin } from "../../src/pkg-builders/client/SdPolyfillPlugin";
|
|
3
|
+
|
|
4
|
+
function captureOnLoad(browserslistQuery: string[]) {
|
|
5
|
+
const plugin = SdPolyfillPlugin(browserslistQuery);
|
|
6
|
+
|
|
7
|
+
let onLoadHandler: (() => { contents: string; loader: string; resolveDir?: string }) | undefined;
|
|
8
|
+
const mockBuild = {
|
|
9
|
+
onResolve: () => {},
|
|
10
|
+
onLoad: (_opts: unknown, handler: () => { contents: string; loader: string; resolveDir?: string }) => {
|
|
11
|
+
onLoadHandler = handler;
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
plugin.setup(mockBuild as never);
|
|
16
|
+
return onLoadHandler!();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe("SdPolyfillPlugin", () => {
|
|
20
|
+
describe("가상 polyfill 모듈의 import가 OS 무관하게 resolve되어야 한다", () => {
|
|
21
|
+
it("모든 import가 bare specifier이다 (절대 경로 없음)", () => {
|
|
22
|
+
const result = captureOnLoad(["Chrome >= 61"]);
|
|
23
|
+
const importLines = result.contents.split("\n").filter((l) => l.startsWith("import"));
|
|
24
|
+
|
|
25
|
+
expect(importLines.length).toBeGreaterThan(0);
|
|
26
|
+
for (const line of importLines) {
|
|
27
|
+
expect(line).not.toMatch(/["'][A-Z]:\//i); // Windows 드라이브 문자 금지
|
|
28
|
+
expect(line).not.toMatch(/["']\//); // Unix 절대 경로 금지
|
|
29
|
+
}
|
|
30
|
+
// core-js bare specifier 사용
|
|
31
|
+
expect(result.contents).toContain('import "core-js/modules/');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("abortcontroller-polyfill import에 절대 경로가 포함되지 않는다", () => {
|
|
35
|
+
const result = captureOnLoad(["Chrome >= 61"]);
|
|
36
|
+
|
|
37
|
+
expect(result.contents).toContain("abortcontroller-polyfill");
|
|
38
|
+
// 절대 경로가 아닌 bare specifier
|
|
39
|
+
const abortLines = result.contents.split("\n").filter((l) => l.includes("abortcontroller"));
|
|
40
|
+
for (const line of abortLines) {
|
|
41
|
+
expect(line).not.toMatch(/[A-Z]:\//i);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("resize-observer-polyfill import에 절대 경로가 포함되지 않는다", () => {
|
|
46
|
+
const result = captureOnLoad(["Chrome >= 61"]);
|
|
47
|
+
|
|
48
|
+
expect(result.contents).toContain("resize-observer-polyfill");
|
|
49
|
+
const roLines = result.contents.split("\n").filter((l) => l.includes("resize-observer"));
|
|
50
|
+
for (const line of roLines) {
|
|
51
|
+
expect(line).not.toMatch(/[A-Z]:\//i);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("web-streams-polyfill import에 절대 경로가 포함되지 않는다", () => {
|
|
56
|
+
const result = captureOnLoad(["Chrome >= 61"]);
|
|
57
|
+
|
|
58
|
+
expect(result.contents).toContain("web-streams-polyfill");
|
|
59
|
+
const wsLines = result.contents.split("\n").filter((l) => l.includes("web-streams-polyfill"));
|
|
60
|
+
for (const line of wsLines) {
|
|
61
|
+
expect(line).not.toMatch(/[A-Z]:\//i);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("resolveDir이 설정되어야 한다", () => {
|
|
66
|
+
const result = captureOnLoad(["Chrome >= 61"]);
|
|
67
|
+
|
|
68
|
+
expect(result.resolveDir).toBeDefined();
|
|
69
|
+
expect(typeof result.resolveDir).toBe("string");
|
|
70
|
+
expect(result.resolveDir!.length).toBeGreaterThan(0);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe("core-js-compat 기반 동적 polyfill 목록이 유지되어야 한다", () => {
|
|
75
|
+
it("browserslistQuery에 따라 core-js 모듈이 포함된다", () => {
|
|
76
|
+
const result = captureOnLoad(["Chrome >= 61"]);
|
|
77
|
+
|
|
78
|
+
// Chrome 61에서 지원하지 않는 ES feature가 포함되어야 함
|
|
79
|
+
expect(result.contents).toContain("core-js/modules/");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("존재하지 않는 모듈은 skip되고 에러 없이 완료된다", () => {
|
|
83
|
+
// core-js-compat이 반환하는 모듈 중 실제 존재하지 않는 것이 있어도 에러 없이 완료
|
|
84
|
+
expect(() => captureOnLoad(["Chrome >= 61"])).not.toThrow();
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -3,6 +3,7 @@ import * as ts from "typescript";
|
|
|
3
3
|
import { PathUtils, TNormPath } from "@simplysm/sd-core-node";
|
|
4
4
|
import { SdDepCache } from "../../src/ts-compiler/SdDepCache";
|
|
5
5
|
import { SdDepAnalyzer } from "../../src/ts-compiler/SdDepAnalyzer";
|
|
6
|
+
import { ScopePathSet } from "../../src/ts-compiler/ScopePathSet";
|
|
6
7
|
|
|
7
8
|
function createMockProgram(sources: Record<string, string>) {
|
|
8
9
|
const fileNames = Object.keys(sources);
|
|
@@ -14,6 +15,11 @@ function createMockProgram(sources: Record<string, string>) {
|
|
|
14
15
|
if (!sourceText) return undefined;
|
|
15
16
|
return ts.createSourceFile(fileName, sourceText, languageVersion);
|
|
16
17
|
};
|
|
18
|
+
const moduleResolutionCache = ts.createModuleResolutionCache(
|
|
19
|
+
compilerHost.getCurrentDirectory(),
|
|
20
|
+
(x) => x,
|
|
21
|
+
);
|
|
22
|
+
compilerHost.getModuleResolutionCache = () => moduleResolutionCache;
|
|
17
23
|
const program = ts.createProgram(fileNames, {}, compilerHost);
|
|
18
24
|
return { program, compilerHost };
|
|
19
25
|
}
|
|
@@ -22,8 +28,27 @@ function norm(path: string): TNormPath {
|
|
|
22
28
|
return PathUtils.norm(path);
|
|
23
29
|
}
|
|
24
30
|
|
|
31
|
+
function getAffectedFileSet(depCache: SdDepCache, modifiedSet: Set<TNormPath>): Set<TNormPath> {
|
|
32
|
+
const map = depCache.getAffectedFileMap(modifiedSet);
|
|
33
|
+
const result = new Set<TNormPath>();
|
|
34
|
+
for (const set of map.values()) {
|
|
35
|
+
for (const p of set) result.add(p);
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function createCache(dep: SdDepCache) {
|
|
41
|
+
return {
|
|
42
|
+
dep,
|
|
43
|
+
type: new WeakMap<ts.Node, ts.Type | undefined>(),
|
|
44
|
+
prop: new WeakMap<ts.Type, Map<string, ts.Symbol | undefined>>(),
|
|
45
|
+
declFiles: new WeakMap<ts.Symbol, TNormPath[]>(),
|
|
46
|
+
ngOrg: new Map<TNormPath, ts.SourceFile>(),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
25
50
|
describe("DependencyAnalyzer", () => {
|
|
26
|
-
const
|
|
51
|
+
const scopePathSet = new ScopePathSet([PathUtils.norm("/")]);
|
|
27
52
|
let depCache: SdDepCache;
|
|
28
53
|
|
|
29
54
|
beforeEach(() => {
|
|
@@ -37,9 +62,9 @@ describe("DependencyAnalyzer", () => {
|
|
|
37
62
|
};
|
|
38
63
|
|
|
39
64
|
const { program, compilerHost } = createMockProgram(files);
|
|
40
|
-
SdDepAnalyzer.analyze(program, compilerHost,
|
|
65
|
+
SdDepAnalyzer.analyze(program, compilerHost, scopePathSet, createCache(depCache));
|
|
41
66
|
|
|
42
|
-
const result =
|
|
67
|
+
const result = getAffectedFileSet(depCache, new Set([norm("/a.ts")]));
|
|
43
68
|
expect(result).toEqual(new Set([norm("/a.ts"), norm("/b.ts")]));
|
|
44
69
|
});
|
|
45
70
|
|
|
@@ -51,21 +76,17 @@ describe("DependencyAnalyzer", () => {
|
|
|
51
76
|
};
|
|
52
77
|
|
|
53
78
|
const { program, compilerHost } = createMockProgram(files);
|
|
54
|
-
SdDepAnalyzer.analyze(program, compilerHost,
|
|
79
|
+
SdDepAnalyzer.analyze(program, compilerHost, scopePathSet, createCache(depCache));
|
|
55
80
|
|
|
56
81
|
{
|
|
57
|
-
const result =
|
|
82
|
+
const result = getAffectedFileSet(depCache, new Set([norm("/a.ts")]));
|
|
58
83
|
expect(result).toEqual(
|
|
59
|
-
new Set([
|
|
60
|
-
norm("/a.ts"),
|
|
61
|
-
norm("/b.ts"), // a파일이 사라지거나 하면 b가 오류를 뱉어야 하므로
|
|
62
|
-
norm("/c.ts"),
|
|
63
|
-
]),
|
|
84
|
+
new Set([norm("/a.ts"), norm("/b.ts"), norm("/c.ts")]),
|
|
64
85
|
);
|
|
65
86
|
}
|
|
66
87
|
|
|
67
88
|
{
|
|
68
|
-
const result =
|
|
89
|
+
const result = getAffectedFileSet(depCache, new Set([norm("/b.ts")]));
|
|
69
90
|
expect(result).toEqual(new Set([norm("/b.ts"), norm("/c.ts")]));
|
|
70
91
|
}
|
|
71
92
|
});
|
|
@@ -78,21 +99,17 @@ describe("DependencyAnalyzer", () => {
|
|
|
78
99
|
};
|
|
79
100
|
|
|
80
101
|
const { program, compilerHost } = createMockProgram(files);
|
|
81
|
-
SdDepAnalyzer.analyze(program, compilerHost,
|
|
102
|
+
SdDepAnalyzer.analyze(program, compilerHost, scopePathSet, createCache(depCache));
|
|
82
103
|
|
|
83
104
|
{
|
|
84
|
-
const result =
|
|
105
|
+
const result = getAffectedFileSet(depCache, new Set([norm("/a.ts")]));
|
|
85
106
|
expect(result).toEqual(
|
|
86
|
-
new Set([
|
|
87
|
-
norm("/a.ts"),
|
|
88
|
-
norm("/b.ts"), // a파일이 사라지거나 하면 b가 오류를 뱉어야 하므로
|
|
89
|
-
norm("/c.ts"),
|
|
90
|
-
]),
|
|
107
|
+
new Set([norm("/a.ts"), norm("/b.ts"), norm("/c.ts")]),
|
|
91
108
|
);
|
|
92
109
|
}
|
|
93
110
|
|
|
94
111
|
{
|
|
95
|
-
const result =
|
|
112
|
+
const result = getAffectedFileSet(depCache, new Set([norm("/b.ts")]));
|
|
96
113
|
expect(result).toEqual(new Set([norm("/b.ts"), norm("/c.ts")]));
|
|
97
114
|
}
|
|
98
115
|
});
|
|
@@ -106,15 +123,15 @@ describe("DependencyAnalyzer", () => {
|
|
|
106
123
|
};
|
|
107
124
|
|
|
108
125
|
const { program, compilerHost } = createMockProgram(files);
|
|
109
|
-
SdDepAnalyzer.analyze(program, compilerHost,
|
|
126
|
+
SdDepAnalyzer.analyze(program, compilerHost, scopePathSet, createCache(depCache));
|
|
110
127
|
|
|
111
128
|
{
|
|
112
|
-
const result =
|
|
129
|
+
const result = getAffectedFileSet(depCache, new Set([norm("/a.ts")]));
|
|
113
130
|
expect(result).toEqual(new Set([norm("/a.ts"), norm("/b.ts"), norm("/c.ts")]));
|
|
114
131
|
}
|
|
115
132
|
|
|
116
133
|
{
|
|
117
|
-
const result =
|
|
134
|
+
const result = getAffectedFileSet(depCache, new Set([norm("/a_1.ts")]));
|
|
118
135
|
expect(result).toEqual(new Set([norm("/a_1.ts"), norm("/b.ts")]));
|
|
119
136
|
}
|
|
120
137
|
});
|
|
@@ -134,19 +151,17 @@ describe("DependencyAnalyzer", () => {
|
|
|
134
151
|
`,
|
|
135
152
|
"/c.ts": `
|
|
136
153
|
import { A } from "./a";
|
|
137
|
-
|
|
154
|
+
|
|
138
155
|
function doSomething() {
|
|
139
|
-
// A.b.c 속성 접근을 통해 B에 간접 의존
|
|
140
156
|
console.log(new A().b.c);
|
|
141
157
|
}
|
|
142
158
|
`,
|
|
143
159
|
};
|
|
144
160
|
|
|
145
161
|
const { program, compilerHost } = createMockProgram(files);
|
|
146
|
-
SdDepAnalyzer.analyze(program, compilerHost,
|
|
162
|
+
SdDepAnalyzer.analyze(program, compilerHost, scopePathSet, createCache(depCache));
|
|
147
163
|
|
|
148
|
-
|
|
149
|
-
const result = depCache.getAffectedFileSet(new Set([norm("/b.ts")]));
|
|
164
|
+
const result = getAffectedFileSet(depCache, new Set([norm("/b.ts")]));
|
|
150
165
|
expect(result).toEqual(new Set([norm("/a.ts"), norm("/b.ts"), norm("/c.ts")]));
|
|
151
166
|
});
|
|
152
167
|
|
|
@@ -165,19 +180,17 @@ describe("DependencyAnalyzer", () => {
|
|
|
165
180
|
`,
|
|
166
181
|
"/c.ts": `
|
|
167
182
|
import { A } from "./a";
|
|
168
|
-
|
|
183
|
+
|
|
169
184
|
function doSomething() {
|
|
170
|
-
// A['b'].c 속성 접근을 통해 B에 간접 의존
|
|
171
185
|
console.log(new A()['b'].c);
|
|
172
186
|
}
|
|
173
187
|
`,
|
|
174
188
|
};
|
|
175
189
|
|
|
176
190
|
const { program, compilerHost } = createMockProgram(files);
|
|
177
|
-
SdDepAnalyzer.analyze(program, compilerHost,
|
|
191
|
+
SdDepAnalyzer.analyze(program, compilerHost, scopePathSet, createCache(depCache));
|
|
178
192
|
|
|
179
|
-
|
|
180
|
-
const result = depCache.getAffectedFileSet(new Set([norm("/b.ts")]));
|
|
193
|
+
const result = getAffectedFileSet(depCache, new Set([norm("/b.ts")]));
|
|
181
194
|
expect(result).toEqual(new Set([norm("/a.ts"), norm("/b.ts"), norm("/c.ts")]));
|
|
182
195
|
});
|
|
183
196
|
|
|
@@ -188,9 +201,9 @@ describe("DependencyAnalyzer", () => {
|
|
|
188
201
|
};
|
|
189
202
|
|
|
190
203
|
const { program, compilerHost } = createMockProgram(files);
|
|
191
|
-
SdDepAnalyzer.analyze(program, compilerHost,
|
|
204
|
+
SdDepAnalyzer.analyze(program, compilerHost, scopePathSet, createCache(depCache));
|
|
192
205
|
|
|
193
|
-
const result =
|
|
206
|
+
const result = getAffectedFileSet(depCache, new Set([norm("/a.ts")]));
|
|
194
207
|
expect(result).toEqual(new Set([norm("/a.ts"), norm("/b.ts")]));
|
|
195
208
|
});
|
|
196
209
|
|
|
@@ -202,9 +215,9 @@ describe("DependencyAnalyzer", () => {
|
|
|
202
215
|
};
|
|
203
216
|
|
|
204
217
|
const { program, compilerHost } = createMockProgram(files);
|
|
205
|
-
SdDepAnalyzer.analyze(program, compilerHost,
|
|
218
|
+
SdDepAnalyzer.analyze(program, compilerHost, scopePathSet, createCache(depCache));
|
|
206
219
|
|
|
207
|
-
const result =
|
|
220
|
+
const result = getAffectedFileSet(depCache, new Set([norm("/a.ts")]));
|
|
208
221
|
expect(result).toEqual(new Set([norm("/a.ts"), norm("/b.ts"), norm("/c.ts")]));
|
|
209
222
|
});
|
|
210
223
|
|
|
@@ -215,13 +228,13 @@ describe("DependencyAnalyzer", () => {
|
|
|
215
228
|
};
|
|
216
229
|
|
|
217
230
|
const { program, compilerHost } = createMockProgram(files);
|
|
218
|
-
SdDepAnalyzer.analyze(program, compilerHost,
|
|
231
|
+
SdDepAnalyzer.analyze(program, compilerHost, scopePathSet, createCache(depCache));
|
|
219
232
|
|
|
220
|
-
const result =
|
|
233
|
+
const result = getAffectedFileSet(depCache, new Set([norm("/a.ts")]));
|
|
221
234
|
expect(result).toEqual(new Set([norm("/a.ts"), norm("/b.ts")]));
|
|
222
235
|
});
|
|
223
236
|
|
|
224
|
-
it("invalidates()는
|
|
237
|
+
it("invalidates()는 해당 파일의 자체 분석 데이터를 제거한다", () => {
|
|
225
238
|
const files = {
|
|
226
239
|
"/a.ts": `export const A = 1;`,
|
|
227
240
|
"/b.ts": `import { A } from "./a";`,
|
|
@@ -229,13 +242,12 @@ describe("DependencyAnalyzer", () => {
|
|
|
229
242
|
};
|
|
230
243
|
|
|
231
244
|
const { program, compilerHost } = createMockProgram(files);
|
|
232
|
-
SdDepAnalyzer.analyze(program, compilerHost,
|
|
245
|
+
SdDepAnalyzer.analyze(program, compilerHost, scopePathSet, createCache(depCache));
|
|
233
246
|
|
|
234
|
-
// invalidate 처리
|
|
235
247
|
depCache.invalidates(new Set([norm("/a.ts")]));
|
|
236
248
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
expect(
|
|
249
|
+
// a의 자체 분석 데이터가 제거됨 (재분석 필요 상태)
|
|
250
|
+
expect(depCache["_exportCache"].has(norm("/a.ts"))).toBe(false);
|
|
251
|
+
expect(depCache["_collectedCache"].has(norm("/a.ts"))).toBe(false);
|
|
240
252
|
});
|
|
241
253
|
});
|
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
import "@simplysm/sd-core-common";
|
|
2
|
-
import { PathUtils } from "@simplysm/sd-core-node";
|
|
2
|
+
import { PathUtils, TNormPath } from "@simplysm/sd-core-node";
|
|
3
3
|
import { beforeEach, describe, expect, it } from "vitest";
|
|
4
|
-
import {
|
|
4
|
+
import { SdDepCache } from "../../src/ts-compiler/SdDepCache";
|
|
5
|
+
|
|
6
|
+
function getAffectedFileSet(depCache: SdDepCache, modifiedSet: Set<TNormPath>): Set<TNormPath> {
|
|
7
|
+
const map = depCache.getAffectedFileMap(modifiedSet);
|
|
8
|
+
const result = new Set<TNormPath>();
|
|
9
|
+
for (const set of map.values()) {
|
|
10
|
+
for (const p of set) result.add(p);
|
|
11
|
+
}
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
5
14
|
|
|
6
15
|
describe("SdDependencyCache", () => {
|
|
7
16
|
const a = PathUtils.norm("/a.ts");
|
|
8
17
|
const b = PathUtils.norm("/b.ts");
|
|
9
18
|
const c = PathUtils.norm("/c.ts");
|
|
10
19
|
const html = PathUtils.norm("/comp.html");
|
|
11
|
-
// const style = PathUtils.norm("/style.scss");
|
|
12
20
|
|
|
13
21
|
let depCache: SdDepCache;
|
|
14
22
|
|
|
@@ -17,75 +25,55 @@ describe("SdDependencyCache", () => {
|
|
|
17
25
|
});
|
|
18
26
|
|
|
19
27
|
it("export * from 구문으로 재export된 심볼이 정확히 전파된다", () => {
|
|
20
|
-
// a.ts → export const A
|
|
21
28
|
depCache.addExport(a, "A");
|
|
22
|
-
|
|
23
|
-
// b.ts → export * from './a.ts'
|
|
24
29
|
depCache.addReexport(b, a, 0);
|
|
25
|
-
|
|
26
|
-
// c.ts → import { A } from './b.ts'
|
|
27
30
|
depCache.addImport(c, b, "A");
|
|
28
31
|
|
|
29
|
-
const result =
|
|
32
|
+
const result = getAffectedFileSet(depCache, new Set([a]));
|
|
30
33
|
expect(result).toEqual(new Set([a, b, c]));
|
|
31
34
|
});
|
|
32
35
|
|
|
33
36
|
it("export { A as B } from 구문으로 이름이 바뀐 심볼도 추적된다", () => {
|
|
34
|
-
// a.ts → export const A
|
|
35
37
|
depCache.addExport(a, "A");
|
|
36
|
-
|
|
37
|
-
// b.ts → export { A as B } from './a.ts'
|
|
38
38
|
depCache.addReexport(b, a, {
|
|
39
39
|
importSymbol: "A",
|
|
40
40
|
exportSymbol: "B",
|
|
41
41
|
});
|
|
42
|
-
|
|
43
|
-
// c.ts → import { B } from './b.ts'
|
|
44
42
|
depCache.addImport(c, b, "B");
|
|
45
43
|
|
|
46
|
-
const result =
|
|
44
|
+
const result = getAffectedFileSet(depCache, new Set([a]));
|
|
47
45
|
expect(result).toEqual(new Set([a, b, c]));
|
|
48
46
|
});
|
|
49
47
|
|
|
50
48
|
it("import { X } 구문은 정확히 사용한 심볼만 추적한다", () => {
|
|
51
|
-
// b.ts → export const Foo
|
|
52
49
|
depCache.addExport(b, "Foo");
|
|
53
|
-
|
|
54
|
-
// a.ts → import { Foo } from './b.ts'
|
|
55
50
|
depCache.addImport(a, b, "Foo");
|
|
56
51
|
|
|
57
|
-
const result =
|
|
52
|
+
const result = getAffectedFileSet(depCache, new Set([b]));
|
|
58
53
|
expect(result).toEqual(new Set([b, a]));
|
|
59
54
|
});
|
|
60
55
|
|
|
61
56
|
it("import * (namespace import)은 모든 export의 영향을 받는다", () => {
|
|
62
|
-
// b.ts → export const Bar
|
|
63
57
|
depCache.addExport(b, "Bar");
|
|
64
|
-
|
|
65
|
-
// a.ts → import * as B from './b.ts'
|
|
66
58
|
depCache.addImport(a, b, 0);
|
|
67
59
|
|
|
68
|
-
const result =
|
|
60
|
+
const result = getAffectedFileSet(depCache, new Set([b]));
|
|
69
61
|
expect(result).toEqual(new Set([b, a]));
|
|
70
62
|
});
|
|
71
63
|
|
|
72
64
|
it("심볼이 일치하지 않으면 영향이 전파되지 않는다", () => {
|
|
73
|
-
// b.ts → export const Foo
|
|
74
65
|
depCache.addExport(b, "Foo");
|
|
75
|
-
|
|
76
|
-
// a.ts → import { NotFoo } from './b.ts'
|
|
77
66
|
depCache.addImport(a, b, "NotFoo");
|
|
78
67
|
|
|
79
|
-
const result =
|
|
80
|
-
expect(result).toEqual(new Set([b]));
|
|
68
|
+
const result = getAffectedFileSet(depCache, new Set([b]));
|
|
69
|
+
expect(result).toEqual(new Set([b]));
|
|
81
70
|
});
|
|
82
71
|
|
|
83
72
|
it("리소스 의존은 역의존만 추적되고 심볼 전파는 없다", () => {
|
|
84
|
-
// a.ts → templateUrl: "comp.html"
|
|
85
73
|
depCache.addImport(a, html, 0);
|
|
86
74
|
|
|
87
|
-
const result =
|
|
88
|
-
expect(result).toEqual(new Set([html, a]));
|
|
75
|
+
const result = getAffectedFileSet(depCache, new Set([html]));
|
|
76
|
+
expect(result).toEqual(new Set([html, a]));
|
|
89
77
|
});
|
|
90
78
|
|
|
91
79
|
it("reexport 후 재import된 경로의 역의존도 정확히 추적된다", () => {
|
|
@@ -93,50 +81,20 @@ describe("SdDependencyCache", () => {
|
|
|
93
81
|
depCache.addReexport(b, a, { importSymbol: "X", exportSymbol: "Y" });
|
|
94
82
|
depCache.addImport(c, b, "Y");
|
|
95
83
|
|
|
96
|
-
const result =
|
|
84
|
+
const result = getAffectedFileSet(depCache, new Set([a]));
|
|
97
85
|
expect(result).toEqual(new Set([a, b, c]));
|
|
98
86
|
});
|
|
99
87
|
|
|
100
|
-
it("invalidates()는
|
|
88
|
+
it("invalidates()는 해당 파일의 자체 분석 데이터를 제거한다", () => {
|
|
101
89
|
depCache.addExport(a, "X");
|
|
102
90
|
depCache.addImport(b, a, "X");
|
|
103
91
|
|
|
104
92
|
depCache.invalidates(new Set([a]));
|
|
105
93
|
|
|
106
|
-
//
|
|
94
|
+
// a의 자체 export/import/collected 캐시가 제거됨
|
|
107
95
|
expect(depCache["_exportCache"].has(a)).toBe(false);
|
|
108
|
-
expect(depCache["
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
it("getAffectedFileTree()는 영향도를 트리 형태로 표현한다", () => {
|
|
112
|
-
// a.ts → export A
|
|
113
|
-
depCache.addExport(a, "A");
|
|
114
|
-
|
|
115
|
-
// b.ts → export { A as B } from './a.ts'
|
|
116
|
-
depCache.addReexport(b, a, {
|
|
117
|
-
importSymbol: "A",
|
|
118
|
-
exportSymbol: "B",
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
// c.ts → import { B } from './b.ts'
|
|
122
|
-
depCache.addImport(c, b, "B");
|
|
123
|
-
|
|
124
|
-
const trees = depCache.getAffectedFileTree(new Set([a]));
|
|
125
|
-
|
|
126
|
-
expect(trees.length).toBeGreaterThan(0);
|
|
127
|
-
const aNode = trees.find((t) => t.fileNPath === a)!;
|
|
128
|
-
expect(aNode.children.some((c1) => c1.fileNPath === b)).toBeTruthy();
|
|
129
|
-
const bNode = aNode.children.find((c1) => c1.fileNPath === b)!;
|
|
130
|
-
expect(bNode.children.some((c2) => c2.fileNPath === c)).toBeTruthy();
|
|
131
|
-
|
|
132
|
-
const printTree = (node: ISdAffectedFileTreeNode, indent = "") => {
|
|
133
|
-
console.log(indent + node.fileNPath);
|
|
134
|
-
for (const child of node.children) {
|
|
135
|
-
printTree(child, indent + " ");
|
|
136
|
-
}
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
console.log(printTree(trees[0]));
|
|
96
|
+
expect(depCache["_importCache"].has(a)).toBe(false);
|
|
97
|
+
expect(depCache["_collectedCache"].has(a)).toBe(false);
|
|
140
98
|
});
|
|
141
99
|
|
|
142
100
|
it("d.ts를 입력하면 js도 함께 영향을 받는다", () => {
|
|
@@ -145,9 +103,9 @@ describe("SdDependencyCache", () => {
|
|
|
145
103
|
const consumer = PathUtils.norm("/consumer.ts");
|
|
146
104
|
|
|
147
105
|
depCache.addExport(dts, "Foo");
|
|
148
|
-
depCache.addImport(consumer, js, "Foo");
|
|
106
|
+
depCache.addImport(consumer, js, "Foo");
|
|
149
107
|
|
|
150
|
-
const result =
|
|
108
|
+
const result = getAffectedFileSet(depCache, new Set([dts]));
|
|
151
109
|
expect(result).toEqual(new Set([dts, js, consumer]));
|
|
152
110
|
});
|
|
153
111
|
});
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
// =============================================================
|
|
2
|
-
// Chrome 61+ Polyfills
|
|
3
|
-
// core-js는 feature detection 기반 — 이미 지원하는 기능은 skip
|
|
4
|
-
// sd-cli가 Angular 클라이언트 빌드 시 자동 주입
|
|
5
|
-
// =============================================================
|
|
6
|
-
|
|
7
|
-
// -- ES2018 --
|
|
8
|
-
import "core-js/actual/promise/finally";
|
|
9
|
-
|
|
10
|
-
// -- ES2019 --
|
|
11
|
-
import "core-js/actual/array/flat";
|
|
12
|
-
import "core-js/actual/array/flat-map";
|
|
13
|
-
import "core-js/actual/object/from-entries";
|
|
14
|
-
import "core-js/actual/string/trim-start";
|
|
15
|
-
import "core-js/actual/string/trim-end";
|
|
16
|
-
|
|
17
|
-
// -- ES2020 --
|
|
18
|
-
import "core-js/actual/global-this";
|
|
19
|
-
import "core-js/actual/string/match-all";
|
|
20
|
-
import "core-js/actual/promise/all-settled";
|
|
21
|
-
|
|
22
|
-
// -- ES2021 --
|
|
23
|
-
import "core-js/actual/promise/any";
|
|
24
|
-
import "core-js/actual/aggregate-error";
|
|
25
|
-
import "core-js/actual/string/replace-all";
|
|
26
|
-
import "core-js/actual/weak-ref";
|
|
27
|
-
import "core-js/actual/finalization-registry";
|
|
28
|
-
|
|
29
|
-
// -- ES2022 --
|
|
30
|
-
import "core-js/actual/array/at";
|
|
31
|
-
import "core-js/actual/string/at";
|
|
32
|
-
import "core-js/actual/object/has-own";
|
|
33
|
-
import "core-js/actual/error/cause";
|
|
34
|
-
|
|
35
|
-
// -- Web APIs --
|
|
36
|
-
import "core-js/actual/queue-microtask";
|
|
37
|
-
import "core-js/actual/structured-clone";
|
|
38
|
-
|
|
39
|
-
// -- AbortController (Chrome 66+) --
|
|
40
|
-
import "abortcontroller-polyfill/dist/abortcontroller-polyfill-only";
|
|
41
|
-
|
|
42
|
-
// -- ResizeObserver (Chrome 64+) --
|
|
43
|
-
import ResizeObserver from "resize-observer-polyfill";
|
|
44
|
-
if (typeof window !== "undefined" && !("ResizeObserver" in window)) {
|
|
45
|
-
window.ResizeObserver = ResizeObserver;
|
|
46
|
-
}
|