@simplysm/sd-cli 14.0.18 → 14.0.19
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/vite-angular-plugin.d.ts +2 -0
- package/dist/angular/vite-angular-plugin.d.ts.map +1 -1
- package/dist/angular/vite-angular-plugin.js +57 -28
- package/dist/angular/vite-angular-plugin.js.map +1 -1
- package/dist/capacitor/capacitor.d.ts +0 -1
- package/dist/capacitor/capacitor.d.ts.map +1 -1
- package/dist/capacitor/capacitor.js +12 -37
- package/dist/capacitor/capacitor.js.map +1 -1
- package/dist/commands/device.d.ts.map +1 -1
- package/dist/commands/device.js +3 -2
- package/dist/commands/device.js.map +1 -1
- package/dist/electron/electron.d.ts.map +1 -1
- package/dist/electron/electron.js +9 -4
- package/dist/electron/electron.js.map +1 -1
- package/dist/orchestrators/DevWatchOrchestrator.d.ts.map +1 -1
- package/dist/orchestrators/DevWatchOrchestrator.js +12 -0
- package/dist/orchestrators/DevWatchOrchestrator.js.map +1 -1
- package/dist/utils/vite-config.d.ts +1 -1
- package/dist/utils/vite-config.d.ts.map +1 -1
- package/dist/utils/vite-config.js +76 -26
- package/dist/utils/vite-config.js.map +1 -1
- package/dist/utils/vite-scope-watch-plugin.d.ts.map +1 -1
- package/dist/utils/vite-scope-watch-plugin.js +7 -1
- package/dist/utils/vite-scope-watch-plugin.js.map +1 -1
- package/dist/workers/server-runtime.worker.d.ts.map +1 -1
- package/dist/workers/server-runtime.worker.js +15 -0
- package/dist/workers/server-runtime.worker.js.map +1 -1
- package/package.json +9 -7
- package/src/angular/vite-angular-plugin.ts +88 -34
- package/src/capacitor/capacitor.ts +14 -46
- package/src/commands/device.ts +3 -2
- package/src/electron/electron.ts +11 -4
- package/src/orchestrators/DevWatchOrchestrator.ts +14 -0
- package/src/utils/vite-config.ts +83 -27
- package/src/utils/vite-scope-watch-plugin.ts +6 -1
- package/src/workers/server-runtime.worker.ts +15 -0
- package/tests/angular/linker-disk-cache.spec.ts +31 -25
- package/tests/angular/vite-angular-plugin-hmr-fallback.spec.ts +15 -15
- package/tests/angular/vite-angular-plugin-hmr.spec.ts +9 -9
- package/tests/angular/vite-angular-plugin-legacy-watch.spec.ts +108 -0
- package/tests/angular/vite-angular-plugin-lint.spec.ts +4 -4
- package/tests/angular/vite-angular-plugin-scss-hmr.spec.ts +10 -15
- package/tests/angular/vite-angular-plugin.spec.ts +80 -15
- package/tests/capacitor/capacitor-workspace.spec.ts +22 -12
- package/tests/commands/device.spec.ts +12 -7
- package/tests/electron/electron.spec.ts +27 -2
- package/tests/utils/vite-config.spec.ts +255 -133
- package/tests/utils/vite-scope-watch-plugin.spec.ts +22 -0
- package/tests/workers/server-runtime-worker.spec.ts +48 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Plugin,
|
|
1
|
+
import type { Plugin, ModuleNode, ViteDevServer } from "vite";
|
|
2
2
|
import type { IncomingMessage, ServerResponse } from "http";
|
|
3
3
|
import { JavaScriptTransformer } from "@angular/build/private";
|
|
4
4
|
import { createHash } from "crypto";
|
|
@@ -49,6 +49,8 @@ export interface SdAngularPluginOptions {
|
|
|
49
49
|
postCssPlugins?: unknown[];
|
|
50
50
|
/** Linker 캐시 디렉토리 (기본값: {cwd}/.cache/linker/{sm|nosm}) */
|
|
51
51
|
linkerCacheDir?: string;
|
|
52
|
+
/** replaceDeps 패키지의 resolved dist 경로 목록 (Linker 캐시 skip + full-reload 트리거) */
|
|
53
|
+
replaceDepDistPaths?: string[];
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
/**
|
|
@@ -108,6 +110,10 @@ export function sdAngularPlugin(options: SdAngularPluginOptions): Plugin {
|
|
|
108
110
|
const templateUpdates = new Map<string, string>();
|
|
109
111
|
let hmrLock: Promise<void> = Promise.resolve();
|
|
110
112
|
const scssDependencies = new Map<string, Set<string>>();
|
|
113
|
+
let devServer: ViteDevServer | undefined;
|
|
114
|
+
|
|
115
|
+
/** Rolldown watch 모드에서 변경된 파일 경로를 수집한다. buildStart 재호출 시 캐시 무효화에 사용. */
|
|
116
|
+
const pendingWatchChanges = new Set<string>();
|
|
111
117
|
|
|
112
118
|
const enableSourcemap = options.sourcemap ?? options.dev;
|
|
113
119
|
|
|
@@ -134,6 +140,10 @@ export function sdAngularPlugin(options: SdAngularPluginOptions): Plugin {
|
|
|
134
140
|
name: "sd-angular",
|
|
135
141
|
enforce: "pre",
|
|
136
142
|
|
|
143
|
+
watchChange(id: string) {
|
|
144
|
+
pendingWatchChanges.add(pathx.posix(id));
|
|
145
|
+
},
|
|
146
|
+
|
|
137
147
|
config() {
|
|
138
148
|
const linkerCacheDir =
|
|
139
149
|
options.linkerCacheDir ??
|
|
@@ -146,37 +156,53 @@ export function sdAngularPlugin(options: SdAngularPluginOptions): Plugin {
|
|
|
146
156
|
ngHmrMode: options.dev && !options.legacyModule ? undefined : "false",
|
|
147
157
|
},
|
|
148
158
|
optimizeDeps: {
|
|
149
|
-
|
|
159
|
+
esbuildOptions: {
|
|
150
160
|
plugins: [
|
|
151
161
|
{
|
|
152
162
|
name: "angular-vite-optimize-deps",
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
163
|
+
setup(build: { onLoad: Function }) {
|
|
164
|
+
build.onLoad(
|
|
165
|
+
{ filter: /\.[cm]?js$/ },
|
|
166
|
+
async (args: { path: string }) => {
|
|
167
|
+
if (!/\.[cm]?js$/.test(args.path)) return null;
|
|
168
|
+
|
|
169
|
+
// replaceDeps 파일은 Linker 캐시를 건너뛴다 (항상 fresh 처리)
|
|
170
|
+
if (
|
|
171
|
+
options.replaceDepDistPaths != null &&
|
|
172
|
+
options.replaceDepDistPaths.some((p) =>
|
|
173
|
+
pathx.posix(args.path).startsWith(p),
|
|
174
|
+
)
|
|
175
|
+
) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const content = await fsp.readFile(args.path, "utf-8");
|
|
180
|
+
const hash = createHash("sha256").update(content).digest("hex");
|
|
181
|
+
const cachePath = path.join(linkerCacheDir, `${hash}.js`);
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
const cached = await fsp.readFile(cachePath, "utf-8");
|
|
185
|
+
return { contents: cached, loader: "js" as const };
|
|
186
|
+
} catch {
|
|
187
|
+
// cache miss
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const result = await prebundleTransformer.transformFile(args.path);
|
|
191
|
+
const resultStr =
|
|
192
|
+
typeof result === "string"
|
|
193
|
+
? result
|
|
194
|
+
: new TextDecoder().decode(result);
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
await fsp.mkdir(linkerCacheDir, { recursive: true });
|
|
198
|
+
await fsp.writeFile(cachePath, resultStr);
|
|
199
|
+
} catch {
|
|
200
|
+
// cache write failure — non-fatal
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return { contents: resultStr, loader: "js" as const };
|
|
204
|
+
},
|
|
205
|
+
);
|
|
180
206
|
},
|
|
181
207
|
},
|
|
182
208
|
],
|
|
@@ -196,6 +222,13 @@ export function sdAngularPlugin(options: SdAngularPluginOptions): Plugin {
|
|
|
196
222
|
// AngularSourceFileCache 생성 (또는 재사용)
|
|
197
223
|
sourceFileCache ??= new AngularSourceFileCache();
|
|
198
224
|
|
|
225
|
+
// Rolldown watch 재빌드: 변경된 파일의 캐시 무효화
|
|
226
|
+
if (pendingWatchChanges.size > 0) {
|
|
227
|
+
logger.debug(`watch 변경 파일 ${pendingWatchChanges.size}개 캐시 무효화`);
|
|
228
|
+
sourceFileCache.invalidate(pendingWatchChanges);
|
|
229
|
+
pendingWatchChanges.clear();
|
|
230
|
+
}
|
|
231
|
+
|
|
199
232
|
// SCSS errors 수집용
|
|
200
233
|
const scssErrors: string[] = [];
|
|
201
234
|
|
|
@@ -229,6 +262,7 @@ export function sdAngularPlugin(options: SdAngularPluginOptions): Plugin {
|
|
|
229
262
|
noEmit: false,
|
|
230
263
|
declaration: false,
|
|
231
264
|
declarationMap: false,
|
|
265
|
+
...(options.dev ? { removeComments: false } : {}),
|
|
232
266
|
}),
|
|
233
267
|
});
|
|
234
268
|
|
|
@@ -281,16 +315,35 @@ export function sdAngularPlugin(options: SdAngularPluginOptions): Plugin {
|
|
|
281
315
|
});
|
|
282
316
|
},
|
|
283
317
|
|
|
284
|
-
async
|
|
318
|
+
async handleHotUpdate({
|
|
285
319
|
file,
|
|
286
320
|
modules,
|
|
287
|
-
|
|
321
|
+
server,
|
|
322
|
+
}: {
|
|
323
|
+
file: string;
|
|
324
|
+
modules: ModuleNode[];
|
|
325
|
+
server: ViteDevServer;
|
|
326
|
+
timestamp: number;
|
|
327
|
+
read: () => string | Promise<string>;
|
|
328
|
+
}): Promise<ModuleNode[] | void> {
|
|
288
329
|
if (compiler == null || !options.dev) return;
|
|
289
330
|
if (
|
|
290
331
|
!file.endsWith(".ts") &&
|
|
291
332
|
!file.endsWith(".html") &&
|
|
292
333
|
!file.endsWith(".scss")
|
|
293
334
|
) {
|
|
335
|
+
// replaceDeps .js 파일 변경 시 full-reload 강제
|
|
336
|
+
if (
|
|
337
|
+
file.endsWith(".js") &&
|
|
338
|
+
devServer != null &&
|
|
339
|
+
options.replaceDepDistPaths != null &&
|
|
340
|
+
options.replaceDepDistPaths.some((p) =>
|
|
341
|
+
pathx.posix(file).startsWith(p),
|
|
342
|
+
)
|
|
343
|
+
) {
|
|
344
|
+
devServer.hot.send({ type: "full-reload" });
|
|
345
|
+
return [];
|
|
346
|
+
}
|
|
294
347
|
return;
|
|
295
348
|
}
|
|
296
349
|
|
|
@@ -372,10 +425,9 @@ export function sdAngularPlugin(options: SdAngularPluginOptions): Plugin {
|
|
|
372
425
|
|
|
373
426
|
if (file.endsWith(".scss")) {
|
|
374
427
|
// SCSS: moduleGraph에서 영향받은 TS 모듈 조회
|
|
375
|
-
const
|
|
376
|
-
const result: EnvironmentModuleNode[] = [];
|
|
428
|
+
const result: ModuleNode[] = [];
|
|
377
429
|
for (const p of affectedPaths) {
|
|
378
|
-
const mods = moduleGraph.getModulesByFile(p);
|
|
430
|
+
const mods = server.moduleGraph.getModulesByFile(p);
|
|
379
431
|
if (mods) result.push(...mods);
|
|
380
432
|
}
|
|
381
433
|
return result;
|
|
@@ -443,6 +495,8 @@ export function sdAngularPlugin(options: SdAngularPluginOptions): Plugin {
|
|
|
443
495
|
},
|
|
444
496
|
|
|
445
497
|
configureServer(server: ViteDevServer) {
|
|
498
|
+
devServer = server;
|
|
499
|
+
|
|
446
500
|
// component-middleware 등록 (HMR template updates 서빙)
|
|
447
501
|
server.middlewares.use(angularComponentMiddleware(templateUpdates, server.config.base));
|
|
448
502
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import path from "path";
|
|
4
|
-
import { symlink } from "fs/promises";
|
|
5
4
|
import { createRequire } from "module";
|
|
6
5
|
import { cpx, fsx, pathx } from "@simplysm/core-node";
|
|
7
6
|
import { env } from "@simplysm/core-common";
|
|
@@ -247,14 +246,12 @@ export class Capacitor {
|
|
|
247
246
|
*/
|
|
248
247
|
private async _initCap(): Promise<boolean> {
|
|
249
248
|
Capacitor._logger.debug("package.json 설정 시작");
|
|
250
|
-
const
|
|
249
|
+
const depChanged = await this._setupNpmConf();
|
|
251
250
|
const nodeModulesExists = await fsx.exists(pathx.posixResolve(this._capPath, "node_modules"));
|
|
252
251
|
Capacitor._logger.debug(`depChanged: ${depChanged}, nodeModulesExists: ${nodeModulesExists}`);
|
|
253
252
|
|
|
254
253
|
if (!depChanged && nodeModulesExists) {
|
|
255
|
-
|
|
256
|
-
Capacitor._logger.debug("의존성 변경 없음, workspace 플러그인 symlink만 갱신");
|
|
257
|
-
await this._linkWorkspacePlugins(workspacePlugins);
|
|
254
|
+
Capacitor._logger.debug("의존성 변경 없음");
|
|
258
255
|
return false;
|
|
259
256
|
}
|
|
260
257
|
|
|
@@ -270,11 +267,6 @@ export class Capacitor {
|
|
|
270
267
|
await this._exec("pnpm", ["approve-builds", "--all"], this._capPath);
|
|
271
268
|
Capacitor._logger.debug("pnpm install 완료");
|
|
272
269
|
|
|
273
|
-
// workspace 플러그인 symlink
|
|
274
|
-
Capacitor._logger.debug("workspace 플러그인 symlink 시작");
|
|
275
|
-
await this._linkWorkspacePlugins(workspacePlugins);
|
|
276
|
-
Capacitor._logger.debug("workspace 플러그인 symlink 완료");
|
|
277
|
-
|
|
278
270
|
// 멱등성: capacitor.config.ts가 없을 때만 cap init 실행
|
|
279
271
|
const configPath = pathx.posixResolve(this._capPath, "capacitor.config.ts");
|
|
280
272
|
if (!(await fsx.exists(configPath))) {
|
|
@@ -300,7 +292,7 @@ export class Capacitor {
|
|
|
300
292
|
/**
|
|
301
293
|
* package.json 설정
|
|
302
294
|
*/
|
|
303
|
-
private async _setupNpmConf(): Promise<
|
|
295
|
+
private async _setupNpmConf(): Promise<boolean> {
|
|
304
296
|
const projNpmConfigPath = pathx.posixResolve(this._findWorkspaceRoot(), "package.json");
|
|
305
297
|
|
|
306
298
|
// 루트 package.json 존재 확인
|
|
@@ -358,16 +350,17 @@ export class Capacitor {
|
|
|
358
350
|
}
|
|
359
351
|
}
|
|
360
352
|
|
|
361
|
-
// 새 플러그인 추가
|
|
362
|
-
const
|
|
353
|
+
// 새 플러그인 추가
|
|
354
|
+
const pkgRequire = createRequire(pathx.posixResolve(this._pkgPath, "package.json"));
|
|
363
355
|
for (const plugin of usePlugins) {
|
|
364
356
|
const version = mainDeps[plugin] ?? "*";
|
|
365
357
|
if (typeof version === "string" && version.startsWith("workspace:")) {
|
|
366
|
-
// workspace 플러그인은
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
358
|
+
// workspace 플러그인은 link: 프로토콜로 실제 경로를 지정
|
|
359
|
+
const pluginPkgJsonPath = pkgRequire.resolve(`${plugin}/package.json`);
|
|
360
|
+
const pluginDir = path.dirname(pluginPkgJsonPath);
|
|
361
|
+
const relativePath = path.relative(this._capPath, pluginDir).replace(/\\/g, "/");
|
|
362
|
+
capNpmConf.dependencies[plugin] = `link:${relativePath}`;
|
|
363
|
+
Capacitor._logger.debug(`workspace 플러그인 (link): ${plugin} → ${relativePath}`);
|
|
371
364
|
} else if (!(plugin in capNpmConf.dependencies)) {
|
|
372
365
|
capNpmConf.dependencies[plugin] = version;
|
|
373
366
|
Capacitor._logger.debug(`플러그인 추가: ${plugin}@${version}`);
|
|
@@ -388,12 +381,11 @@ export class Capacitor {
|
|
|
388
381
|
await fsx.writeJson(capNpmConfPath, capNpmConf, { space: 2 });
|
|
389
382
|
|
|
390
383
|
// 의존성 변경 여부 확인
|
|
391
|
-
|
|
384
|
+
return (
|
|
392
385
|
orgCapNpmConf.volta !== capNpmConf.volta ||
|
|
393
386
|
JSON.stringify(orgCapNpmConf.dependencies) !== JSON.stringify(capNpmConf.dependencies) ||
|
|
394
|
-
JSON.stringify(orgCapNpmConf.devDependencies) !== JSON.stringify(capNpmConf.devDependencies)
|
|
395
|
-
|
|
396
|
-
return { depChanged: isChanged, workspacePlugins };
|
|
387
|
+
JSON.stringify(orgCapNpmConf.devDependencies) !== JSON.stringify(capNpmConf.devDependencies)
|
|
388
|
+
);
|
|
397
389
|
}
|
|
398
390
|
|
|
399
391
|
/**
|
|
@@ -1087,30 +1079,6 @@ export default config;
|
|
|
1087
1079
|
* workspace:* 플러그인을 .capacitor/node_modules/에 symlink로 연결한다.
|
|
1088
1080
|
* cap sync는 플러그인의 android/ 네이티브 코드만 필요하므로 JS 의존성 resolve 불필요.
|
|
1089
1081
|
*/
|
|
1090
|
-
private async _linkWorkspacePlugins(plugins: string[]): Promise<void> {
|
|
1091
|
-
if (plugins.length === 0) return;
|
|
1092
|
-
|
|
1093
|
-
const require = createRequire(pathx.posixResolve(this._pkgPath, "package.json"));
|
|
1094
|
-
|
|
1095
|
-
for (const plugin of plugins) {
|
|
1096
|
-
const pluginPkgJsonPath = require.resolve(`${plugin}/package.json`);
|
|
1097
|
-
const pluginDir = path.dirname(pluginPkgJsonPath);
|
|
1098
|
-
|
|
1099
|
-
const linkPath = pathx.posixResolve(this._capPath, "node_modules", ...plugin.split("/"));
|
|
1100
|
-
|
|
1101
|
-
// scope 디렉토리 생성 (예: @simplysm/)
|
|
1102
|
-
await fsx.mkdir(path.dirname(linkPath));
|
|
1103
|
-
|
|
1104
|
-
// 기존 symlink가 있으면 삭제
|
|
1105
|
-
if (await fsx.exists(linkPath)) {
|
|
1106
|
-
await fsx.rm(linkPath);
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
await symlink(pluginDir, linkPath, "junction");
|
|
1110
|
-
Capacitor._logger.debug(`workspace 플러그인 symlink: ${plugin} → ${pluginDir}`);
|
|
1111
|
-
}
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
1082
|
/**
|
|
1115
1083
|
* pnpm-workspace.yaml이 있는 워크스페이스 루트 디렉토리를 찾는다.
|
|
1116
1084
|
*/
|
package/src/commands/device.ts
CHANGED
|
@@ -64,8 +64,9 @@ export async function runDevice(options: DeviceOptions): Promise<void> {
|
|
|
64
64
|
if (typeof clientConfig.server === "number") {
|
|
65
65
|
serverUrl = `http://localhost:${clientConfig.server}/${targetName}/`;
|
|
66
66
|
} else {
|
|
67
|
-
// server가 패키지명(string)인 경우: .dev-port 파일에서 포트 자동 탐지
|
|
68
|
-
const
|
|
67
|
+
// server가 패키지명(string)인 경우: 서버 패키지의 .dev-port 파일에서 포트 자동 탐지
|
|
68
|
+
const serverPkgDir = pathx.posixResolve(cwd, "packages", clientConfig.server);
|
|
69
|
+
const portFile = path.join(serverPkgDir, "dist", ".dev-port");
|
|
69
70
|
let portStr: string;
|
|
70
71
|
try {
|
|
71
72
|
portStr = fs.readFileSync(portFile, "utf-8").trim();
|
package/src/electron/electron.ts
CHANGED
|
@@ -125,6 +125,9 @@ export class Electron {
|
|
|
125
125
|
};
|
|
126
126
|
|
|
127
127
|
const envBanner = createEnvBanner({ ELECTRON_DEV_URL: url, ...this._config.env });
|
|
128
|
+
const bannerJs =
|
|
129
|
+
"import { createRequire } from 'module'; const require = createRequire(import.meta.url);" +
|
|
130
|
+
envBanner;
|
|
128
131
|
|
|
129
132
|
Electron._logger.debug("esbuild context 생성 시작");
|
|
130
133
|
const ctx = await esbuild.context({
|
|
@@ -132,10 +135,10 @@ export class Electron {
|
|
|
132
135
|
outfile: pathx.posixResolve(this._srcPath, "electron-main.js"),
|
|
133
136
|
platform: "node",
|
|
134
137
|
target: "node20",
|
|
135
|
-
format: "
|
|
138
|
+
format: "esm",
|
|
136
139
|
bundle: true,
|
|
137
140
|
external: ["electron", ...builtinModules, ...reinstallDeps, ...this._exclude],
|
|
138
|
-
banner: { js:
|
|
141
|
+
banner: { js: bannerJs },
|
|
139
142
|
plugins: [
|
|
140
143
|
{
|
|
141
144
|
name: "electron-restart",
|
|
@@ -264,6 +267,7 @@ export class Electron {
|
|
|
264
267
|
name: this._npmConfig.name.replace(/^@/, "").replace(/\//, "-"),
|
|
265
268
|
version: this._npmConfig.version,
|
|
266
269
|
description: this._npmConfig.description,
|
|
270
|
+
type: "module",
|
|
267
271
|
main: "electron-main.js",
|
|
268
272
|
dependencies,
|
|
269
273
|
devDependencies,
|
|
@@ -292,6 +296,9 @@ export class Electron {
|
|
|
292
296
|
const reinstallDeps = this._config.reinstallDependencies ?? [];
|
|
293
297
|
|
|
294
298
|
const envBanner = createEnvBanner(this._config.env);
|
|
299
|
+
const bannerJs =
|
|
300
|
+
"import { createRequire } from 'module'; const require = createRequire(import.meta.url);" +
|
|
301
|
+
envBanner;
|
|
295
302
|
|
|
296
303
|
Electron._logger.debug(`esbuild 번들링: ${entryPoint}`);
|
|
297
304
|
await esbuild.build({
|
|
@@ -299,10 +306,10 @@ export class Electron {
|
|
|
299
306
|
outfile: pathx.posixResolve(this._srcPath, "electron-main.js"),
|
|
300
307
|
platform: "node",
|
|
301
308
|
target: "node20",
|
|
302
|
-
format: "
|
|
309
|
+
format: "esm",
|
|
303
310
|
bundle: true,
|
|
304
311
|
external: ["electron", ...builtinModules, ...reinstallDeps, ...this._exclude],
|
|
305
|
-
banner: { js:
|
|
312
|
+
banner: { js: bannerJs },
|
|
306
313
|
});
|
|
307
314
|
}
|
|
308
315
|
|
|
@@ -277,6 +277,20 @@ export class DevWatchOrchestrator {
|
|
|
277
277
|
|
|
278
278
|
private async _startWatchMode(): Promise<void> {
|
|
279
279
|
this._logger.debug("watch 모드 시작");
|
|
280
|
+
|
|
281
|
+
// [DEBUG] angular/dist 삭제 감지용 임시 워처
|
|
282
|
+
{
|
|
283
|
+
const debugDistDir = pathx.posixResolve(this._cwd, "packages", "angular", "dist");
|
|
284
|
+
const debugWatcher = await FsWatcher.watch([debugDistDir]);
|
|
285
|
+
debugWatcher.onChange({ delay: 100 }, (changes) => {
|
|
286
|
+
for (const c of changes) {
|
|
287
|
+
if (c.event === "unlink" || c.event === "unlinkDir") {
|
|
288
|
+
this._logger.error(`[DEBUG:angular-dist] ${c.event}: ${c.path}\n${new Error().stack}`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
280
294
|
// Start copySrc watchers for library packages
|
|
281
295
|
for (const pkg of this._libraryPackages) {
|
|
282
296
|
if (pkg.config.copySrc != null && pkg.config.copySrc.length > 0) {
|
package/src/utils/vite-config.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { InlineConfig, PluginOption } from "vite";
|
|
2
2
|
import path from "path";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import tsconfigPaths from "vite-tsconfig-paths";
|
|
3
5
|
import browserslistToEsbuild from "browserslist-to-esbuild";
|
|
6
|
+
import { pathx } from "@simplysm/core-node";
|
|
4
7
|
import { sdAngularPlugin } from "../angular/vite-angular-plugin.js";
|
|
5
8
|
import solidPlugin from "vite-plugin-solid";
|
|
6
9
|
import {
|
|
@@ -9,7 +12,8 @@ import {
|
|
|
9
12
|
} from "./vite-scope-watch-plugin.js";
|
|
10
13
|
import { sdPostCssInlinePlugin } from "../angular/vite-postcss-inline-plugin.js";
|
|
11
14
|
import type { SdPwaConfig } from "../sd-config.types.js";
|
|
12
|
-
import {
|
|
15
|
+
import { VitePWA } from "vite-plugin-pwa";
|
|
16
|
+
import { generatePwaIcons } from "./generate-pwa-icons.js";
|
|
13
17
|
|
|
14
18
|
/** createClientViteConfig 옵션 */
|
|
15
19
|
export interface CreateClientViteConfigOptions {
|
|
@@ -68,9 +72,9 @@ export interface CreateClientViteConfigOptions {
|
|
|
68
72
|
* Angular AOT 플러그인, tsconfigPaths, env define, server/build 기본 설정,
|
|
69
73
|
* browserslist, PostCSS, polyfills, legacyModule (inlineDynamicImports) 등을 통합 구성한다.
|
|
70
74
|
*/
|
|
71
|
-
export function createClientViteConfig(
|
|
75
|
+
export async function createClientViteConfig(
|
|
72
76
|
options: CreateClientViteConfigOptions,
|
|
73
|
-
): InlineConfig {
|
|
77
|
+
): Promise<InlineConfig> {
|
|
74
78
|
const name = options.pkgName.replace(/^@[^/]+\//, "");
|
|
75
79
|
|
|
76
80
|
// browserslist → esbuild target
|
|
@@ -98,8 +102,29 @@ export function createClientViteConfig(
|
|
|
98
102
|
}
|
|
99
103
|
}
|
|
100
104
|
|
|
105
|
+
// replaceDeps dist 경로 (symlink → realpath 해결)
|
|
106
|
+
let replaceDepDistPaths: string[] | undefined;
|
|
107
|
+
if (options.replaceDeps != null && options.replaceDeps.length > 0) {
|
|
108
|
+
replaceDepDistPaths = [];
|
|
109
|
+
for (const dep of options.replaceDeps) {
|
|
110
|
+
const distDir = path.join(
|
|
111
|
+
options.pkgDir,
|
|
112
|
+
"node_modules",
|
|
113
|
+
...dep.packageName.split("/"),
|
|
114
|
+
"dist",
|
|
115
|
+
);
|
|
116
|
+
try {
|
|
117
|
+
replaceDepDistPaths.push(pathx.posix(fs.realpathSync(distDir)));
|
|
118
|
+
} catch {
|
|
119
|
+
replaceDepDistPaths.push(pathx.posix(distDir));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
101
124
|
// plugins
|
|
102
|
-
const plugins: PluginOption[] = [
|
|
125
|
+
const plugins: PluginOption[] = [
|
|
126
|
+
tsconfigPaths({ projects: [options.tsconfigPath] }),
|
|
127
|
+
];
|
|
103
128
|
|
|
104
129
|
if (options.framework === "solid") {
|
|
105
130
|
plugins.push(solidPlugin());
|
|
@@ -115,6 +140,7 @@ export function createClientViteConfig(
|
|
|
115
140
|
enableLint: options.enableLint,
|
|
116
141
|
browserslist: normalizedBrowserslist,
|
|
117
142
|
postCssPlugins: options.postCssPlugins,
|
|
143
|
+
replaceDepDistPaths,
|
|
118
144
|
}),
|
|
119
145
|
);
|
|
120
146
|
|
|
@@ -157,32 +183,70 @@ export function createClientViteConfig(
|
|
|
157
183
|
? { postcss: { plugins: options.postCssPlugins as import("postcss").AcceptedPlugin[] } }
|
|
158
184
|
: undefined;
|
|
159
185
|
|
|
160
|
-
// optimizeDeps.exclude (사용자 지정 exclude)
|
|
186
|
+
// optimizeDeps.exclude (사용자 지정 exclude + replaceDeps 패키지)
|
|
187
|
+
const excludeList = [
|
|
188
|
+
...(options.exclude ?? []),
|
|
189
|
+
...(options.replaceDeps?.map((dep) => dep.packageName) ?? []),
|
|
190
|
+
];
|
|
161
191
|
const optimizeDepsConfig =
|
|
162
|
-
|
|
163
|
-
? { exclude: options.exclude }
|
|
164
|
-
: undefined;
|
|
192
|
+
excludeList.length > 0 ? { exclude: excludeList } : undefined;
|
|
165
193
|
|
|
166
194
|
const config: InlineConfig = {
|
|
167
195
|
root: options.pkgDir,
|
|
168
196
|
base: options.base ?? `/${name}/`,
|
|
169
|
-
resolve: { tsconfigPaths: true },
|
|
170
197
|
define: Object.keys(define).length > 0 ? define : undefined,
|
|
171
198
|
plugins,
|
|
172
199
|
server: serverConfig,
|
|
173
200
|
css: cssConfig,
|
|
201
|
+
esbuild: {
|
|
202
|
+
target: esbuildTarget,
|
|
203
|
+
},
|
|
174
204
|
build: {
|
|
175
205
|
target: esbuildTarget,
|
|
176
206
|
},
|
|
177
207
|
optimizeDeps: {
|
|
178
208
|
...optimizeDepsConfig,
|
|
209
|
+
esbuildOptions: {
|
|
210
|
+
target: esbuildTarget as string[],
|
|
211
|
+
},
|
|
179
212
|
},
|
|
180
213
|
};
|
|
181
214
|
|
|
182
215
|
// PWA (build 모드 + pwa !== false)
|
|
183
216
|
if (options.mode === "build" && options.pwa !== false) {
|
|
217
|
+
const pwaConfig = typeof options.pwa === "object" ? options.pwa : {};
|
|
218
|
+
|
|
219
|
+
// 아이콘 자동 생성 (커스텀 icons 미설정 시)
|
|
220
|
+
let iconsConfig: Record<string, unknown> = {};
|
|
221
|
+
if (pwaConfig.manifest?.icons != null) {
|
|
222
|
+
iconsConfig = { icons: pwaConfig.manifest.icons };
|
|
223
|
+
} else {
|
|
224
|
+
const generatedIcons = await generatePwaIcons(options.pkgDir);
|
|
225
|
+
if (generatedIcons.length > 0) {
|
|
226
|
+
iconsConfig = { icons: generatedIcons };
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const pwaManifest = {
|
|
231
|
+
name: pwaConfig.manifest?.name ?? name,
|
|
232
|
+
short_name: pwaConfig.manifest?.short_name ?? name,
|
|
233
|
+
display: pwaConfig.manifest?.display ?? "standalone",
|
|
234
|
+
theme_color: pwaConfig.manifest?.theme_color ?? "#ffffff",
|
|
235
|
+
background_color: pwaConfig.manifest?.background_color ?? "#ffffff",
|
|
236
|
+
...iconsConfig,
|
|
237
|
+
};
|
|
238
|
+
const pwaWorkbox = {
|
|
239
|
+
globPatterns: pwaConfig.workbox?.globPatterns ?? [
|
|
240
|
+
"**/*.{js,css,html,ico,png,svg,woff2}",
|
|
241
|
+
],
|
|
242
|
+
};
|
|
184
243
|
(config.plugins as PluginOption[]).push(
|
|
185
|
-
|
|
244
|
+
VitePWA({
|
|
245
|
+
registerType: "prompt",
|
|
246
|
+
injectRegister: "script",
|
|
247
|
+
manifest: pwaManifest,
|
|
248
|
+
workbox: pwaWorkbox,
|
|
249
|
+
}),
|
|
186
250
|
);
|
|
187
251
|
}
|
|
188
252
|
|
|
@@ -231,18 +295,24 @@ export function createClientViteConfig(
|
|
|
231
295
|
}
|
|
232
296
|
}
|
|
233
297
|
|
|
234
|
-
// legacyModule: true → 코드 스플리팅 비활성화 + import.meta
|
|
298
|
+
// legacyModule: true → 코드 스플리팅 비활성화 + esbuild import.meta 변환 + 잔여 import() 제거
|
|
235
299
|
if (options.legacyModule === true) {
|
|
300
|
+
config.esbuild = {
|
|
301
|
+
...config.esbuild,
|
|
302
|
+
supported: {
|
|
303
|
+
"import-meta": false,
|
|
304
|
+
},
|
|
305
|
+
};
|
|
236
306
|
config.build = {
|
|
237
307
|
...config.build,
|
|
238
|
-
|
|
308
|
+
rollupOptions: {
|
|
239
309
|
output: {
|
|
240
310
|
inlineDynamicImports: true,
|
|
241
311
|
},
|
|
242
312
|
},
|
|
243
313
|
};
|
|
244
314
|
|
|
245
|
-
//
|
|
315
|
+
// Rollup이 인라인하지 못한 잔여 dynamic import()를 제거한다.
|
|
246
316
|
// inlineDynamicImports가 정적 경로를 모두 인라인한 후에도,
|
|
247
317
|
// @vite-ignore나 런타임 계산 경로의 import()가 남을 수 있다.
|
|
248
318
|
// Chrome 61은 import() 구문을 파싱하지 못하므로 no-op 함수로 치환한다.
|
|
@@ -260,20 +330,6 @@ export function createClientViteConfig(
|
|
|
260
330
|
};
|
|
261
331
|
},
|
|
262
332
|
});
|
|
263
|
-
|
|
264
|
-
// import.meta 구문을 치환한다. Chrome 61은 import.meta를 파싱하지 못한다 (Chrome 64+).
|
|
265
|
-
// Vite/Rolldown이 빌드 시 대부분의 import.meta를 resolve하지만, 잔여분에 대한 안전망이다.
|
|
266
|
-
(config.plugins as PluginOption[]).push({
|
|
267
|
-
name: "sd-legacy-strip-import-meta",
|
|
268
|
-
enforce: "post",
|
|
269
|
-
renderChunk(code) {
|
|
270
|
-
if (!code.includes("import.meta")) return null;
|
|
271
|
-
return {
|
|
272
|
-
code: code.replace(/\bimport\.meta\b/g, "(void 0)"),
|
|
273
|
-
map: null,
|
|
274
|
-
};
|
|
275
|
-
},
|
|
276
|
-
});
|
|
277
333
|
}
|
|
278
334
|
|
|
279
335
|
// build 모드 설정 (프로덕션 빌드 또는 legacyModule dev)
|
|
@@ -51,7 +51,12 @@ export function sdScopeWatchPlugin(options: SdScopeWatchPluginOptions): Plugin {
|
|
|
51
51
|
"dist",
|
|
52
52
|
);
|
|
53
53
|
if (fs.existsSync(distDir)) {
|
|
54
|
-
|
|
54
|
+
// symlink → realpath 해결 (Vite 모듈 그래프가 realpath를 키로 사용)
|
|
55
|
+
try {
|
|
56
|
+
watchPaths.push(fs.realpathSync(distDir));
|
|
57
|
+
} catch {
|
|
58
|
+
watchPaths.push(distDir);
|
|
59
|
+
}
|
|
55
60
|
}
|
|
56
61
|
}
|
|
57
62
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "path";
|
|
1
3
|
import { createWorker } from "@simplysm/core-node";
|
|
2
4
|
import { env, err as errNs } from "@simplysm/core-common";
|
|
3
5
|
import { consola } from "consola";
|
|
@@ -49,10 +51,18 @@ const logger = consola.withTag("sd:cli:server-runtime:worker");
|
|
|
49
51
|
/** 서버 인스턴스 (정리 대상) */
|
|
50
52
|
let serverInstance: { close: () => Promise<void> } | undefined;
|
|
51
53
|
|
|
54
|
+
/** .dev-port 기록 경로 (cleanup에서 삭제용) */
|
|
55
|
+
let mainJsDir: string | undefined;
|
|
56
|
+
|
|
52
57
|
/**
|
|
53
58
|
* 리소스 정리
|
|
54
59
|
*/
|
|
55
60
|
async function cleanup(): Promise<void> {
|
|
61
|
+
if (mainJsDir != null) {
|
|
62
|
+
try { fs.unlinkSync(path.join(mainJsDir, ".dev-port")); } catch { /* 파일 없으면 무시 */ }
|
|
63
|
+
mainJsDir = undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
56
66
|
const server = serverInstance;
|
|
57
67
|
serverInstance = undefined;
|
|
58
68
|
if (server != null) {
|
|
@@ -172,6 +182,11 @@ async function start(info: ServerRuntimeStartInfo): Promise<void> {
|
|
|
172
182
|
await server.listen();
|
|
173
183
|
logger.debug(`서버 리슨 완료 (${Math.round(performance.now() - stepStart)}ms)`);
|
|
174
184
|
|
|
185
|
+
// .dev-port 기록 (device 명령어에서 자동 탐지용)
|
|
186
|
+
mainJsDir = path.dirname(info.mainJsPath);
|
|
187
|
+
fs.mkdirSync(mainJsDir, { recursive: true });
|
|
188
|
+
fs.writeFileSync(path.join(mainJsDir, ".dev-port"), String(server.options.port));
|
|
189
|
+
|
|
175
190
|
logger.debug(
|
|
176
191
|
`런타임 총 시작 시간: ${Math.round(performance.now() - startTime)}ms`,
|
|
177
192
|
);
|