@simplysm/sd-cli 12.13.13 → 12.13.15

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.
Files changed (87) hide show
  1. package/dist/entry/sd-cli-electron.js +1 -1
  2. package/dist/entry/sd-cli-electron.js.map +1 -1
  3. package/dist/entry/sd-cli-project.d.ts +2 -0
  4. package/dist/entry/sd-cli-project.js +11 -3
  5. package/dist/entry/sd-cli-project.js.map +1 -1
  6. package/dist/pkg-builders/client/sd-client.build-runner.d.ts +1 -1
  7. package/dist/pkg-builders/client/sd-client.build-runner.js +74 -50
  8. package/dist/pkg-builders/client/sd-client.build-runner.js.map +1 -1
  9. package/dist/pkg-builders/client/sd-ng.bundler.d.ts +2 -0
  10. package/dist/pkg-builders/client/sd-ng.bundler.js +19 -20
  11. package/dist/pkg-builders/client/sd-ng.bundler.js.map +1 -1
  12. package/dist/pkg-builders/client/sd-ng.plugin-creator.d.ts +2 -0
  13. package/dist/pkg-builders/client/sd-ng.plugin-creator.js +94 -33
  14. package/dist/pkg-builders/client/sd-ng.plugin-creator.js.map +1 -1
  15. package/dist/pkg-builders/commons/build-runner.base.d.ts +4 -2
  16. package/dist/pkg-builders/commons/build-runner.base.js +45 -25
  17. package/dist/pkg-builders/commons/build-runner.base.js.map +1 -1
  18. package/dist/pkg-builders/lib/sd-js-lib.build-runner.d.ts +1 -1
  19. package/dist/pkg-builders/lib/sd-js-lib.build-runner.js +14 -4
  20. package/dist/pkg-builders/lib/sd-js-lib.build-runner.js.map +1 -1
  21. package/dist/pkg-builders/lib/sd-ts-lib.build-runner.d.ts +1 -1
  22. package/dist/pkg-builders/lib/sd-ts-lib.build-runner.js +21 -40
  23. package/dist/pkg-builders/lib/sd-ts-lib.build-runner.js.map +1 -1
  24. package/dist/pkg-builders/lib/sd-ts-lib.builder.d.ts +1 -1
  25. package/dist/pkg-builders/lib/sd-ts-lib.builder.js +3 -1
  26. package/dist/pkg-builders/lib/sd-ts-lib.builder.js.map +1 -1
  27. package/dist/pkg-builders/sd-multi.build-runner.js +116 -86
  28. package/dist/pkg-builders/sd-multi.build-runner.js.map +1 -1
  29. package/dist/pkg-builders/server/sd-server.build-runner.d.ts +1 -1
  30. package/dist/pkg-builders/server/sd-server.build-runner.js +9 -5
  31. package/dist/pkg-builders/server/sd-server.build-runner.js.map +1 -1
  32. package/dist/pkg-builders/server/sd-server.bundler.d.ts +2 -0
  33. package/dist/pkg-builders/server/sd-server.bundler.js +3 -1
  34. package/dist/pkg-builders/server/sd-server.bundler.js.map +1 -1
  35. package/dist/pkg-builders/server/sd-server.plugin-creator.d.ts +2 -0
  36. package/dist/pkg-builders/server/sd-server.plugin-creator.js +4 -1
  37. package/dist/pkg-builders/server/sd-server.plugin-creator.js.map +1 -1
  38. package/dist/sd-cli-entry.js +10 -0
  39. package/dist/sd-cli-entry.js.map +1 -1
  40. package/dist/sd-cli.js +8 -6
  41. package/dist/sd-cli.js.map +1 -1
  42. package/dist/ts-compiler/sd-dependency-analyzer.d.ts +8 -1
  43. package/dist/ts-compiler/sd-dependency-analyzer.js +128 -63
  44. package/dist/ts-compiler/sd-dependency-analyzer.js.map +1 -1
  45. package/dist/ts-compiler/sd-dependency-cache.d.ts +0 -34
  46. package/dist/ts-compiler/sd-dependency-cache.js +73 -119
  47. package/dist/ts-compiler/sd-dependency-cache.js.map +1 -1
  48. package/dist/ts-compiler/sd-style-bundler.d.ts +16 -0
  49. package/dist/ts-compiler/sd-style-bundler.js +118 -0
  50. package/dist/ts-compiler/sd-style-bundler.js.map +1 -0
  51. package/dist/ts-compiler/sd-ts-compiler.js +418 -235
  52. package/dist/ts-compiler/sd-ts-compiler.js.map +1 -1
  53. package/dist/types/build-runner.types.d.ts +2 -0
  54. package/dist/types/config.types.d.ts +2 -0
  55. package/dist/types/ts-compiler.types.d.ts +2 -0
  56. package/dist/types/worker.types.d.ts +1 -1
  57. package/dist/workers/build-runner.worker.js +1 -1
  58. package/dist/workers/build-runner.worker.js.map +1 -1
  59. package/dist/workers/server.worker.js +2 -2
  60. package/dist/workers/server.worker.js.map +1 -1
  61. package/package.json +13 -12
  62. package/src/entry/sd-cli-electron.ts +2 -1
  63. package/src/entry/sd-cli-project.ts +13 -3
  64. package/src/pkg-builders/client/sd-client.build-runner.ts +93 -54
  65. package/src/pkg-builders/client/sd-ng.bundler.ts +79 -85
  66. package/src/pkg-builders/client/sd-ng.plugin-creator.ts +119 -39
  67. package/src/pkg-builders/commons/build-runner.base.ts +78 -33
  68. package/src/pkg-builders/lib/sd-js-lib.build-runner.ts +24 -7
  69. package/src/pkg-builders/lib/sd-ts-lib.build-runner.ts +36 -42
  70. package/src/pkg-builders/lib/sd-ts-lib.builder.ts +4 -0
  71. package/src/pkg-builders/sd-multi.build-runner.ts +137 -108
  72. package/src/pkg-builders/server/sd-server.build-runner.ts +10 -4
  73. package/src/pkg-builders/server/sd-server.bundler.ts +5 -1
  74. package/src/pkg-builders/server/sd-server.plugin-creator.ts +10 -5
  75. package/src/sd-cli-entry.ts +10 -0
  76. package/src/sd-cli.ts +8 -6
  77. package/src/ts-compiler/sd-dependency-analyzer.ts +161 -86
  78. package/src/ts-compiler/sd-dependency-cache.ts +118 -99
  79. package/src/ts-compiler/sd-style-bundler.ts +150 -0
  80. package/src/ts-compiler/sd-ts-compiler.ts +559 -296
  81. package/src/types/build-runner.types.ts +2 -0
  82. package/src/types/config.types.ts +3 -0
  83. package/src/types/ts-compiler.types.ts +15 -11
  84. package/src/types/worker.types.ts +7 -10
  85. package/src/workers/build-runner.worker.ts +9 -5
  86. package/src/workers/server.worker.ts +2 -2
  87. package/tests/deps/sd-dependency-cache.spec.ts +1 -1
@@ -1,33 +1,37 @@
1
1
  import ts from "typescript";
2
2
  import path from "path";
3
3
  import { FsUtils, PathUtils, SdLogger, TNormPath } from "@simplysm/sd-core-node";
4
- import { StringUtils, Wait } from "@simplysm/sd-core-common";
4
+ import { StringUtils } from "@simplysm/sd-core-common";
5
5
  import { NgtscProgram, OptimizeFor } from "@angular/compiler-cli";
6
6
  import { AngularCompilerHost } from "@angular/build/src/tools/angular/angular-host";
7
7
  import { replaceBootstrap } from "@angular/build/src/tools/angular/transformers/jit-bootstrap-transformer";
8
8
  import { SdCliPerformanceTimer } from "../utils/sd-cli-performance-time";
9
9
  import { SdCliConvertMessageUtils } from "../utils/sd-cli-convert-message.utils";
10
- import { ISdTsCompilerResult, SdTsCompilerOptions, TStylesheetBundlingResult } from "../types/ts-compiler.types";
10
+ import { ISdTsCompilerResult, SdTsCompilerOptions } from "../types/ts-compiler.types";
11
11
  import { createWorkerTransformer } from "@angular/build/src/tools/angular/transformers/web-worker-transformer";
12
- import { ISdAffectedFileTreeNode, SdDependencyCache } from "./sd-dependency-cache";
12
+ import { SdDependencyCache } from "./sd-dependency-cache";
13
13
  import { SdDependencyAnalyzer } from "./sd-dependency-analyzer";
14
14
  import { FlatESLint } from "eslint/use-at-your-own-risk";
15
- import { ComponentStylesheetBundler } from "@angular/build/src/tools/esbuild/angular/component-stylesheets";
16
- import { transformSupportedBrowsersToTargets } from "@angular/build/src/tools/esbuild/utils";
17
- import browserslist from "browserslist";
15
+ import { SdStyleBundler } from "./sd-style-bundler";
18
16
 
19
17
  export class SdTsCompiler {
20
18
  #logger = SdLogger.get(["simplysm", "sd-cli", "SdTsCompiler"]);
21
19
 
22
20
  #isForAngular: boolean;
23
21
 
24
- #stylesheetBundler: ComponentStylesheetBundler;
22
+ #styleBundler: SdStyleBundler | undefined;
25
23
 
26
24
  #ngProgram: NgtscProgram | undefined;
27
25
  #program: ts.Program | undefined;
28
26
 
29
27
  // 빌드정보 캐싱
30
- #depCache = new SdDependencyCache();
28
+ #cache = {
29
+ dep: new SdDependencyCache(),
30
+ type: new WeakMap<ts.Node, ts.Type | undefined>(),
31
+ prop: new WeakMap<ts.Type, Map<string, ts.Symbol | undefined>>(),
32
+ declFiles: new WeakMap<ts.Symbol, TNormPath[]>(),
33
+ ngOrg: new Map<TNormPath, ts.SourceFile>(),
34
+ };
31
35
  #sourceFileCacheMap = new Map<TNormPath, ts.SourceFile>();
32
36
  #emittedFilesCacheMap = new Map<
33
37
  TNormPath,
@@ -37,9 +41,6 @@ export class SdTsCompiler {
37
41
  }[]
38
42
  >();
39
43
 
40
- // 빌드결과 캐싱
41
- #stylesheetBundlingResultMap = new Map<TNormPath, TStylesheetBundlingResult>();
42
-
43
44
  #perf!: SdCliPerformanceTimer;
44
45
 
45
46
  constructor(private readonly _opt: SdTsCompilerOptions) {
@@ -49,32 +50,13 @@ export class SdTsCompiler {
49
50
  const tsconfig = FsUtils.readJson(tsconfigPath);
50
51
  this.#isForAngular = Boolean(tsconfig.angularCompilerOptions);
51
52
 
52
- this.#stylesheetBundler = new ComponentStylesheetBundler(
53
- {
54
- workspaceRoot: this._opt.pkgPath,
55
- optimization: !this._opt.isDevMode,
56
- inlineFonts: true,
57
- preserveSymlinks: false,
58
- sourcemap: this._opt.isDevMode ? "inline" : false,
59
- outputNames: { bundles: "[name]", media: "media/[name]" },
60
- includePaths: [],
61
- // sass:
62
- externalDependencies: [],
63
- target: transformSupportedBrowsersToTargets(browserslist(["Chrome > 78"])),
64
- tailwindConfiguration: undefined,
65
- /*postcssConfiguration: {
66
- plugins: [["css-has-pseudo"]],
67
- },*/
68
- // publicPath:
69
- cacheOptions: {
70
- enabled: true,
71
- path: ".cache/angular",
72
- basePath: ".cache",
73
- },
74
- },
75
- "scss",
76
- this._opt.isDevMode,
77
- );
53
+ if (!this._opt.isNoEmit) {
54
+ this.#styleBundler = new SdStyleBundler(
55
+ this._opt.pkgPath,
56
+ this._opt.isDevMode,
57
+ this._opt.watchScopePathSet,
58
+ );
59
+ }
78
60
  }
79
61
 
80
62
  #parseTsConfig() {
@@ -83,9 +65,38 @@ export class SdTsCompiler {
83
65
  const parsedTsconfig = ts.parseJsonConfigFileContent(tsconfig, ts.sys, this._opt.pkgPath, {
84
66
  ...tsconfig.angularCompilerOptions,
85
67
  ...this._opt.additionalOptions,
68
+ ...(this._opt.isEmitOnly
69
+ ? {
70
+ // typescript
71
+ noEmitOnError: false,
72
+ strict: false,
73
+ noImplicitAny: false,
74
+ noImplicitThis: false,
75
+ strictNullChecks: false,
76
+ strictFunctionTypes: false,
77
+ strictBindCallApply: false,
78
+ strictPropertyInitialization: false,
79
+ useUnknownInCatchVariables: false,
80
+ exactOptionalPropertyTypes: false,
81
+ noUncheckedIndexedAccess: false,
82
+ noUnusedLocals: false,
83
+ noUnusedParameters: false,
84
+ skipLibCheck: true,
85
+ checkJs: false,
86
+ alwaysStrict: false,
87
+
88
+ // angular
89
+ strictTemplates: false,
90
+ strictInjectionParameters: false,
91
+ strictInputAccessModifiers: false,
92
+ strictStandalone: false,
93
+ }
94
+ : {}),
86
95
  });
87
96
 
88
- const distPath = PathUtils.norm(parsedTsconfig.options.outDir ?? path.resolve(this._opt.pkgPath, "dist"));
97
+ const distPath = PathUtils.norm(
98
+ parsedTsconfig.options.outDir ?? path.resolve(this._opt.pkgPath, "dist"),
99
+ );
89
100
 
90
101
  return {
91
102
  fileNames: parsedTsconfig.fileNames,
@@ -141,28 +152,47 @@ export class SdTsCompiler {
141
152
  return compilerHost.readFile(fileName) ?? "";
142
153
  };
143
154
 
144
- (compilerHost as AngularCompilerHost).transformResource = async (
145
- data: string,
146
- context: {
147
- type: string;
148
- containingFile: string;
149
- resourceFile: string | null;
150
- },
151
- ) => {
152
- if (context.type !== "style") {
153
- return null;
154
- }
155
+ if (!this._opt.isNoEmit) {
156
+ (compilerHost as AngularCompilerHost).transformResource = async (
157
+ data: string,
158
+ context: {
159
+ type: string;
160
+ containingFile: string;
161
+ resourceFile: string | null;
162
+ },
163
+ ) => {
164
+ if (context.type !== "style") {
165
+ return null;
166
+ }
155
167
 
156
- const stylesheetBundlingResult = await this.#bundleStylesheetAsync(
157
- data,
158
- PathUtils.norm(context.containingFile),
159
- context.resourceFile != null ? PathUtils.norm(context.resourceFile) : undefined,
160
- );
168
+ const styleBundleResult = await this.#styleBundler!.bundleAsync(
169
+ data,
170
+ PathUtils.norm(context.containingFile),
171
+ context.resourceFile != null ? PathUtils.norm(context.resourceFile) : undefined,
172
+ );
161
173
 
162
- return StringUtils.isNullOrEmpty(stylesheetBundlingResult.contents)
163
- ? null
164
- : { content: stylesheetBundlingResult.contents };
165
- };
174
+ if (!styleBundleResult.cached && !StringUtils.isNullOrEmpty(styleBundleResult.contents)) {
175
+ const relPath = path.relative(
176
+ path.resolve(this._opt.pkgPath, "src"),
177
+ context.containingFile,
178
+ );
179
+ const outAbsPath = PathUtils.norm(
180
+ compilerOptions.outDir!,
181
+ relPath.replace(/\.ts$/, ".css"),
182
+ );
183
+ const cache = this.#emittedFilesCacheMap.getOrCreate(
184
+ PathUtils.norm(context.containingFile),
185
+ [],
186
+ );
187
+ cache.remove((item) => item.outAbsPath === outAbsPath);
188
+ cache.push({ outAbsPath, text: styleBundleResult.contents });
189
+ }
190
+
191
+ return StringUtils.isNullOrEmpty(styleBundleResult.contents)
192
+ ? null
193
+ : { content: "" /*styleBundleResult.contents*/ };
194
+ };
195
+ }
166
196
 
167
197
  (compilerHost as AngularCompilerHost).getModifiedResourceFiles = () => {
168
198
  return new Set(Array.from(modifiedFileSet).map((item) => PathUtils.posix(item)));
@@ -172,100 +202,24 @@ export class SdTsCompiler {
172
202
  return compilerHost;
173
203
  }
174
204
 
175
- // private async _getOrCreateStyleBundleWorkerAsync() {
176
- // if (this._stylesheetBundlingWorker) {
177
- // return this._stylesheetBundlingWorker;
178
- // }
179
- //
180
- // this._stylesheetBundlingWorker = new SdWorker<TStyleBundlerWorkerType>(
181
- // import.meta.resolve("../workers/style-bundler.worker"),
182
- // );
183
- //
184
- // await this._stylesheetBundlingWorker.run(
185
- // "prepare",
186
- // [this._opt.pkgPath, this._opt.isDevMode],
187
- // );
188
- //
189
- // return this._stylesheetBundlingWorker;
190
- // }
191
-
192
- async #bundleStylesheetAsync(
193
- data: string,
194
- containingFile: TNormPath,
195
- resourceFile: TNormPath | null = null,
196
- ): Promise<TStylesheetBundlingResult> {
197
- // containingFile: 포함된 파일 (.ts)
198
- // resourceFile: 외부 리소스 파일 (styleUrls로 입력하지 않고 styles에 직접 입력한 경우 null)
199
- // referencedFiles: import한 외부 scss 파일 혹은 woff파일등 외부 파일
200
-
201
- // this.#debug(`bundle stylesheet...(${containingFile}, ${resourceFile})`);
202
-
203
- return await this.#perf.run("스타일 번들링", async () => {
204
- const fileNPath = PathUtils.norm(resourceFile ?? containingFile);
205
- if (this.#stylesheetBundlingResultMap.has(fileNPath)) {
206
- return this.#stylesheetBundlingResultMap.get(fileNPath)!;
207
- }
208
-
209
- try {
210
- // const result = await new SdSassEmbeddedBundler(this._opt.isDevMode)
211
- // .bundleAsync(data, resourceFile ?? containingFile);
212
- // const worker = this._stylesheetBundlingWorker!;
213
- // const result = await worker.run("bundle", [data, containingFile, resourceFile]);
214
-
215
- const result =
216
- resourceFile != null
217
- ? await this.#stylesheetBundler.bundleFile(resourceFile)
218
- : await this.#stylesheetBundler.bundleInline(data, containingFile, "scss");
219
-
220
- for (const referencedFile of result.referencedFiles ?? []) {
221
- // for (const referencedFile of result.referencedFiles) {
222
- // 참조하는 파일과 참조된 파일 사이의 의존성 관계 추가
223
- this.#depCache.addImport(fileNPath, PathUtils.norm(referencedFile), 0);
224
- }
225
-
226
- this.#stylesheetBundlingResultMap.set(fileNPath, result);
227
-
228
- // this._stylesheetBundlingResultMap.set(fileNPath, {
229
- // errors: undefined,
230
- // warnings: [],
231
- // ...result,
232
- // });
233
- //
234
- // return {
235
- // errors: undefined,
236
- // warnings: [],
237
- // ...result,
238
- // };
239
- return result;
240
- } catch (err) {
241
- const result = {
242
- errors: [
243
- {
244
- text: `스타일 번들링 실패: ${err.message ?? "알 수 없는 오류"}`,
245
- location: { file: containingFile },
246
- },
247
- ],
248
- warnings: [],
249
- };
250
- this.#stylesheetBundlingResultMap.set(fileNPath, result);
251
- return result;
252
- }
253
- });
254
- }
255
-
256
205
  async compileAsync(modifiedFileSet: Set<TNormPath>): Promise<ISdTsCompilerResult> {
257
206
  this.#perf = new SdCliPerformanceTimer("esbuild compile");
258
207
 
259
208
  const prepareResult = await this.#prepareAsync(modifiedFileSet);
260
209
 
261
210
  const [globalStyleSheet, buildResult, lintResults] = await Promise.all([
262
- this.#buildGlobalStyleAsync(),
211
+ this._opt.isNoEmit ? undefined : this.#buildGlobalStyleAsync(),
263
212
  this.#build(prepareResult),
264
- this.#lintAsync(prepareResult),
213
+ this._opt.isEmitOnly ? [] : this.#lintAsync(prepareResult),
214
+ ]);
215
+
216
+ const affectedFileSet = new Set([
217
+ ...prepareResult.affectedFileSet,
218
+ ...prepareResult.styleAffectedFileSet,
265
219
  ]);
266
220
 
267
221
  this.#debug(`빌드 완료됨`, this.#perf.toString());
268
- this.#debug(`영향 받은 파일: ${prepareResult.affectedFileSet.size}개`);
222
+ this.#debug(`영향 받은 파일: ${affectedFileSet.size}개`);
269
223
  this.#debug(`감시 중인 파일: ${prepareResult.watchFileSet.size}개`);
270
224
 
271
225
  return {
@@ -273,9 +227,9 @@ export class SdTsCompiler {
273
227
  ...SdCliConvertMessageUtils.convertToBuildMessagesFromTsDiag(buildResult.diagnostics),
274
228
  ...SdCliConvertMessageUtils.convertToBuildMessagesFromEslint(lintResults),
275
229
  ],
276
- affectedFileSet: prepareResult.affectedFileSet,
230
+ affectedFileSet: affectedFileSet,
277
231
  watchFileSet: prepareResult.watchFileSet,
278
- stylesheetBundlingResultMap: this.#stylesheetBundlingResultMap,
232
+ stylesheetBundlingResultMap: this.#styleBundler?.getResultCache() ?? new Map(),
279
233
  emittedFilesCacheMap: this.#emittedFilesCacheMap,
280
234
  emitFileSet: new Set([...buildResult.emitFileSet, globalStyleSheet].filterExists()),
281
235
  };
@@ -288,57 +242,46 @@ export class SdTsCompiler {
288
242
 
289
243
  if (modifiedFileSet.size !== 0) {
290
244
  this.#debug(`캐시 무효화 및 초기화 중...`);
291
- await Wait.time(100);
292
245
 
293
246
  // this._perf.run("캐시 무효화 및 초기화", () => {
294
247
  this.#perf.run("캐시 무효화 및 초기화", () => {
295
- // 기존 의존성에 의해 영향받는 파일들 계산
296
- const affectedFileMap = this.#depCache.getAffectedFileMap(modifiedFileSet);
297
- const affectedFiles = Array.from(affectedFileMap.values()).mapMany((item) => Array.from(item));
248
+ // 소스파일은 변경된 파일들로 무효화
249
+ for (const modifiedFile of modifiedFileSet) {
250
+ this.#sourceFileCacheMap.delete(modifiedFile);
251
+ }
298
252
 
299
- const getTreeText = (node: ISdAffectedFileTreeNode, indent = "") => {
300
- let result = indent + node.fileNPath + "\n";
301
- for (const child of node.children) {
302
- result += getTreeText(child, indent + " ");
253
+ // 스타일 번들러 무효화 (transformResource 재실행 땜에 필요할듯)
254
+ if (this.#styleBundler) {
255
+ const styleAffectedFileSet = this.#styleBundler.invalidate(modifiedFileSet);
256
+ // 스타일 변경된 파일들로 소스파일 무효화
257
+ for (const styleAffectedFile of styleAffectedFileSet) {
258
+ this.#sourceFileCacheMap.delete(styleAffectedFile);
303
259
  }
260
+ }
304
261
 
305
- return result;
306
- };
307
-
308
- const affectedFileTree = this.#depCache.getAffectedFileTree(modifiedFileSet);
309
- this.#debug(`영향받은 기존파일: ${affectedFileTree.map((item) => getTreeText(item)).join("\n")}`.trim());
310
-
311
- // 스타일 번들러에서 영향받은 파일 관련 항목 무효화 (.scss만 해당)
312
- this.#stylesheetBundler.invalidate(
313
- new Set([...modifiedFileSet, ...affectedFiles.filter((item) => item.endsWith(".scss"))]),
314
- );
315
- // await worker.run("invalidate", [affectedFileSet]);
316
-
317
- // 소스파일은 변경된 파일들로 무효화 (스타일 변화시 타고들어가야됨)
262
+ // angular origin 파일 매핑은 변경된 파일들로 무효화
318
263
  for (const modifiedFile of modifiedFileSet) {
319
- if (modifiedFile.endsWith(".scss")) {
320
- for (const fileNPath of affectedFileMap.get(modifiedFile) ?? []) {
321
- this.#sourceFileCacheMap.delete(fileNPath);
322
- }
323
- } else {
324
- this.#sourceFileCacheMap.delete(modifiedFile);
325
- }
264
+ this.#cache.ngOrg.delete(modifiedFile);
326
265
  }
327
266
 
267
+ // 기존 의존성에 의해 영향받는 파일들 계산
268
+ const affectedFileMap = this.#cache.dep.getAffectedFileMap(modifiedFileSet);
269
+ const affectedFileSet = new Set(
270
+ Array.from(affectedFileMap.values()).mapMany((item) => Array.from(item)),
271
+ );
272
+
328
273
  // 의존성 캐시에서 영향받은 파일 관련 항목 무효화 (Affected더라도 SF는 동일하므로, modifiedFileSet만 넣어도될듯?)
329
274
  // 250715: sourceFile 타입체크를 다시 해야해서 affected로 넣는게 맞는듯.
330
- this.#depCache.invalidates(new Set(affectedFiles));
275
+ this.#cache.dep.invalidates(affectedFileSet);
331
276
 
332
- // 내부 캐시에서 영향받은 파일 관련 항목 무효화
333
- for (const affectedFile of affectedFiles) {
334
- this.#emittedFilesCacheMap.delete(affectedFile);
335
- this.#stylesheetBundlingResultMap.delete(affectedFile);
277
+ // 결과물이 바뀌어야 하는 캐시 모두 무효화 (modified만 다시쓰면될듯..)
278
+ for (const modifiedFile of modifiedFileSet) {
279
+ this.#emittedFilesCacheMap.delete(modifiedFile);
336
280
  }
337
281
  });
338
282
  }
339
283
 
340
284
  this.#debug(`ts.Program 생성 중...`);
341
- await Wait.time(300);
342
285
 
343
286
  const compilerHost = this.#perf.run("ts.CompilerHost 생성", () => {
344
287
  return this.#createCompilerHost(tsconfig.options, modifiedFileSet);
@@ -346,14 +289,23 @@ export class SdTsCompiler {
346
289
 
347
290
  this.#perf.run("ts.Program 생성", () => {
348
291
  if (this.#isForAngular) {
349
- this.#ngProgram = new NgtscProgram(tsconfig.fileNames, tsconfig.options, compilerHost, this.#ngProgram);
292
+ this.#ngProgram = new NgtscProgram(
293
+ tsconfig.fileNames,
294
+ tsconfig.options,
295
+ compilerHost,
296
+ this.#ngProgram,
297
+ );
350
298
  this.#program = this.#ngProgram.getTsProgram();
351
299
  } else {
352
- this.#program = ts.createProgram(tsconfig.fileNames, tsconfig.options, compilerHost, this.#program);
300
+ this.#program = ts.createProgram(
301
+ tsconfig.fileNames,
302
+ tsconfig.options,
303
+ compilerHost,
304
+ this.#program,
305
+ );
353
306
  }
354
307
  });
355
308
  this.#debug(`ts.Program 생성`);
356
- await Wait.time(300);
357
309
 
358
310
  if (this.#ngProgram) {
359
311
  await this.#perf.run("Angular 템플릿 분석", async () => {
@@ -361,30 +313,54 @@ export class SdTsCompiler {
361
313
  });
362
314
  }
363
315
 
364
- this.#debug(`새 의존성 분석 중...`);
316
+ if (!this._opt.isEmitOnly) {
317
+ this.#debug(`새 의존성 분석 중...`);
365
318
 
366
- this.#perf.run("새 의존성 분석", () => {
367
- // SdTsDependencyAnalyzer를 통해 의존성 분석 및 SdDepCache 업데이트
368
- SdDependencyAnalyzer.analyze(this.#program!, compilerHost, this._opt.watchScopePathSet, this.#depCache);
319
+ this.#perf.run("새 의존성 분석", () => {
320
+ // SdTsDependencyAnalyzer를 통해 의존성 분석 및 SdDepCache 업데이트
321
+ SdDependencyAnalyzer.analyze(
322
+ this.#program!,
323
+ compilerHost,
324
+ this._opt.watchScopePathSet,
325
+ this.#cache,
326
+ );
327
+ });
328
+
329
+ this.#debug(`새 의존성 분석(Angular) 중...`);
369
330
 
370
331
  // Angular 리소스 의존성 추가
371
332
  if (this.#ngProgram) {
372
- SdDependencyAnalyzer.analyzeAngularResources(this.#ngProgram, this._opt.watchScopePathSet, this.#depCache);
333
+ this.#perf.run("새 의존성 분석(Angular)", () => {
334
+ SdDependencyAnalyzer.analyzeAngularResources(
335
+ this.#ngProgram!,
336
+ this._opt.watchScopePathSet,
337
+ this.#cache.dep,
338
+ );
339
+ });
373
340
  }
374
- });
341
+ }
375
342
 
376
- const affectedFileSet =
377
- modifiedFileSet.size === 0
378
- ? this.#depCache.getFiles()
379
- : new Set(
380
- Array.from(this.#depCache.getAffectedFileMap(modifiedFileSet).values()).mapMany((item) => Array.from(item)),
381
- );
382
- const watchFileSet = this.#depCache.getFiles();
343
+ const allFiles = this.#program!.getSourceFiles().map((item) => PathUtils.norm(item.fileName));
344
+ const watchFileSet = new Set(
345
+ allFiles.filter((item) => this._opt.watchScopePathSet.inScope(item)),
346
+ );
347
+
348
+ let affectedFileSet: Set<TNormPath>;
349
+ if (modifiedFileSet.size === 0) {
350
+ affectedFileSet = new Set(allFiles);
351
+ } else {
352
+ const affectedFileMap = this.#cache.dep.getAffectedFileMap(modifiedFileSet);
353
+ this.#debug("영향받은 파일:", affectedFileMap);
354
+ affectedFileSet = new Set(
355
+ Array.from(affectedFileMap.values()).mapMany((item) => Array.from(item)),
356
+ );
357
+ }
383
358
 
384
359
  return {
385
360
  tsconfig,
386
361
  compilerHost,
387
362
  affectedFileSet,
363
+ styleAffectedFileSet: this.#styleBundler?.getAffectedFileSet(affectedFileSet) ?? new Set(),
388
364
  watchFileSet,
389
365
  };
390
366
  }
@@ -425,12 +401,15 @@ export class SdTsCompiler {
425
401
 
426
402
  await this.#perf.run("전역 스타일 번들링", async () => {
427
403
  const data = await FsUtils.readFileAsync(this._opt.globalStyleFilePath!);
428
- const stylesheetBundlingResult = await this.#bundleStylesheetAsync(
404
+ const stylesheetBundlingResult = await this.#styleBundler!.bundleAsync(
429
405
  data,
430
406
  this._opt.globalStyleFilePath!,
431
407
  this._opt.globalStyleFilePath,
432
408
  );
433
- const emitFileInfos = this.#emittedFilesCacheMap.getOrCreate(this._opt.globalStyleFilePath!, []);
409
+ const emitFileInfos = this.#emittedFilesCacheMap.getOrCreate(
410
+ this._opt.globalStyleFilePath!,
411
+ [],
412
+ );
434
413
  emitFileInfos.push({
435
414
  outAbsPath: PathUtils.norm(
436
415
  this._opt.pkgPath,
@@ -452,132 +431,408 @@ export class SdTsCompiler {
452
431
  const emitFileSet = new Set<TNormPath>();
453
432
  const diagnostics: ts.Diagnostic[] = [];
454
433
 
455
- this.#debug(`프로그램 진단 수집 중...`);
456
-
457
- this.#perf.run("프로그램 진단 수집", () => {
458
- diagnostics.push(
459
- ...this.#program!.getConfigFileParsingDiagnostics(),
460
- ...this.#program!.getOptionsDiagnostics(),
461
- ...this.#program!.getGlobalDiagnostics(),
462
- );
463
-
464
- if (this.#ngProgram) {
465
- diagnostics.push(...this.#ngProgram.compiler.getOptionDiagnostics());
466
- }
467
- });
434
+ if (!this._opt.isEmitOnly) {
435
+ this.#debug(`프로그램 진단 수집 중...`);
468
436
 
469
- this.#debug(`개별 파일 진단 수집 중...`);
437
+ this.#perf.run("프로그램 진단 수집", () => {
438
+ diagnostics.push(
439
+ ...this.#program!.getConfigFileParsingDiagnostics(),
440
+ ...this.#program!.getOptionsDiagnostics(),
441
+ ...this.#program!.getGlobalDiagnostics(),
442
+ );
470
443
 
471
- for (const affectedFile of prepareResult.affectedFileSet) {
472
- if (!PathUtils.isChildPath(affectedFile, this._opt.pkgPath)) continue;
444
+ if (this.#ngProgram) {
445
+ diagnostics.push(...this.#ngProgram.compiler.getOptionDiagnostics());
446
+ }
447
+ });
473
448
 
474
- const affectedSourceFile = this.#program!.getSourceFile(affectedFile);
475
- if (
476
- !affectedSourceFile ||
477
- (this.#ngProgram && this.#ngProgram.compiler.ignoreForDiagnostics.has(affectedSourceFile))
478
- ) {
479
- continue;
480
- }
449
+ this.#debug(`개별 파일 진단 수집 중...`);
481
450
 
482
- // this.#debug(`get diagnostics of file ${affectedFile}...`);
451
+ for (const affectedFile of prepareResult.affectedFileSet) {
452
+ if (!PathUtils.isChildPath(affectedFile, this._opt.pkgPath)) continue;
483
453
 
484
- this.#perf.run("개별 파일 진단 수집", () => {
485
- diagnostics.push(
486
- ...this.#program!.getSyntacticDiagnostics(affectedSourceFile),
487
- ...this.#program!.getSemanticDiagnostics(affectedSourceFile),
488
- );
489
- });
454
+ const affectedSourceFile = this.#program!.getSourceFile(affectedFile);
455
+ if (
456
+ !affectedSourceFile ||
457
+ (this.#ngProgram && this.#ngProgram.compiler.ignoreForDiagnostics.has(affectedSourceFile))
458
+ ) {
459
+ continue;
460
+ }
490
461
 
491
- if (this.#ngProgram) {
492
- this.#perf.run("개별 파일 진단 수집(Angular)", () => {
493
- if (affectedSourceFile.isDeclarationFile) return;
462
+ // this.#debug(`get diagnostics of file ${affectedFile}...`);
494
463
 
464
+ this.#perf.run("개별 파일 진단 수집", () => {
495
465
  diagnostics.push(
496
- ...this.#ngProgram!.compiler.getDiagnosticsForFile(affectedSourceFile, OptimizeFor.WholeProgram),
466
+ ...this.#program!.getSyntacticDiagnostics(affectedSourceFile),
467
+ ...this.#program!.getSemanticDiagnostics(affectedSourceFile),
497
468
  );
498
469
  });
470
+
471
+ if (this.#ngProgram) {
472
+ this.#perf.run("개별 파일 진단 수집(Angular)", () => {
473
+ if (affectedSourceFile.isDeclarationFile) return;
474
+
475
+ diagnostics.push(
476
+ ...this.#ngProgram!.compiler.getDiagnosticsForFile(
477
+ affectedSourceFile,
478
+ OptimizeFor.WholeProgram,
479
+ ),
480
+ );
481
+ });
482
+ }
499
483
  }
500
484
  }
501
485
 
502
- this.#perf.run("파일 출력 (emit)", () => {
503
- this.#debug(`파일 출력 준비 중...`);
486
+ if (!this._opt.isNoEmit) {
487
+ this.#perf.run("파일 출력 (emit)", () => {
488
+ this.#debug(`파일 출력 준비 중...`);
504
489
 
505
- let transformers: ts.CustomTransformers = {};
490
+ let transformers: ts.CustomTransformers = {};
506
491
 
507
- if (this.#ngProgram) {
508
- const angularTransfomers = this.#ngProgram.compiler.prepareEmit().transformers;
509
- (transformers.before ??= []).push(...(angularTransfomers.before ?? []));
510
- (transformers.after ??= []).push(...(angularTransfomers.after ?? []));
511
- (transformers.afterDeclarations ??= []).push(...(angularTransfomers.afterDeclarations ?? []));
512
-
513
- (transformers.before ??= []).push(replaceBootstrap(() => this.#program!.getTypeChecker()));
514
- (transformers.before ??= []).push(
515
- createWorkerTransformer((file, importer) => {
516
- const fullPath = path.resolve(path.dirname(importer), file);
517
- const relPath = path.relative(path.resolve(this._opt.pkgPath, "src"), fullPath);
518
- return relPath.replace(/\.ts$/, "").replaceAll("\\", "/") + ".js";
519
- }),
520
- );
521
- }
492
+ if (this.#ngProgram) {
493
+ const angularTransfomers = this.#ngProgram.compiler.prepareEmit().transformers;
494
+ (transformers.before ??= []).push(...(angularTransfomers.before ?? []));
495
+ (transformers.after ??= []).push(...(angularTransfomers.after ?? []));
496
+ (transformers.afterDeclarations ??= []).push(
497
+ ...(angularTransfomers.afterDeclarations ?? []),
498
+ );
522
499
 
523
- this.#debug(`파일 출력 중...`);
500
+ (transformers.before ??= []).push(
501
+ replaceBootstrap(() => this.#program!.getTypeChecker()),
502
+ );
503
+ (transformers.before ??= []).push(
504
+ createWorkerTransformer((file, importer) => {
505
+ const fullPath = path.resolve(path.dirname(importer), file);
506
+ const relPath = path.relative(path.resolve(this._opt.pkgPath, "src"), fullPath);
507
+ return relPath.replace(/\.ts$/, "").replaceAll("\\", "/") + ".js";
508
+ }),
509
+ );
524
510
 
525
- // affected에 새로 추가된 파일은 포함되지 않는 현상이 있어 sourceFileSet으로 바꿈
526
- // 비교해보니, 딱히 getSourceFiles라서 더 느려지는것 같지는 않음
527
- // 그래도 affected로 다시 테스트 (조금이라도 더 빠르게)
528
- for (const affectedFile of prepareResult.affectedFileSet) {
529
- if (this.#emittedFilesCacheMap.has(affectedFile)) continue;
511
+ (transformers.before ??= []).push(this.#createExternalizeComponentStylesTransformer());
512
+ }
530
513
 
531
- const sf = this.#program!.getSourceFile(affectedFile);
532
- if (!sf || sf.isDeclarationFile) continue;
533
- if (this.#ngProgram?.compiler.ignoreForEmit.has(sf)) continue;
534
- if (this.#ngProgram?.compiler.incrementalCompilation.safeToSkipEmit(sf)) continue;
514
+ this.#debug(`파일 출력 중...`);
515
+
516
+ // affected에 새로 추가된 파일은 포함되지 않는 현상이 있어 sourceFileSet으로 바꿈
517
+ // 비교해보니, 딱히 getSourceFiles라서 더 느려지는것 같지는 않음
518
+ // 그래도 affected로 다시 테스트 (조금이라도 더 빠르게)
519
+ for (const affectedFile of [
520
+ ...prepareResult.affectedFileSet /*,
521
+ ...prepareResult.styleAffectedFileSet,*/,
522
+ ]) {
523
+ if (
524
+ this.#emittedFilesCacheMap
525
+ .get(affectedFile)
526
+ ?.some((item) => !item.outAbsPath?.endsWith(".css"))
527
+ )
528
+ continue;
529
+
530
+ const sf = this.#program!.getSourceFile(affectedFile);
531
+ if (!sf || sf.isDeclarationFile) continue;
532
+ if (this.#ngProgram?.compiler.ignoreForEmit.has(sf)) continue;
533
+ if (this.#ngProgram?.compiler.incrementalCompilation.safeToSkipEmit(sf)) continue;
534
+
535
+ // 번들이 아닌 외부패키지는 보통 emit안해도 됨
536
+ // but esbuild를 통해 bundle로 묶어야 하는놈들은 모든 output이 있어야 함.
537
+ if (!this._opt.isForBundle && !PathUtils.isChildPath(sf.fileName, this._opt.pkgPath)) {
538
+ continue;
539
+ }
535
540
 
536
- // 번들이 아닌 외부패키지는 보통 emit안해도 됨
537
- // but esbuild를 통해 bundle로 묶어야 하는놈들은 모든 output이 있어야 함.
538
- if (!this._opt.isForBundle && !PathUtils.isChildPath(sf.fileName, this._opt.pkgPath)) {
539
- continue;
541
+ this.#program!.emit(
542
+ sf,
543
+ (fileName, text, writeByteOrderMark, onError, sourceFiles, data) => {
544
+ if (!sourceFiles || sourceFiles.length === 0) {
545
+ prepareResult.compilerHost.writeFile(
546
+ fileName,
547
+ text,
548
+ writeByteOrderMark,
549
+ onError,
550
+ sourceFiles,
551
+ data,
552
+ );
553
+ return;
554
+ }
555
+
556
+ const sourceFile = ts.getOriginalNode(sourceFiles[0], ts.isSourceFile);
557
+ if (this.#ngProgram) {
558
+ if (this.#ngProgram.compiler.ignoreForEmit.has(sourceFile)) return;
559
+ this.#ngProgram.compiler.incrementalCompilation.recordSuccessfulEmit(sourceFile);
560
+ }
561
+
562
+ const emitFileInfoCaches = this.#emittedFilesCacheMap.getOrCreate(
563
+ PathUtils.norm(sourceFile.fileName),
564
+ [],
565
+ );
566
+
567
+ if (PathUtils.isChildPath(sourceFile.fileName, this._opt.pkgPath)) {
568
+ const real = this.#convertOutputToReal(
569
+ fileName,
570
+ prepareResult.tsconfig.distPath,
571
+ text,
572
+ );
573
+
574
+ emitFileInfoCaches.push({
575
+ outAbsPath: real.filePath,
576
+ text: this.#removeOutputDevModeLine(real.text),
577
+ });
578
+ } else {
579
+ emitFileInfoCaches.push({ text });
580
+ }
581
+
582
+ emitFileSet.add(PathUtils.norm(sourceFile.fileName));
583
+ },
584
+ undefined,
585
+ undefined,
586
+ transformers,
587
+ );
540
588
  }
589
+ });
590
+ }
541
591
 
542
- this.#program!.emit(
543
- sf,
544
- (fileName, text, writeByteOrderMark, onError, sourceFiles, data) => {
545
- if (!sourceFiles || sourceFiles.length === 0) {
546
- prepareResult.compilerHost.writeFile(fileName, text, writeByteOrderMark, onError, sourceFiles, data);
547
- return;
548
- }
549
-
550
- const sourceFile = ts.getOriginalNode(sourceFiles[0], ts.isSourceFile);
551
- if (this.#ngProgram) {
552
- if (this.#ngProgram.compiler.ignoreForEmit.has(sourceFile)) return;
553
- this.#ngProgram.compiler.incrementalCompilation.recordSuccessfulEmit(sourceFile);
554
- }
555
-
556
- const emitFileInfoCaches = this.#emittedFilesCacheMap.getOrCreate(PathUtils.norm(sourceFile.fileName), []);
557
-
558
- if (PathUtils.isChildPath(sourceFile.fileName, this._opt.pkgPath)) {
559
- const real = this.#convertOutputToReal(fileName, prepareResult.tsconfig.distPath, text);
560
-
561
- emitFileInfoCaches.push({
562
- outAbsPath: real.filePath,
563
- text: real.text,
564
- });
565
- } else {
566
- emitFileInfoCaches.push({ text });
567
- }
568
-
569
- emitFileSet.add(PathUtils.norm(sourceFile.fileName));
570
- },
592
+ return {
593
+ emitFileSet,
594
+ diagnostics,
595
+ };
596
+ }
597
+
598
+ #createExternalizeComponentStylesTransformer() {
599
+ const f = ts.factory;
600
+
601
+ /*function makeEnsureStyleFunc() {
602
+ // function __sdEnsureStyle(href) { ... }
603
+ const hrefParam = f.createParameterDeclaration(undefined, undefined, "href");
604
+
605
+ // const d = document;
606
+ const declD = f.createVariableStatement(
607
+ undefined,
608
+ f.createVariableDeclarationList(
609
+ [f.createVariableDeclaration("d", undefined, undefined, f.createIdentifier("document"))],
610
+ ts.NodeFlags.Const,
611
+ ),
612
+ );
613
+
614
+ // let link = d.querySelector(`link[data-sd-style="${href}"]`);
615
+ const tpl = f.createTemplateExpression(f.createTemplateHead('link[data-sd-style="'), [
616
+ f.createTemplateSpan(f.createIdentifier("href"), f.createTemplateTail('"]')),
617
+ ]);
618
+ const declLink = f.createVariableStatement(
619
+ undefined,
620
+ f.createVariableDeclarationList(
621
+ [
622
+ f.createVariableDeclaration(
623
+ "link",
624
+ undefined,
625
+ undefined,
626
+ f.createCallExpression(
627
+ f.createPropertyAccessExpression(f.createIdentifier("d"), "querySelector"),
628
+ undefined,
629
+ [tpl],
630
+ ),
631
+ ),
632
+ ],
633
+ ts.NodeFlags.Let,
634
+ ),
635
+ );
636
+
637
+ // if (link) return;
638
+ const ifReturn = f.createIfStatement(
639
+ f.createIdentifier("link"),
640
+ f.createBlock([f.createReturnStatement()], true),
641
+ );
642
+
643
+ // link = d.createElement('link');
644
+ const mkLink = f.createExpressionStatement(
645
+ f.createBinaryExpression(
646
+ f.createIdentifier("link"),
647
+ ts.SyntaxKind.EqualsToken,
648
+ f.createCallExpression(
649
+ f.createPropertyAccessExpression(f.createIdentifier("d"), "createElement"),
650
+ undefined,
651
+ [f.createStringLiteral("link")],
652
+ ),
653
+ ),
654
+ );
655
+
656
+ // link.rel = 'stylesheet';
657
+ const setRel = f.createExpressionStatement(
658
+ f.createBinaryExpression(
659
+ f.createPropertyAccessExpression(f.createIdentifier("link"), "rel"),
660
+ ts.SyntaxKind.EqualsToken,
661
+ f.createStringLiteral("stylesheet"),
662
+ ),
663
+ );
664
+
665
+ // link.setAttribute('data-sd-style', href);
666
+ const setData = f.createExpressionStatement(
667
+ f.createCallExpression(
668
+ f.createPropertyAccessExpression(f.createIdentifier("link"), "setAttribute"),
669
+ undefined,
670
+ [f.createStringLiteral("data-sd-style"), f.createIdentifier("href")],
671
+ ),
672
+ );
673
+
674
+ // link.href = href;
675
+ const setHref = f.createExpressionStatement(
676
+ f.createBinaryExpression(
677
+ f.createPropertyAccessExpression(f.createIdentifier("link"), "href"),
678
+ ts.SyntaxKind.EqualsToken,
679
+ f.createIdentifier("href"),
680
+ ),
681
+ );
682
+
683
+ // d.head.appendChild(link);
684
+ const append = f.createExpressionStatement(
685
+ f.createCallExpression(
686
+ f.createPropertyAccessExpression(
687
+ f.createPropertyAccessExpression(f.createIdentifier("d"), "head"),
688
+ "appendChild",
689
+ ),
690
+ undefined,
691
+ [f.createIdentifier("link")],
692
+ ),
693
+ );
694
+
695
+ const body = f.createBlock(
696
+ [declD, declLink, ifReturn, mkLink, setRel, setData, setHref, append],
697
+ true,
698
+ );
699
+
700
+ return f.createFunctionDeclaration(
701
+ undefined,
702
+ undefined,
703
+ f.createIdentifier("__sdEnsureStyle"),
704
+ undefined,
705
+ [hrefParam],
706
+ undefined,
707
+ body,
708
+ );
709
+ }
710
+
711
+ function makeEnsureCallInStatic(href: string) {
712
+ return f.createExpressionStatement(
713
+ f.createCallExpression(
714
+ f.createIdentifier("__sdEnsureStyle"),
715
+ undefined,
716
+ [f.createStringLiteral(href)], // 필요하면 devBust 인자도 추가 가능
717
+ ),
718
+ );
719
+ }
720
+
721
+ const upsertEnsureStyleHelper = (sf: ts.SourceFile): ts.SourceFile => {
722
+ // 이미 있으면 스킵
723
+ if (
724
+ sf.statements.some((s) => ts.isFunctionDeclaration(s) && s.name?.text === "__sdEnsureStyle")
725
+ ) {
726
+ return sf;
727
+ }
728
+
729
+ const fn = makeEnsureStyleFunc();
730
+
731
+ const href = path.basename(sf.fileName).replace(/\.ts$/, ".css");
732
+ const call = makeEnsureCallInStatic(href);
733
+
734
+ if (this._opt.isForBundle) {
735
+ return f.updateSourceFile(sf, [fn, call, ...sf.statements]);
736
+ } else {
737
+ const importTarget = "./" + path.basename(sf.fileName).replace(/\.ts$/, ".css");
738
+ const importDecl = f.createImportDeclaration(
571
739
  undefined,
572
740
  undefined,
573
- transformers,
741
+ f.createStringLiteral(importTarget),
574
742
  );
743
+
744
+ return f.updateSourceFile(sf, [fn, call, importDecl, ...sf.statements]);
575
745
  }
576
- });
746
+ };*/
577
747
 
578
- return {
579
- emitFileSet,
580
- diagnostics,
748
+ function upsertEnsureStyleHelper(sf: ts.SourceFile): ts.SourceFile {
749
+ // 이미 있으면 스킵
750
+ if (
751
+ sf.statements.some((s) => ts.isFunctionDeclaration(s) && s.name?.text === "__sdEnsureStyle")
752
+ ) {
753
+ return sf;
754
+ }
755
+
756
+ const importTarget = "./" + path.basename(sf.fileName).replace(/\.ts$/, ".css");
757
+ const importDecl = f.createImportDeclaration(
758
+ undefined, // decorators
759
+ undefined, // modifiers
760
+ f.createStringLiteral(importTarget),
761
+ );
762
+
763
+ return f.updateSourceFile(sf, [importDecl, ...sf.statements]);
764
+ }
765
+
766
+ const removeStyleProp = (node: ts.ClassDeclaration) => {
767
+ const allDecorators = ts.getDecorators(node);
768
+ if (!allDecorators || allDecorators.length === 0) return node;
769
+
770
+ const decoratorsUpdated = allDecorators.map((dec) => {
771
+ if (!ts.isCallExpression(dec.expression)) return dec;
772
+ const call = dec.expression;
773
+ if (!ts.isIdentifier(call.expression) || call.expression.text !== "Component") return dec;
774
+ if (call.arguments.length !== 1) return dec;
775
+ const arg = call.arguments[0];
776
+ if (!ts.isObjectLiteralExpression(arg)) return dec;
777
+
778
+ const filteredProps = arg.properties.filter((p) => {
779
+ if (!ts.isPropertyAssignment(p)) return true;
780
+ const name = p.name;
781
+ const key = ts.isIdentifier(name)
782
+ ? name.text
783
+ : ts.isStringLiteralLike(name)
784
+ ? name.text
785
+ : undefined;
786
+ return !(key === "styles" || key === "styleUrls");
787
+ });
788
+
789
+ const newArg = f.updateObjectLiteralExpression(arg, filteredProps);
790
+ const newCall = f.updateCallExpression(call, call.expression, call.typeArguments, [newArg]);
791
+ return f.updateDecorator(dec, newCall);
792
+ });
793
+
794
+ const existingModifiers = node.modifiers ?? [];
795
+ const modifiersWithoutOldDecos = existingModifiers.filter((m) => !ts.isDecorator(m));
796
+ const newModifiers: readonly ts.ModifierLike[] = [
797
+ ...decoratorsUpdated,
798
+ ...modifiersWithoutOldDecos,
799
+ ];
800
+
801
+ const newNode = f.updateClassDeclaration(
802
+ node,
803
+ newModifiers,
804
+ node.name,
805
+ node.typeParameters,
806
+ node.heritageClauses,
807
+ node.members,
808
+ );
809
+
810
+ return f.updateClassDeclaration(
811
+ newNode,
812
+ newNode.modifiers,
813
+ newNode.name,
814
+ newNode.typeParameters,
815
+ newNode.heritageClauses,
816
+ newNode.members,
817
+ );
818
+ };
819
+
820
+ return (ctx: ts.TransformationContext) => {
821
+ return (sf: ts.SourceFile) => {
822
+ const has = this.#styleBundler!.getResultCache().get(PathUtils.norm(sf.fileName));
823
+ if (!has) return sf;
824
+
825
+ const realSf = upsertEnsureStyleHelper(sf);
826
+
827
+ function visitor(node: ts.Node): ts.Node {
828
+ if (ts.isClassDeclaration(node) && Boolean(ts.getDecorators(node)?.length)) {
829
+ return removeStyleProp(node);
830
+ }
831
+ return ts.visitEachChild(node, visitor, ctx);
832
+ }
833
+
834
+ return ts.visitNode(realSf, visitor) as ts.SourceFile;
835
+ };
581
836
  };
582
837
  }
583
838
 
@@ -601,6 +856,13 @@ export class SdTsCompiler {
601
856
  return { filePath: realFilePath, text: realText };
602
857
  }
603
858
 
859
+ #removeOutputDevModeLine(str: string) {
860
+ return str.replace(
861
+ /\(\(\) => \{ \(typeof ngDevMode === "undefined" \|\| ngDevMode\) && i0.ɵsetClassDebugInfo\(.*, \{ className: ".*", filePath: ".*", lineNumber: [0-9]* }\); }\)\(\);/,
862
+ "",
863
+ );
864
+ }
865
+
604
866
  #debug(...msg: any[]): void {
605
867
  this.#logger.debug(`[${path.basename(this._opt.pkgPath)}]`, ...msg);
606
868
  }
@@ -616,5 +878,6 @@ interface IPrepareResult {
616
878
  tsconfig: ITsConfigInfo;
617
879
  compilerHost: ts.CompilerHost;
618
880
  affectedFileSet: Set<TNormPath>;
881
+ styleAffectedFileSet: Set<TNormPath>;
619
882
  watchFileSet: Set<TNormPath>;
620
883
  }