@simplysm/sd-cli 12.8.20 → 12.8.22

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 (40) hide show
  1. package/dist/entry/sd-cli-cordova.d.ts +30 -0
  2. package/dist/entry/sd-cli-cordova.js +307 -246
  3. package/dist/entry/sd-cli-cordova.js.map +1 -1
  4. package/dist/entry/sd-cli-project.d.ts +1 -1
  5. package/dist/entry/sd-cli-project.js +8 -9
  6. package/dist/entry/sd-cli-project.js.map +1 -1
  7. package/dist/index.d.ts +1 -0
  8. package/dist/index.js +1 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/pkg-builders/client/sd-ng.bundler.d.ts +15 -1
  11. package/dist/pkg-builders/client/sd-ng.bundler.js +60 -70
  12. package/dist/pkg-builders/client/sd-ng.bundler.js.map +1 -1
  13. package/dist/pkg-builders/client/sd-ng.plugin-creator.js +49 -29
  14. package/dist/pkg-builders/client/sd-ng.plugin-creator.js.map +1 -1
  15. package/dist/pkg-builders/lib/sd-ts-lib.builder.js +7 -4
  16. package/dist/pkg-builders/lib/sd-ts-lib.builder.js.map +1 -1
  17. package/dist/pkg-builders/server/sd-server.bundler.js +11 -10
  18. package/dist/pkg-builders/server/sd-server.bundler.js.map +1 -1
  19. package/dist/ts-compiler/sd-ts-compiler.d.ts +25 -2
  20. package/dist/ts-compiler/sd-ts-compiler.js +306 -575
  21. package/dist/ts-compiler/sd-ts-compiler.js.map +1 -1
  22. package/dist/ts-compiler/sd-ts-dependency-analyzer.d.ts +6 -0
  23. package/dist/ts-compiler/sd-ts-dependency-analyzer.js +141 -0
  24. package/dist/ts-compiler/sd-ts-dependency-analyzer.js.map +1 -0
  25. package/dist/types/ts-compiler.types.d.ts +12 -7
  26. package/dist/types/worker.types.d.ts +13 -0
  27. package/dist/utils/sd-cli-performance-time.js +1 -1
  28. package/package.json +6 -8
  29. package/src/entry/sd-cli-cordova.ts +393 -280
  30. package/src/entry/sd-cli-project.ts +11 -23
  31. package/src/index.ts +1 -0
  32. package/src/pkg-builders/client/sd-ng.bundler.ts +67 -69
  33. package/src/pkg-builders/client/sd-ng.plugin-creator.ts +47 -27
  34. package/src/pkg-builders/lib/sd-ts-lib.builder.ts +14 -7
  35. package/src/pkg-builders/server/sd-server.bundler.ts +22 -12
  36. package/src/ts-compiler/sd-ts-compiler.ts +379 -704
  37. package/src/ts-compiler/sd-ts-dependency-analyzer.ts +185 -0
  38. package/src/types/ts-compiler.types.ts +11 -6
  39. package/src/types/worker.types.ts +7 -6
  40. package/src/utils/sd-cli-performance-time.ts +1 -1
@@ -16,26 +16,25 @@ import { SdCliPerformanceTimer } from "../utils/sd-cli-performance-time";
16
16
  import { SdCliConvertMessageUtils } from "../utils/sd-cli-convert-message.utils";
17
17
  import {
18
18
  ISdTsCompilerResult,
19
- IStylesheetBundlingResult,
20
19
  SdTsCompilerOptions,
20
+ TStylesheetBundlingResult,
21
21
  } from "../types/ts-compiler.types";
22
22
  import { ISdBuildMessage } from "../types/build.types";
23
23
  import {
24
24
  createWorkerTransformer,
25
25
  } from "@angular/build/src/tools/angular/transformers/web-worker-transformer";
26
26
  import { ESLint } from "eslint";
27
+ import { SdTsDependencyAnalyzer } from "./sd-ts-dependency-analyzer";
27
28
 
28
29
  export class SdTsCompiler {
29
- readonly #logger = SdLogger.get(["simplysm", "sd-cli", "SdTsCompiler"]);
30
+ private _logger = SdLogger.get(["simplysm", "sd-cli", "SdTsCompiler"]);
30
31
 
31
- readonly #parsedTsconfig: ts.ParsedCommandLine;
32
- readonly #isForAngular: boolean;
32
+ private _isForAngular: boolean;
33
33
 
34
- // readonly #workerRevDependencyCacheMap = new Map<TNormPath, Set<TNormPath>>();
35
- readonly #revDependencyCacheMap = new Map<TNormPath, Set<TNormPath>>();
36
- readonly #resourceDependencyCacheMap = new Map<TNormPath, Set<TNormPath>>();
37
- readonly #sourceFileCacheMap = new Map<TNormPath, ts.SourceFile>();
38
- readonly #emittedFilesCacheMap = new Map<
34
+ private _allDepCacheMap = new Map<TNormPath, Set<TNormPath>>();
35
+ private _revDepCacheMap = new Map<TNormPath, Set<TNormPath>>();
36
+ private _sourceFileCacheMap = new Map<TNormPath, ts.SourceFile>();
37
+ private _emittedFilesCacheMap = new Map<
39
38
  TNormPath,
40
39
  {
41
40
  outAbsPath?: TNormPath;
@@ -43,73 +42,103 @@ export class SdTsCompiler {
43
42
  }[]
44
43
  >();
45
44
 
46
- readonly #stylesheetBundler: ComponentStylesheetBundler | undefined;
47
- readonly #compilerHost: ts.CompilerHost | AngularCompilerHost;
45
+ private _stylesheetBundler: ComponentStylesheetBundler | undefined;
48
46
 
49
- #ngProgram: NgtscProgram | undefined;
50
- #program: ts.Program | undefined;
47
+ private _ngProgram: NgtscProgram | undefined;
48
+ private _program: ts.Program | undefined;
51
49
 
52
- #modifiedFileSet = new Set<TNormPath>();
53
- #affectedFileSet = new Set<TNormPath>();
50
+ private _modifiedFileSet = new Set<TNormPath>();
51
+ private _affectedFileSet = new Set<TNormPath>();
54
52
 
55
- readonly #watchFileSet = new Set<TNormPath>();
56
- readonly #stylesheetBundlingResultMap = new Map<TNormPath, IStylesheetBundlingResult>();
53
+ private _watchFileSet = new Set<TNormPath>();
54
+ private _stylesheetBundlingResultMap = new Map<TNormPath, TStylesheetBundlingResult>();
57
55
 
58
- readonly #pkgPath: TNormPath;
59
- readonly #distPath: TNormPath;
60
- readonly #globalStyleFilePath?: TNormPath;
61
- readonly #watchScopePaths: TNormPath[];
56
+ private _perf!: SdCliPerformanceTimer;
62
57
 
63
- readonly #isForBundle: boolean;
58
+ constructor(private readonly _opt: SdTsCompilerOptions) {
59
+ this._debug("초기화 중...");
64
60
 
65
- // readonly #lintWorker = new SdWorker<TSdLintWorkerType>(import.meta.resolve("../workers/lint-worker"));
66
-
67
- #perf!: SdCliPerformanceTimer;
68
-
69
- // #processWebWorker?: (workerFile: string, containingFile: string) => string;
70
-
71
- constructor(opt: SdTsCompilerOptions) {
72
- this.#pkgPath = opt.pkgPath;
73
- this.#globalStyleFilePath = opt.globalStyleFilePath;
74
- this.#isForBundle = opt.isForBundle;
75
- this.#watchScopePaths = opt.watchScopePaths;
76
- // this.#processWebWorker = opt.processWebWorker;
61
+ const tsconfigPath = path.resolve(this._opt.pkgPath, "tsconfig.json");
62
+ const tsconfig = FsUtils.readJson(tsconfigPath);
63
+ this._isForAngular = Boolean(tsconfig.angularCompilerOptions);
77
64
 
78
- this.#debug("초기화...");
65
+ if (this._isForAngular) {
66
+ //-- stylesheetBundler
67
+ this._stylesheetBundler = new ComponentStylesheetBundler(
68
+ {
69
+ workspaceRoot: this._opt.pkgPath,
70
+ optimization: !this._opt.isDevMode,
71
+ inlineFonts: true,
72
+ preserveSymlinks: false,
73
+ sourcemap: this._opt.isDevMode ? "inline" : false,
74
+ outputNames: { bundles: "[name]", media: "media/[name]" },
75
+ includePaths: [],
76
+ externalDependencies: [],
77
+ target: transformSupportedBrowsersToTargets(browserslist(["Chrome > 78"])),
78
+ postcssConfiguration: {
79
+ plugins: [["css-has-pseudo"]],
80
+ },
81
+ tailwindConfiguration: undefined,
82
+ cacheOptions: {
83
+ enabled: true,
84
+ path: ".cache/angular",
85
+ basePath: ".cache",
86
+ },
87
+ },
88
+ "scss",
89
+ this._opt.isDevMode,
90
+ );
91
+ }
92
+ }
79
93
 
80
- //-- isForAngular / parsedTsConfig
94
+ private _parseTsConfig(): ITsConfigInfo {
95
+ const config = this._loadTsConfig();
96
+ const compilerHost = this._createCompilerHost(config.options);
97
+ return {
98
+ ...config,
99
+ compilerHost,
100
+ };
101
+ }
81
102
 
82
- const tsconfigPath = path.resolve(opt.pkgPath, "tsconfig.json");
103
+ private _loadTsConfig() {
104
+ const tsconfigPath = path.resolve(this._opt.pkgPath, "tsconfig.json");
83
105
  const tsconfig = FsUtils.readJson(tsconfigPath);
84
- this.#isForAngular = Boolean(tsconfig.angularCompilerOptions);
85
- this.#parsedTsconfig = ts.parseJsonConfigFileContent(tsconfig, ts.sys, opt.pkgPath, {
106
+ const parsedTsconfig = ts.parseJsonConfigFileContent(tsconfig, ts.sys, this._opt.pkgPath, {
86
107
  ...tsconfig.angularCompilerOptions,
87
- ...opt.additionalOptions,
108
+ ...this._opt.additionalOptions,
88
109
  });
89
110
 
90
- this.#distPath = PathUtils.norm(this.#parsedTsconfig.options.outDir ?? path.resolve(
91
- opt.pkgPath,
111
+ const distPath = PathUtils.norm(parsedTsconfig.options.outDir ?? path.resolve(
112
+ this._opt.pkgPath,
92
113
  "dist",
93
114
  ));
94
115
 
95
- //-- compilerHost
116
+ return {
117
+ fileNames: parsedTsconfig.fileNames,
118
+ options: parsedTsconfig.options,
119
+ distPath: distPath,
120
+ };
121
+ }
96
122
 
97
- this.#compilerHost = ts.createCompilerHost(this.#parsedTsconfig.options);
123
+ private _createCompilerHost(compilerOptions: ts.CompilerOptions) {
124
+ const compilerHost = ts.createCompilerHost(compilerOptions);
98
125
 
99
- const baseGetSourceFile = this.#compilerHost.getSourceFile;
100
- this.#compilerHost.getSourceFile = (
126
+ const baseGetSourceFile = compilerHost.getSourceFile;
127
+ compilerHost.getSourceFile = (
101
128
  fileName: string,
102
129
  languageVersionOrOptions: ts.ScriptTarget | ts.CreateSourceFileOptions,
103
130
  onError?: ((message: string) => void) | undefined,
104
131
  shouldCreateNewSourceFile?: boolean,
105
132
  ...args
106
133
  ): ts.SourceFile | undefined => {
107
- if (!shouldCreateNewSourceFile && this.#sourceFileCacheMap.has(PathUtils.norm(fileName))) {
108
- return this.#sourceFileCacheMap.get(PathUtils.norm(fileName));
134
+ const fileNPath = PathUtils.norm(fileName);
135
+
136
+ if (!shouldCreateNewSourceFile && this._sourceFileCacheMap.has(fileNPath)) {
137
+ return this._sourceFileCacheMap.get(fileNPath);
109
138
  }
110
139
 
111
140
  const sf: ts.SourceFile | undefined = baseGetSourceFile.call(
112
- this.#compilerHost,
141
+ compilerHost,
113
142
  fileName,
114
143
  languageVersionOrOptions,
115
144
  onError,
@@ -118,55 +147,27 @@ export class SdTsCompiler {
118
147
  );
119
148
 
120
149
  if (sf) {
121
- this.#sourceFileCacheMap.set(PathUtils.norm(fileName), sf);
150
+ this._sourceFileCacheMap.set(fileNPath, sf);
122
151
  }
123
152
  else {
124
- this.#sourceFileCacheMap.delete(PathUtils.norm(fileName));
153
+ this._sourceFileCacheMap.delete(fileNPath);
125
154
  }
126
155
 
127
156
  return sf;
128
157
  };
129
158
 
130
- const baseReadFile = this.#compilerHost.readFile;
131
- this.#compilerHost.readFile = (fileName) => {
132
- this.#watchFileSet.add(PathUtils.norm(fileName));
133
- return baseReadFile.call(this.#compilerHost, fileName);
159
+ const baseReadFile = compilerHost.readFile;
160
+ compilerHost.readFile = (fileName) => {
161
+ this._watchFileSet.add(PathUtils.norm(fileName));
162
+ return baseReadFile.call(compilerHost, fileName);
134
163
  };
135
164
 
136
- if (this.#isForAngular) {
137
- //-- stylesheetBundler
138
- this.#stylesheetBundler = new ComponentStylesheetBundler(
139
- {
140
- workspaceRoot: opt.pkgPath,
141
- optimization: !opt.isDevMode,
142
- inlineFonts: true,
143
- preserveSymlinks: false,
144
- sourcemap: opt.isDevMode ? "inline" : false,
145
- outputNames: { bundles: "[name]", media: "media/[name]" },
146
- includePaths: [],
147
- externalDependencies: [],
148
- target: transformSupportedBrowsersToTargets(browserslist(["Chrome > 78"])),
149
- postcssConfiguration: {
150
- plugins: [["css-has-pseudo"]],
151
- },
152
- tailwindConfiguration: undefined,
153
- cacheOptions: {
154
- enabled: true,
155
- path: ".cache/angular",
156
- basePath: ".cache",
157
- },
158
- },
159
- "scss",
160
- opt.isDevMode,
161
- );
162
-
163
- //-- compilerHost
164
-
165
- (this.#compilerHost as AngularCompilerHost).readResource = (fileName: string) => {
166
- return this.#compilerHost.readFile(fileName) ?? "";
165
+ if (this._isForAngular) {
166
+ (compilerHost as AngularCompilerHost).readResource = (fileName: string) => {
167
+ return compilerHost.readFile(fileName) ?? "";
167
168
  };
168
169
 
169
- (this.#compilerHost as AngularCompilerHost).transformResource = async (
170
+ (compilerHost as AngularCompilerHost).transformResource = async (
170
171
  data: string,
171
172
  context: {
172
173
  type: string;
@@ -178,69 +179,88 @@ export class SdTsCompiler {
178
179
  return null;
179
180
  }
180
181
 
181
- const contents = await this.#bundleStylesheetAsync(
182
+ const stylesheetBundlingResult = await this._bundleStylesheetAsync(
182
183
  data,
183
184
  PathUtils.norm(context.containingFile),
184
185
  context.resourceFile != null ? PathUtils.norm(context.resourceFile) : undefined,
185
186
  );
186
187
 
187
- return StringUtils.isNullOrEmpty(contents) ? null : { content: contents };
188
+ return StringUtils.isNullOrEmpty(stylesheetBundlingResult.contents)
189
+ ? null
190
+ : { content: stylesheetBundlingResult.contents };
188
191
  };
189
192
 
190
- (this.#compilerHost as AngularCompilerHost).getModifiedResourceFiles = () => {
191
- return new Set(Array.from(this.#modifiedFileSet).map((item) => PathUtils.posix(item)));
193
+ (compilerHost as AngularCompilerHost).getModifiedResourceFiles = () => {
194
+ return new Set(Array.from(this._modifiedFileSet).map((item) => PathUtils.posix(item)));
192
195
  };
193
196
  }
197
+
198
+ return compilerHost;
194
199
  }
195
200
 
196
- async #bundleStylesheetAsync(
201
+ private async _bundleStylesheetAsync(
197
202
  data: string,
198
203
  containingFile: TNormPath,
199
204
  resourceFile: TNormPath | null = null,
200
- ) {
205
+ ): Promise<TStylesheetBundlingResult> {
201
206
  // containingFile: 포함된 파일 (.ts)
202
207
  // resourceFile: 외부 리소스 파일 (styleUrls로 입력하지 않고 styles에 직접 입력한 경우 null)
203
208
  // referencedFiles: import한 외부 scss 파일 혹은 woff파일등 외부 파일
204
209
 
205
210
  // this.#debug(`bundle stylesheet...(${containingFile}, ${resourceFile})`);
206
211
 
207
- return await this.#perf.run("bundle style", async () => {
208
- const stylesheetResult =
209
- resourceFile != null
210
- ? await this.#stylesheetBundler!.bundleFile(resourceFile)
211
- : await this.#stylesheetBundler!.bundleInline(data, containingFile, "scss");
212
-
213
- if (stylesheetResult.referencedFiles) {
214
- for (const referencedFile of stylesheetResult.referencedFiles) {
215
- const depCacheSet = this.#resourceDependencyCacheMap.getOrCreate(
216
- PathUtils.norm(referencedFile),
217
- new Set<TNormPath>(),
212
+ return await this._perf.run("스타일 번들링", async () => {
213
+ const fileNPath = PathUtils.norm(resourceFile ?? containingFile);
214
+ if (this._stylesheetBundlingResultMap.has(fileNPath)) {
215
+ return this._stylesheetBundlingResultMap.get(fileNPath)!;
216
+ }
217
+
218
+ try {
219
+ const result =
220
+ resourceFile != null
221
+ ? await this._stylesheetBundler!.bundleFile(resourceFile)
222
+ : await this._stylesheetBundler!.bundleInline(data, containingFile, "scss");
223
+
224
+ if (result.referencedFiles) {
225
+ for (const referencedFile of result.referencedFiles) {
226
+ const depCacheSet = this._revDepCacheMap.getOrCreate(
227
+ PathUtils.norm(referencedFile),
228
+ new Set<TNormPath>(),
229
+ );
230
+ depCacheSet.add(fileNPath);
231
+ }
232
+
233
+ this._watchFileSet.adds(
234
+ ...Array.from(result.referencedFiles.values())
235
+ .map((item) => PathUtils.norm(item)),
218
236
  );
219
- depCacheSet.add(resourceFile ?? containingFile);
220
237
  }
221
238
 
222
- this.#watchFileSet.adds(
223
- ...Array.from(stylesheetResult.referencedFiles.values())
224
- .map((item) => PathUtils.norm(item)),
225
- );
226
- }
227
-
228
- this.#stylesheetBundlingResultMap.set(PathUtils.norm(resourceFile ?? containingFile), {
229
- outputFiles: stylesheetResult.errors == null ? stylesheetResult.outputFiles : undefined,
230
- metafile: stylesheetResult.errors == null ? stylesheetResult.metafile : undefined,
231
- errors: stylesheetResult.errors,
232
- warnings: stylesheetResult.warnings,
233
- });
239
+ this._stylesheetBundlingResultMap.set(fileNPath, result);
234
240
 
235
- return stylesheetResult.contents;
241
+ return result;
242
+ }
243
+ catch (err) {
244
+ const result = {
245
+ errors: [
246
+ {
247
+ text: `스타일 번들링 실패: ${err.message ?? "알 수 없는 오류"}`,
248
+ location: { file: containingFile },
249
+ },
250
+ ],
251
+ warnings: [],
252
+ };
253
+ this._stylesheetBundlingResultMap.set(fileNPath, result);
254
+ return result;
255
+ }
236
256
  });
237
257
  }
238
258
 
239
- async compileAsync(modifiedFileSet: Set<TNormPath>): Promise<ISdTsCompilerResult> {
240
- this.#perf = new SdCliPerformanceTimer("esbuild compile");
259
+ public async compileAsync(modifiedFileSet: Set<TNormPath>): Promise<ISdTsCompilerResult> {
260
+ this._perf = new SdCliPerformanceTimer("esbuild compile");
241
261
 
242
- this.#modifiedFileSet = new Set(modifiedFileSet);
243
- this.#affectedFileSet = new Set<TNormPath>();
262
+ this._modifiedFileSet = new Set(modifiedFileSet);
263
+ this._affectedFileSet = new Set<TNormPath>();
244
264
 
245
265
  /*for (const mod of modifiedFileSet) {
246
266
  const workerImporters = this.#workerRevDependencyCacheMap.get(mod);
@@ -251,11 +271,18 @@ export class SdTsCompiler {
251
271
  }
252
272
  }*/
253
273
 
254
- const prepareResult = await this.#prepareAsync();
274
+ const tsconf = this._parseTsConfig();
255
275
 
256
- const [buildResult, lintResults] = await Promise.all([this.#buildAsync(), this.#lintAsync()]);
276
+ const prepareResult = await this._prepareAsync(tsconf);
257
277
 
258
- this.#debug(`build completed`, this.#perf.toString());
278
+ const [buildResult, lintResults] = await Promise.all([
279
+ this._buildAsync(tsconf),
280
+ this._lintAsync(),
281
+ ]);
282
+
283
+ this._debug(`빌드 완료됨`, this._perf.toString());
284
+ this._debug(`영향 받은 파일: ${this._affectedFileSet.size}개`);
285
+ this._debug(`감시 중인 파일: ${this._watchFileSet.size}개`);
259
286
 
260
287
  return {
261
288
  messages: [
@@ -263,210 +290,192 @@ export class SdTsCompiler {
263
290
  ...SdCliConvertMessageUtils.convertToBuildMessagesFromTsDiag(buildResult.diagnostics),
264
291
  ...SdCliConvertMessageUtils.convertToBuildMessagesFromEslint(lintResults),
265
292
  ],
266
- watchFileSet: this.#watchFileSet,
267
- affectedFileSet: this.#affectedFileSet,
268
- stylesheetBundlingResultMap: this.#stylesheetBundlingResultMap,
269
- emittedFilesCacheMap: this.#emittedFilesCacheMap,
293
+ watchFileSet: this._watchFileSet,
294
+ affectedFileSet: this._affectedFileSet,
295
+ stylesheetBundlingResultMap: this._stylesheetBundlingResultMap,
296
+ emittedFilesCacheMap: this._emittedFilesCacheMap,
270
297
  emitFileSet: buildResult.emitFileSet,
271
298
  };
272
299
  }
273
300
 
274
- async #prepareAsync() {
275
- if (this.#modifiedFileSet.size !== 0) {
276
- this.#debug(`get affected (old deps & old res deps)...`);
301
+ private async _prepareAsync(tsconf: ITsConfigInfo) {
302
+ if (this._modifiedFileSet.size !== 0) {
303
+ this._debug(`영향 받은 파일 추적 중... (구 의존성)`);
304
+
305
+ this._perf.run("영향 받은 파일 추적", () => {
306
+ for (const modifiedFile of this._modifiedFileSet) {
307
+ this._affectedFileSet.add(modifiedFile);
308
+
309
+ this._affectedFileSet.adds(...(this._revDepCacheMap.get(modifiedFile) ?? []));
277
310
 
278
- this.#perf.run("get affected", () => {
279
- for (const modifiedFile of this.#modifiedFileSet) {
280
- this.#affectedFileSet.add(modifiedFile);
281
- this.#affectedFileSet.adds(...(this.#revDependencyCacheMap.get(modifiedFile) ?? []));
282
- this.#affectedFileSet.adds(...(this.#resourceDependencyCacheMap.get(modifiedFile) ?? []));
311
+ // .d.ts .js 대응
312
+ if (modifiedFile.endsWith(".d.ts")) {
313
+ const jsEquivalent = PathUtils.norm(modifiedFile.replace(/\.d\.ts$/, ".js"));
314
+ this._affectedFileSet.add(jsEquivalent);
315
+ }
283
316
  }
284
317
  });
285
318
 
286
- this.#debug(`invalidate & clear cache...`);
319
+ this._debug(`캐시 무효화 초기화 중...`);
320
+
321
+ this._perf.run("캐시 무효화 및 초기화", () => {
322
+ this._stylesheetBundler?.invalidate(this._affectedFileSet);
323
+
324
+ const toDeleteSet = new Set<TNormPath>();
287
325
 
288
- this.#perf.run("invalidate & clear cache", () => {
289
- this.#stylesheetBundler?.invalidate(this.#affectedFileSet);
326
+ // 초기: 명시적으로 수정된 파일들
327
+ for (const file of this._affectedFileSet) {
328
+ toDeleteSet.add(file);
290
329
 
291
- for (const affectedFile of this.#affectedFileSet) {
292
- this.#emittedFilesCacheMap.delete(affectedFile);
293
- this.#sourceFileCacheMap.delete(affectedFile);
294
- this.#stylesheetBundlingResultMap.delete(affectedFile);
295
- this.#watchFileSet.delete(affectedFile);
330
+ // 역방향으로 영향을 받는 파일들도 포함
331
+ const dependents = this._revDepCacheMap.get(file);
332
+ if (dependents) {
333
+ for (const dep of dependents) {
334
+ toDeleteSet.add(dep);
335
+ }
336
+ }
337
+ }
338
+
339
+ for (const toDeleteFile of toDeleteSet) {
340
+ this._emittedFilesCacheMap.delete(toDeleteFile);
341
+ this._sourceFileCacheMap.delete(toDeleteFile);
342
+ this._stylesheetBundlingResultMap.delete(toDeleteFile);
343
+ this._watchFileSet.delete(toDeleteFile);
344
+ this._allDepCacheMap.delete(toDeleteFile);
345
+ this._revDepCacheMap.delete(toDeleteFile);
296
346
  }
297
347
 
298
- this.#revDependencyCacheMap.clear();
299
- this.#resourceDependencyCacheMap.clear();
348
+ for (const [key, deps] of this._revDepCacheMap) {
349
+ for (const file of toDeleteSet) {
350
+ deps.delete(file);
351
+ }
352
+ if (deps.size === 0) {
353
+ this._revDepCacheMap.delete(key);
354
+ }
355
+ }
300
356
  });
301
357
  }
302
358
 
303
- this.#debug(`create program...`);
359
+ this._debug(`ts.Program 생성 중...`);
304
360
 
305
- this.#perf.run("create program", () => {
306
- if (this.#isForAngular) {
307
- this.#ngProgram = new NgtscProgram(
308
- this.#parsedTsconfig.fileNames,
309
- this.#parsedTsconfig.options,
310
- this.#compilerHost,
311
- this.#ngProgram,
361
+ this._perf.run("ts.Program 생성", () => {
362
+ if (this._isForAngular) {
363
+ this._ngProgram = new NgtscProgram(
364
+ tsconf.fileNames,
365
+ tsconf.options,
366
+ tsconf.compilerHost,
367
+ this._ngProgram,
312
368
  );
313
- this.#program = this.#ngProgram.getTsProgram();
369
+ this._program = this._ngProgram.getTsProgram();
314
370
  }
315
371
  else {
316
- this.#program = ts.createProgram(
317
- this.#parsedTsconfig.fileNames,
318
- this.#parsedTsconfig.options,
319
- this.#compilerHost,
320
- this.#program,
372
+ this._program = ts.createProgram(
373
+ tsconf.fileNames,
374
+ tsconf.options,
375
+ tsconf.compilerHost,
376
+ this._program,
321
377
  );
322
378
  }
323
379
  });
324
380
 
325
- if (this.#ngProgram) {
326
- await this.#perf.run("ng analyze", async () => {
327
- await this.#ngProgram!.compiler.analyzeAsync();
381
+ if (this._ngProgram) {
382
+ await this._perf.run("Angular 템플릿 분석", async () => {
383
+ await this._ngProgram!.compiler.analyzeAsync();
328
384
  });
329
385
  }
330
386
 
331
- const getOrgSourceFile = (sf: ts.SourceFile) => {
332
- if (sf.fileName.endsWith(".ngtypecheck.ts")) {
333
- const orgFileName = sf.fileName.slice(0, -15) + ".ts";
334
- return this.#program!.getSourceFile(orgFileName);
335
- }
336
-
337
- return sf;
338
- };
339
-
340
- const sourceFileSet = new Set(
341
- this.#program!.getSourceFiles()
342
- .map((sf) => getOrgSourceFile(sf))
343
- .filterExists(),
344
- );
345
-
346
- this.#debug(`get new deps...`);
387
+ this._debug(`새 의존성 분석 중...`);
347
388
 
348
389
  const messages: ISdBuildMessage[] = [];
390
+ this._perf.run("새 의존성 분석", () => {
391
+ const analysed = SdTsDependencyAnalyzer.analyze(
392
+ this._program!,
393
+ tsconf.compilerHost,
394
+ this._opt.watchScopePaths,
395
+ this._allDepCacheMap,
396
+ );
397
+ messages.push(...analysed);
349
398
 
350
- this.#perf.run("get new deps", () => {
351
- const depMap = new Map<
352
- TNormPath,
353
- {
354
- fileName: TNormPath;
355
- importName: string;
356
- exportName?: string;
357
- }[]
358
- >();
359
- for (const sf of sourceFileSet) {
360
- if (!this.#watchScopePaths.some((scopePath) => PathUtils.isChildPath(
361
- sf.fileName,
362
- scopePath,
363
- ))) {
364
- continue;
365
- }
366
-
367
- const refs = this.#findDeps(sf);
368
- messages.push(...refs.filter((item) => "severity" in item));
369
- depMap.set(
370
- PathUtils.norm(sf.fileName),
371
- refs
372
- .filter((item) => "fileName" in item)
373
- .filter((item) =>
374
- this.#watchScopePaths.some((scopePath) => PathUtils.isChildPath(
375
- item.fileName,
376
- scopePath,
377
- )),
378
- ),
379
- );
380
- }
381
-
382
- const allDepMap = new Map<TNormPath, Set<TNormPath>>();
383
- const getAllDeps = (fileName: TNormPath, prevSet?: Set<TNormPath>) => {
384
- if (allDepMap.has(fileName)) {
385
- return allDepMap.get(fileName)!;
386
- }
387
-
388
- const result = new Set<TNormPath>();
389
-
390
- const deps = depMap.get(fileName) ?? [];
391
- result.adds(...deps.map((item) => item.fileName));
399
+ for (const fileNPath of this._allDepCacheMap.keys()) {
400
+ // const filePath = PathUtils.norm(sf.fileName);
401
+ const deps = this._allDepCacheMap.get(fileNPath)!;
392
402
 
393
403
  for (const dep of deps) {
394
- const targetDeps = depMap.get(dep.fileName) ?? [];
395
-
396
- if (dep.importName === "*") {
397
- for (const targetRefItem of targetDeps.filter((item) => item.exportName != null)) {
398
- if (prevSet?.has(targetRefItem.fileName)) continue;
399
-
400
- result.add(targetRefItem.fileName);
401
- result.adds(...getAllDeps(
402
- targetRefItem.fileName,
403
- new Set<TNormPath>(prevSet).adds(...result),
404
- ));
405
- }
406
- }
407
- else {
408
- for (const targetRefItem of targetDeps.filter((item) => item.exportName
409
- === dep.importName)) {
410
- if (prevSet?.has(targetRefItem.fileName)) continue;
411
-
412
- result.add(targetRefItem.fileName);
413
- result.adds(...getAllDeps(
414
- targetRefItem.fileName,
415
- new Set<TNormPath>(prevSet).adds(...result),
416
- ));
417
- }
418
- }
404
+ const depCache = this._revDepCacheMap.getOrCreate(dep, new Set<TNormPath>());
405
+ depCache.add(fileNPath);
419
406
  }
420
407
 
421
- return result;
422
- };
408
+ if (this._ngProgram) {
409
+ const sf = this._program!.getSourceFile(fileNPath)!;
423
410
 
424
- for (const sf of sourceFileSet) {
425
- const deps = getAllDeps(PathUtils.norm(sf.fileName));
426
- allDepMap.set(PathUtils.norm(sf.fileName), deps);
427
-
428
- for (const dep of getAllDeps(PathUtils.norm(sf.fileName))) {
429
- const depCache = this.#revDependencyCacheMap.getOrCreate(dep, new Set<TNormPath>());
430
- depCache.add(PathUtils.norm(sf.fileName));
431
- }
432
-
433
- if (this.#ngProgram) {
434
- if (this.#ngProgram.compiler.ignoreForEmit.has(sf)) {
411
+ if (this._ngProgram.compiler.ignoreForEmit.has(sf)) {
435
412
  continue;
436
413
  }
437
414
 
438
- for (const dep of this.#ngProgram.compiler.getResourceDependencies(sf)) {
439
- const ref = this.#resourceDependencyCacheMap.getOrCreate(
415
+ for (const dep of this._ngProgram.compiler.getResourceDependencies(sf)) {
416
+ const ref = this._revDepCacheMap.getOrCreate(
440
417
  PathUtils.norm(dep),
441
418
  new Set<TNormPath>(),
442
419
  );
443
- ref.add(PathUtils.norm(sf.fileName));
420
+ ref.add(fileNPath);
444
421
  }
445
422
  }
446
423
  }
447
424
  });
448
425
 
449
- if (this.#modifiedFileSet.size === 0) {
450
- this.#debug(`get affected (init)...`);
426
+ if (this._modifiedFileSet.size === 0) {
427
+ this._debug(`영향 받은 파일 추가 중... (새 의존성)`);
451
428
 
452
- this.#perf.run("get affected (init)", () => {
453
- for (const sf of sourceFileSet) {
454
- if (!this.#watchScopePaths.some((scopePath) => PathUtils.isChildPath(
455
- sf.fileName,
429
+ this._perf.run("영향 받은 파일 추가 중 (새 의존성)", () => {
430
+ for (const fileNPath of this._allDepCacheMap.keys()) {
431
+ if (!this._opt.watchScopePaths.some((scopePath) => PathUtils.isChildPath(
432
+ fileNPath,
456
433
  scopePath,
457
434
  ))) {
458
435
  continue;
459
436
  }
460
437
 
461
- this.#affectedFileSet.add(PathUtils.norm(sf.fileName));
438
+ this._affectedFileSet.add(fileNPath);
462
439
  }
463
440
  });
464
441
  }
465
442
 
466
- for (const dep of this.#revDependencyCacheMap.keys()) {
467
- if (this.#modifiedFileSet.has(dep)) {
468
- this.#affectedFileSet.adds(
469
- ...Array.from(this.#revDependencyCacheMap.get(dep)!).mapMany((item) =>
443
+ /**
444
+ * AI가 넣으라고해서 넣었지만 이유는 잘 모르겠음.
445
+ * AI측의 설명은 아래와 같음:
446
+ *
447
+ * 변경된 파일이 .d.ts일 경우, 타입 정보만을 가져다 쓰는 다수의 .ts 파일들이
448
+ * 직접적으로 import하고 있어도 revDepMap에는 기록되지 않을 수 있음.
449
+ *
450
+ * 따라서 .d.ts 파일을 참조하는 모든 파일을 allDepCacheMap에서 역추적하여
451
+ * 간접 영향 파일들을 정확하게 affectedFileSet에 포함시켜야 함.
452
+ *
453
+ * 이 블록은 정확도 보완을 위한 보증 로직이며, 특히 초기 빌드 또는 watch 시 의존성 누락을 방지함.
454
+ */
455
+ for (const modifiedFile of this._modifiedFileSet) {
456
+ // 신규 추가된 파일이 누락되지 않도록 추가하라고 하여 넣음
457
+ if (
458
+ !this._revDepCacheMap.has(modifiedFile) &&
459
+ !this._allDepCacheMap.has(modifiedFile) &&
460
+ this._program!.getSourceFile(modifiedFile)
461
+ ) {
462
+ this._affectedFileSet.add(modifiedFile);
463
+ }
464
+
465
+ // AI가 넣으라 한부분에 대한 설명은 여기서부터임
466
+ if (!modifiedFile.endsWith(".d.ts")) continue;
467
+
468
+ for (const [importer, deps] of this._allDepCacheMap) {
469
+ if (deps.has(modifiedFile)) {
470
+ this._affectedFileSet.add(importer);
471
+ }
472
+ }
473
+ }
474
+
475
+ for (const dep of this._revDepCacheMap.keys()) {
476
+ if (this._modifiedFileSet.has(dep)) {
477
+ this._affectedFileSet.adds(
478
+ ...Array.from(this._revDepCacheMap.get(dep)!).mapMany((item) =>
470
479
  [
471
480
  item,
472
481
  // .d.ts면 .js파일도 affected에 추가
@@ -478,22 +487,8 @@ export class SdTsCompiler {
478
487
 
479
488
  // dep이 emit된적이 없으면 affected에 추가해야함.
480
489
  // dep파일이 추가된후 기존 파일에서 import하면 dep파일이 affected에 포함이 안되는 현상 때문
481
- if (!this.#emittedFilesCacheMap.has(dep)) {
482
- this.#affectedFileSet.add(dep);
483
- }
484
- }
485
-
486
- if (this.#ngProgram) {
487
- for (const dep of this.#resourceDependencyCacheMap.keys()) {
488
- if (this.#modifiedFileSet.has(dep)) {
489
- this.#affectedFileSet.adds(...this.#resourceDependencyCacheMap.get(dep)!);
490
- }
491
-
492
- // dep이 emit된적이 없으면 affected에 추가해야함.
493
- // dep파일이 추가된후 기존 파일에서 import하면 dep파일이 affected에 포함이 안되는 현상 때문
494
- if (!this.#emittedFilesCacheMap.has(dep)) {
495
- this.#affectedFileSet.add(dep);
496
- }
490
+ if (!this._emittedFilesCacheMap.has(dep)) {
491
+ this._affectedFileSet.add(dep);
497
492
  }
498
493
  }
499
494
 
@@ -502,9 +497,9 @@ export class SdTsCompiler {
502
497
  };
503
498
  }
504
499
 
505
- async #lintAsync() {
506
- const lintFilePaths = Array.from(this.#affectedFileSet)
507
- .filter((item) => PathUtils.isChildPath(item, this.#pkgPath))
500
+ private async _lintAsync() {
501
+ const lintFilePaths = Array.from(this._affectedFileSet)
502
+ .filter((item) => PathUtils.isChildPath(item, this._opt.pkgPath))
508
503
  .filter((item) => (
509
504
  (!item.endsWith(".d.ts") && item.endsWith(".ts")) ||
510
505
  item.endsWith(".js")
@@ -516,14 +511,14 @@ export class SdTsCompiler {
516
511
  }
517
512
 
518
513
  const linter = new ESLint({
519
- cwd: this.#pkgPath,
514
+ cwd: this._opt.pkgPath,
520
515
  cache: false,
521
516
  overrideConfig: {
522
517
  languageOptions: {
523
518
  parserOptions: {
524
519
  // parser: tseslint.parser,
525
520
  project: null,
526
- programs: [this.#program],
521
+ programs: [this._program],
527
522
  },
528
523
  },
529
524
  },
@@ -538,57 +533,52 @@ export class SdTsCompiler {
538
533
  // ]);
539
534
  }
540
535
 
541
- async #buildAsync() {
536
+ private async _buildAsync(tsconf: ITsConfigInfo) {
542
537
  const emitFileSet = new Set<TNormPath>();
543
538
  const diagnostics: ts.Diagnostic[] = [];
544
539
 
545
- this.#debug(`get diagnostics...`);
540
+ this._debug(`프로그램 진단 수집 중...`);
546
541
 
547
- this.#perf.run("get program diagnostics", () => {
542
+ this._perf.run("프로그램 진단 수집", () => {
548
543
  diagnostics.push(
549
- ...this.#program!.getConfigFileParsingDiagnostics(),
550
- ...this.#program!.getOptionsDiagnostics(),
551
- ...this.#program!.getGlobalDiagnostics(),
544
+ ...this._program!.getConfigFileParsingDiagnostics(),
545
+ ...this._program!.getOptionsDiagnostics(),
546
+ ...this._program!.getGlobalDiagnostics(),
552
547
  );
553
548
 
554
- if (this.#ngProgram) {
555
- diagnostics.push(...this.#ngProgram.compiler.getOptionDiagnostics());
549
+ if (this._ngProgram) {
550
+ diagnostics.push(...this._ngProgram.compiler.getOptionDiagnostics());
556
551
  }
557
552
  });
558
553
 
559
- this.#debug(`get diagnostics of files...`);
560
-
561
- for (const affectedFile of this.#affectedFileSet) {
562
- if (!PathUtils.isChildPath(affectedFile, this.#pkgPath)) {
563
- continue;
564
- }
554
+ this._debug(`개별 파일 진단 수집 중...`);
565
555
 
566
- const affectedSourceFile = this.#program!.getSourceFile(affectedFile);
556
+ for (const affectedFile of this._affectedFileSet) {
557
+ if (!PathUtils.isChildPath(affectedFile, this._opt.pkgPath)) continue;
567
558
 
559
+ const affectedSourceFile = this._program!.getSourceFile(affectedFile);
568
560
  if (
569
561
  !affectedSourceFile ||
570
- (this.#ngProgram && this.#ngProgram.compiler.ignoreForDiagnostics.has(affectedSourceFile))
562
+ (this._ngProgram && this._ngProgram.compiler.ignoreForDiagnostics.has(affectedSourceFile))
571
563
  ) {
572
564
  continue;
573
565
  }
574
566
 
575
567
  // this.#debug(`get diagnostics of file ${affectedFile}...`);
576
568
 
577
- this.#perf.run("get file diagnostics", () => {
569
+ this._perf.run("개별 파일 진단 수집", () => {
578
570
  diagnostics.push(
579
- ...this.#program!.getSyntacticDiagnostics(affectedSourceFile),
580
- ...this.#program!.getSemanticDiagnostics(affectedSourceFile),
571
+ ...this._program!.getSyntacticDiagnostics(affectedSourceFile),
572
+ ...this._program!.getSemanticDiagnostics(affectedSourceFile),
581
573
  );
582
574
  });
583
575
 
584
- if (this.#ngProgram) {
585
- this.#perf.run("get file diagnostics: ng", () => {
586
- if (affectedSourceFile.isDeclarationFile) {
587
- return;
588
- }
576
+ if (this._ngProgram) {
577
+ this._perf.run("개별 파일 진단 수집(Angular)", () => {
578
+ if (affectedSourceFile.isDeclarationFile) return;
589
579
 
590
580
  diagnostics.push(
591
- ...this.#ngProgram!.compiler.getDiagnosticsForFile(
581
+ ...this._ngProgram!.compiler.getDiagnosticsForFile(
592
582
  affectedSourceFile,
593
583
  OptimizeFor.WholeProgram,
594
584
  ),
@@ -597,163 +587,50 @@ export class SdTsCompiler {
597
587
  }
598
588
  }
599
589
 
600
- this.#perf.run("emit", () => {
601
- this.#debug(`prepare emit...`);
590
+ this._perf.run("파일 출력 (emit)", () => {
591
+ this._debug(`파일 출력 준비 중...`);
602
592
 
603
593
  let transformers: ts.CustomTransformers = {};
604
594
 
605
- if (this.#ngProgram) {
595
+ if (this._ngProgram) {
606
596
  transformers = {
607
597
  ...transformers,
608
- ...this.#ngProgram.compiler.prepareEmit().transformers,
598
+ ...this._ngProgram.compiler.prepareEmit().transformers,
609
599
  };
610
- (transformers.before ??= []).push(replaceBootstrap(() => this.#program!.getTypeChecker()));
600
+ (transformers.before ??= []).push(replaceBootstrap(() => this._program!.getTypeChecker()));
611
601
  (transformers.before ??= []).push(
612
602
  createWorkerTransformer((file, importer) => {
613
603
  const fullPath = path.resolve(path.dirname(importer), file);
614
- const relPath = path.relative(path.resolve(this.#pkgPath, "src"), fullPath);
604
+ const relPath = path.relative(path.resolve(this._opt.pkgPath, "src"), fullPath);
615
605
  return relPath.replace(/\.ts$/, "").replaceAll("\\", "/") + ".js";
616
606
  }),
617
607
  );
618
608
  }
619
- // (transformers.before ??= []).push(transformKeys(this.#program));
620
-
621
- /*const fixImportTransformer: ts.TransformerFactory<ts.SourceFile> = (context) => {
622
- return (sf) => {
623
- const shouldAppendJs = (importText: string): string | undefined => {
624
- const resolved = ts.resolveModuleName(
625
- importText,
626
- sf.fileName,
627
- this.#program!.getCompilerOptions(),
628
- ts.sys,
629
- );
630
-
631
- const resolvedInfo = resolved.resolvedModule;
632
- if (!resolvedInfo) return undefined;
633
-
634
- const resolvedFileName = resolvedInfo.resolvedFileName;
635
-
636
- // ① .ts / .tsx / .js / .jsx 만 대상
637
- if (!/\.(d\.ts|ts|tsx|js|jsx)$/i.test(resolvedFileName)) return undefined;
638
-
639
- // ② 사용자가 .js, .mjs, .json 등 명시한 경우 무시
640
- if (/\.[mc]?js$|\.json$/i.test(importText)) return undefined;
641
-
642
- // 3. import 경로의 마지막 부분이 파일명(확장자 제외)과 같으면 → .js 붙여야 함
643
- const importLastName = importText.split("/").pop();
644
- const resolvedFileNameOnly = path.basename(resolvedFileName)
645
- .replace(/\.(d\.ts|ts|tsx|js|jsx)$/, "");
646
-
647
- if (importLastName === resolvedFileNameOnly) {
648
- return importText + ".js";
649
- }
650
-
651
- // 4. 그렇지 않으면 → index.ts 같은 루트 패키지 import → .js 붙이지 않음
652
- return undefined;
653
- };
654
-
655
- const visitor: ts.Visitor = (node): ts.Node => {
656
- // import { x } from "./foo"
657
- if (
658
- ts.isImportDeclaration(node) &&
659
- ts.isStringLiteral(node.moduleSpecifier)
660
- ) {
661
- const newPath = shouldAppendJs(node.moduleSpecifier.text);
662
- if (newPath != null) {
663
- return ts.factory.updateImportDeclaration(
664
- node,
665
- node.modifiers,
666
- node.importClause,
667
- ts.factory.createStringLiteral(newPath),
668
- undefined,
669
- );
670
- }
671
- }
672
609
 
673
- // export * from "./bar"
674
- if (
675
- ts.isExportDeclaration(node) &&
676
- node.moduleSpecifier &&
677
- ts.isStringLiteral(node.moduleSpecifier)
678
- ) {
679
- const newPath = shouldAppendJs(node.moduleSpecifier.text);
680
- if (newPath != null) {
681
- return ts.factory.updateExportDeclaration(
682
- node,
683
- node.modifiers,
684
- node.isTypeOnly,
685
- node.exportClause,
686
- ts.factory.createStringLiteral(newPath),
687
- undefined,
688
- );
689
- }
690
- }
691
-
692
- // dynamic import("./baz")
693
- if (
694
- ts.isCallExpression(node) &&
695
- node.expression.kind === ts.SyntaxKind.ImportKeyword &&
696
- node.arguments.length === 1 &&
697
- ts.isStringLiteral(node.arguments[0])
698
- ) {
699
- const newPath = shouldAppendJs(node.arguments[0].text);
700
- if (newPath != null) {
701
- return ts.factory.updateCallExpression(
702
- node,
703
- node.expression,
704
- undefined,
705
- [ts.factory.createStringLiteral(newPath)],
706
- );
707
- }
708
- }
709
-
710
- return ts.visitEachChild(node, visitor, context);
711
- };
712
-
713
- return ts.visitNode(sf, visitor) as ts.SourceFile;
714
- };
715
- };
716
- (transformers.before ??= []).push(fixImportTransformer);*/
717
-
718
- this.#debug(`emit for files...`);
610
+ this._debug(`파일 출력 중...`);
719
611
 
720
612
  // affected에 새로 추가된 파일은 포함되지 않는 현상이 있어 sourceFileSet으로 바꿈
721
613
  // 비교해보니, 딱히 getSourceFiles라서 더 느려지는것 같지는 않음
722
614
  // 그래도 affected로 다시 테스트 (조금이라도 더 빠르게)
723
- for (const affectedFile of this.#affectedFileSet) {
724
- if (this.#emittedFilesCacheMap.has(affectedFile)) {
725
- continue;
726
- }
727
-
728
- const sf = this.#program!.getSourceFile(affectedFile);
729
- if (!sf) {
730
- continue;
731
- }
615
+ for (const affectedFile of this._affectedFileSet) {
616
+ if (this._emittedFilesCacheMap.has(affectedFile)) continue;
732
617
 
733
- if (sf.isDeclarationFile) {
734
- continue;
735
- }
618
+ const sf = this._program!.getSourceFile(affectedFile);
619
+ if (!sf || sf.isDeclarationFile) continue;
620
+ if (this._ngProgram?.compiler.ignoreForEmit.has(sf)) continue;
621
+ if (this._ngProgram?.compiler.incrementalCompilation.safeToSkipEmit(sf)) continue;
736
622
 
737
- if (this.#ngProgram?.compiler.ignoreForEmit.has(sf)) {
623
+ // 번들이 아닌 외부패키지는 보통 emit안해도 됨
624
+ // but esbuild를 통해 bundle로 묶어야 하는놈들은 모든 output이 있어야 함.
625
+ if (!this._opt.isForBundle && !PathUtils.isChildPath(sf.fileName, this._opt.pkgPath)) {
738
626
  continue;
739
627
  }
740
628
 
741
- if (this.#ngProgram?.compiler.incrementalCompilation.safeToSkipEmit(sf)) {
742
- continue;
743
- }
744
-
745
- // esbuild를 통해 bundle로 묶어야 하는놈들은 모든 output이 있어야 함.
746
- if (!this.#isForBundle) {
747
- if (!PathUtils.isChildPath(sf.fileName, this.#pkgPath)) {
748
- continue;
749
- }
750
- }
751
-
752
- this.#program!.emit(
629
+ this._program!.emit(
753
630
  sf,
754
631
  (fileName, text, writeByteOrderMark, onError, sourceFiles, data) => {
755
632
  if (!sourceFiles || sourceFiles.length === 0) {
756
- this.#compilerHost.writeFile(
633
+ tsconf.compilerHost.writeFile(
757
634
  fileName,
758
635
  text,
759
636
  writeByteOrderMark,
@@ -765,44 +642,22 @@ export class SdTsCompiler {
765
642
  }
766
643
 
767
644
  const sourceFile = ts.getOriginalNode(sourceFiles[0], ts.isSourceFile);
768
- if (this.#ngProgram) {
769
- if (this.#ngProgram.compiler.ignoreForEmit.has(sourceFile)) {
770
- return;
771
- }
772
- this.#ngProgram.compiler.incrementalCompilation.recordSuccessfulEmit(sourceFile);
645
+ if (this._ngProgram) {
646
+ if (this._ngProgram.compiler.ignoreForEmit.has(sourceFile)) return;
647
+ this._ngProgram.compiler.incrementalCompilation.recordSuccessfulEmit(sourceFile);
773
648
  }
774
649
 
775
- const emitFileInfoCaches = this.#emittedFilesCacheMap.getOrCreate(PathUtils.norm(
776
- sourceFile.fileName), []);
777
-
778
- if (PathUtils.isChildPath(sourceFile.fileName, this.#pkgPath)) {
779
- let realFilePath = PathUtils.norm(fileName);
780
- let realText = text;
781
- if (
782
- PathUtils.isChildPath(
783
- realFilePath,
784
- path.resolve(this.#distPath, path.basename(this.#pkgPath), "src"),
785
- )
786
- ) {
787
- realFilePath = PathUtils.norm(
788
- this.#distPath,
789
- path.relative(
790
- path.resolve(this.#distPath, path.basename(this.#pkgPath), "src"),
791
- realFilePath,
792
- ),
793
- );
794
-
795
- if (fileName.endsWith(".js.map")) {
796
- const sourceMapContents = JSON.parse(realText);
797
- // remove "../../"
798
- sourceMapContents.sources[0] = sourceMapContents.sources[0].slice(6);
799
- realText = JSON.stringify(sourceMapContents);
800
- }
801
- }
650
+ const emitFileInfoCaches = this._emittedFilesCacheMap.getOrCreate(
651
+ PathUtils.norm(sourceFile.fileName),
652
+ [],
653
+ );
654
+
655
+ if (PathUtils.isChildPath(sourceFile.fileName, this._opt.pkgPath)) {
656
+ const real = this._convertOutputToReal(fileName, tsconf.distPath, text);
802
657
 
803
658
  emitFileInfoCaches.push({
804
- outAbsPath: realFilePath,
805
- text: realText,
659
+ outAbsPath: real.filePath,
660
+ text: real.text,
806
661
  });
807
662
  }
808
663
  else {
@@ -820,32 +675,32 @@ export class SdTsCompiler {
820
675
 
821
676
  //-- global style
822
677
  if (
823
- this.#globalStyleFilePath != null &&
824
- FsUtils.exists(this.#globalStyleFilePath) &&
825
- !this.#emittedFilesCacheMap.has(this.#globalStyleFilePath)
678
+ this._opt.globalStyleFilePath != null &&
679
+ FsUtils.exists(this._opt.globalStyleFilePath) &&
680
+ !this._emittedFilesCacheMap.has(this._opt.globalStyleFilePath)
826
681
  ) {
827
- this.#debug(`bundle global style...`);
682
+ this._debug(`전역 스타일 번들링 중...`);
828
683
 
829
- await this.#perf.run("bundle global style", async () => {
830
- const data = FsUtils.readFile(this.#globalStyleFilePath!);
831
- const contents = await this.#bundleStylesheetAsync(
684
+ await this._perf.run("전역 스타일 번들링", async () => {
685
+ const data = FsUtils.readFile(this._opt.globalStyleFilePath!);
686
+ const stylesheetBundlingResult = await this._bundleStylesheetAsync(
832
687
  data,
833
- this.#globalStyleFilePath!,
834
- this.#globalStyleFilePath,
688
+ this._opt.globalStyleFilePath!,
689
+ this._opt.globalStyleFilePath,
835
690
  );
836
- const emitFileInfos = this.#emittedFilesCacheMap.getOrCreate(
837
- this.#globalStyleFilePath!,
691
+ const emitFileInfos = this._emittedFilesCacheMap.getOrCreate(
692
+ this._opt.globalStyleFilePath!,
838
693
  [],
839
694
  );
840
695
  emitFileInfos.push({
841
696
  outAbsPath: PathUtils.norm(
842
- this.#pkgPath,
843
- path.relative(path.resolve(this.#pkgPath, "src"), this.#globalStyleFilePath!)
697
+ this._opt.pkgPath,
698
+ path.relative(path.resolve(this._opt.pkgPath, "src"), this._opt.globalStyleFilePath!)
844
699
  .replace(/\.scss$/, ".css"),
845
700
  ),
846
- text: contents,
701
+ text: stylesheetBundlingResult.contents ?? "",
847
702
  });
848
- emitFileSet.add(this.#globalStyleFilePath!);
703
+ emitFileSet.add(this._opt.globalStyleFilePath!);
849
704
  });
850
705
  }
851
706
 
@@ -855,215 +710,35 @@ export class SdTsCompiler {
855
710
  };
856
711
  }
857
712
 
858
- #debug(...msg: any[]): void {
859
- this.#logger.debug(`[${path.basename(this.#pkgPath)}]`, ...msg);
860
- }
861
-
862
- #findDeps(sf: ts.SourceFile) {
863
- const deps: ({
864
- fileName: TNormPath;
865
- importName: string;
866
- exportName?: string;
867
- } | ISdBuildMessage)[] = [];
868
-
869
- const tc = this.#program!.getTypeChecker();
870
-
871
- const visit = (node: ts.Node) => {
872
- if (ts.isExportDeclaration(node)) {
873
- if (node.moduleSpecifier) {
874
- const moduleSymbol = tc.getSymbolAtLocation(node.moduleSpecifier);
875
- if (!moduleSymbol) {
876
- const pos = ts.getLineAndCharacterOfPosition(sf, node.getStart());
877
- deps.push({
878
- filePath: PathUtils.norm(sf.fileName),
879
- line: pos.line,
880
- char: pos.character,
881
- code: undefined,
882
- severity: "error",
883
- message: "export moduleSymbol not found",
884
- type: "deps",
885
- });
886
- return;
887
- }
888
-
889
- const decls = moduleSymbol.getDeclarations();
890
- if (!decls) {
891
- const pos = ts.getLineAndCharacterOfPosition(sf, node.getStart());
892
- deps.push({
893
- filePath: PathUtils.norm(sf.fileName),
894
- line: pos.line,
895
- char: pos.character,
896
- code: undefined,
897
- severity: "error",
898
- message: "export decls not found",
899
- type: "deps",
900
- });
901
- return;
902
- }
903
-
904
- const namedBindings = node.exportClause;
905
- if (namedBindings && ts.isNamedExports(namedBindings)) {
906
- for (const el of namedBindings.elements) {
907
- for (const decl of decls) {
908
- deps.push({
909
- fileName: PathUtils.norm(decl.getSourceFile().fileName),
910
- importName: el.name.text,
911
- exportName: el.propertyName?.text ?? el.name.text,
912
- });
913
- }
914
- }
915
- }
916
- else {
917
- if (!moduleSymbol.exports) {
918
- const pos = ts.getLineAndCharacterOfPosition(sf, node.getStart());
919
- deps.push({
920
- filePath: PathUtils.norm(sf.fileName),
921
- line: pos.line,
922
- char: pos.character,
923
- code: undefined,
924
- severity: "error",
925
- message: "moduleSymbol exports not found",
926
- type: "deps",
927
- });
928
- return;
929
- }
930
-
931
- for (const decl of decls) {
932
- for (const key of moduleSymbol.exports.keys()) {
933
- deps.push({
934
- fileName: PathUtils.norm(decl.getSourceFile().fileName),
935
- importName: key.toString(),
936
- exportName: key.toString(),
937
- });
938
- }
939
- }
940
- }
941
- }
942
- }
943
- else if (ts.isImportDeclaration(node)) {
944
- const moduleSymbol = tc.getSymbolAtLocation(node.moduleSpecifier);
945
- if (!moduleSymbol) {
946
- if (ts.isStringLiteral(node.moduleSpecifier)
947
- && node.moduleSpecifier.text.startsWith("./")) {
948
- deps.push({
949
- fileName: PathUtils.norm(path.resolve(
950
- path.dirname(sf.fileName),
951
- node.moduleSpecifier.text + ".ts",
952
- )),
953
- importName: "*",
954
- });
955
-
956
- // const pos = ts.getLineAndCharacterOfPosition(sf, node.getStart());
957
- // deps.push({
958
- // filePath: PathUtil.norm(sf.fileName),
959
- // line: pos.line,
960
- // char: pos.character,
961
- // code: undefined,
962
- // severity: "error",
963
- // message: `import moduleSymbol not found (${node.moduleSpecifier.text})`,
964
- // type: "deps",
965
- // });
966
- // return;
967
- }
968
-
969
- /*else {
970
- throw new NeverEntryError(`import moduleSymbol: ${sf.fileName} ${node.moduleSpecifier["text"]}`);
971
- }*/
972
- }
973
- else {
974
- const decls = moduleSymbol.getDeclarations();
975
- if (!decls) {
976
- const pos = ts.getLineAndCharacterOfPosition(sf, node.getStart());
977
- deps.push({
978
- filePath: PathUtils.norm(sf.fileName),
979
- line: pos.line,
980
- char: pos.character,
981
- code: undefined,
982
- severity: "error",
983
- message: `import decls not found (${moduleSymbol.name})`,
984
- type: "deps",
985
- });
986
- return;
987
- }
713
+ private _convertOutputToReal(filePath: string, distPath: string, text: string) {
714
+ let realFilePath = PathUtils.norm(filePath);
715
+ let realText = text;
988
716
 
989
- const namedBindings = node.importClause?.namedBindings;
990
- if (namedBindings && ts.isNamedImports(namedBindings)) {
991
- for (const el of namedBindings.elements) {
992
- for (const decl of decls) {
993
- deps.push({
994
- fileName: PathUtils.norm(decl.getSourceFile().fileName),
995
- importName: el.name.text,
996
- });
997
- }
998
- }
999
- }
1000
- else {
1001
- for (const decl of decls) {
1002
- deps.push({
1003
- fileName: PathUtils.norm(decl.getSourceFile().fileName),
1004
- importName: "*",
1005
- });
1006
- }
1007
- }
1008
- }
1009
- }
717
+ const srcRelBasePath = path.resolve(distPath, path.basename(this._opt.pkgPath), "src");
1010
718
 
1011
- if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
1012
- if (ts.isStringLiteral(node.arguments[0]) && node.arguments[0].text.startsWith("./")) {
1013
-
1014
- const moduleSymbol = tc.getSymbolAtLocation(node.arguments[0]);
1015
- if (!moduleSymbol) {
1016
- deps.push({
1017
- fileName: PathUtils.norm(path.resolve(
1018
- path.dirname(sf.fileName),
1019
- node.arguments[0].text + ".ts",
1020
- )),
1021
- importName: "*",
1022
- });
1023
-
1024
- // const pos = ts.getLineAndCharacterOfPosition(sf, node.getStart());
1025
- // deps.push({
1026
- // filePath: PathUtil.norm(sf.fileName),
1027
- // line: pos.line,
1028
- // char: pos.character,
1029
- // code: undefined,
1030
- // severity: "error",
1031
- // message: `import() moduleSymbol not found (${node.arguments[0].text})`,
1032
- // type: "deps",
1033
- // });
1034
- // return;
1035
- }
1036
- else {
1037
- const decls = moduleSymbol.getDeclarations();
1038
- if (!decls) {
1039
- const pos = ts.getLineAndCharacterOfPosition(sf, node.getStart());
1040
- deps.push({
1041
- filePath: PathUtils.norm(sf.fileName),
1042
- line: pos.line,
1043
- char: pos.character,
1044
- code: undefined,
1045
- severity: "error",
1046
- message: `import() decls not found (${node.arguments[0].text})`,
1047
- type: "deps",
1048
- });
1049
- return;
1050
- }
719
+ if (PathUtils.isChildPath(realFilePath, srcRelBasePath)) {
720
+ realFilePath = PathUtils.norm(distPath, path.relative(srcRelBasePath, realFilePath));
1051
721
 
1052
- for (const decl of decls) {
1053
- deps.push({
1054
- fileName: PathUtils.norm(decl.getSourceFile().fileName),
1055
- importName: "*",
1056
- });
1057
- }
1058
- }
1059
- }
722
+ // source map 위치 정확히 찾아가기
723
+ if (filePath.endsWith(".js.map")) {
724
+ const sourceMapContents = JSON.parse(realText);
725
+ sourceMapContents.sources[0] = sourceMapContents.sources[0].slice(6); // remove "../../"
726
+ realText = JSON.stringify(sourceMapContents);
1060
727
  }
728
+ }
1061
729
 
1062
- node.forEachChild(visit);
1063
- };
1064
-
1065
- sf.forEachChild(visit);
730
+ return { filePath: realFilePath, text: realText };
731
+ }
1066
732
 
1067
- return deps;
733
+ private _debug(...msg: any[]): void {
734
+ this._logger.debug(`[${path.basename(this._opt.pkgPath)}]`, ...msg);
1068
735
  }
1069
736
  }
737
+
738
+
739
+ interface ITsConfigInfo {
740
+ fileNames: string[];
741
+ options: ts.CompilerOptions;
742
+ compilerHost: ts.CompilerHost;
743
+ distPath: string;
744
+ }