@simplysm/sd-cli 14.0.38 → 14.0.40
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/angular/angular-build-pipeline.d.ts +1 -1
- package/dist/angular/angular-build-pipeline.js +1 -1
- package/dist/angular/client-transform-stylesheet.d.ts +1 -1
- package/dist/angular/client-transform-stylesheet.js +3 -3
- package/dist/dev-server/hmr-client-script.d.ts +2 -2
- package/dist/dev-server/hmr-client-script.d.ts.map +1 -1
- package/dist/dev-server/hmr-client-script.js +4 -4
- package/dist/dev-server/hmr-client-script.js.map +1 -1
- package/dist/esbuild/esbuild-client-config.d.ts +0 -2
- package/dist/esbuild/esbuild-client-config.d.ts.map +1 -1
- package/dist/esbuild/esbuild-client-config.js +20 -10
- package/dist/esbuild/esbuild-client-config.js.map +1 -1
- package/dist/esbuild/esbuild-postcss-plugin.d.ts +8 -0
- package/dist/esbuild/esbuild-postcss-plugin.d.ts.map +1 -0
- package/dist/esbuild/esbuild-postcss-plugin.js +105 -0
- package/dist/esbuild/esbuild-postcss-plugin.js.map +1 -0
- package/dist/esbuild/esbuild-tsc-plugin.d.ts +23 -0
- package/dist/esbuild/esbuild-tsc-plugin.d.ts.map +1 -0
- package/dist/esbuild/esbuild-tsc-plugin.js +60 -0
- package/dist/esbuild/esbuild-tsc-plugin.js.map +1 -0
- package/dist/workers/client.worker.d.ts.map +1 -1
- package/dist/workers/client.worker.js +94 -26
- package/dist/workers/client.worker.js.map +1 -1
- package/dist/workers/server-build.worker.d.ts.map +1 -1
- package/dist/workers/server-build.worker.js +129 -90
- package/dist/workers/server-build.worker.js.map +1 -1
- package/dist/workers/server-esbuild-context.d.ts +27 -0
- package/dist/workers/server-esbuild-context.d.ts.map +1 -1
- package/dist/workers/server-esbuild-context.js +57 -4
- package/dist/workers/server-esbuild-context.js.map +1 -1
- package/package.json +6 -4
- package/src/angular/angular-build-pipeline.ts +2 -2
- package/src/angular/client-transform-stylesheet.ts +4 -4
- package/src/dev-server/hmr-client-script.ts +4 -4
- package/src/esbuild/esbuild-client-config.ts +22 -13
- package/src/esbuild/esbuild-postcss-plugin.ts +117 -0
- package/src/esbuild/esbuild-tsc-plugin.ts +83 -0
- package/src/workers/client.worker.ts +96 -29
- package/src/workers/server-build.worker.ts +136 -97
- package/src/workers/server-esbuild-context.ts +72 -4
- package/tests/angular/client-transform-stylesheet.spec.ts +1 -1
- package/tests/esbuild/esbuild-tsc-plugin.acc.spec.ts +349 -0
- package/tests/esbuild/esbuild-tsc-plugin.spec.ts +230 -0
- package/tests/utils/esbuild-client-config-postcss.verify.md +6 -0
- package/tests/utils/esbuild-client-config.acc.spec.ts +34 -20
- package/tests/utils/esbuild-client-config.spec.ts +79 -16
- package/tests/utils/esbuild-postcss-plugin.acc.spec.ts +299 -0
- package/tests/utils/esbuild-postcss-plugin.spec.ts +290 -0
- package/tests/utils/esbuild-scss-plugin.acc.spec.ts +1 -0
- package/tests/utils/hmr-client-script.acc.spec.ts +8 -8
- package/tests/utils/hmr-client-script.spec.ts +5 -5
- package/tests/workers/server-build-lint.spec.ts +43 -0
- package/tests/workers/server-build-worker-refactoring.verify.md +14 -0
- package/tests/workers/server-build-worker.spec.ts +122 -9
- package/tests/workers/server-esbuild-context-tsc.verify.md +7 -0
- package/tests/workers/server-esbuild-context.acc.spec.ts +188 -2
- package/tests/workers/server-esbuild-context.spec.ts +401 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
import fs from "node:fs";
|
|
3
|
-
import { createWorker } from "@simplysm/core-node";
|
|
3
|
+
import { createWorker, FsWatcher } from "@simplysm/core-node";
|
|
4
4
|
import { err as errNs } from "@simplysm/core-common";
|
|
5
5
|
import { setupWorkerLifecycle } from "./shared-worker-lifecycle.js";
|
|
6
6
|
import {
|
|
@@ -14,7 +14,6 @@ import { createHmrService, type HmrService } from "../dev-server/hmr-service.js"
|
|
|
14
14
|
import { createHmrPostTransform } from "../dev-server/hmr-client-script.js";
|
|
15
15
|
import { copyPublicFiles, watchPublicFiles } from "../utils/copy-public.js";
|
|
16
16
|
import type { SdBrowserSupportConfig, SdPwaConfig } from "../sd-config.types.js";
|
|
17
|
-
import type { FsWatcher } from "@simplysm/core-node";
|
|
18
17
|
import type esbuild from "esbuild";
|
|
19
18
|
|
|
20
19
|
//#region Types
|
|
@@ -63,6 +62,7 @@ let esbuildResult: ClientEsbuildResult | undefined;
|
|
|
63
62
|
let devServer: DevHttpServer | undefined;
|
|
64
63
|
let hmrService: HmrService | undefined;
|
|
65
64
|
let publicWatcher: FsWatcher | undefined;
|
|
65
|
+
let indexHtmlWatcher: FsWatcher | undefined;
|
|
66
66
|
|
|
67
67
|
const { logger, guardStartWatch } = setupWorkerLifecycle("client", async () => {
|
|
68
68
|
await stopWatch();
|
|
@@ -118,7 +118,6 @@ async function build(info: ClientBuildInfo): Promise<ClientBuildResult> {
|
|
|
118
118
|
polyfills,
|
|
119
119
|
legacyModule,
|
|
120
120
|
postcssPlugins,
|
|
121
|
-
postcssConfigPath: info.pkgDir,
|
|
122
121
|
templateUpdates,
|
|
123
122
|
});
|
|
124
123
|
|
|
@@ -211,8 +210,29 @@ async function startWatch(info: ClientBuildInfo): Promise<ClientBuildResult> {
|
|
|
211
210
|
const polyfills = fs.existsSync(polyfillsPath) ? ["src/polyfills.ts"] : undefined;
|
|
212
211
|
const entryNames = ["main", ...(polyfills != null ? ["polyfills"] : [])];
|
|
213
212
|
|
|
214
|
-
// 4. templateUpdates Map
|
|
213
|
+
// 4. templateUpdates Map
|
|
215
214
|
const templateUpdates = new Map<string, string>();
|
|
215
|
+
|
|
216
|
+
// 5. HTTP dev server 생성 + 시작 (포트 확정 — HMR 스크립트에 포트 주입 필요)
|
|
217
|
+
const httpDevServer = createDevHttpServer({
|
|
218
|
+
distDir: outdir,
|
|
219
|
+
basePath,
|
|
220
|
+
port: info.port ?? 0,
|
|
221
|
+
onRequest: (req, res) => hmrService?.handleRequest(req, res) ?? false,
|
|
222
|
+
});
|
|
223
|
+
devServer = httpDevServer;
|
|
224
|
+
const actualPort = await httpDevServer.listen();
|
|
225
|
+
|
|
226
|
+
// 6. HMR WebSocket 서비스 생성
|
|
227
|
+
hmrService = createHmrService({
|
|
228
|
+
httpServer: httpDevServer.httpServer,
|
|
229
|
+
basePath,
|
|
230
|
+
templateUpdates,
|
|
231
|
+
outDir: outdir,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// 7. esbuild context 생성
|
|
235
|
+
let lastMetafile: esbuild.Metafile | undefined;
|
|
216
236
|
let initialBuildResolve: ((result: ClientBuildResult) => void) | undefined;
|
|
217
237
|
let isInitialBuild = true;
|
|
218
238
|
|
|
@@ -226,25 +246,61 @@ async function startWatch(info: ClientBuildInfo): Promise<ClientBuildResult> {
|
|
|
226
246
|
polyfills,
|
|
227
247
|
legacyModule,
|
|
228
248
|
postcssPlugins,
|
|
229
|
-
postcssConfigPath: info.pkgDir,
|
|
230
249
|
templateUpdates: legacyModule ? undefined : templateUpdates,
|
|
231
250
|
plugins: [
|
|
232
251
|
{
|
|
233
252
|
name: "sd-build-start",
|
|
234
253
|
setup(pluginBuild: esbuild.PluginBuild) {
|
|
254
|
+
const prevMtimes = new Map<string, number>();
|
|
255
|
+
|
|
235
256
|
pluginBuild.onStart(() => {
|
|
257
|
+
// sourceFileCache 무효화: 변경된 파일의 loadResultCache + TypeScript 소스 캐시 모두 제거
|
|
258
|
+
if (esbuildResult != null) {
|
|
259
|
+
const { loadResultCache } = esbuildResult.sourceFileCache;
|
|
260
|
+
const changedFiles = new Set<string>();
|
|
261
|
+
for (const file of loadResultCache.watchFiles) {
|
|
262
|
+
try {
|
|
263
|
+
const mtime = fs.statSync(file).mtimeMs;
|
|
264
|
+
const prev = prevMtimes.get(file);
|
|
265
|
+
if (prev != null && prev !== mtime) {
|
|
266
|
+
changedFiles.add(file);
|
|
267
|
+
}
|
|
268
|
+
} catch {
|
|
269
|
+
if (prevMtimes.has(file)) {
|
|
270
|
+
changedFiles.add(file);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (changedFiles.size > 0) {
|
|
275
|
+
esbuildResult.sourceFileCache.invalidate(changedFiles);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
236
279
|
if (!isInitialBuild) {
|
|
237
280
|
sender.send("buildStart", {});
|
|
238
281
|
}
|
|
239
282
|
});
|
|
283
|
+
|
|
284
|
+
pluginBuild.onEnd(() => {
|
|
285
|
+
if (esbuildResult == null) return;
|
|
286
|
+
prevMtimes.clear();
|
|
287
|
+
for (const file of esbuildResult.sourceFileCache.loadResultCache.watchFiles) {
|
|
288
|
+
try {
|
|
289
|
+
prevMtimes.set(file, fs.statSync(file).mtimeMs);
|
|
290
|
+
} catch {
|
|
291
|
+
// 삭제된 파일
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
});
|
|
240
295
|
},
|
|
241
296
|
},
|
|
242
297
|
],
|
|
243
298
|
onEnd: async (result: esbuild.BuildResult) => {
|
|
244
299
|
try {
|
|
245
|
-
// index.html 재생성
|
|
300
|
+
// index.html 재생성 (lastMetafile 보관 — index.html 단독 변경 시 재생성용)
|
|
246
301
|
if (result.metafile != null) {
|
|
247
|
-
|
|
302
|
+
lastMetafile = result.metafile;
|
|
303
|
+
const hmrPostTransform = createHmrPostTransform(basePath, actualPort);
|
|
248
304
|
const indexPath = path.join(info.pkgDir, "src", "index.html");
|
|
249
305
|
const indexResult = await generateIndexHtml({
|
|
250
306
|
indexPath,
|
|
@@ -301,37 +357,42 @@ async function startWatch(info: ClientBuildInfo): Promise<ClientBuildResult> {
|
|
|
301
357
|
},
|
|
302
358
|
});
|
|
303
359
|
|
|
304
|
-
//
|
|
305
|
-
const httpDevServer = createDevHttpServer({
|
|
306
|
-
distDir: outdir,
|
|
307
|
-
basePath,
|
|
308
|
-
port: info.port ?? 0,
|
|
309
|
-
onRequest: (req, res) => hmrService?.handleRequest(req, res) ?? false,
|
|
310
|
-
});
|
|
311
|
-
devServer = httpDevServer;
|
|
312
|
-
|
|
313
|
-
// 6. HMR WebSocket 서비스 생성
|
|
314
|
-
hmrService = createHmrService({
|
|
315
|
-
httpServer: httpDevServer.httpServer,
|
|
316
|
-
basePath,
|
|
317
|
-
templateUpdates,
|
|
318
|
-
outDir: outdir,
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
// 7. esbuild watch 시작 + 초기 빌드 대기
|
|
360
|
+
// 8. esbuild watch 시작 + 초기 빌드 대기
|
|
322
361
|
await esbuildResult.context.watch();
|
|
323
362
|
|
|
324
363
|
const initialResult = await new Promise<ClientBuildResult>((resolve) => {
|
|
325
364
|
initialBuildResolve = resolve;
|
|
326
365
|
});
|
|
327
366
|
|
|
328
|
-
//
|
|
329
|
-
const
|
|
367
|
+
// 9. src/index.html 감시 (esbuild watch는 HTML을 감시하지 않음)
|
|
368
|
+
const indexHtmlSrcPath = path.join(info.pkgDir, "src", "index.html");
|
|
369
|
+
indexHtmlWatcher = await FsWatcher.watch([indexHtmlSrcPath]);
|
|
370
|
+
indexHtmlWatcher.onChange({ delay: 300 }, async () => {
|
|
371
|
+
if (lastMetafile == null) return;
|
|
372
|
+
try {
|
|
373
|
+
sender.send("buildStart", {});
|
|
374
|
+
const hmrPostTransform = createHmrPostTransform(basePath, actualPort);
|
|
375
|
+
const indexResult = await generateIndexHtml({
|
|
376
|
+
indexPath: indexHtmlSrcPath,
|
|
377
|
+
metafile: lastMetafile,
|
|
378
|
+
outdir,
|
|
379
|
+
baseHref: basePath,
|
|
380
|
+
mode: "dev",
|
|
381
|
+
entryNames,
|
|
382
|
+
postTransform: hmrPostTransform,
|
|
383
|
+
});
|
|
384
|
+
fs.writeFileSync(path.join(outdir, "index.html"), indexResult.content);
|
|
385
|
+
hmrService?.broadcast({ type: "full-reload" });
|
|
386
|
+
sender.send("build", { success: true });
|
|
387
|
+
} catch (err) {
|
|
388
|
+
sender.send("error", { message: errNs.message(err) });
|
|
389
|
+
}
|
|
390
|
+
});
|
|
330
391
|
|
|
331
|
-
//
|
|
392
|
+
// 10. serverReady 이벤트 전송
|
|
332
393
|
sender.send("serverReady", { port: actualPort });
|
|
333
394
|
|
|
334
|
-
//
|
|
395
|
+
// 11. .config.json + .dev-port 기록
|
|
335
396
|
writeConfigJson(outdir, info.configs);
|
|
336
397
|
fs.writeFileSync(path.join(outdir, ".dev-port"), String(actualPort));
|
|
337
398
|
|
|
@@ -373,6 +434,12 @@ async function stopWatch(): Promise<void> {
|
|
|
373
434
|
publicWatcher = undefined;
|
|
374
435
|
}
|
|
375
436
|
|
|
437
|
+
// 5. index.html 감시 종료
|
|
438
|
+
if (indexHtmlWatcher != null) {
|
|
439
|
+
await indexHtmlWatcher.close();
|
|
440
|
+
indexHtmlWatcher = undefined;
|
|
441
|
+
}
|
|
442
|
+
|
|
376
443
|
logger.debug("esbuild watch 정리 완료");
|
|
377
444
|
}
|
|
378
445
|
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
} from "../esbuild/esbuild-config";
|
|
18
18
|
import { collectAllExternals, generateProductionFiles } from "../deps/server-externals/server-production-files";
|
|
19
19
|
import { runTscPackageBuild } from "../utils/tsc-build";
|
|
20
|
+
import { createTscPlugin } from "../esbuild/esbuild-tsc-plugin";
|
|
20
21
|
import { LintWithProgramRunner } from "../lint/lint-with-program";
|
|
21
22
|
import { setupWorkerLifecycle } from "./shared-worker-lifecycle";
|
|
22
23
|
import { buildWatchPaths } from "./build-watch-paths";
|
|
@@ -106,7 +107,6 @@ let srcWatcher: FsWatcher | undefined;
|
|
|
106
107
|
|
|
107
108
|
async function cleanup(): Promise<void> {
|
|
108
109
|
await esbuildCtx.dispose();
|
|
109
|
-
lastBuilderProgram = undefined;
|
|
110
110
|
|
|
111
111
|
const watcherToClose = publicWatcher;
|
|
112
112
|
publicWatcher = undefined;
|
|
@@ -143,16 +143,30 @@ async function build(info: ServerBuildInfo): Promise<ServerBuildResult> {
|
|
|
143
143
|
// 외부 모듈 수집
|
|
144
144
|
const external = collectAllExternals(info.pkgDir, info.externals);
|
|
145
145
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
146
|
+
let jsResult: { success: boolean; errors?: string[]; warnings?: string[] };
|
|
147
|
+
let tscErrors: string[];
|
|
148
|
+
let tscDiagnostics: SerializedDiagnostic[];
|
|
149
|
+
let tscProgram: ts.Program | undefined;
|
|
150
|
+
|
|
151
|
+
if (info.output.js) {
|
|
152
|
+
// js=true: tsc 플러그인 통합 — 단일 esbuild.build() 호출
|
|
153
|
+
const tscPlugin = createTscPlugin({
|
|
154
|
+
pkgDir: info.pkgDir,
|
|
155
|
+
cwd: info.cwd,
|
|
156
|
+
output: { dts: info.output.dts },
|
|
157
|
+
env: info.output.env,
|
|
158
|
+
includeTests: info.output.includeTests,
|
|
159
|
+
});
|
|
153
160
|
|
|
154
|
-
|
|
155
|
-
|
|
161
|
+
const esbuildOptions = createServerEsbuildOptions({
|
|
162
|
+
pkgDir: info.pkgDir,
|
|
163
|
+
entryPoints,
|
|
164
|
+
env: info.env,
|
|
165
|
+
external,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
jsResult = await esbuild.build({ ...esbuildOptions, plugins: [tscPlugin.plugin] })
|
|
169
|
+
.then(async (result) => {
|
|
156
170
|
if (result.outputFiles) {
|
|
157
171
|
await writeChangedOutputFiles(result.outputFiles);
|
|
158
172
|
}
|
|
@@ -163,36 +177,42 @@ async function build(info: ServerBuildInfo): Promise<ServerBuildResult> {
|
|
|
163
177
|
errors: errors.length > 0 ? errors : undefined,
|
|
164
178
|
warnings: warnings.length > 0 ? warnings : undefined,
|
|
165
179
|
};
|
|
166
|
-
})
|
|
180
|
+
})
|
|
181
|
+
.catch((err) => ({
|
|
167
182
|
success: false,
|
|
168
183
|
errors: [errNs.message(err)],
|
|
169
184
|
warnings: undefined,
|
|
170
|
-
}))
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
185
|
+
}));
|
|
186
|
+
|
|
187
|
+
tscErrors = tscPlugin.getErrors() ?? [];
|
|
188
|
+
tscDiagnostics = tscPlugin.getDiagnostics();
|
|
189
|
+
tscProgram = tscPlugin.getProgram();
|
|
190
|
+
} else {
|
|
191
|
+
// js=false: runTscPackageBuild 직접 호출 (플러그인 경유 불가)
|
|
192
|
+
const tscResult = runTscPackageBuild({
|
|
193
|
+
pkgDir: info.pkgDir,
|
|
194
|
+
cwd: info.cwd,
|
|
195
|
+
output: { js: false, dts: info.output.dts },
|
|
196
|
+
parsedConfig,
|
|
197
|
+
env: info.output.env,
|
|
198
|
+
includeTests: info.output.includeTests,
|
|
199
|
+
});
|
|
182
200
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
201
|
+
jsResult = { success: true, errors: undefined, warnings: undefined };
|
|
202
|
+
tscErrors = tscResult.errors ?? [];
|
|
203
|
+
tscDiagnostics = tscResult.diagnostics;
|
|
204
|
+
tscProgram = tscResult.program;
|
|
205
|
+
}
|
|
186
206
|
|
|
187
207
|
// lint 실행 (활성화 + program 사용 가능 시)
|
|
188
208
|
let lint: LintWithProgramResult | undefined;
|
|
189
|
-
if (info.output.lint === true &&
|
|
209
|
+
if (info.output.lint === true && tscProgram != null) {
|
|
190
210
|
logger.debug(`[${info.name}] lint 시작`);
|
|
191
211
|
const lintRunner = new LintWithProgramRunner({
|
|
192
212
|
cwd: info.cwd,
|
|
193
213
|
pkgName: info.name,
|
|
194
214
|
});
|
|
195
|
-
lint = await lintRunner.lint({ program:
|
|
215
|
+
lint = await lintRunner.lint({ program: tscProgram });
|
|
196
216
|
logger.debug(`[${info.name}] lint 완료`);
|
|
197
217
|
}
|
|
198
218
|
|
|
@@ -206,14 +226,15 @@ async function build(info: ServerBuildInfo): Promise<ServerBuildResult> {
|
|
|
206
226
|
generateProductionFiles(info, external);
|
|
207
227
|
}
|
|
208
228
|
|
|
209
|
-
const allErrors = [...(jsResult.errors ?? []), ...
|
|
210
|
-
|
|
229
|
+
const allErrors = [...(jsResult.errors ?? []), ...tscErrors];
|
|
230
|
+
const tscSuccess = tscErrors.length === 0;
|
|
231
|
+
logger.debug(`[${info.name}] server worker build 완료 (js: ${jsResult.success}, tsc: ${tscSuccess})`);
|
|
211
232
|
return {
|
|
212
233
|
build: {
|
|
213
|
-
success: jsResult.success &&
|
|
234
|
+
success: jsResult.success && tscSuccess,
|
|
214
235
|
errors: allErrors.length > 0 ? allErrors : undefined,
|
|
215
236
|
warnings: jsResult.warnings,
|
|
216
|
-
diagnostics:
|
|
237
|
+
diagnostics: tscDiagnostics,
|
|
217
238
|
},
|
|
218
239
|
lint,
|
|
219
240
|
mainJsPath,
|
|
@@ -232,76 +253,88 @@ async function build(info: ServerBuildInfo): Promise<ServerBuildResult> {
|
|
|
232
253
|
}
|
|
233
254
|
}
|
|
234
255
|
|
|
235
|
-
// watch 모드용 가변 상태
|
|
236
|
-
let watchInfo: ServerWatchInfo | undefined;
|
|
237
|
-
let watchLintRunner: LintWithProgramRunner | undefined;
|
|
238
|
-
let lastBuilderProgram: ts.EmitAndSemanticDiagnosticsBuilderProgram | undefined;
|
|
239
|
-
|
|
240
256
|
/**
|
|
241
|
-
*
|
|
257
|
+
* watch 모드 시작
|
|
242
258
|
*/
|
|
243
|
-
async function
|
|
244
|
-
|
|
245
|
-
logger.debug(`[${info.name}]
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
259
|
+
async function startWatch(info: ServerWatchInfo): Promise<void> {
|
|
260
|
+
guardStartWatch();
|
|
261
|
+
logger.debug(`[${info.name}] server worker startWatch 시작`);
|
|
262
|
+
|
|
263
|
+
// watch 모드 로컬 상태 (클로저로 관리)
|
|
264
|
+
let lastBuilderProgram: ts.EmitAndSemanticDiagnosticsBuilderProgram | undefined;
|
|
265
|
+
let watchLintRunner: LintWithProgramRunner | undefined;
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* esbuild + tsc 리빌드 (watch 모드)
|
|
269
|
+
* js=true: esbuildCtx.rebuild() 단일 호출 (tsc 플러그인 자동 트리거)
|
|
270
|
+
* js=false: runTscPackageBuild 직접 호출
|
|
271
|
+
*/
|
|
272
|
+
async function rebuildAll(): Promise<ServerCombinedBuildEvent> {
|
|
273
|
+
logger.debug(`[${info.name}] rebuildAll 시작`);
|
|
274
|
+
const mainJsPath = pathx.posixResolve(info.pkgDir, "dist", "main.js");
|
|
275
|
+
|
|
276
|
+
let jsResult: { success: boolean; errors?: string[]; warnings?: string[] };
|
|
277
|
+
let tscProgram: ts.Program | undefined;
|
|
278
|
+
let tscAffectedFiles: ReadonlySet<string> | undefined;
|
|
279
|
+
let tscErrors: string[];
|
|
280
|
+
|
|
281
|
+
if (info.output.js) {
|
|
282
|
+
// js=true: esbuildCtx.rebuild() 단일 호출 (tsc 에러 자동 병합)
|
|
283
|
+
const rebuildResult = await esbuildCtx.rebuild();
|
|
284
|
+
jsResult = rebuildResult ?? { success: true, errors: undefined, warnings: undefined };
|
|
285
|
+
tscProgram = esbuildCtx.getTscProgram();
|
|
286
|
+
tscAffectedFiles = esbuildCtx.getTscAffectedFiles();
|
|
287
|
+
tscErrors = [];
|
|
288
|
+
} else {
|
|
289
|
+
// js=false: runTscPackageBuild 직접 호출
|
|
290
|
+
const parsedConfig = parseTsconfig(info.pkgDir);
|
|
291
|
+
const tscResult = runTscPackageBuild({
|
|
292
|
+
pkgDir: info.pkgDir,
|
|
270
293
|
cwd: info.cwd,
|
|
271
|
-
|
|
294
|
+
output: { js: false, dts: info.output.dts },
|
|
295
|
+
parsedConfig,
|
|
296
|
+
env: info.output.env,
|
|
297
|
+
includeTests: info.output.includeTests,
|
|
298
|
+
oldBuilderProgram: lastBuilderProgram,
|
|
272
299
|
});
|
|
300
|
+
lastBuilderProgram = tscResult.builderProgram ?? lastBuilderProgram;
|
|
301
|
+
|
|
302
|
+
jsResult = { success: true, errors: undefined, warnings: undefined };
|
|
303
|
+
tscProgram = tscResult.program;
|
|
304
|
+
tscAffectedFiles = tscResult.affectedFiles;
|
|
305
|
+
tscErrors = tscResult.errors ?? [];
|
|
273
306
|
}
|
|
274
|
-
lint = await watchLintRunner.lint({
|
|
275
|
-
program: tscResult.program,
|
|
276
|
-
affectedFiles: tscResult.affectedFiles,
|
|
277
|
-
});
|
|
278
|
-
logger.debug(`[${info.name}] lint 완료`);
|
|
279
|
-
}
|
|
280
307
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
}
|
|
308
|
+
// lint 실행 (활성화 + program 사용 가능 시)
|
|
309
|
+
let lint: LintWithProgramResult | undefined;
|
|
310
|
+
if (info.output.lint === true && tscProgram != null) {
|
|
311
|
+
logger.debug(`[${info.name}] lint 시작`);
|
|
312
|
+
if (watchLintRunner == null) {
|
|
313
|
+
watchLintRunner = new LintWithProgramRunner({
|
|
314
|
+
cwd: info.cwd,
|
|
315
|
+
pkgName: info.name,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
lint = await watchLintRunner.lint({
|
|
319
|
+
program: tscProgram,
|
|
320
|
+
affectedFiles: tscAffectedFiles,
|
|
321
|
+
});
|
|
322
|
+
logger.debug(`[${info.name}] lint 완료`);
|
|
323
|
+
}
|
|
297
324
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
325
|
+
const allErrors = [...(jsResult.errors ?? []), ...tscErrors];
|
|
326
|
+
const allSuccess = jsResult.success && tscErrors.length === 0;
|
|
327
|
+
logger.debug(`[${info.name}] rebuildAll 완료`);
|
|
328
|
+
return {
|
|
329
|
+
build: {
|
|
330
|
+
success: allSuccess,
|
|
331
|
+
errors: allErrors.length > 0 ? allErrors : undefined,
|
|
332
|
+
warnings: jsResult.warnings,
|
|
333
|
+
},
|
|
334
|
+
lint,
|
|
335
|
+
mainJsPath,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
305
338
|
|
|
306
339
|
try {
|
|
307
340
|
const parsedConfig = parseTsconfig(info.pkgDir);
|
|
@@ -310,17 +343,23 @@ async function startWatch(info: ServerWatchInfo): Promise<void> {
|
|
|
310
343
|
// 외부 모듈 수집 (watch 모드용 — watch manager가 자체 캐시를 유지)
|
|
311
344
|
const cachedExternal = collectAllExternals(info.pkgDir, info.externals);
|
|
312
345
|
|
|
313
|
-
// esbuild 컨텍스트 생성 (JS 출력 필요
|
|
346
|
+
// esbuild 컨텍스트 생성 (JS 출력 필요 시, tsc 플러그인 포함)
|
|
314
347
|
if (info.output.js) {
|
|
315
348
|
await esbuildCtx.createContext({
|
|
316
349
|
pkgDir: info.pkgDir,
|
|
317
350
|
entryPoints,
|
|
318
351
|
env: info.env,
|
|
319
352
|
external: cachedExternal,
|
|
353
|
+
tsc: {
|
|
354
|
+
cwd: info.cwd,
|
|
355
|
+
output: { dts: info.output.dts },
|
|
356
|
+
env: info.output.env,
|
|
357
|
+
includeTests: info.output.includeTests,
|
|
358
|
+
},
|
|
320
359
|
});
|
|
321
360
|
}
|
|
322
361
|
|
|
323
|
-
// 초기
|
|
362
|
+
// 초기 빌드
|
|
324
363
|
sender.send("buildStart", {});
|
|
325
364
|
const initialResult = await rebuildAll();
|
|
326
365
|
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
import type ts from "typescript";
|
|
1
2
|
import esbuild from "esbuild";
|
|
3
|
+
import { err as errNs } from "@simplysm/core-common";
|
|
2
4
|
import {
|
|
3
5
|
createServerEsbuildOptions,
|
|
4
6
|
writeChangedOutputFiles,
|
|
5
7
|
} from "../esbuild/esbuild-config";
|
|
8
|
+
import { createTscPlugin, type TscPluginResult } from "../esbuild/esbuild-tsc-plugin";
|
|
9
|
+
import type { TypecheckEnv } from "../utils/tsconfig";
|
|
10
|
+
import type { SerializedDiagnostic } from "../typecheck/typecheck-serialization";
|
|
6
11
|
|
|
7
12
|
/**
|
|
8
13
|
* esbuild watch context 생성 옵션
|
|
@@ -12,6 +17,13 @@ export interface EsbuildContextOptions {
|
|
|
12
17
|
entryPoints: string[];
|
|
13
18
|
env?: Record<string, string>;
|
|
14
19
|
external: string[];
|
|
20
|
+
/** tsc 플러그인 옵션. 제공 시 createTscPlugin으로 플러그인을 생성하여 esbuild context에 포함한다. */
|
|
21
|
+
tsc?: {
|
|
22
|
+
cwd: string;
|
|
23
|
+
output: { dts: boolean };
|
|
24
|
+
env?: TypecheckEnv;
|
|
25
|
+
includeTests?: boolean;
|
|
26
|
+
};
|
|
15
27
|
}
|
|
16
28
|
|
|
17
29
|
/** esbuild watch context (모듈 스코프 상태) */
|
|
@@ -20,11 +32,24 @@ let context: esbuild.BuildContext | undefined;
|
|
|
20
32
|
/** 마지막 빌드의 metafile (변경 필터링용) */
|
|
21
33
|
let lastMetafile: esbuild.Metafile | undefined;
|
|
22
34
|
|
|
35
|
+
/** tsc 플러그인 인스턴스 (모듈 스코프 상태) */
|
|
36
|
+
let tscPlugin: TscPluginResult | undefined;
|
|
37
|
+
|
|
23
38
|
/**
|
|
24
39
|
* esbuild watch context를 생성한다.
|
|
25
40
|
* dev 모드 전용 (metafile:true, write:false).
|
|
26
41
|
*/
|
|
27
42
|
export async function createContext(options: EsbuildContextOptions): Promise<void> {
|
|
43
|
+
if (options.tsc != null) {
|
|
44
|
+
tscPlugin = createTscPlugin({
|
|
45
|
+
pkgDir: options.pkgDir,
|
|
46
|
+
cwd: options.tsc.cwd,
|
|
47
|
+
output: options.tsc.output,
|
|
48
|
+
env: options.tsc.env,
|
|
49
|
+
includeTests: options.tsc.includeTests,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
28
53
|
const baseOptions = createServerEsbuildOptions({
|
|
29
54
|
pkgDir: options.pkgDir,
|
|
30
55
|
entryPoints: options.entryPoints,
|
|
@@ -35,6 +60,7 @@ export async function createContext(options: EsbuildContextOptions): Promise<voi
|
|
|
35
60
|
|
|
36
61
|
context = await esbuild.context({
|
|
37
62
|
...baseOptions,
|
|
63
|
+
plugins: tscPlugin != null ? [tscPlugin.plugin] : [],
|
|
38
64
|
metafile: true,
|
|
39
65
|
write: false,
|
|
40
66
|
});
|
|
@@ -51,7 +77,18 @@ export async function rebuild(): Promise<{
|
|
|
51
77
|
} | null> {
|
|
52
78
|
if (context == null) return null;
|
|
53
79
|
|
|
54
|
-
|
|
80
|
+
let result: esbuild.BuildResult;
|
|
81
|
+
try {
|
|
82
|
+
result = await context.rebuild();
|
|
83
|
+
} catch (err) {
|
|
84
|
+
const tscErrors = tscPlugin?.getErrors() ?? [];
|
|
85
|
+
const allErrors = [errNs.message(err), ...tscErrors];
|
|
86
|
+
return {
|
|
87
|
+
success: false,
|
|
88
|
+
errors: allErrors.length > 0 ? allErrors : undefined,
|
|
89
|
+
warnings: undefined,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
55
92
|
|
|
56
93
|
if (result.metafile != null) {
|
|
57
94
|
lastMetafile = result.metafile;
|
|
@@ -61,12 +98,14 @@ export async function rebuild(): Promise<{
|
|
|
61
98
|
await writeChangedOutputFiles(result.outputFiles);
|
|
62
99
|
}
|
|
63
100
|
|
|
64
|
-
const
|
|
101
|
+
const esbuildErrors = result.errors.map((e) => e.text);
|
|
102
|
+
const tscErrors = tscPlugin?.getErrors() ?? [];
|
|
103
|
+
const allErrors = [...esbuildErrors, ...tscErrors];
|
|
65
104
|
const warnings = result.warnings.map((w) => w.text);
|
|
66
105
|
|
|
67
106
|
return {
|
|
68
|
-
success:
|
|
69
|
-
errors:
|
|
107
|
+
success: allErrors.length === 0,
|
|
108
|
+
errors: allErrors.length > 0 ? allErrors : undefined,
|
|
70
109
|
warnings: warnings.length > 0 ? warnings : undefined,
|
|
71
110
|
};
|
|
72
111
|
}
|
|
@@ -85,6 +124,10 @@ export async function recreateContext(options: EsbuildContextOptions): Promise<v
|
|
|
85
124
|
context = undefined;
|
|
86
125
|
lastMetafile = undefined;
|
|
87
126
|
|
|
127
|
+
if (tscPlugin != null) {
|
|
128
|
+
tscPlugin.resetBuilderProgram();
|
|
129
|
+
}
|
|
130
|
+
|
|
88
131
|
try {
|
|
89
132
|
await createContext(options);
|
|
90
133
|
} finally {
|
|
@@ -101,6 +144,7 @@ export async function dispose(): Promise<void> {
|
|
|
101
144
|
const contextToDispose = context;
|
|
102
145
|
context = undefined;
|
|
103
146
|
lastMetafile = undefined;
|
|
147
|
+
tscPlugin = undefined;
|
|
104
148
|
|
|
105
149
|
if (contextToDispose != null) {
|
|
106
150
|
await contextToDispose.dispose();
|
|
@@ -120,3 +164,27 @@ export function getMetafile(): esbuild.Metafile | undefined {
|
|
|
120
164
|
export function hasContext(): boolean {
|
|
121
165
|
return context != null;
|
|
122
166
|
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* tsc 플러그인의 ts.Program을 반환한다.
|
|
170
|
+
* 플러그인이 없으면 undefined를 반환한다.
|
|
171
|
+
*/
|
|
172
|
+
export function getTscProgram(): ts.Program | undefined {
|
|
173
|
+
return tscPlugin?.getProgram();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* tsc 플러그인의 affected files를 반환한다.
|
|
178
|
+
* 플러그인이 없으면 undefined를 반환한다.
|
|
179
|
+
*/
|
|
180
|
+
export function getTscAffectedFiles(): ReadonlySet<string> | undefined {
|
|
181
|
+
return tscPlugin?.getAffectedFiles();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* tsc 플러그인의 diagnostics를 반환한다.
|
|
186
|
+
* 플러그인이 없으면 빈 배열을 반환한다.
|
|
187
|
+
*/
|
|
188
|
+
export function getTscDiagnostics(): SerializedDiagnostic[] {
|
|
189
|
+
return tscPlugin?.getDiagnostics() ?? [];
|
|
190
|
+
}
|
|
@@ -118,7 +118,7 @@ describe("createClientTransformStylesheet", () => {
|
|
|
118
118
|
const deps = new Map<string, Set<string>>();
|
|
119
119
|
const transform = createClientTransformStylesheet({
|
|
120
120
|
loadPaths: [],
|
|
121
|
-
|
|
121
|
+
postcssPlugins: [testPlugin],
|
|
122
122
|
scssErrors: errors,
|
|
123
123
|
scssDependencies: deps,
|
|
124
124
|
});
|