@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
|
@@ -1,31 +1,59 @@
|
|
|
1
|
-
# Web Worker
|
|
1
|
+
# Web Worker 통합 (Feature 1.2) — LLM 검증
|
|
2
2
|
|
|
3
3
|
## 검증 항목
|
|
4
4
|
|
|
5
|
-
###
|
|
6
|
-
|
|
7
|
-
- [x]
|
|
8
|
-
- [x]
|
|
9
|
-
- [x]
|
|
10
|
-
- [x]
|
|
11
|
-
- [x]
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
- [x]
|
|
19
|
-
- [x]
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
- [x]
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
- [x]
|
|
28
|
-
- [x]
|
|
29
|
-
- [x]
|
|
30
|
-
- [x]
|
|
31
|
-
|
|
5
|
+
### 제거된 코드
|
|
6
|
+
|
|
7
|
+
- [x] `createWorkerTransformer` import가 제거됨: `esbuild-angular-compiler-plugin.ts`에 `web-worker-transformer.js`로부터의 import가 존재하지 않음
|
|
8
|
+
- [x] `AdditionalResult` 인터페이스 선언이 제거됨
|
|
9
|
+
- [x] `bundleWebWorker` 함수 및 `#region bundleWebWorker` 주석이 제거됨
|
|
10
|
+
- [x] `additionalResults` Map 선언이 제거됨
|
|
11
|
+
- [x] `createWebWorkerProcessor` 함수가 제거됨
|
|
12
|
+
- [x] onStart 내부의 `processWebWorker` + `workerTransformer` 생성 코드가 제거됨
|
|
13
|
+
- [x] `compileAsync` 호출에서 `additionalTransformers` 옵션이 제거됨
|
|
14
|
+
- [x] 증분 빌드 루프에서 `additionalResults.delete(file)` 호출이 제거됨
|
|
15
|
+
|
|
16
|
+
### 추가된 코드
|
|
17
|
+
|
|
18
|
+
- [x] `transformWorkerPatterns` import가 `./esbuild-worker-plugin`에서 추가됨 (같은 디렉토리, .js 확장자 미사용)
|
|
19
|
+
- [x] setup 스코프에 `workerResultsByContainingFile = new Map<string, { outputFiles?: esbuild.OutputFile[]; metafile?: esbuild.Metafile }>()` 선언이 존재함 (증분 빌드 시 변경되지 않은 Worker metafile 유지 목적)
|
|
20
|
+
|
|
21
|
+
### onStart — TS 파일 Worker 패턴 처리 (Rule 1)
|
|
22
|
+
|
|
23
|
+
- [x] 증분 빌드 시 `expandedModifiedFiles` 각 파일에 대해 `workerResultsByContainingFile.delete(file)` 호출 (선택적 제거)
|
|
24
|
+
- [x] `emitResults` 루프에서 각 파일에 대해 `transformWorkerPatterns(contents, normalized, build)` 호출
|
|
25
|
+
- [x] `workerResult != null`이면 `typeScriptFileCache.set(normalized, workerResult.contents)` 저장
|
|
26
|
+
- [x] `workerResult.errors`/`workerResult.warnings`를 onStart의 `errors`/`warnings` 배열에 push
|
|
27
|
+
- [x] `workerResult.workerMetafile != null`이면 `referencedFileTracker.add(normalized, Object.keys(workerResult.workerMetafile.inputs).map(input => path.join(cwd, input)))` 호출
|
|
28
|
+
- [x] `workerMetafile` 또는 `workerOutputFiles`가 있으면 `workerResultsByContainingFile.set(normalized, { outputFiles, metafile })` 저장
|
|
29
|
+
- [x] `workerResult == null`이면 기존처럼 `typeScriptFileCache.set(normalized, contents)` 저장
|
|
30
|
+
- [x] Worker 번들 에러가 errors 배열에 포함되어 onStart 결과에 반영됨 (Scenario: 에러 전파)
|
|
31
|
+
|
|
32
|
+
### JS onLoad — .js 파일 Worker 패턴 처리 (Rule 2)
|
|
33
|
+
|
|
34
|
+
- [x] `createCachedLoad` 콜백에서 `javascriptTransformer.transformFile` 호출 후 결과를 `TextDecoder`로 문자열로 변환
|
|
35
|
+
- [x] 변환된 문자열에 `transformWorkerPatterns(textContents, request, build)` 적용
|
|
36
|
+
- [x] `workerResult != null`일 때:
|
|
37
|
+
- `workerResult.workerMetafile`이 있으면 `referencedFileTracker.add` 호출
|
|
38
|
+
- `workerMetafile` 또는 `workerOutputFiles`가 있으면 `workerResultsByContainingFile.set(request, { outputFiles, metafile })` 저장
|
|
39
|
+
- 반환: `{ contents, loader: "js", resolveDir, errors (>0일때), warnings (>0일때) }` — TS/JS 에러 처리 일관성 확보 (L2 리뷰 반영)
|
|
40
|
+
- [x] `workerResult == null`일 때 기존과 동일한 `{ contents, loader: "js", resolveDir }` 반환
|
|
41
|
+
|
|
42
|
+
### onEnd — metafile 병합 (Rule 3)
|
|
43
|
+
|
|
44
|
+
- [x] onEnd에서 `workerResultsByContainingFile.values()` 순회
|
|
45
|
+
- [x] 각 항목의 `outputFiles`가 있으면 `result.outputFiles?.push(...outputFiles)`
|
|
46
|
+
- [x] 각 항목의 `metafile`이 있으면 `Object.assign(result.metafile.inputs, wr.metafile.inputs)` + `Object.assign(result.metafile.outputs, wr.metafile.outputs)`
|
|
47
|
+
- [x] onEnd에서 Map 전체 리셋 없음 — 증분 빌드에서 변경되지 않은 Worker 결과가 다음 빌드에서도 병합됨
|
|
48
|
+
- [x] 기존 `additionalResults` 순회 루프가 제거됨
|
|
49
|
+
|
|
50
|
+
### client config 무변경 (Rule 6)
|
|
51
|
+
|
|
52
|
+
- [x] `esbuild-client-config.ts`에 Worker 플러그인 추가 코드가 존재하지 않음 (plugins 배열에 `createWorkerBundlePlugin` 또는 `sd-worker-bundle` 미포함)
|
|
53
|
+
- [x] `esbuild-client-config.ts` 파일 자체가 Feature 1.2로 인해 변경되지 않음 (git diff 기준)
|
|
54
|
+
|
|
55
|
+
### 회귀 방지
|
|
56
|
+
|
|
57
|
+
- [x] `pnpm test --run packages/sd-cli/tests/esbuild/esbuild-angular-compiler-plugin.spec.ts` 통과 (기존 plugin 구조 테스트)
|
|
58
|
+
- [x] `pnpm test --run packages/sd-cli/tests/esbuild/esbuild-worker-plugin.spec.ts` 통과 (Feature 1.1 Unit 테스트)
|
|
59
|
+
- [x] `pnpm test --run packages/sd-cli/tests/esbuild/esbuild-worker-plugin.acc.spec.ts` 통과 (Feature 1.1 Acceptance 테스트)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# import.meta.resolve Worker 패턴 — LLM 검증
|
|
2
|
+
|
|
3
|
+
## 검증 항목
|
|
4
|
+
|
|
5
|
+
- [x] `NODE_WORKER_PATTERN` 정규식이 상대 경로만 감지: `esbuild-worker-plugin.ts:25` — `(\.\.?\/[^"']+)` 패턴으로 `./` 또는 `../`로 시작하는 경로만 캡처. `"some-package"` 같은 절대 모듈 경로는 매칭되지 않음.
|
|
6
|
+
- [x] `bundleWorker`에 platform 파라미터 추가: `esbuild-worker-plugin.ts:42` — `platform: esbuild.Platform` 파라미터 사용. 기존 `"browser"` 하드코딩 제거.
|
|
7
|
+
- [x] 브라우저 Worker 패턴이 `platform: "browser"`로 빌드: `esbuild-worker-plugin.ts:137` — `processWorkerBundle(fullWorkerPath, "browser")` 호출. 기존 동작 유지.
|
|
8
|
+
- [x] Node.js Worker 패턴이 메인 빌드의 platform 계승: `esbuild-worker-plugin.ts:149` — `build.initialOptions.platform ?? "browser"` 사용. 서버 빌드(platform: "node")에서는 "node"로 빌드.
|
|
9
|
+
- [x] 경로 치환이 `new URL("path", import.meta.url).href` 형태: `esbuild-worker-plugin.ts:153` — `new URL("${workerCodePath}", import.meta.url).href` 반환. file:// URL을 반환하므로 core-node Worker 호환.
|
|
10
|
+
- [x] 기존 브라우저 Worker 테스트 22개 모두 통과: `pnpm test --run` 결과 32개 전체 통과 (기존 22 + 신규 10).
|
|
11
|
+
- [x] `external: undefined` 설정 유지: `esbuild-worker-plugin.ts:62` — 메인 빌드의 external이 Worker 번들에 상속되지 않음.
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import esbuild from "esbuild";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import os from "os";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const fixturesDir = path.join(__dirname, "fixtures", "worker-plugin");
|
|
10
|
+
|
|
11
|
+
const { createWorkerBundlePlugin } = await import(
|
|
12
|
+
"../../src/esbuild/esbuild-worker-plugin.js"
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 임시 디렉토리에 entry 파일을 생성하고 esbuild로 빌드한다.
|
|
17
|
+
* Worker fixture 파일은 fixturesDir에서 참조된다.
|
|
18
|
+
*/
|
|
19
|
+
async function buildWithPlugin(
|
|
20
|
+
entryContent: string,
|
|
21
|
+
options?: { write?: boolean; entryExt?: string },
|
|
22
|
+
): Promise<esbuild.BuildResult & { outdir: string }> {
|
|
23
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "worker-plugin-test-"));
|
|
24
|
+
const ext = options?.entryExt ?? ".js";
|
|
25
|
+
const entryFile = path.join(tmpDir, `entry${ext}`);
|
|
26
|
+
|
|
27
|
+
// Worker fixture 파일을 tmpDir에 복사 (entry에서 상대경로 참조 가능하도록)
|
|
28
|
+
for (const f of ["worker.js", "worker2.js", "shared-worker.js", "worker-error.js", "node-worker.js"]) {
|
|
29
|
+
const src = path.join(fixturesDir, f);
|
|
30
|
+
if (fs.existsSync(src)) {
|
|
31
|
+
fs.copyFileSync(src, path.join(tmpDir, f));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
fs.writeFileSync(entryFile, entryContent);
|
|
36
|
+
|
|
37
|
+
const outdir = path.join(tmpDir, "dist");
|
|
38
|
+
const write = options?.write ?? false;
|
|
39
|
+
|
|
40
|
+
const result = await esbuild.build({
|
|
41
|
+
entryPoints: [entryFile],
|
|
42
|
+
bundle: true,
|
|
43
|
+
write,
|
|
44
|
+
outdir,
|
|
45
|
+
format: "esm",
|
|
46
|
+
platform: "browser",
|
|
47
|
+
metafile: true,
|
|
48
|
+
logLevel: "silent",
|
|
49
|
+
plugins: [createWorkerBundlePlugin()],
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return { ...result, outdir };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Node.js platform으로 esbuild 빌드한다 (서버 빌드 시뮬레이션).
|
|
57
|
+
*/
|
|
58
|
+
async function buildNodeWithPlugin(
|
|
59
|
+
entryContent: string,
|
|
60
|
+
options?: { write?: boolean },
|
|
61
|
+
): Promise<esbuild.BuildResult & { outdir: string }> {
|
|
62
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "worker-plugin-node-test-"));
|
|
63
|
+
const entryFile = path.join(tmpDir, "entry.js");
|
|
64
|
+
|
|
65
|
+
for (const f of ["worker.js", "worker2.js", "shared-worker.js", "worker-error.js", "node-worker.js"]) {
|
|
66
|
+
const src = path.join(fixturesDir, f);
|
|
67
|
+
if (fs.existsSync(src)) {
|
|
68
|
+
fs.copyFileSync(src, path.join(tmpDir, f));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
fs.writeFileSync(entryFile, entryContent);
|
|
73
|
+
|
|
74
|
+
const outdir = path.join(tmpDir, "dist");
|
|
75
|
+
const write = options?.write ?? false;
|
|
76
|
+
|
|
77
|
+
const result = await esbuild.build({
|
|
78
|
+
entryPoints: [entryFile],
|
|
79
|
+
bundle: true,
|
|
80
|
+
write,
|
|
81
|
+
outdir,
|
|
82
|
+
format: "esm",
|
|
83
|
+
platform: "node",
|
|
84
|
+
metafile: true,
|
|
85
|
+
logLevel: "silent",
|
|
86
|
+
plugins: [createWorkerBundlePlugin()],
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return { ...result, outdir };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
describe("esbuild Worker Bundle Plugin — Acceptance", () => {
|
|
93
|
+
it("JS 파일의 Worker 패턴을 감지하여 번들링하고 URL을 치환한다", async () => {
|
|
94
|
+
const result = await buildWithPlugin(
|
|
95
|
+
'const w = new Worker(new URL("./worker.js", import.meta.url));',
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
expect(result.errors).toHaveLength(0);
|
|
99
|
+
|
|
100
|
+
const mainOutput = result.outputFiles!.find((f) => path.basename(f.path).startsWith("entry"));
|
|
101
|
+
expect(mainOutput).toBeDefined();
|
|
102
|
+
const content = mainOutput!.text;
|
|
103
|
+
|
|
104
|
+
// 원본 경로가 치환됨
|
|
105
|
+
expect(content).not.toContain("./worker.js");
|
|
106
|
+
// worker-[HASH].js 패턴으로 치환
|
|
107
|
+
expect(content).toMatch(/worker-[A-Z0-9]+\.js/i);
|
|
108
|
+
// { type: "module" } 추가됨
|
|
109
|
+
expect(content).toContain("module");
|
|
110
|
+
|
|
111
|
+
// Worker 번들 파일이 outputFiles에 포함됨
|
|
112
|
+
const workerOutput = result.outputFiles!.find((f) =>
|
|
113
|
+
/worker-[a-z0-9]+\.js$/i.test(path.basename(f.path)),
|
|
114
|
+
);
|
|
115
|
+
expect(workerOutput).toBeDefined();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("SharedWorker 패턴을 감지하여 번들링하고 URL을 치환한다", async () => {
|
|
119
|
+
const result = await buildWithPlugin(
|
|
120
|
+
'const sw = new SharedWorker(new URL("./shared-worker.js", import.meta.url));',
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
expect(result.errors).toHaveLength(0);
|
|
124
|
+
|
|
125
|
+
const mainOutput = result.outputFiles!.find((f) => path.basename(f.path).startsWith("entry"));
|
|
126
|
+
const content = mainOutput!.text;
|
|
127
|
+
|
|
128
|
+
expect(content).not.toContain("./shared-worker.js");
|
|
129
|
+
expect(content).toMatch(/worker-[A-Z0-9]+\.js/i);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("TS 파일의 Worker 패턴을 감지하여 번들링한다", async () => {
|
|
133
|
+
const result = await buildWithPlugin(
|
|
134
|
+
'const w: Worker = new Worker(new URL("./worker.js", import.meta.url));',
|
|
135
|
+
{ entryExt: ".ts" },
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
expect(result.errors).toHaveLength(0);
|
|
139
|
+
|
|
140
|
+
const mainOutput = result.outputFiles!.find((f) => path.basename(f.path).startsWith("entry"));
|
|
141
|
+
const content = mainOutput!.text;
|
|
142
|
+
|
|
143
|
+
expect(content).not.toContain("./worker.js");
|
|
144
|
+
expect(content).toMatch(/worker-[A-Z0-9]+\.js/i);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("Worker 패턴 없는 파일은 변환 없이 통과한다", async () => {
|
|
148
|
+
const result = await buildWithPlugin(
|
|
149
|
+
'console.log("no worker");',
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
expect(result.errors).toHaveLength(0);
|
|
153
|
+
|
|
154
|
+
const mainOutput = result.outputFiles!.find((f) => path.basename(f.path).startsWith("entry"));
|
|
155
|
+
const content = mainOutput!.text;
|
|
156
|
+
|
|
157
|
+
expect(content).toContain("no worker");
|
|
158
|
+
expect(content).not.toMatch(/worker-[A-Z0-9]+\.js/i);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("한 파일에 복수 Worker 패턴이 있으면 모두 번들링한다", async () => {
|
|
162
|
+
const result = await buildWithPlugin(
|
|
163
|
+
[
|
|
164
|
+
'const w1 = new Worker(new URL("./worker.js", import.meta.url));',
|
|
165
|
+
'const w2 = new Worker(new URL("./worker2.js", import.meta.url));',
|
|
166
|
+
].join("\n"),
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
expect(result.errors).toHaveLength(0);
|
|
170
|
+
|
|
171
|
+
const mainOutput = result.outputFiles!.find((f) => path.basename(f.path).startsWith("entry"));
|
|
172
|
+
const content = mainOutput!.text;
|
|
173
|
+
|
|
174
|
+
expect(content).not.toContain("./worker.js");
|
|
175
|
+
expect(content).not.toContain("./worker2.js");
|
|
176
|
+
|
|
177
|
+
// 2개의 서로 다른 worker 번들이 있어야 함
|
|
178
|
+
const workerOutputs = result.outputFiles!.filter((f) =>
|
|
179
|
+
/worker-[a-z0-9]+\.js$/i.test(path.basename(f.path)),
|
|
180
|
+
);
|
|
181
|
+
expect(workerOutputs.length).toBeGreaterThanOrEqual(2);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("기존 옵션이 있으면 유지하고 URL 경로만 치환한다", async () => {
|
|
185
|
+
const result = await buildWithPlugin(
|
|
186
|
+
'const w = new Worker(new URL("./worker.js", import.meta.url), { type: "module" });',
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
expect(result.errors).toHaveLength(0);
|
|
190
|
+
|
|
191
|
+
const mainOutput = result.outputFiles!.find((f) => path.basename(f.path).startsWith("entry"));
|
|
192
|
+
const content = mainOutput!.text;
|
|
193
|
+
|
|
194
|
+
expect(content).not.toContain("./worker.js");
|
|
195
|
+
expect(content).toMatch(/worker-[A-Z0-9]+\.js/i);
|
|
196
|
+
// 기존 { type: "module" }이 유지됨
|
|
197
|
+
expect(content).toContain("module");
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("Worker 빌드 에러가 메인 빌드로 전파된다", async () => {
|
|
201
|
+
try {
|
|
202
|
+
await buildWithPlugin(
|
|
203
|
+
'const w = new Worker(new URL("./worker-error.js", import.meta.url));',
|
|
204
|
+
);
|
|
205
|
+
expect.fail("빌드 에러가 발생해야 한다");
|
|
206
|
+
} catch (e: unknown) {
|
|
207
|
+
// esbuild.build()는 에러 시 errors 프로퍼티를 가진 예외를 throw
|
|
208
|
+
const buildError = e as { errors?: esbuild.Message[] };
|
|
209
|
+
expect(buildError.errors).toBeDefined();
|
|
210
|
+
expect(buildError.errors!.length).toBeGreaterThan(0);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("write: true 빌드에서 Worker 파일이 디스크에 기록된다", async () => {
|
|
215
|
+
const result = await buildWithPlugin(
|
|
216
|
+
'const w = new Worker(new URL("./worker.js", import.meta.url));',
|
|
217
|
+
{ write: true },
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
expect(result.errors).toHaveLength(0);
|
|
221
|
+
|
|
222
|
+
// outdir에 worker-[HASH].js 파일이 존재
|
|
223
|
+
const files = fs.readdirSync(result.outdir);
|
|
224
|
+
const workerFile = files.find((f) => /worker-[a-z0-9]+\.js$/i.test(f));
|
|
225
|
+
expect(workerFile).toBeDefined();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("write: false 빌드에서 Worker 파일이 outputFiles에 포함된다", async () => {
|
|
229
|
+
const result = await buildWithPlugin(
|
|
230
|
+
'const w = new Worker(new URL("./worker.js", import.meta.url));',
|
|
231
|
+
{ write: false },
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
expect(result.errors).toHaveLength(0);
|
|
235
|
+
|
|
236
|
+
const workerOutput = result.outputFiles!.find((f) =>
|
|
237
|
+
/worker-[a-z0-9]+\.js$/i.test(path.basename(f.path)),
|
|
238
|
+
);
|
|
239
|
+
expect(workerOutput).toBeDefined();
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
describe("esbuild Worker Bundle Plugin — Node.js import.meta.resolve Acceptance", () => {
|
|
244
|
+
it("import.meta.resolve 패턴을 감지하여 번들링하고 경로를 치환한다", async () => {
|
|
245
|
+
const result = await buildNodeWithPlugin(
|
|
246
|
+
'const p = import.meta.resolve("./node-worker.js");',
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
expect(result.errors).toHaveLength(0);
|
|
250
|
+
|
|
251
|
+
const mainOutput = result.outputFiles!.find((f) => path.basename(f.path).startsWith("entry"));
|
|
252
|
+
expect(mainOutput).toBeDefined();
|
|
253
|
+
const content = mainOutput!.text;
|
|
254
|
+
|
|
255
|
+
// import.meta.resolve가 치환됨
|
|
256
|
+
expect(content).not.toContain("import.meta.resolve");
|
|
257
|
+
// new URL("worker-HASH.js", import.meta.url).href 패턴으로 치환
|
|
258
|
+
expect(content).toMatch(/new URL\("worker-[A-Z0-9]+\.js",\s*import\.meta\.url\)\.href/i);
|
|
259
|
+
|
|
260
|
+
// Worker 번들 파일이 outputFiles에 포함됨
|
|
261
|
+
const workerOutput = result.outputFiles!.find((f) =>
|
|
262
|
+
/worker-[a-z0-9]+\.js$/i.test(path.basename(f.path)),
|
|
263
|
+
);
|
|
264
|
+
expect(workerOutput).toBeDefined();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("절대 모듈 경로의 import.meta.resolve는 무시한다", async () => {
|
|
268
|
+
const result = await buildNodeWithPlugin(
|
|
269
|
+
'const p = import.meta.resolve("path");',
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
expect(result.errors).toHaveLength(0);
|
|
273
|
+
|
|
274
|
+
const mainOutput = result.outputFiles!.find((f) => path.basename(f.path).startsWith("entry"));
|
|
275
|
+
const content = mainOutput!.text;
|
|
276
|
+
|
|
277
|
+
// import.meta.resolve가 그대로 남음 (치환되지 않음)
|
|
278
|
+
expect(content).toContain("import.meta.resolve");
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it("브라우저 + Node.js Worker 패턴 공존 시 모두 번들링한다", async () => {
|
|
282
|
+
const result = await buildNodeWithPlugin(
|
|
283
|
+
[
|
|
284
|
+
'const w = new Worker(new URL("./worker.js", import.meta.url));',
|
|
285
|
+
'const p = import.meta.resolve("./node-worker.js");',
|
|
286
|
+
].join("\n"),
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
expect(result.errors).toHaveLength(0);
|
|
290
|
+
|
|
291
|
+
const mainOutput = result.outputFiles!.find((f) => path.basename(f.path).startsWith("entry"));
|
|
292
|
+
const content = mainOutput!.text;
|
|
293
|
+
|
|
294
|
+
// 브라우저 Worker 치환
|
|
295
|
+
expect(content).not.toContain('"./worker.js"');
|
|
296
|
+
// Node.js resolve 치환
|
|
297
|
+
expect(content).not.toContain("import.meta.resolve");
|
|
298
|
+
|
|
299
|
+
// 최소 2개의 worker 번들
|
|
300
|
+
const workerOutputs = result.outputFiles!.filter((f) =>
|
|
301
|
+
/worker-[a-z0-9]+\.js$/i.test(path.basename(f.path)),
|
|
302
|
+
);
|
|
303
|
+
expect(workerOutputs.length).toBeGreaterThanOrEqual(2);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it("write: true 빌드에서 Worker 파일이 디스크에 기록된다", async () => {
|
|
307
|
+
const result = await buildNodeWithPlugin(
|
|
308
|
+
'const p = import.meta.resolve("./node-worker.js");',
|
|
309
|
+
{ write: true },
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
expect(result.errors).toHaveLength(0);
|
|
313
|
+
|
|
314
|
+
const files = fs.readdirSync(result.outdir);
|
|
315
|
+
const workerFile = files.find((f) => /worker-[a-z0-9]+\.js$/i.test(f));
|
|
316
|
+
expect(workerFile).toBeDefined();
|
|
317
|
+
});
|
|
318
|
+
});
|