@simplysm/sd-cli 14.0.44 → 14.0.45
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 +22 -26
- 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-worker-plugin.d.ts +30 -0
- package/dist/esbuild/esbuild-worker-plugin.d.ts.map +1 -0
- package/dist/esbuild/esbuild-worker-plugin.js +197 -0
- package/dist/esbuild/esbuild-worker-plugin.js.map +1 -0
- 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 +5 -4
- package/src/deps/server-externals/server-production-files.ts +26 -28
- package/src/esbuild/esbuild-angular-compiler-plugin.ts +82 -123
- package/src/esbuild/esbuild-worker-plugin.ts +266 -0
- 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/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 +297 -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
|
@@ -0,0 +1,297 @@
|
|
|
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 { transformWorkerPatterns, createWorkerBundlePlugin } = await import(
|
|
12
|
+
"../../src/esbuild/esbuild-worker-plugin.js"
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* transformWorkerPatterns 테스트용 최소 PluginBuild mock 생성.
|
|
17
|
+
* 실제 esbuild 모듈을 사용하여 Worker 번들링이 동작한다.
|
|
18
|
+
*/
|
|
19
|
+
function createMockBuild(overrides?: Partial<esbuild.BuildOptions>): esbuild.PluginBuild {
|
|
20
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "worker-unit-"));
|
|
21
|
+
return {
|
|
22
|
+
esbuild,
|
|
23
|
+
initialOptions: {
|
|
24
|
+
outdir: tmpDir,
|
|
25
|
+
write: false,
|
|
26
|
+
...overrides,
|
|
27
|
+
},
|
|
28
|
+
} as unknown as esbuild.PluginBuild;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe("transformWorkerPatterns — 패턴 감지", () => {
|
|
32
|
+
it("Worker 패턴이 없는 content에 대해 undefined를 반환한다", () => {
|
|
33
|
+
const result = transformWorkerPatterns(
|
|
34
|
+
'console.log("hello");',
|
|
35
|
+
"/test/entry.js",
|
|
36
|
+
createMockBuild(),
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
expect(result).toBeUndefined();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("'Worker' 문자열이 있지만 new URL 패턴이 아니면 undefined를 반환한다", () => {
|
|
43
|
+
const result = transformWorkerPatterns(
|
|
44
|
+
'const w = new Worker("./worker.js");',
|
|
45
|
+
"/test/entry.js",
|
|
46
|
+
createMockBuild(),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
expect(result).toBeUndefined();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("import.meta.url이 아닌 URL 생성은 undefined를 반환한다", () => {
|
|
53
|
+
const result = transformWorkerPatterns(
|
|
54
|
+
'const w = new Worker(new URL("./worker.js", location.href));',
|
|
55
|
+
"/test/entry.js",
|
|
56
|
+
createMockBuild(),
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
expect(result).toBeUndefined();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("Worker + new URL + import.meta.url 패턴을 감지하여 치환한다", () => {
|
|
63
|
+
const entryPath = path.join(fixturesDir, "entry.js");
|
|
64
|
+
|
|
65
|
+
const result = transformWorkerPatterns(
|
|
66
|
+
`const w = new Worker(new URL("./worker.js", import.meta.url));`,
|
|
67
|
+
entryPath,
|
|
68
|
+
createMockBuild(),
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
expect(result).toBeDefined();
|
|
72
|
+
expect(result!.contents).not.toContain("./worker.js");
|
|
73
|
+
expect(result!.contents).toMatch(/worker-[a-z0-9]+\.js/i);
|
|
74
|
+
expect(result!.errors).toHaveLength(0);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("SharedWorker + new URL + import.meta.url 패턴을 감지하여 치환한다", () => {
|
|
78
|
+
const entryPath = path.join(fixturesDir, "entry.js");
|
|
79
|
+
|
|
80
|
+
const result = transformWorkerPatterns(
|
|
81
|
+
`const sw = new SharedWorker(new URL("./shared-worker.js", import.meta.url));`,
|
|
82
|
+
entryPath,
|
|
83
|
+
createMockBuild(),
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
expect(result).toBeDefined();
|
|
87
|
+
expect(result!.contents).not.toContain("./shared-worker.js");
|
|
88
|
+
expect(result!.contents).toMatch(/worker-[a-z0-9]+\.js/i);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("복수 Worker 패턴을 모두 치환한다", () => {
|
|
92
|
+
const entryPath = path.join(fixturesDir, "entry.js");
|
|
93
|
+
|
|
94
|
+
const result = transformWorkerPatterns(
|
|
95
|
+
[
|
|
96
|
+
`const w1 = new Worker(new URL("./worker.js", import.meta.url));`,
|
|
97
|
+
`const w2 = new Worker(new URL("./worker2.js", import.meta.url));`,
|
|
98
|
+
].join("\n"),
|
|
99
|
+
entryPath,
|
|
100
|
+
createMockBuild(),
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
expect(result).toBeDefined();
|
|
104
|
+
expect(result!.contents).not.toContain("./worker.js");
|
|
105
|
+
expect(result!.contents).not.toContain("./worker2.js");
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe("transformWorkerPatterns — type: module 처리", () => {
|
|
110
|
+
it("옵션 없는 Worker에 { type: 'module' }을 추가한다", () => {
|
|
111
|
+
const entryPath = path.join(fixturesDir, "entry.js");
|
|
112
|
+
|
|
113
|
+
const result = transformWorkerPatterns(
|
|
114
|
+
`const w = new Worker(new URL("./worker.js", import.meta.url));`,
|
|
115
|
+
entryPath,
|
|
116
|
+
createMockBuild(),
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
expect(result).toBeDefined();
|
|
120
|
+
// { type: "module" } 추가됨
|
|
121
|
+
expect(result!.contents).toMatch(/\{\s*type:\s*"module"\s*\}/);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("기존 옵션이 있으면 그대로 유지한다", () => {
|
|
125
|
+
const entryPath = path.join(fixturesDir, "entry.js");
|
|
126
|
+
|
|
127
|
+
const result = transformWorkerPatterns(
|
|
128
|
+
`const w = new Worker(new URL("./worker.js", import.meta.url), { type: "module" });`,
|
|
129
|
+
entryPath,
|
|
130
|
+
createMockBuild(),
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
expect(result).toBeDefined();
|
|
134
|
+
expect(result!.contents).toContain('{ type: "module" }');
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe("transformWorkerPatterns — 에러 처리", () => {
|
|
139
|
+
it("Worker 빌드 에러를 errors에 포함하여 반환한다", () => {
|
|
140
|
+
const entryPath = path.join(fixturesDir, "entry.js");
|
|
141
|
+
|
|
142
|
+
const result = transformWorkerPatterns(
|
|
143
|
+
`const w = new Worker(new URL("./worker-error.js", import.meta.url));`,
|
|
144
|
+
entryPath,
|
|
145
|
+
createMockBuild(),
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
expect(result).toBeDefined();
|
|
149
|
+
expect(result!.errors.length).toBeGreaterThan(0);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe("transformWorkerPatterns — write 옵션", () => {
|
|
154
|
+
it("write: false일 때 workerOutputFiles를 반환한다", () => {
|
|
155
|
+
const entryPath = path.join(fixturesDir, "entry.js");
|
|
156
|
+
|
|
157
|
+
const result = transformWorkerPatterns(
|
|
158
|
+
`const w = new Worker(new URL("./worker.js", import.meta.url));`,
|
|
159
|
+
entryPath,
|
|
160
|
+
createMockBuild({ write: false }),
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
expect(result).toBeDefined();
|
|
164
|
+
expect(result!.workerOutputFiles).toBeDefined();
|
|
165
|
+
expect(result!.workerOutputFiles!.length).toBeGreaterThan(0);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("write: true일 때 workerOutputFiles가 undefined이다", () => {
|
|
169
|
+
const entryPath = path.join(fixturesDir, "entry.js");
|
|
170
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "worker-write-"));
|
|
171
|
+
|
|
172
|
+
const result = transformWorkerPatterns(
|
|
173
|
+
`const w = new Worker(new URL("./worker.js", import.meta.url));`,
|
|
174
|
+
entryPath,
|
|
175
|
+
createMockBuild({ write: true, outdir: tmpDir }),
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
expect(result).toBeDefined();
|
|
179
|
+
expect(result!.workerOutputFiles).toBeUndefined();
|
|
180
|
+
|
|
181
|
+
// 대신 디스크에 파일이 기록됨
|
|
182
|
+
const files = fs.readdirSync(tmpDir);
|
|
183
|
+
const workerFile = files.find((f) => /worker-[a-z0-9]+\.js$/i.test(f));
|
|
184
|
+
expect(workerFile).toBeDefined();
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
describe("transformWorkerPatterns — import.meta.resolve 패턴", () => {
|
|
189
|
+
it("import.meta.resolve 상대 경로 패턴을 감지하여 치환한다", () => {
|
|
190
|
+
const entryPath = path.join(fixturesDir, "entry.js");
|
|
191
|
+
|
|
192
|
+
const result = transformWorkerPatterns(
|
|
193
|
+
`const p = import.meta.resolve("./node-worker.js");`,
|
|
194
|
+
entryPath,
|
|
195
|
+
createMockBuild({ platform: "node" }),
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
expect(result).toBeDefined();
|
|
199
|
+
expect(result!.contents).not.toContain("./node-worker.js");
|
|
200
|
+
expect(result!.contents).toMatch(/new URL\("worker-[a-z0-9]+\.js", import\.meta\.url\)\.href/i);
|
|
201
|
+
expect(result!.errors).toHaveLength(0);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("절대 모듈 경로의 import.meta.resolve는 무시한다", () => {
|
|
205
|
+
const result = transformWorkerPatterns(
|
|
206
|
+
`const p = import.meta.resolve("some-package");`,
|
|
207
|
+
"/test/entry.js",
|
|
208
|
+
createMockBuild({ platform: "node" }),
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
expect(result).toBeUndefined();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("import.meta.resolve 패턴 없는 파일은 undefined를 반환한다", () => {
|
|
215
|
+
const result = transformWorkerPatterns(
|
|
216
|
+
`console.log("no resolve");`,
|
|
217
|
+
"/test/entry.js",
|
|
218
|
+
createMockBuild({ platform: "node" }),
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
expect(result).toBeUndefined();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("브라우저 + Node.js Worker 패턴이 공존하면 모두 치환한다", () => {
|
|
225
|
+
const entryPath = path.join(fixturesDir, "entry.js");
|
|
226
|
+
|
|
227
|
+
const result = transformWorkerPatterns(
|
|
228
|
+
[
|
|
229
|
+
`const w = new Worker(new URL("./worker.js", import.meta.url));`,
|
|
230
|
+
`const p = import.meta.resolve("./node-worker.js");`,
|
|
231
|
+
].join("\n"),
|
|
232
|
+
entryPath,
|
|
233
|
+
createMockBuild({ platform: "node" }),
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
expect(result).toBeDefined();
|
|
237
|
+
// 브라우저 Worker 치환
|
|
238
|
+
expect(result!.contents).not.toContain('"./worker.js"');
|
|
239
|
+
// Node.js resolve 치환
|
|
240
|
+
expect(result!.contents).not.toContain("import.meta.resolve");
|
|
241
|
+
expect(result!.contents).toMatch(/new URL\("worker-[a-z0-9]+\.js", import\.meta\.url\)\.href/i);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("import.meta.resolve Worker 빌드 에러를 errors에 포함한다", () => {
|
|
245
|
+
const entryPath = path.join(fixturesDir, "entry.js");
|
|
246
|
+
|
|
247
|
+
const result = transformWorkerPatterns(
|
|
248
|
+
`const p = import.meta.resolve("./worker-error.js");`,
|
|
249
|
+
entryPath,
|
|
250
|
+
createMockBuild({ platform: "node" }),
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
expect(result).toBeDefined();
|
|
254
|
+
expect(result!.errors.length).toBeGreaterThan(0);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("write: false일 때 workerOutputFiles를 반환한다", () => {
|
|
258
|
+
const entryPath = path.join(fixturesDir, "entry.js");
|
|
259
|
+
|
|
260
|
+
const result = transformWorkerPatterns(
|
|
261
|
+
`const p = import.meta.resolve("./node-worker.js");`,
|
|
262
|
+
entryPath,
|
|
263
|
+
createMockBuild({ write: false, platform: "node" }),
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
expect(result).toBeDefined();
|
|
267
|
+
expect(result!.workerOutputFiles).toBeDefined();
|
|
268
|
+
expect(result!.workerOutputFiles!.length).toBeGreaterThan(0);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
describe("createWorkerBundlePlugin — 플러그인 구조", () => {
|
|
273
|
+
it("esbuild Plugin 프로토콜을 따르는 객체를 반환한다", () => {
|
|
274
|
+
const plugin = createWorkerBundlePlugin();
|
|
275
|
+
|
|
276
|
+
expect(plugin.name).toBe("sd-worker-bundle");
|
|
277
|
+
expect(typeof plugin.setup).toBe("function");
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("setup에서 onLoad/onEnd 훅이 등록된다", async () => {
|
|
281
|
+
const plugin = createWorkerBundlePlugin();
|
|
282
|
+
|
|
283
|
+
let hasOnLoad = false;
|
|
284
|
+
let hasOnEnd = false;
|
|
285
|
+
|
|
286
|
+
const mockBuild = {
|
|
287
|
+
initialOptions: {},
|
|
288
|
+
onLoad(_opts: unknown, _cb: unknown) { hasOnLoad = true; },
|
|
289
|
+
onEnd(_cb: unknown) { hasOnEnd = true; },
|
|
290
|
+
} as unknown as esbuild.PluginBuild;
|
|
291
|
+
|
|
292
|
+
await plugin.setup(mockBuild);
|
|
293
|
+
|
|
294
|
+
expect(hasOnLoad).toBe(true);
|
|
295
|
+
expect(hasOnEnd).toBe(true);
|
|
296
|
+
});
|
|
297
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Worker 빌드 경고가 메인 빌드로 전파됨 — LLM 검증
|
|
2
|
+
|
|
3
|
+
## 검증 항목
|
|
4
|
+
|
|
5
|
+
- [x] `transformWorkerPatterns`에서 `workerResult.warnings`를 warnings 배열에 push: `esbuild-worker-plugin.ts:109` — `warnings.push(...workerResult.warnings)` 확인. esbuild.buildSync의 BuildResult.warnings가 그대로 전달됨.
|
|
6
|
+
- [x] 반환 결과의 `warnings` 필드에 수집된 경고가 포함됨: `esbuild-worker-plugin.ts:172` — `warnings` 배열이 결과 객체에 포함됨.
|
|
7
|
+
- [x] 플러그인의 onLoad에서 warnings를 esbuild에 전달: `esbuild-worker-plugin.ts:210` — `warnings: result.warnings.length > 0 ? result.warnings : undefined` — onLoad 결과에 warnings를 포함하여 esbuild가 빌드 경고로 처리.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
this is not valid javascript }{][
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Feature 1.3 서버 빌드에 Worker 플러그인 적용 — LLM 검증
|
|
2
|
+
|
|
3
|
+
## 검증 항목
|
|
4
|
+
|
|
5
|
+
- [x] 프로덕션 빌드에 Worker 플러그인 포함: `server-build.worker.ts:169`에서 `plugins: [createWorkerBundlePlugin(), tscPlugin.plugin]`로 설정됨. Worker 플러그인이 tsc 플러그인보다 앞에 위치.
|
|
6
|
+
- [x] Watch/dev 빌드에 Worker 플러그인 포함: `server-esbuild-context.ts:66-69`에서 `workerPlugin` 변수로 생성 후 tsc 유무에 따라 `[workerPlugin, tscPlugin.plugin]` 또는 `[workerPlugin]`으로 설정됨.
|
|
7
|
+
- [x] Worker 패턴 없는 서버 코드 투명 통과: `esbuild-worker-plugin.ts:83-85`에서 `content.includes("Worker")`가 false면 즉시 `undefined` 반환. `esbuild-worker-plugin.ts:197`의 onLoad에서 `result == null`이면 `undefined` 반환하여 다음 핸들러에 위임.
|
|
8
|
+
- [x] 기존 tsc 플러그인/external 처리 미변경: `createServerEsbuildOptions` 함수 미수정. tsc 플러그인 생성 로직 미변경. external 처리 미변경.
|
|
9
|
+
- [x] import 경로에 `.js` 확장자 미사용: 두 파일 모두 `from "../esbuild/esbuild-worker-plugin"` (확장자 없음)
|
|
@@ -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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"}
|