@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,7 +1,7 @@
1
1
  import ts from "typescript";
2
2
  import path from "path";
3
3
  import { FsUtils, PathUtils, SdLogger } 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 { replaceBootstrap } from "@angular/build/src/tools/angular/transformers/jit-bootstrap-transformer";
7
7
  import { SdCliPerformanceTimer } from "../utils/sd-cli-performance-time";
@@ -10,21 +10,23 @@ import { createWorkerTransformer } from "@angular/build/src/tools/angular/transf
10
10
  import { SdDependencyCache } from "./sd-dependency-cache";
11
11
  import { SdDependencyAnalyzer } from "./sd-dependency-analyzer";
12
12
  import { FlatESLint } from "eslint/use-at-your-own-risk";
13
- import { ComponentStylesheetBundler } from "@angular/build/src/tools/esbuild/angular/component-stylesheets";
14
- import { transformSupportedBrowsersToTargets } from "@angular/build/src/tools/esbuild/utils";
15
- import browserslist from "browserslist";
13
+ import { SdStyleBundler } from "./sd-style-bundler";
16
14
  export class SdTsCompiler {
17
15
  #logger = SdLogger.get(["simplysm", "sd-cli", "SdTsCompiler"]);
18
16
  #isForAngular;
19
- #stylesheetBundler;
17
+ #styleBundler;
20
18
  #ngProgram;
21
19
  #program;
22
20
  // 빌드정보 캐싱
23
- #depCache = new SdDependencyCache();
21
+ #cache = {
22
+ dep: new SdDependencyCache(),
23
+ type: new WeakMap(),
24
+ prop: new WeakMap(),
25
+ declFiles: new WeakMap(),
26
+ ngOrg: new Map(),
27
+ };
24
28
  #sourceFileCacheMap = new Map();
25
29
  #emittedFilesCacheMap = new Map();
26
- // 빌드결과 캐싱
27
- #stylesheetBundlingResultMap = new Map();
28
30
  #perf;
29
31
  constructor(_opt) {
30
32
  this._opt = _opt;
@@ -32,28 +34,9 @@ export class SdTsCompiler {
32
34
  const tsconfigPath = path.resolve(this._opt.pkgPath, "tsconfig.json");
33
35
  const tsconfig = FsUtils.readJson(tsconfigPath);
34
36
  this.#isForAngular = Boolean(tsconfig.angularCompilerOptions);
35
- this.#stylesheetBundler = new ComponentStylesheetBundler({
36
- workspaceRoot: this._opt.pkgPath,
37
- optimization: !this._opt.isDevMode,
38
- inlineFonts: true,
39
- preserveSymlinks: false,
40
- sourcemap: this._opt.isDevMode ? "inline" : false,
41
- outputNames: { bundles: "[name]", media: "media/[name]" },
42
- includePaths: [],
43
- // sass:
44
- externalDependencies: [],
45
- target: transformSupportedBrowsersToTargets(browserslist(["Chrome > 78"])),
46
- tailwindConfiguration: undefined,
47
- /*postcssConfiguration: {
48
- plugins: [["css-has-pseudo"]],
49
- },*/
50
- // publicPath:
51
- cacheOptions: {
52
- enabled: true,
53
- path: ".cache/angular",
54
- basePath: ".cache",
55
- },
56
- }, "scss", this._opt.isDevMode);
37
+ if (!this._opt.isNoEmit) {
38
+ this.#styleBundler = new SdStyleBundler(this._opt.pkgPath, this._opt.isDevMode, this._opt.watchScopePathSet);
39
+ }
57
40
  }
58
41
  #parseTsConfig() {
59
42
  const tsconfigPath = path.resolve(this._opt.pkgPath, "tsconfig.json");
@@ -61,6 +44,32 @@ export class SdTsCompiler {
61
44
  const parsedTsconfig = ts.parseJsonConfigFileContent(tsconfig, ts.sys, this._opt.pkgPath, {
62
45
  ...tsconfig.angularCompilerOptions,
63
46
  ...this._opt.additionalOptions,
47
+ ...(this._opt.isEmitOnly
48
+ ? {
49
+ // typescript
50
+ noEmitOnError: false,
51
+ strict: false,
52
+ noImplicitAny: false,
53
+ noImplicitThis: false,
54
+ strictNullChecks: false,
55
+ strictFunctionTypes: false,
56
+ strictBindCallApply: false,
57
+ strictPropertyInitialization: false,
58
+ useUnknownInCatchVariables: false,
59
+ exactOptionalPropertyTypes: false,
60
+ noUncheckedIndexedAccess: false,
61
+ noUnusedLocals: false,
62
+ noUnusedParameters: false,
63
+ skipLibCheck: true,
64
+ checkJs: false,
65
+ alwaysStrict: false,
66
+ // angular
67
+ strictTemplates: false,
68
+ strictInjectionParameters: false,
69
+ strictInputAccessModifiers: false,
70
+ strictStandalone: false,
71
+ }
72
+ : {}),
64
73
  });
65
74
  const distPath = PathUtils.norm(parsedTsconfig.options.outDir ?? path.resolve(this._opt.pkgPath, "dist"));
66
75
  return {
@@ -93,108 +102,53 @@ export class SdTsCompiler {
93
102
  compilerHost.readResource = (fileName) => {
94
103
  return compilerHost.readFile(fileName) ?? "";
95
104
  };
96
- compilerHost.transformResource = async (data, context) => {
97
- if (context.type !== "style") {
98
- return null;
99
- }
100
- const stylesheetBundlingResult = await this.#bundleStylesheetAsync(data, PathUtils.norm(context.containingFile), context.resourceFile != null ? PathUtils.norm(context.resourceFile) : undefined);
101
- return StringUtils.isNullOrEmpty(stylesheetBundlingResult.contents)
102
- ? null
103
- : { content: stylesheetBundlingResult.contents };
104
- };
105
+ if (!this._opt.isNoEmit) {
106
+ compilerHost.transformResource = async (data, context) => {
107
+ if (context.type !== "style") {
108
+ return null;
109
+ }
110
+ const styleBundleResult = await this.#styleBundler.bundleAsync(data, PathUtils.norm(context.containingFile), context.resourceFile != null ? PathUtils.norm(context.resourceFile) : undefined);
111
+ if (!styleBundleResult.cached && !StringUtils.isNullOrEmpty(styleBundleResult.contents)) {
112
+ const relPath = path.relative(path.resolve(this._opt.pkgPath, "src"), context.containingFile);
113
+ const outAbsPath = PathUtils.norm(compilerOptions.outDir, relPath.replace(/\.ts$/, ".css"));
114
+ const cache = this.#emittedFilesCacheMap.getOrCreate(PathUtils.norm(context.containingFile), []);
115
+ cache.remove((item) => item.outAbsPath === outAbsPath);
116
+ cache.push({ outAbsPath, text: styleBundleResult.contents });
117
+ }
118
+ return StringUtils.isNullOrEmpty(styleBundleResult.contents)
119
+ ? null
120
+ : { content: "" /*styleBundleResult.contents*/ };
121
+ };
122
+ }
105
123
  compilerHost.getModifiedResourceFiles = () => {
106
124
  return new Set(Array.from(modifiedFileSet).map((item) => PathUtils.posix(item)));
107
125
  };
108
126
  }
109
127
  return compilerHost;
110
128
  }
111
- // private async _getOrCreateStyleBundleWorkerAsync() {
112
- // if (this._stylesheetBundlingWorker) {
113
- // return this._stylesheetBundlingWorker;
114
- // }
115
- //
116
- // this._stylesheetBundlingWorker = new SdWorker<TStyleBundlerWorkerType>(
117
- // import.meta.resolve("../workers/style-bundler.worker"),
118
- // );
119
- //
120
- // await this._stylesheetBundlingWorker.run(
121
- // "prepare",
122
- // [this._opt.pkgPath, this._opt.isDevMode],
123
- // );
124
- //
125
- // return this._stylesheetBundlingWorker;
126
- // }
127
- async #bundleStylesheetAsync(data, containingFile, resourceFile = null) {
128
- // containingFile: 포함된 파일 (.ts)
129
- // resourceFile: 외부 리소스 파일 (styleUrls로 입력하지 않고 styles에 직접 입력한 경우 null)
130
- // referencedFiles: import한 외부 scss 파일 혹은 woff파일등 외부 파일
131
- // this.#debug(`bundle stylesheet...(${containingFile}, ${resourceFile})`);
132
- return await this.#perf.run("스타일 번들링", async () => {
133
- const fileNPath = PathUtils.norm(resourceFile ?? containingFile);
134
- if (this.#stylesheetBundlingResultMap.has(fileNPath)) {
135
- return this.#stylesheetBundlingResultMap.get(fileNPath);
136
- }
137
- try {
138
- // const result = await new SdSassEmbeddedBundler(this._opt.isDevMode)
139
- // .bundleAsync(data, resourceFile ?? containingFile);
140
- // const worker = this._stylesheetBundlingWorker!;
141
- // const result = await worker.run("bundle", [data, containingFile, resourceFile]);
142
- const result = resourceFile != null
143
- ? await this.#stylesheetBundler.bundleFile(resourceFile)
144
- : await this.#stylesheetBundler.bundleInline(data, containingFile, "scss");
145
- for (const referencedFile of result.referencedFiles ?? []) {
146
- // for (const referencedFile of result.referencedFiles) {
147
- // 참조하는 파일과 참조된 파일 사이의 의존성 관계 추가
148
- this.#depCache.addImport(fileNPath, PathUtils.norm(referencedFile), 0);
149
- }
150
- this.#stylesheetBundlingResultMap.set(fileNPath, result);
151
- // this._stylesheetBundlingResultMap.set(fileNPath, {
152
- // errors: undefined,
153
- // warnings: [],
154
- // ...result,
155
- // });
156
- //
157
- // return {
158
- // errors: undefined,
159
- // warnings: [],
160
- // ...result,
161
- // };
162
- return result;
163
- }
164
- catch (err) {
165
- const result = {
166
- errors: [
167
- {
168
- text: `스타일 번들링 실패: ${err.message ?? "알 수 없는 오류"}`,
169
- location: { file: containingFile },
170
- },
171
- ],
172
- warnings: [],
173
- };
174
- this.#stylesheetBundlingResultMap.set(fileNPath, result);
175
- return result;
176
- }
177
- });
178
- }
179
129
  async compileAsync(modifiedFileSet) {
180
130
  this.#perf = new SdCliPerformanceTimer("esbuild compile");
181
131
  const prepareResult = await this.#prepareAsync(modifiedFileSet);
182
132
  const [globalStyleSheet, buildResult, lintResults] = await Promise.all([
183
- this.#buildGlobalStyleAsync(),
133
+ this._opt.isNoEmit ? undefined : this.#buildGlobalStyleAsync(),
184
134
  this.#build(prepareResult),
185
- this.#lintAsync(prepareResult),
135
+ this._opt.isEmitOnly ? [] : this.#lintAsync(prepareResult),
136
+ ]);
137
+ const affectedFileSet = new Set([
138
+ ...prepareResult.affectedFileSet,
139
+ ...prepareResult.styleAffectedFileSet,
186
140
  ]);
187
141
  this.#debug(`빌드 완료됨`, this.#perf.toString());
188
- this.#debug(`영향 받은 파일: ${prepareResult.affectedFileSet.size}개`);
142
+ this.#debug(`영향 받은 파일: ${affectedFileSet.size}개`);
189
143
  this.#debug(`감시 중인 파일: ${prepareResult.watchFileSet.size}개`);
190
144
  return {
191
145
  messages: [
192
146
  ...SdCliConvertMessageUtils.convertToBuildMessagesFromTsDiag(buildResult.diagnostics),
193
147
  ...SdCliConvertMessageUtils.convertToBuildMessagesFromEslint(lintResults),
194
148
  ],
195
- affectedFileSet: prepareResult.affectedFileSet,
149
+ affectedFileSet: affectedFileSet,
196
150
  watchFileSet: prepareResult.watchFileSet,
197
- stylesheetBundlingResultMap: this.#stylesheetBundlingResultMap,
151
+ stylesheetBundlingResultMap: this.#styleBundler?.getResultCache() ?? new Map(),
198
152
  emittedFilesCacheMap: this.#emittedFilesCacheMap,
199
153
  emitFileSet: new Set([...buildResult.emitFileSet, globalStyleSheet].filterExists()),
200
154
  };
@@ -204,47 +158,37 @@ export class SdTsCompiler {
204
158
  const tsconfig = this.#parseTsConfig();
205
159
  if (modifiedFileSet.size !== 0) {
206
160
  this.#debug(`캐시 무효화 및 초기화 중...`);
207
- await Wait.time(100);
208
161
  // this._perf.run("캐시 무효화 및 초기화", () => {
209
162
  this.#perf.run("캐시 무효화 및 초기화", () => {
210
- // 기존 의존성에 의해 영향받는 파일들 계산
211
- const affectedFileMap = this.#depCache.getAffectedFileMap(modifiedFileSet);
212
- const affectedFiles = Array.from(affectedFileMap.values()).mapMany((item) => Array.from(item));
213
- const getTreeText = (node, indent = "") => {
214
- let result = indent + node.fileNPath + "\n";
215
- for (const child of node.children) {
216
- result += getTreeText(child, indent + " ");
217
- }
218
- return result;
219
- };
220
- const affectedFileTree = this.#depCache.getAffectedFileTree(modifiedFileSet);
221
- this.#debug(`영향받은 기존파일: ${affectedFileTree.map((item) => getTreeText(item)).join("\n")}`.trim());
222
- // 스타일 번들러에서 영향받은 파일 관련 항목 무효화 (.scss만 해당)
223
- this.#stylesheetBundler.invalidate(new Set([...modifiedFileSet, ...affectedFiles.filter((item) => item.endsWith(".scss"))]));
224
- // await worker.run("invalidate", [affectedFileSet]);
225
- // 소스파일은 변경된 파일들로 무효화 (스타일 변화시 타고들어가야됨)
163
+ // 소스파일은 변경된 파일들로 무효화
226
164
  for (const modifiedFile of modifiedFileSet) {
227
- if (modifiedFile.endsWith(".scss")) {
228
- for (const fileNPath of affectedFileMap.get(modifiedFile) ?? []) {
229
- this.#sourceFileCacheMap.delete(fileNPath);
230
- }
231
- }
232
- else {
233
- this.#sourceFileCacheMap.delete(modifiedFile);
165
+ this.#sourceFileCacheMap.delete(modifiedFile);
166
+ }
167
+ // 스타일 번들러 무효화 (transformResource 재실행 땜에 필요할듯)
168
+ if (this.#styleBundler) {
169
+ const styleAffectedFileSet = this.#styleBundler.invalidate(modifiedFileSet);
170
+ // 스타일 변경된 파일들로 소스파일 무효화
171
+ for (const styleAffectedFile of styleAffectedFileSet) {
172
+ this.#sourceFileCacheMap.delete(styleAffectedFile);
234
173
  }
235
174
  }
175
+ // angular origin 파일 매핑은 변경된 파일들로 무효화
176
+ for (const modifiedFile of modifiedFileSet) {
177
+ this.#cache.ngOrg.delete(modifiedFile);
178
+ }
179
+ // 기존 의존성에 의해 영향받는 파일들 계산
180
+ const affectedFileMap = this.#cache.dep.getAffectedFileMap(modifiedFileSet);
181
+ const affectedFileSet = new Set(Array.from(affectedFileMap.values()).mapMany((item) => Array.from(item)));
236
182
  // 의존성 캐시에서 영향받은 파일 관련 항목 무효화 (Affected더라도 SF는 동일하므로, modifiedFileSet만 넣어도될듯?)
237
183
  // 250715: sourceFile 타입체크를 다시 해야해서 affected로 넣는게 맞는듯.
238
- this.#depCache.invalidates(new Set(affectedFiles));
239
- // 내부 캐시에서 영향받은 파일 관련 항목 무효화
240
- for (const affectedFile of affectedFiles) {
241
- this.#emittedFilesCacheMap.delete(affectedFile);
242
- this.#stylesheetBundlingResultMap.delete(affectedFile);
184
+ this.#cache.dep.invalidates(affectedFileSet);
185
+ // 결과물이 바뀌어야 하는 캐시 모두 무효화 (modified만 다시쓰면될듯..)
186
+ for (const modifiedFile of modifiedFileSet) {
187
+ this.#emittedFilesCacheMap.delete(modifiedFile);
243
188
  }
244
189
  });
245
190
  }
246
191
  this.#debug(`ts.Program 생성 중...`);
247
- await Wait.time(300);
248
192
  const compilerHost = this.#perf.run("ts.CompilerHost 생성", () => {
249
193
  return this.#createCompilerHost(tsconfig.options, modifiedFileSet);
250
194
  });
@@ -258,29 +202,41 @@ export class SdTsCompiler {
258
202
  }
259
203
  });
260
204
  this.#debug(`ts.Program 생성`);
261
- await Wait.time(300);
262
205
  if (this.#ngProgram) {
263
206
  await this.#perf.run("Angular 템플릿 분석", async () => {
264
207
  await this.#ngProgram.compiler.analyzeAsync();
265
208
  });
266
209
  }
267
- this.#debug(`새 의존성 분석 중...`);
268
- this.#perf.run("새 의존성 분석", () => {
269
- // SdTsDependencyAnalyzer를 통해 의존성 분석 SdDepCache 업데이트
270
- SdDependencyAnalyzer.analyze(this.#program, compilerHost, this._opt.watchScopePathSet, this.#depCache);
210
+ if (!this._opt.isEmitOnly) {
211
+ this.#debug(`새 의존성 분석 중...`);
212
+ this.#perf.run("새 의존성 분석", () => {
213
+ // SdTsDependencyAnalyzer를 통해 의존성 분석 및 SdDepCache 업데이트
214
+ SdDependencyAnalyzer.analyze(this.#program, compilerHost, this._opt.watchScopePathSet, this.#cache);
215
+ });
216
+ this.#debug(`새 의존성 분석(Angular) 중...`);
271
217
  // Angular 리소스 의존성 추가
272
218
  if (this.#ngProgram) {
273
- SdDependencyAnalyzer.analyzeAngularResources(this.#ngProgram, this._opt.watchScopePathSet, this.#depCache);
219
+ this.#perf.run("새 의존성 분석(Angular)", () => {
220
+ SdDependencyAnalyzer.analyzeAngularResources(this.#ngProgram, this._opt.watchScopePathSet, this.#cache.dep);
221
+ });
274
222
  }
275
- });
276
- const affectedFileSet = modifiedFileSet.size === 0
277
- ? this.#depCache.getFiles()
278
- : new Set(Array.from(this.#depCache.getAffectedFileMap(modifiedFileSet).values()).mapMany((item) => Array.from(item)));
279
- const watchFileSet = this.#depCache.getFiles();
223
+ }
224
+ const allFiles = this.#program.getSourceFiles().map((item) => PathUtils.norm(item.fileName));
225
+ const watchFileSet = new Set(allFiles.filter((item) => this._opt.watchScopePathSet.inScope(item)));
226
+ let affectedFileSet;
227
+ if (modifiedFileSet.size === 0) {
228
+ affectedFileSet = new Set(allFiles);
229
+ }
230
+ else {
231
+ const affectedFileMap = this.#cache.dep.getAffectedFileMap(modifiedFileSet);
232
+ this.#debug("영향받은 파일:", affectedFileMap);
233
+ affectedFileSet = new Set(Array.from(affectedFileMap.values()).mapMany((item) => Array.from(item)));
234
+ }
280
235
  return {
281
236
  tsconfig,
282
237
  compilerHost,
283
238
  affectedFileSet,
239
+ styleAffectedFileSet: this.#styleBundler?.getAffectedFileSet(affectedFileSet) ?? new Set(),
284
240
  watchFileSet,
285
241
  };
286
242
  }
@@ -314,7 +270,7 @@ export class SdTsCompiler {
314
270
  this.#debug(`전역 스타일 번들링 중...`);
315
271
  await this.#perf.run("전역 스타일 번들링", async () => {
316
272
  const data = await FsUtils.readFileAsync(this._opt.globalStyleFilePath);
317
- const stylesheetBundlingResult = await this.#bundleStylesheetAsync(data, this._opt.globalStyleFilePath, this._opt.globalStyleFilePath);
273
+ const stylesheetBundlingResult = await this.#styleBundler.bundleAsync(data, this._opt.globalStyleFilePath, this._opt.globalStyleFilePath);
318
274
  const emitFileInfos = this.#emittedFilesCacheMap.getOrCreate(this._opt.globalStyleFilePath, []);
319
275
  emitFileInfos.push({
320
276
  outAbsPath: PathUtils.norm(this._opt.pkgPath, path
@@ -330,99 +286,323 @@ export class SdTsCompiler {
330
286
  #build(prepareResult) {
331
287
  const emitFileSet = new Set();
332
288
  const diagnostics = [];
333
- this.#debug(`프로그램 진단 수집 중...`);
334
- this.#perf.run("프로그램 진단 수집", () => {
335
- diagnostics.push(...this.#program.getConfigFileParsingDiagnostics(), ...this.#program.getOptionsDiagnostics(), ...this.#program.getGlobalDiagnostics());
336
- if (this.#ngProgram) {
337
- diagnostics.push(...this.#ngProgram.compiler.getOptionDiagnostics());
338
- }
339
- });
340
- this.#debug(`개별 파일 진단 수집 중...`);
341
- for (const affectedFile of prepareResult.affectedFileSet) {
342
- if (!PathUtils.isChildPath(affectedFile, this._opt.pkgPath))
343
- continue;
344
- const affectedSourceFile = this.#program.getSourceFile(affectedFile);
345
- if (!affectedSourceFile ||
346
- (this.#ngProgram && this.#ngProgram.compiler.ignoreForDiagnostics.has(affectedSourceFile))) {
347
- continue;
348
- }
349
- // this.#debug(`get diagnostics of file ${affectedFile}...`);
350
- this.#perf.run("개별 파일 진단 수집", () => {
351
- diagnostics.push(...this.#program.getSyntacticDiagnostics(affectedSourceFile), ...this.#program.getSemanticDiagnostics(affectedSourceFile));
289
+ if (!this._opt.isEmitOnly) {
290
+ this.#debug(`프로그램 진단 수집 중...`);
291
+ this.#perf.run("프로그램 진단 수집", () => {
292
+ diagnostics.push(...this.#program.getConfigFileParsingDiagnostics(), ...this.#program.getOptionsDiagnostics(), ...this.#program.getGlobalDiagnostics());
293
+ if (this.#ngProgram) {
294
+ diagnostics.push(...this.#ngProgram.compiler.getOptionDiagnostics());
295
+ }
352
296
  });
353
- if (this.#ngProgram) {
354
- this.#perf.run("개별 파일 진단 수집(Angular)", () => {
355
- if (affectedSourceFile.isDeclarationFile)
356
- return;
357
- diagnostics.push(...this.#ngProgram.compiler.getDiagnosticsForFile(affectedSourceFile, OptimizeFor.WholeProgram));
358
- });
359
- }
360
- }
361
- this.#perf.run("파일 출력 (emit)", () => {
362
- this.#debug(`파일 출력 준비 중...`);
363
- let transformers = {};
364
- if (this.#ngProgram) {
365
- const angularTransfomers = this.#ngProgram.compiler.prepareEmit().transformers;
366
- (transformers.before ??= []).push(...(angularTransfomers.before ?? []));
367
- (transformers.after ??= []).push(...(angularTransfomers.after ?? []));
368
- (transformers.afterDeclarations ??= []).push(...(angularTransfomers.afterDeclarations ?? []));
369
- (transformers.before ??= []).push(replaceBootstrap(() => this.#program.getTypeChecker()));
370
- (transformers.before ??= []).push(createWorkerTransformer((file, importer) => {
371
- const fullPath = path.resolve(path.dirname(importer), file);
372
- const relPath = path.relative(path.resolve(this._opt.pkgPath, "src"), fullPath);
373
- return relPath.replace(/\.ts$/, "").replaceAll("\\", "/") + ".js";
374
- }));
375
- }
376
- this.#debug(`파일 출력 중...`);
377
- // affected에 새로 추가된 파일은 포함되지 않는 현상이 있어 sourceFileSet으로 바꿈
378
- // 비교해보니, 딱히 getSourceFiles라서 더 느려지는것 같지는 않음
379
- // 그래도 affected로 다시 테스트 (조금이라도 더 빠르게)
297
+ this.#debug(`개별 파일 진단 수집 중...`);
380
298
  for (const affectedFile of prepareResult.affectedFileSet) {
381
- if (this.#emittedFilesCacheMap.has(affectedFile))
382
- continue;
383
- const sf = this.#program.getSourceFile(affectedFile);
384
- if (!sf || sf.isDeclarationFile)
299
+ if (!PathUtils.isChildPath(affectedFile, this._opt.pkgPath))
385
300
  continue;
386
- if (this.#ngProgram?.compiler.ignoreForEmit.has(sf))
387
- continue;
388
- if (this.#ngProgram?.compiler.incrementalCompilation.safeToSkipEmit(sf))
389
- continue;
390
- // 번들이 아닌 외부패키지는 보통 emit안해도 됨
391
- // but esbuild를 통해 bundle로 묶어야 하는놈들은 모든 output이 있어야 함.
392
- if (!this._opt.isForBundle && !PathUtils.isChildPath(sf.fileName, this._opt.pkgPath)) {
301
+ const affectedSourceFile = this.#program.getSourceFile(affectedFile);
302
+ if (!affectedSourceFile ||
303
+ (this.#ngProgram && this.#ngProgram.compiler.ignoreForDiagnostics.has(affectedSourceFile))) {
393
304
  continue;
394
305
  }
395
- this.#program.emit(sf, (fileName, text, writeByteOrderMark, onError, sourceFiles, data) => {
396
- if (!sourceFiles || sourceFiles.length === 0) {
397
- prepareResult.compilerHost.writeFile(fileName, text, writeByteOrderMark, onError, sourceFiles, data);
398
- return;
399
- }
400
- const sourceFile = ts.getOriginalNode(sourceFiles[0], ts.isSourceFile);
401
- if (this.#ngProgram) {
402
- if (this.#ngProgram.compiler.ignoreForEmit.has(sourceFile))
306
+ // this.#debug(`get diagnostics of file ${affectedFile}...`);
307
+ this.#perf.run("개별 파일 진단 수집", () => {
308
+ diagnostics.push(...this.#program.getSyntacticDiagnostics(affectedSourceFile), ...this.#program.getSemanticDiagnostics(affectedSourceFile));
309
+ });
310
+ if (this.#ngProgram) {
311
+ this.#perf.run("개별 파일 진단 수집(Angular)", () => {
312
+ if (affectedSourceFile.isDeclarationFile)
403
313
  return;
404
- this.#ngProgram.compiler.incrementalCompilation.recordSuccessfulEmit(sourceFile);
405
- }
406
- const emitFileInfoCaches = this.#emittedFilesCacheMap.getOrCreate(PathUtils.norm(sourceFile.fileName), []);
407
- if (PathUtils.isChildPath(sourceFile.fileName, this._opt.pkgPath)) {
408
- const real = this.#convertOutputToReal(fileName, prepareResult.tsconfig.distPath, text);
409
- emitFileInfoCaches.push({
410
- outAbsPath: real.filePath,
411
- text: real.text,
412
- });
413
- }
414
- else {
415
- emitFileInfoCaches.push({ text });
416
- }
417
- emitFileSet.add(PathUtils.norm(sourceFile.fileName));
418
- }, undefined, undefined, transformers);
314
+ diagnostics.push(...this.#ngProgram.compiler.getDiagnosticsForFile(affectedSourceFile, OptimizeFor.WholeProgram));
315
+ });
316
+ }
419
317
  }
420
- });
318
+ }
319
+ if (!this._opt.isNoEmit) {
320
+ this.#perf.run("파일 출력 (emit)", () => {
321
+ this.#debug(`파일 출력 준비 중...`);
322
+ let transformers = {};
323
+ if (this.#ngProgram) {
324
+ const angularTransfomers = this.#ngProgram.compiler.prepareEmit().transformers;
325
+ (transformers.before ??= []).push(...(angularTransfomers.before ?? []));
326
+ (transformers.after ??= []).push(...(angularTransfomers.after ?? []));
327
+ (transformers.afterDeclarations ??= []).push(...(angularTransfomers.afterDeclarations ?? []));
328
+ (transformers.before ??= []).push(replaceBootstrap(() => this.#program.getTypeChecker()));
329
+ (transformers.before ??= []).push(createWorkerTransformer((file, importer) => {
330
+ const fullPath = path.resolve(path.dirname(importer), file);
331
+ const relPath = path.relative(path.resolve(this._opt.pkgPath, "src"), fullPath);
332
+ return relPath.replace(/\.ts$/, "").replaceAll("\\", "/") + ".js";
333
+ }));
334
+ (transformers.before ??= []).push(this.#createExternalizeComponentStylesTransformer());
335
+ }
336
+ this.#debug(`파일 출력 중...`);
337
+ // affected에 새로 추가된 파일은 포함되지 않는 현상이 있어 sourceFileSet으로 바꿈
338
+ // 비교해보니, 딱히 getSourceFiles라서 더 느려지는것 같지는 않음
339
+ // 그래도 affected로 다시 테스트 (조금이라도 더 빠르게)
340
+ for (const affectedFile of [
341
+ ...prepareResult.affectedFileSet /*,
342
+ ...prepareResult.styleAffectedFileSet,*/,
343
+ ]) {
344
+ if (this.#emittedFilesCacheMap
345
+ .get(affectedFile)
346
+ ?.some((item) => !item.outAbsPath?.endsWith(".css")))
347
+ continue;
348
+ const sf = this.#program.getSourceFile(affectedFile);
349
+ if (!sf || sf.isDeclarationFile)
350
+ continue;
351
+ if (this.#ngProgram?.compiler.ignoreForEmit.has(sf))
352
+ continue;
353
+ if (this.#ngProgram?.compiler.incrementalCompilation.safeToSkipEmit(sf))
354
+ continue;
355
+ // 번들이 아닌 외부패키지는 보통 emit안해도 됨
356
+ // but esbuild를 통해 bundle로 묶어야 하는놈들은 모든 output이 있어야 함.
357
+ if (!this._opt.isForBundle && !PathUtils.isChildPath(sf.fileName, this._opt.pkgPath)) {
358
+ continue;
359
+ }
360
+ this.#program.emit(sf, (fileName, text, writeByteOrderMark, onError, sourceFiles, data) => {
361
+ if (!sourceFiles || sourceFiles.length === 0) {
362
+ prepareResult.compilerHost.writeFile(fileName, text, writeByteOrderMark, onError, sourceFiles, data);
363
+ return;
364
+ }
365
+ const sourceFile = ts.getOriginalNode(sourceFiles[0], ts.isSourceFile);
366
+ if (this.#ngProgram) {
367
+ if (this.#ngProgram.compiler.ignoreForEmit.has(sourceFile))
368
+ return;
369
+ this.#ngProgram.compiler.incrementalCompilation.recordSuccessfulEmit(sourceFile);
370
+ }
371
+ const emitFileInfoCaches = this.#emittedFilesCacheMap.getOrCreate(PathUtils.norm(sourceFile.fileName), []);
372
+ if (PathUtils.isChildPath(sourceFile.fileName, this._opt.pkgPath)) {
373
+ const real = this.#convertOutputToReal(fileName, prepareResult.tsconfig.distPath, text);
374
+ emitFileInfoCaches.push({
375
+ outAbsPath: real.filePath,
376
+ text: this.#removeOutputDevModeLine(real.text),
377
+ });
378
+ }
379
+ else {
380
+ emitFileInfoCaches.push({ text });
381
+ }
382
+ emitFileSet.add(PathUtils.norm(sourceFile.fileName));
383
+ }, undefined, undefined, transformers);
384
+ }
385
+ });
386
+ }
421
387
  return {
422
388
  emitFileSet,
423
389
  diagnostics,
424
390
  };
425
391
  }
392
+ #createExternalizeComponentStylesTransformer() {
393
+ const f = ts.factory;
394
+ /*function makeEnsureStyleFunc() {
395
+ // function __sdEnsureStyle(href) { ... }
396
+ const hrefParam = f.createParameterDeclaration(undefined, undefined, "href");
397
+
398
+ // const d = document;
399
+ const declD = f.createVariableStatement(
400
+ undefined,
401
+ f.createVariableDeclarationList(
402
+ [f.createVariableDeclaration("d", undefined, undefined, f.createIdentifier("document"))],
403
+ ts.NodeFlags.Const,
404
+ ),
405
+ );
406
+
407
+ // let link = d.querySelector(`link[data-sd-style="${href}"]`);
408
+ const tpl = f.createTemplateExpression(f.createTemplateHead('link[data-sd-style="'), [
409
+ f.createTemplateSpan(f.createIdentifier("href"), f.createTemplateTail('"]')),
410
+ ]);
411
+ const declLink = f.createVariableStatement(
412
+ undefined,
413
+ f.createVariableDeclarationList(
414
+ [
415
+ f.createVariableDeclaration(
416
+ "link",
417
+ undefined,
418
+ undefined,
419
+ f.createCallExpression(
420
+ f.createPropertyAccessExpression(f.createIdentifier("d"), "querySelector"),
421
+ undefined,
422
+ [tpl],
423
+ ),
424
+ ),
425
+ ],
426
+ ts.NodeFlags.Let,
427
+ ),
428
+ );
429
+
430
+ // if (link) return;
431
+ const ifReturn = f.createIfStatement(
432
+ f.createIdentifier("link"),
433
+ f.createBlock([f.createReturnStatement()], true),
434
+ );
435
+
436
+ // link = d.createElement('link');
437
+ const mkLink = f.createExpressionStatement(
438
+ f.createBinaryExpression(
439
+ f.createIdentifier("link"),
440
+ ts.SyntaxKind.EqualsToken,
441
+ f.createCallExpression(
442
+ f.createPropertyAccessExpression(f.createIdentifier("d"), "createElement"),
443
+ undefined,
444
+ [f.createStringLiteral("link")],
445
+ ),
446
+ ),
447
+ );
448
+
449
+ // link.rel = 'stylesheet';
450
+ const setRel = f.createExpressionStatement(
451
+ f.createBinaryExpression(
452
+ f.createPropertyAccessExpression(f.createIdentifier("link"), "rel"),
453
+ ts.SyntaxKind.EqualsToken,
454
+ f.createStringLiteral("stylesheet"),
455
+ ),
456
+ );
457
+
458
+ // link.setAttribute('data-sd-style', href);
459
+ const setData = f.createExpressionStatement(
460
+ f.createCallExpression(
461
+ f.createPropertyAccessExpression(f.createIdentifier("link"), "setAttribute"),
462
+ undefined,
463
+ [f.createStringLiteral("data-sd-style"), f.createIdentifier("href")],
464
+ ),
465
+ );
466
+
467
+ // link.href = href;
468
+ const setHref = f.createExpressionStatement(
469
+ f.createBinaryExpression(
470
+ f.createPropertyAccessExpression(f.createIdentifier("link"), "href"),
471
+ ts.SyntaxKind.EqualsToken,
472
+ f.createIdentifier("href"),
473
+ ),
474
+ );
475
+
476
+ // d.head.appendChild(link);
477
+ const append = f.createExpressionStatement(
478
+ f.createCallExpression(
479
+ f.createPropertyAccessExpression(
480
+ f.createPropertyAccessExpression(f.createIdentifier("d"), "head"),
481
+ "appendChild",
482
+ ),
483
+ undefined,
484
+ [f.createIdentifier("link")],
485
+ ),
486
+ );
487
+
488
+ const body = f.createBlock(
489
+ [declD, declLink, ifReturn, mkLink, setRel, setData, setHref, append],
490
+ true,
491
+ );
492
+
493
+ return f.createFunctionDeclaration(
494
+ undefined,
495
+ undefined,
496
+ f.createIdentifier("__sdEnsureStyle"),
497
+ undefined,
498
+ [hrefParam],
499
+ undefined,
500
+ body,
501
+ );
502
+ }
503
+
504
+ function makeEnsureCallInStatic(href: string) {
505
+ return f.createExpressionStatement(
506
+ f.createCallExpression(
507
+ f.createIdentifier("__sdEnsureStyle"),
508
+ undefined,
509
+ [f.createStringLiteral(href)], // 필요하면 devBust 인자도 추가 가능
510
+ ),
511
+ );
512
+ }
513
+
514
+ const upsertEnsureStyleHelper = (sf: ts.SourceFile): ts.SourceFile => {
515
+ // 이미 있으면 스킵
516
+ if (
517
+ sf.statements.some((s) => ts.isFunctionDeclaration(s) && s.name?.text === "__sdEnsureStyle")
518
+ ) {
519
+ return sf;
520
+ }
521
+
522
+ const fn = makeEnsureStyleFunc();
523
+
524
+ const href = path.basename(sf.fileName).replace(/\.ts$/, ".css");
525
+ const call = makeEnsureCallInStatic(href);
526
+
527
+ if (this._opt.isForBundle) {
528
+ return f.updateSourceFile(sf, [fn, call, ...sf.statements]);
529
+ } else {
530
+ const importTarget = "./" + path.basename(sf.fileName).replace(/\.ts$/, ".css");
531
+ const importDecl = f.createImportDeclaration(
532
+ undefined,
533
+ undefined,
534
+ f.createStringLiteral(importTarget),
535
+ );
536
+
537
+ return f.updateSourceFile(sf, [fn, call, importDecl, ...sf.statements]);
538
+ }
539
+ };*/
540
+ function upsertEnsureStyleHelper(sf) {
541
+ // 이미 있으면 스킵
542
+ if (sf.statements.some((s) => ts.isFunctionDeclaration(s) && s.name?.text === "__sdEnsureStyle")) {
543
+ return sf;
544
+ }
545
+ const importTarget = "./" + path.basename(sf.fileName).replace(/\.ts$/, ".css");
546
+ const importDecl = f.createImportDeclaration(undefined, // decorators
547
+ undefined, // modifiers
548
+ f.createStringLiteral(importTarget));
549
+ return f.updateSourceFile(sf, [importDecl, ...sf.statements]);
550
+ }
551
+ const removeStyleProp = (node) => {
552
+ const allDecorators = ts.getDecorators(node);
553
+ if (!allDecorators || allDecorators.length === 0)
554
+ return node;
555
+ const decoratorsUpdated = allDecorators.map((dec) => {
556
+ if (!ts.isCallExpression(dec.expression))
557
+ return dec;
558
+ const call = dec.expression;
559
+ if (!ts.isIdentifier(call.expression) || call.expression.text !== "Component")
560
+ return dec;
561
+ if (call.arguments.length !== 1)
562
+ return dec;
563
+ const arg = call.arguments[0];
564
+ if (!ts.isObjectLiteralExpression(arg))
565
+ return dec;
566
+ const filteredProps = arg.properties.filter((p) => {
567
+ if (!ts.isPropertyAssignment(p))
568
+ return true;
569
+ const name = p.name;
570
+ const key = ts.isIdentifier(name)
571
+ ? name.text
572
+ : ts.isStringLiteralLike(name)
573
+ ? name.text
574
+ : undefined;
575
+ return !(key === "styles" || key === "styleUrls");
576
+ });
577
+ const newArg = f.updateObjectLiteralExpression(arg, filteredProps);
578
+ const newCall = f.updateCallExpression(call, call.expression, call.typeArguments, [newArg]);
579
+ return f.updateDecorator(dec, newCall);
580
+ });
581
+ const existingModifiers = node.modifiers ?? [];
582
+ const modifiersWithoutOldDecos = existingModifiers.filter((m) => !ts.isDecorator(m));
583
+ const newModifiers = [
584
+ ...decoratorsUpdated,
585
+ ...modifiersWithoutOldDecos,
586
+ ];
587
+ const newNode = f.updateClassDeclaration(node, newModifiers, node.name, node.typeParameters, node.heritageClauses, node.members);
588
+ return f.updateClassDeclaration(newNode, newNode.modifiers, newNode.name, newNode.typeParameters, newNode.heritageClauses, newNode.members);
589
+ };
590
+ return (ctx) => {
591
+ return (sf) => {
592
+ const has = this.#styleBundler.getResultCache().get(PathUtils.norm(sf.fileName));
593
+ if (!has)
594
+ return sf;
595
+ const realSf = upsertEnsureStyleHelper(sf);
596
+ function visitor(node) {
597
+ if (ts.isClassDeclaration(node) && Boolean(ts.getDecorators(node)?.length)) {
598
+ return removeStyleProp(node);
599
+ }
600
+ return ts.visitEachChild(node, visitor, ctx);
601
+ }
602
+ return ts.visitNode(realSf, visitor);
603
+ };
604
+ };
605
+ }
426
606
  #convertOutputToReal(filePath, distPath, text) {
427
607
  let realFilePath = PathUtils.norm(filePath);
428
608
  let realText = text;
@@ -438,6 +618,9 @@ export class SdTsCompiler {
438
618
  }
439
619
  return { filePath: realFilePath, text: realText };
440
620
  }
621
+ #removeOutputDevModeLine(str) {
622
+ return str.replace(/\(\(\) => \{ \(typeof ngDevMode === "undefined" \|\| ngDevMode\) && i0.ɵsetClassDebugInfo\(.*, \{ className: ".*", filePath: ".*", lineNumber: [0-9]* }\); }\)\(\);/, "");
623
+ }
441
624
  #debug(...msg) {
442
625
  this.#logger.debug(`[${path.basename(this._opt.pkgPath)}]`, ...msg);
443
626
  }