@simplysm/sd-cli 14.0.44 → 14.0.46
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/deps/server-externals/server-production-files.d.ts +10 -5
- package/dist/deps/server-externals/server-production-files.d.ts.map +1 -1
- package/dist/deps/server-externals/server-production-files.js +27 -29
- package/dist/deps/server-externals/server-production-files.js.map +1 -1
- package/dist/esbuild/esbuild-angular-compiler-plugin.d.ts +3 -8
- package/dist/esbuild/esbuild-angular-compiler-plugin.d.ts.map +1 -1
- package/dist/esbuild/esbuild-angular-compiler-plugin.js +57 -83
- package/dist/esbuild/esbuild-angular-compiler-plugin.js.map +1 -1
- package/dist/esbuild/esbuild-config.d.ts.map +1 -1
- package/dist/esbuild/esbuild-config.js +2 -5
- package/dist/esbuild/esbuild-config.js.map +1 -1
- package/dist/esbuild/esbuild-worker-plugin.d.ts +52 -0
- package/dist/esbuild/esbuild-worker-plugin.d.ts.map +1 -0
- package/dist/esbuild/esbuild-worker-plugin.js +319 -0
- package/dist/esbuild/esbuild-worker-plugin.js.map +1 -0
- package/dist/utils/output-path-rewriter.d.ts +5 -2
- package/dist/utils/output-path-rewriter.d.ts.map +1 -1
- package/dist/utils/output-path-rewriter.js +60 -12
- package/dist/utils/output-path-rewriter.js.map +1 -1
- package/dist/workers/server-build.worker.d.ts.map +1 -1
- package/dist/workers/server-build.worker.js +6 -5
- package/dist/workers/server-build.worker.js.map +1 -1
- package/dist/workers/server-esbuild-context.d.ts.map +1 -1
- package/dist/workers/server-esbuild-context.js +3 -1
- package/dist/workers/server-esbuild-context.js.map +1 -1
- package/dist/workers/server-watch-manager.js +1 -1
- package/dist/workers/server-watch-manager.js.map +1 -1
- package/package.json +7 -4
- package/src/deps/server-externals/server-production-files.ts +31 -31
- package/src/esbuild/esbuild-angular-compiler-plugin.ts +82 -123
- package/src/esbuild/esbuild-config.ts +2 -7
- package/src/esbuild/esbuild-worker-plugin.ts +419 -0
- package/src/utils/output-path-rewriter.ts +65 -17
- package/src/workers/server-build.worker.ts +6 -5
- package/src/workers/server-esbuild-context.ts +3 -1
- package/src/workers/server-watch-manager.ts +1 -1
- package/tests/deps/server-externals/mise-toml-parse-intent.verify.md +16 -0
- package/tests/esbuild/esbuild-angular-compiler-plugin-worker.verify.md +56 -28
- package/tests/esbuild/esbuild-worker-plugin-node.verify.md +11 -0
- package/tests/esbuild/esbuild-worker-plugin.acc.spec.ts +318 -0
- package/tests/esbuild/esbuild-worker-plugin.spec.ts +606 -0
- package/tests/esbuild/esbuild-worker-plugin.verify.md +7 -0
- package/tests/esbuild/fixtures/worker-plugin/node-worker.js +2 -0
- package/tests/esbuild/fixtures/worker-plugin/shared-worker.js +6 -0
- package/tests/esbuild/fixtures/worker-plugin/worker-error.js +1 -0
- package/tests/esbuild/fixtures/worker-plugin/worker.js +3 -0
- package/tests/esbuild/fixtures/worker-plugin/worker2.js +3 -0
- package/tests/workers/server-build-worker-plugin.verify.md +9 -0
- package/tests/workers/server-build-worker.spec.ts +26 -12
- package/tests/workers/server-esbuild-context.spec.ts +13 -5
- package/tests/workers/server-watch-manager.acc.spec.ts +2 -2
- package/tests/workers/server-watch-manager.spec.ts +2 -2
- package/dist/angular/web-worker-transformer.d.ts +0 -9
- package/dist/angular/web-worker-transformer.d.ts.map +0 -1
- package/dist/angular/web-worker-transformer.js +0 -73
- package/dist/angular/web-worker-transformer.js.map +0 -1
- package/src/angular/web-worker-transformer.ts +0 -117
- package/tests/angular/web-worker-transformer.spec.ts +0 -154
|
@@ -246,6 +246,20 @@ describe("server-build.worker build()", () => {
|
|
|
246
246
|
});
|
|
247
247
|
});
|
|
248
248
|
|
|
249
|
+
// Acceptance: production build includes worker bundle plugin
|
|
250
|
+
it("includes worker bundle plugin in esbuild.build() plugins", async () => {
|
|
251
|
+
await workerFns["build"](baseBuildInfo);
|
|
252
|
+
|
|
253
|
+
expect(esbuild.build).toHaveBeenCalledWith(
|
|
254
|
+
expect.objectContaining({
|
|
255
|
+
plugins: [
|
|
256
|
+
expect.objectContaining({ name: "sd-worker-bundle" }),
|
|
257
|
+
mockTscPlugin.plugin,
|
|
258
|
+
],
|
|
259
|
+
}),
|
|
260
|
+
);
|
|
261
|
+
});
|
|
262
|
+
|
|
249
263
|
// Acceptance: esbuild + typecheck parallel execution
|
|
250
264
|
it("runs esbuild and tsc in parallel for server build", async () => {
|
|
251
265
|
const result = await workerFns["build"](baseBuildInfo);
|
|
@@ -442,7 +456,7 @@ describe("server-build.worker build()", () => {
|
|
|
442
456
|
expect(pkg.volta.node).toBe("v20.11.0");
|
|
443
457
|
});
|
|
444
458
|
|
|
445
|
-
it("
|
|
459
|
+
it("includes nativeModules and manual externals in dist/package.json but excludes missing optional peer deps", async () => {
|
|
446
460
|
vi.mocked(collectAllDependencyExternals).mockReturnValue({
|
|
447
461
|
optionalPeerDeps: ["opt-dep"],
|
|
448
462
|
nativeModules: ["native-mod"],
|
|
@@ -451,9 +465,6 @@ describe("server-build.worker build()", () => {
|
|
|
451
465
|
mockLockfileContent = [
|
|
452
466
|
"packages:",
|
|
453
467
|
"",
|
|
454
|
-
" 'opt-dep@1.2.3':",
|
|
455
|
-
" resolution: {integrity: sha512-a}",
|
|
456
|
-
"",
|
|
457
468
|
" 'native-mod@2.4.0':",
|
|
458
469
|
" resolution: {integrity: sha512-b}",
|
|
459
470
|
"",
|
|
@@ -473,16 +484,16 @@ describe("server-build.worker build()", () => {
|
|
|
473
484
|
|
|
474
485
|
const pkgJsonPath = path.join(baseBuildInfo.pkgDir, "dist", "package.json");
|
|
475
486
|
const pkg = JSON.parse(writtenFiles.get(pkgJsonPath)!);
|
|
476
|
-
expect(pkg.dependencies["opt-dep"]).
|
|
487
|
+
expect(pkg.dependencies["opt-dep"]).toBeUndefined();
|
|
477
488
|
expect(pkg.dependencies["native-mod"]).toBe("2.4.0");
|
|
478
489
|
expect(pkg.dependencies["manual-ext"]).toBe("3.1.0");
|
|
479
490
|
});
|
|
480
491
|
|
|
481
|
-
// Unit: reports error
|
|
492
|
+
// Unit: reports error when a nativeModule/manual external is missing from lockfile
|
|
482
493
|
it("reports error for external dependency not in lockfile", async () => {
|
|
483
494
|
vi.mocked(collectAllDependencyExternals).mockReturnValue({
|
|
484
|
-
optionalPeerDeps: [
|
|
485
|
-
nativeModules: [],
|
|
495
|
+
optionalPeerDeps: [],
|
|
496
|
+
nativeModules: ["unknown-native"],
|
|
486
497
|
});
|
|
487
498
|
|
|
488
499
|
mockLockfileContent = [
|
|
@@ -499,7 +510,7 @@ describe("server-build.worker build()", () => {
|
|
|
499
510
|
|
|
500
511
|
const result = await workerFns["build"](baseBuildInfo);
|
|
501
512
|
expect(result.build.success).toBe(false);
|
|
502
|
-
expect(result.build.errors[0]).toContain("unknown-
|
|
513
|
+
expect(result.build.errors[0]).toContain("unknown-native");
|
|
503
514
|
expect(result.build.errors[0]).toContain("not found in pnpm-lock.yaml");
|
|
504
515
|
});
|
|
505
516
|
|
|
@@ -699,8 +710,8 @@ describe("server-build.worker startWatch()", () => {
|
|
|
699
710
|
}));
|
|
700
711
|
});
|
|
701
712
|
|
|
702
|
-
// Acceptance: startWatch passes tsc options to createContext
|
|
703
|
-
it("passes tsc
|
|
713
|
+
// Acceptance: startWatch passes tsc options to createContext with worker plugin
|
|
714
|
+
it("passes worker bundle plugin and tsc plugin to esbuildCtx.createContext", async () => {
|
|
704
715
|
await workerFns["startWatch"]({
|
|
705
716
|
...watchInfo,
|
|
706
717
|
output: { js: true, dts: true, env: "node" as any, includeTests: true },
|
|
@@ -708,7 +719,10 @@ describe("server-build.worker startWatch()", () => {
|
|
|
708
719
|
|
|
709
720
|
expect(esbuild.context).toHaveBeenCalledWith(
|
|
710
721
|
expect.objectContaining({
|
|
711
|
-
plugins: [
|
|
722
|
+
plugins: [
|
|
723
|
+
expect.objectContaining({ name: "sd-worker-bundle" }),
|
|
724
|
+
mockTscPlugin.plugin,
|
|
725
|
+
],
|
|
712
726
|
}),
|
|
713
727
|
);
|
|
714
728
|
});
|
|
@@ -351,7 +351,7 @@ describe("createContext — tsc plugin", () => {
|
|
|
351
351
|
await dispose();
|
|
352
352
|
});
|
|
353
353
|
|
|
354
|
-
it("includes
|
|
354
|
+
it("includes worker bundle plugin and tsc plugin when tsc options provided", async () => {
|
|
355
355
|
await createContext({
|
|
356
356
|
...baseOptions,
|
|
357
357
|
tsc: { cwd: "/workspace", output: { dts: true } },
|
|
@@ -359,17 +359,22 @@ describe("createContext — tsc plugin", () => {
|
|
|
359
359
|
|
|
360
360
|
expect(esbuild.context).toHaveBeenCalledWith(
|
|
361
361
|
expect.objectContaining({
|
|
362
|
-
plugins: [
|
|
362
|
+
plugins: [
|
|
363
|
+
expect.objectContaining({ name: "sd-worker-bundle" }),
|
|
364
|
+
mockTscPlugin.plugin,
|
|
365
|
+
],
|
|
363
366
|
}),
|
|
364
367
|
);
|
|
365
368
|
});
|
|
366
369
|
|
|
367
|
-
it("
|
|
370
|
+
it("includes worker bundle plugin only when no tsc options", async () => {
|
|
368
371
|
await createContext(baseOptions);
|
|
369
372
|
|
|
370
373
|
expect(esbuild.context).toHaveBeenCalledWith(
|
|
371
374
|
expect.objectContaining({
|
|
372
|
-
plugins: [
|
|
375
|
+
plugins: [
|
|
376
|
+
expect.objectContaining({ name: "sd-worker-bundle" }),
|
|
377
|
+
],
|
|
373
378
|
}),
|
|
374
379
|
);
|
|
375
380
|
});
|
|
@@ -389,7 +394,10 @@ describe("createContext — tsc plugin", () => {
|
|
|
389
394
|
|
|
390
395
|
expect(esbuild.context).toHaveBeenCalledWith(
|
|
391
396
|
expect.objectContaining({
|
|
392
|
-
plugins: [
|
|
397
|
+
plugins: [
|
|
398
|
+
expect.objectContaining({ name: "sd-worker-bundle" }),
|
|
399
|
+
mockTscPlugin.plugin,
|
|
400
|
+
],
|
|
393
401
|
}),
|
|
394
402
|
);
|
|
395
403
|
});
|
|
@@ -48,7 +48,7 @@ vi.mock("../../src/utils/tsconfig", () => ({
|
|
|
48
48
|
}));
|
|
49
49
|
|
|
50
50
|
vi.mock("../../src/deps/server-externals/server-production-files", () => ({
|
|
51
|
-
collectAllExternals: vi.fn(() => []),
|
|
51
|
+
collectAllExternals: vi.fn(() => ({ bundleExternals: [], prodDependencies: [] })),
|
|
52
52
|
}));
|
|
53
53
|
|
|
54
54
|
//#endregion
|
|
@@ -93,7 +93,7 @@ describe("startServerWatchLoop", () => {
|
|
|
93
93
|
vi.mocked(esbuildCtx.hasContext).mockReturnValue(true);
|
|
94
94
|
vi.mocked(esbuildCtx.getMetafile).mockReturnValue(undefined);
|
|
95
95
|
vi.mocked(esbuildCtx.recreateContext).mockResolvedValue();
|
|
96
|
-
vi.mocked(collectAllExternals).mockReturnValue([]);
|
|
96
|
+
vi.mocked(collectAllExternals).mockReturnValue({ bundleExternals: [], prodDependencies: [] });
|
|
97
97
|
(mockLogger.debug as unknown as ReturnType<typeof vi.fn>).mockClear();
|
|
98
98
|
});
|
|
99
99
|
|
|
@@ -45,7 +45,7 @@ vi.mock("../../src/utils/tsconfig", () => ({
|
|
|
45
45
|
}));
|
|
46
46
|
|
|
47
47
|
vi.mock("../../src/deps/server-externals/server-production-files", () => ({
|
|
48
|
-
collectAllExternals: vi.fn(() => []),
|
|
48
|
+
collectAllExternals: vi.fn(() => ({ bundleExternals: [], prodDependencies: [] })),
|
|
49
49
|
}));
|
|
50
50
|
|
|
51
51
|
//#endregion
|
|
@@ -89,7 +89,7 @@ describe("startServerWatchLoop", () => {
|
|
|
89
89
|
vi.mocked(esbuildCtx.hasContext).mockReset().mockReturnValue(true);
|
|
90
90
|
vi.mocked(esbuildCtx.getMetafile).mockReset().mockReturnValue(undefined);
|
|
91
91
|
vi.mocked(esbuildCtx.recreateContext).mockReset().mockResolvedValue();
|
|
92
|
-
vi.mocked(collectAllExternals).mockReset().mockReturnValue([]);
|
|
92
|
+
vi.mocked(collectAllExternals).mockReset().mockReturnValue({ bundleExternals: [], prodDependencies: [] });
|
|
93
93
|
(mockLogger.debug as unknown as ReturnType<typeof vi.fn>).mockClear();
|
|
94
94
|
});
|
|
95
95
|
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import ts from "typescript";
|
|
2
|
-
/**
|
|
3
|
-
* Web Worker/SharedWorker의 `new Worker(new URL('path', import.meta.url))` 패턴을
|
|
4
|
-
* 감지하여 fileProcessor로 번들된 경로로 치환하는 TypeScript transformer를 생성한다.
|
|
5
|
-
*
|
|
6
|
-
* @angular/build의 web-worker-transformer.js 원본을 이식한 구현.
|
|
7
|
-
*/
|
|
8
|
-
export declare function createWorkerTransformer(fileProcessor: (workerFile: string, containingFile: string) => string): ts.TransformerFactory<ts.SourceFile>;
|
|
9
|
-
//# sourceMappingURL=web-worker-transformer.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"web-worker-transformer.d.ts","sourceRoot":"","sources":["../../src/angular/web-worker-transformer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,aAAa,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,KAAK,MAAM,GACpE,EAAE,CAAC,kBAAkB,CAAC,EAAE,CAAC,UAAU,CAAC,CA0GtC"}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import ts from "typescript";
|
|
2
|
-
/**
|
|
3
|
-
* Web Worker/SharedWorker의 `new Worker(new URL('path', import.meta.url))` 패턴을
|
|
4
|
-
* 감지하여 fileProcessor로 번들된 경로로 치환하는 TypeScript transformer를 생성한다.
|
|
5
|
-
*
|
|
6
|
-
* @angular/build의 web-worker-transformer.js 원본을 이식한 구현.
|
|
7
|
-
*/
|
|
8
|
-
export function createWorkerTransformer(fileProcessor) {
|
|
9
|
-
return (context) => {
|
|
10
|
-
const nodeFactory = context.factory;
|
|
11
|
-
const visitNode = (node) => {
|
|
12
|
-
// new Worker(...) 또는 new SharedWorker(...) 감지
|
|
13
|
-
if (!ts.isNewExpression(node) ||
|
|
14
|
-
!ts.isIdentifier(node.expression) ||
|
|
15
|
-
(node.expression.text !== "Worker" && node.expression.text !== "SharedWorker")) {
|
|
16
|
-
return ts.visitEachChild(node, visitNode, context);
|
|
17
|
-
}
|
|
18
|
-
// Worker 인자: 1개 또는 2개
|
|
19
|
-
if (node.arguments == null || node.arguments.length < 1 || node.arguments.length > 2) {
|
|
20
|
-
return node;
|
|
21
|
-
}
|
|
22
|
-
// 첫 인자: new URL(...)
|
|
23
|
-
const workerUrlNode = node.arguments[0];
|
|
24
|
-
if (!ts.isNewExpression(workerUrlNode) ||
|
|
25
|
-
!ts.isIdentifier(workerUrlNode.expression) ||
|
|
26
|
-
workerUrlNode.expression.text !== "URL") {
|
|
27
|
-
return node;
|
|
28
|
-
}
|
|
29
|
-
// URL 인자: 정확히 2개
|
|
30
|
-
if (workerUrlNode.arguments == null || workerUrlNode.arguments.length !== 2) {
|
|
31
|
-
return node;
|
|
32
|
-
}
|
|
33
|
-
// URL 첫 인자: 문자열 리터럴
|
|
34
|
-
if (!ts.isStringLiteralLike(workerUrlNode.arguments[0])) {
|
|
35
|
-
return node;
|
|
36
|
-
}
|
|
37
|
-
// URL 둘째 인자: import.meta.url
|
|
38
|
-
const secondArg = workerUrlNode.arguments[1];
|
|
39
|
-
if (!ts.isPropertyAccessExpression(secondArg) ||
|
|
40
|
-
!ts.isMetaProperty(secondArg.expression) ||
|
|
41
|
-
secondArg.name.text !== "url") {
|
|
42
|
-
return node;
|
|
43
|
-
}
|
|
44
|
-
const filePath = workerUrlNode.arguments[0].text;
|
|
45
|
-
const importer = node.getSourceFile().fileName;
|
|
46
|
-
// fileProcessor 호출
|
|
47
|
-
const replacementPath = fileProcessor(filePath, importer);
|
|
48
|
-
// 경로가 변경되지 않았으면 원본 유지
|
|
49
|
-
if (replacementPath === filePath) {
|
|
50
|
-
return node;
|
|
51
|
-
}
|
|
52
|
-
// AST 치환
|
|
53
|
-
return nodeFactory.updateNewExpression(node, node.expression, node.typeArguments, ts.setTextRange(nodeFactory.createNodeArray([
|
|
54
|
-
// URL 인자 치환
|
|
55
|
-
nodeFactory.updateNewExpression(workerUrlNode, workerUrlNode.expression, workerUrlNode.typeArguments, ts.setTextRange(nodeFactory.createNodeArray([nodeFactory.createStringLiteral(replacementPath), workerUrlNode.arguments[1]], workerUrlNode.arguments.hasTrailingComma), workerUrlNode.arguments)),
|
|
56
|
-
// 두 번째 인자: 기존 options가 있으면 유지, 없으면 { type: 'module' } 추가
|
|
57
|
-
node.arguments.length > 1
|
|
58
|
-
? node.arguments[1]
|
|
59
|
-
: nodeFactory.createObjectLiteralExpression([
|
|
60
|
-
nodeFactory.createPropertyAssignment("type", nodeFactory.createStringLiteral("module")),
|
|
61
|
-
]),
|
|
62
|
-
], node.arguments.hasTrailingComma), node.arguments));
|
|
63
|
-
};
|
|
64
|
-
return (sourceFile) => {
|
|
65
|
-
// 'Worker' 문자열이 없으면 변환을 건너뛴다
|
|
66
|
-
if (!sourceFile.text.includes("Worker")) {
|
|
67
|
-
return sourceFile;
|
|
68
|
-
}
|
|
69
|
-
return ts.visitEachChild(sourceFile, visitNode, context);
|
|
70
|
-
};
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
//# sourceMappingURL=web-worker-transformer.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"web-worker-transformer.js","sourceRoot":"","sources":["../../src/angular/web-worker-transformer.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import ts from "typescript";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Web Worker/SharedWorker의 `new Worker(new URL('path', import.meta.url))` 패턴을
|
|
5
|
-
* 감지하여 fileProcessor로 번들된 경로로 치환하는 TypeScript transformer를 생성한다.
|
|
6
|
-
*
|
|
7
|
-
* @angular/build의 web-worker-transformer.js 원본을 이식한 구현.
|
|
8
|
-
*/
|
|
9
|
-
export function createWorkerTransformer(
|
|
10
|
-
fileProcessor: (workerFile: string, containingFile: string) => string,
|
|
11
|
-
): ts.TransformerFactory<ts.SourceFile> {
|
|
12
|
-
return (context) => {
|
|
13
|
-
const nodeFactory = context.factory;
|
|
14
|
-
|
|
15
|
-
const visitNode = (node: ts.Node): ts.Node => {
|
|
16
|
-
// new Worker(...) 또는 new SharedWorker(...) 감지
|
|
17
|
-
if (
|
|
18
|
-
!ts.isNewExpression(node) ||
|
|
19
|
-
!ts.isIdentifier(node.expression) ||
|
|
20
|
-
(node.expression.text !== "Worker" && node.expression.text !== "SharedWorker")
|
|
21
|
-
) {
|
|
22
|
-
return ts.visitEachChild(node, visitNode, context);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Worker 인자: 1개 또는 2개
|
|
26
|
-
if (node.arguments == null || node.arguments.length < 1 || node.arguments.length > 2) {
|
|
27
|
-
return node;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// 첫 인자: new URL(...)
|
|
31
|
-
const workerUrlNode = node.arguments[0];
|
|
32
|
-
if (
|
|
33
|
-
!ts.isNewExpression(workerUrlNode) ||
|
|
34
|
-
!ts.isIdentifier(workerUrlNode.expression) ||
|
|
35
|
-
workerUrlNode.expression.text !== "URL"
|
|
36
|
-
) {
|
|
37
|
-
return node;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// URL 인자: 정확히 2개
|
|
41
|
-
if (workerUrlNode.arguments == null || workerUrlNode.arguments.length !== 2) {
|
|
42
|
-
return node;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// URL 첫 인자: 문자열 리터럴
|
|
46
|
-
if (!ts.isStringLiteralLike(workerUrlNode.arguments[0])) {
|
|
47
|
-
return node;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// URL 둘째 인자: import.meta.url
|
|
51
|
-
const secondArg = workerUrlNode.arguments[1];
|
|
52
|
-
if (
|
|
53
|
-
!ts.isPropertyAccessExpression(secondArg) ||
|
|
54
|
-
!ts.isMetaProperty(secondArg.expression) ||
|
|
55
|
-
secondArg.name.text !== "url"
|
|
56
|
-
) {
|
|
57
|
-
return node;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const filePath = workerUrlNode.arguments[0].text;
|
|
61
|
-
const importer = node.getSourceFile().fileName;
|
|
62
|
-
|
|
63
|
-
// fileProcessor 호출
|
|
64
|
-
const replacementPath = fileProcessor(filePath, importer);
|
|
65
|
-
|
|
66
|
-
// 경로가 변경되지 않았으면 원본 유지
|
|
67
|
-
if (replacementPath === filePath) {
|
|
68
|
-
return node;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// AST 치환
|
|
72
|
-
return nodeFactory.updateNewExpression(
|
|
73
|
-
node,
|
|
74
|
-
node.expression,
|
|
75
|
-
node.typeArguments,
|
|
76
|
-
ts.setTextRange(
|
|
77
|
-
nodeFactory.createNodeArray(
|
|
78
|
-
[
|
|
79
|
-
// URL 인자 치환
|
|
80
|
-
nodeFactory.updateNewExpression(
|
|
81
|
-
workerUrlNode,
|
|
82
|
-
workerUrlNode.expression,
|
|
83
|
-
workerUrlNode.typeArguments,
|
|
84
|
-
ts.setTextRange(
|
|
85
|
-
nodeFactory.createNodeArray(
|
|
86
|
-
[nodeFactory.createStringLiteral(replacementPath), workerUrlNode.arguments[1]],
|
|
87
|
-
workerUrlNode.arguments.hasTrailingComma,
|
|
88
|
-
),
|
|
89
|
-
workerUrlNode.arguments,
|
|
90
|
-
),
|
|
91
|
-
),
|
|
92
|
-
// 두 번째 인자: 기존 options가 있으면 유지, 없으면 { type: 'module' } 추가
|
|
93
|
-
node.arguments.length > 1
|
|
94
|
-
? node.arguments[1]
|
|
95
|
-
: nodeFactory.createObjectLiteralExpression([
|
|
96
|
-
nodeFactory.createPropertyAssignment(
|
|
97
|
-
"type",
|
|
98
|
-
nodeFactory.createStringLiteral("module"),
|
|
99
|
-
),
|
|
100
|
-
]),
|
|
101
|
-
],
|
|
102
|
-
node.arguments.hasTrailingComma,
|
|
103
|
-
),
|
|
104
|
-
node.arguments,
|
|
105
|
-
),
|
|
106
|
-
);
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
return (sourceFile) => {
|
|
110
|
-
// 'Worker' 문자열이 없으면 변환을 건너뛴다
|
|
111
|
-
if (!sourceFile.text.includes("Worker")) {
|
|
112
|
-
return sourceFile;
|
|
113
|
-
}
|
|
114
|
-
return ts.visitEachChild(sourceFile, visitNode, context);
|
|
115
|
-
};
|
|
116
|
-
};
|
|
117
|
-
}
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from "vitest";
|
|
2
|
-
import ts from "typescript";
|
|
3
|
-
|
|
4
|
-
const { createWorkerTransformer } = await import(
|
|
5
|
-
"../../src/angular/web-worker-transformer.js"
|
|
6
|
-
);
|
|
7
|
-
|
|
8
|
-
//#region 헬퍼
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* TypeScript 소스 코드에 transformer를 적용하고 결과 코드를 반환한다.
|
|
12
|
-
*/
|
|
13
|
-
function transformCode(
|
|
14
|
-
code: string,
|
|
15
|
-
fileProcessor: (workerFile: string, containingFile: string) => string,
|
|
16
|
-
fileName = "test.ts",
|
|
17
|
-
): string {
|
|
18
|
-
const sourceFile = ts.createSourceFile(fileName, code, ts.ScriptTarget.ES2022, true);
|
|
19
|
-
const transformer = createWorkerTransformer(fileProcessor);
|
|
20
|
-
const result = ts.transform(sourceFile, [transformer]);
|
|
21
|
-
const printer = ts.createPrinter();
|
|
22
|
-
const output = printer.printFile(result.transformed[0]);
|
|
23
|
-
result.dispose();
|
|
24
|
-
return output;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
//#endregion
|
|
28
|
-
|
|
29
|
-
describe("createWorkerTransformer", () => {
|
|
30
|
-
// Acceptance: Worker 표준 패턴을 감지하여 번들된 경로로 치환한다
|
|
31
|
-
it("Worker 표준 패턴 — URL 치환 + { type: 'module' } 자동 추가", () => {
|
|
32
|
-
const fileProcessor = vi.fn().mockReturnValue("worker-ABCD1234.js");
|
|
33
|
-
const code = `const w = new Worker(new URL('./my-worker.ts', import.meta.url));`;
|
|
34
|
-
|
|
35
|
-
const output = transformCode(code, fileProcessor);
|
|
36
|
-
|
|
37
|
-
expect(fileProcessor).toHaveBeenCalledWith("./my-worker.ts", "test.ts");
|
|
38
|
-
expect(output).toContain('"worker-ABCD1234.js"');
|
|
39
|
-
expect(output).toContain('type');
|
|
40
|
-
expect(output).toContain('"module"');
|
|
41
|
-
expect(output).not.toContain("./my-worker.ts");
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
// Acceptance: SharedWorker도 동일하게 처리한다
|
|
45
|
-
it("SharedWorker도 동일하게 변환한다", () => {
|
|
46
|
-
const fileProcessor = vi.fn().mockReturnValue("worker-SHARED01.js");
|
|
47
|
-
const code = `const sw = new SharedWorker(new URL('./shared.ts', import.meta.url));`;
|
|
48
|
-
|
|
49
|
-
const output = transformCode(code, fileProcessor);
|
|
50
|
-
|
|
51
|
-
expect(fileProcessor).toHaveBeenCalledWith("./shared.ts", "test.ts");
|
|
52
|
-
expect(output).toContain('"worker-SHARED01.js"');
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
// Acceptance: Worker의 기존 options 인자가 있으면 유지한다
|
|
56
|
-
it("기존 options 인자가 있으면 유지한다", () => {
|
|
57
|
-
const fileProcessor = vi.fn().mockReturnValue("worker-OPT00001.js");
|
|
58
|
-
const code = `const w = new Worker(new URL('./w.ts', import.meta.url), { name: 'test' });`;
|
|
59
|
-
|
|
60
|
-
const output = transformCode(code, fileProcessor);
|
|
61
|
-
|
|
62
|
-
expect(output).toContain('"worker-OPT00001.js"');
|
|
63
|
-
expect(output).toContain("name");
|
|
64
|
-
// { type: 'module' } 자동 추가 없이 기존 options가 유지됨
|
|
65
|
-
expect(output).not.toMatch(/type.*module.*name/);
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
describe("createWorkerTransformer — 변환하지 않는 케이스", () => {
|
|
70
|
-
const noopProcessor = vi.fn().mockReturnValue("should-not-appear.js");
|
|
71
|
-
|
|
72
|
-
// URL 패턴이 아닌 Worker 생성
|
|
73
|
-
it("URL 패턴 없이 문자열 인자만 있으면 변환하지 않는다", () => {
|
|
74
|
-
const code = `const w = new Worker('./my-worker.ts');`;
|
|
75
|
-
const output = transformCode(code, noopProcessor);
|
|
76
|
-
|
|
77
|
-
expect(noopProcessor).not.toHaveBeenCalled();
|
|
78
|
-
expect(output).toContain("./my-worker.ts");
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
// import.meta.url이 아닌 URL
|
|
82
|
-
it("import.meta.url이 아닌 URL은 변환하지 않는다", () => {
|
|
83
|
-
const code = `const w = new Worker(new URL('./w.ts', 'http://example.com'));`;
|
|
84
|
-
const output = transformCode(code, noopProcessor);
|
|
85
|
-
|
|
86
|
-
expect(noopProcessor).not.toHaveBeenCalled();
|
|
87
|
-
expect(output).toContain("./w.ts");
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// Worker 인자 없음
|
|
91
|
-
it("Worker 인자가 없으면 변환하지 않는다", () => {
|
|
92
|
-
const code = `const w = new Worker();`;
|
|
93
|
-
transformCode(code, noopProcessor);
|
|
94
|
-
|
|
95
|
-
expect(noopProcessor).not.toHaveBeenCalled();
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
// Worker 인자 3개 이상
|
|
99
|
-
it("Worker 인자가 3개 이상이면 변환하지 않는다", () => {
|
|
100
|
-
const code = `const w = new Worker(new URL('./w.ts', import.meta.url), {}, 'extra');`;
|
|
101
|
-
transformCode(code, noopProcessor);
|
|
102
|
-
|
|
103
|
-
expect(noopProcessor).not.toHaveBeenCalled();
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
// URL 인자가 1개
|
|
107
|
-
it("URL 인자가 1개면 변환하지 않는다", () => {
|
|
108
|
-
const code = `const w = new Worker(new URL('./w.ts'));`;
|
|
109
|
-
transformCode(code, noopProcessor);
|
|
110
|
-
|
|
111
|
-
expect(noopProcessor).not.toHaveBeenCalled();
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
// URL 인자가 3개
|
|
115
|
-
it("URL 인자가 3개면 변환하지 않는다", () => {
|
|
116
|
-
const code = `const w = new Worker(new URL('./w.ts', import.meta.url, 'extra'));`;
|
|
117
|
-
transformCode(code, noopProcessor);
|
|
118
|
-
|
|
119
|
-
expect(noopProcessor).not.toHaveBeenCalled();
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
// 'Worker' 문자열 없으면 skip
|
|
123
|
-
it("소스에 'Worker' 문자열이 없으면 변환을 건너뛴다", () => {
|
|
124
|
-
const code = `const x = 1 + 2;`;
|
|
125
|
-
const output = transformCode(code, noopProcessor);
|
|
126
|
-
|
|
127
|
-
expect(noopProcessor).not.toHaveBeenCalled();
|
|
128
|
-
expect(output).toContain("1 + 2");
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
describe("createWorkerTransformer — 추가 경계 케이스", () => {
|
|
133
|
-
// fileProcessor가 원본과 동일한 경로를 반환하면 변환하지 않는다
|
|
134
|
-
it("fileProcessor가 원본 경로를 그대로 반환하면 AST를 변경하지 않는다", () => {
|
|
135
|
-
const fileProcessor = vi.fn().mockReturnValue("./my-worker.ts");
|
|
136
|
-
const code = `const w = new Worker(new URL('./my-worker.ts', import.meta.url));`;
|
|
137
|
-
|
|
138
|
-
const output = transformCode(code, fileProcessor);
|
|
139
|
-
|
|
140
|
-
expect(fileProcessor).toHaveBeenCalledWith("./my-worker.ts", "test.ts");
|
|
141
|
-
// 원본 경로가 그대로 유지 (변환 없음)
|
|
142
|
-
expect(output).toContain("./my-worker.ts");
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
// 소스에 Worker 문자열은 있지만 new 표현이 아닌 경우
|
|
146
|
-
it("Worker 문자열이 있지만 new 표현이 아니면 변환하지 않는다", () => {
|
|
147
|
-
const fileProcessor = vi.fn();
|
|
148
|
-
const code = `const WorkerName = "test";`;
|
|
149
|
-
|
|
150
|
-
transformCode(code, fileProcessor);
|
|
151
|
-
|
|
152
|
-
expect(fileProcessor).not.toHaveBeenCalled();
|
|
153
|
-
});
|
|
154
|
-
});
|