@simplysm/sd-cli 11.1.45 → 11.1.51

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 (53) hide show
  1. package/dist/build-cluster.js +16 -3
  2. package/dist/build-cluster.js.map +1 -1
  3. package/dist/build-tools/SdLibBundler.d.ts +13 -0
  4. package/dist/build-tools/SdLibBundler.js +51 -0
  5. package/dist/build-tools/SdLibBundler.js.map +1 -0
  6. package/dist/build-tools/SdLinter.js +4 -4
  7. package/dist/build-tools/SdLinter.js.map +1 -1
  8. package/dist/build-tools/SdNgBundler.d.ts +11 -20
  9. package/dist/build-tools/SdNgBundler.js +103 -96
  10. package/dist/build-tools/SdNgBundler.js.map +1 -1
  11. package/dist/build-tools/SdServerBundler.js.map +1 -1
  12. package/dist/build-tools/SdTsCompiler.d.ts +24 -27
  13. package/dist/build-tools/SdTsCompiler.js +237 -184
  14. package/dist/build-tools/SdTsCompiler.js.map +1 -1
  15. package/dist/builders/SdCliTsLibBuilder.d.ts +2 -6
  16. package/dist/builders/SdCliTsLibBuilder.js +30 -30
  17. package/dist/builders/SdCliTsLibBuilder.js.map +1 -1
  18. package/dist/bundle-plugins/sdNgPlugin.d.ts +3 -3
  19. package/dist/bundle-plugins/sdNgPlugin.js +22 -212
  20. package/dist/bundle-plugins/sdNgPlugin.js.map +1 -1
  21. package/dist/bundle-plugins/sdServerPlugin.d.ts +2 -2
  22. package/dist/bundle-plugins/sdServerPlugin.js +17 -110
  23. package/dist/bundle-plugins/sdServerPlugin.js.map +1 -1
  24. package/dist/commons.d.ts +1 -0
  25. package/dist/entry/SdCliProject.d.ts +1 -0
  26. package/dist/entry/SdCliProject.js +14 -30
  27. package/dist/entry/SdCliProject.js.map +1 -1
  28. package/dist/index.d.ts +1 -0
  29. package/dist/index.js +1 -0
  30. package/dist/index.js.map +1 -1
  31. package/dist/sd-cli.d.ts +1 -1
  32. package/dist/sd-cli.js +8 -2
  33. package/dist/sd-cli.js.map +1 -1
  34. package/dist/server-worker.js.map +1 -1
  35. package/dist/utils/SdCliBuildResultUtil.d.ts +10 -0
  36. package/dist/utils/SdCliBuildResultUtil.js +17 -1
  37. package/dist/utils/SdCliBuildResultUtil.js.map +1 -1
  38. package/package.json +15 -15
  39. package/src/build-cluster.ts +18 -4
  40. package/src/build-tools/SdLibBundler.ts +70 -0
  41. package/src/build-tools/SdLinter.ts +4 -4
  42. package/src/build-tools/SdNgBundler.ts +120 -112
  43. package/src/build-tools/SdServerBundler.ts +2 -2
  44. package/src/build-tools/SdTsCompiler.ts +352 -241
  45. package/src/builders/SdCliTsLibBuilder.ts +33 -31
  46. package/src/bundle-plugins/sdNgPlugin.ts +26 -320
  47. package/src/bundle-plugins/sdServerPlugin.ts +20 -168
  48. package/src/commons.ts +1 -0
  49. package/src/entry/SdCliProject.ts +15 -31
  50. package/src/index.ts +1 -0
  51. package/src/sd-cli.ts +8 -2
  52. package/src/server-worker.ts +3 -3
  53. package/src/utils/SdCliBuildResultUtil.ts +22 -3
@@ -1,316 +1,427 @@
1
+ import ts, {CompilerOptions} from "typescript";
1
2
  import path from "path";
2
- import ts from "typescript";
3
- import {SdCliBuildResultUtil} from "../utils/SdCliBuildResultUtil";
4
- import {FsUtil, Logger, PathUtil} from "@simplysm/sd-core-node";
5
- import {ISdCliPackageBuildResult, ITsConfig} from "../commons";
3
+ import {FsUtil, PathUtil} from "@simplysm/sd-core-node";
4
+ import {transformSupportedBrowsersToTargets} from "@angular-devkit/build-angular/src/tools/esbuild/utils";
5
+ import browserslist from "browserslist";
6
+ import {
7
+ ComponentStylesheetBundler
8
+ } from "@angular-devkit/build-angular/src/tools/esbuild/angular/component-stylesheets";
9
+ import {AngularCompilerHost} from "@angular-devkit/build-angular/src/tools/esbuild/angular/angular-host";
10
+ import {StringUtil} from "@simplysm/sd-core-common";
11
+ import esbuild from "esbuild";
6
12
  import {NgtscProgram, OptimizeFor} from "@angular/compiler-cli";
7
13
  import {createHash} from "crypto";
8
- import {fileURLToPath, pathToFileURL} from "url";
9
- import * as sass from "sass";
10
14
 
11
15
  export class SdTsCompiler {
12
- private readonly _logger = Logger.get(["simplysm", "sd-cli", "SdTsCompiler"]);
13
-
14
- private readonly _parsedTsConfig: ts.ParsedCommandLine;
15
- private readonly _writeFileCache = new Map<string, string>();
16
- private readonly _compilerHost: ts.CompilerHost;
17
- private _program?: ts.Program;
18
- private _builder?: ts.EmitAndSemanticDiagnosticsBuilderProgram;
19
-
20
- //-- for ng
21
- private readonly _isForAngular: boolean;
22
- private _ngProgram?: NgtscProgram;
23
- private readonly _styleDepsCache = new Map<string, Set<string>>();
24
- private _markedChanges: string[] = [];
25
-
26
- public get program(): ts.Program {
27
- if (!this._program) {
28
- throw new Error("TS 프로그램 NULL");
29
- }
30
- return this._program;
31
- }
16
+ readonly #parsedTsconfig: ts.ParsedCommandLine;
17
+ readonly #isForAngular: boolean;
18
+
19
+ readonly #resourceDependencyCacheMap = new Map<string, Set<string>>();
20
+ readonly #sourceFileCacheMap = new Map<string, ts.SourceFile>();
21
+ readonly #emittedFilesCacheMap = new Map<string, {
22
+ outRelPath?: string;
23
+ text: string;
24
+ }[]>();
25
+
26
+ readonly #stylesheetBundler: ComponentStylesheetBundler | undefined;
27
+ readonly #compilerHost: ts.CompilerHost | AngularCompilerHost;
28
+
29
+ #ngProgram: NgtscProgram | undefined;
30
+ #program: ts.Program | undefined;
31
+ #builder: ts.EmitAndSemanticDiagnosticsBuilderProgram | undefined;
32
+
33
+ readonly #modifiedFileSet = new Set<string>();
34
+
35
+ readonly #watchFileSet = new Set<string>();
36
+ readonly #stylesheetResultMap = new Map<string, IStylesheetResult>();
37
+ readonly #affectedFileSet = new Set<string>();
38
+
39
+ readonly #pkgPath: string;
40
+ readonly #distPath: string;
41
+ readonly #globalStyleFilePath?: string;
32
42
 
33
- public constructor(private readonly _opt: {
34
- pkgPath: string,
35
- emit: boolean;
36
- emitDts: boolean;
37
- globalStyle: boolean;
38
- }) {
39
- //-- tsconfig
40
- const tsConfigFilePath = path.resolve(_opt.pkgPath, "tsconfig.json");
41
- const tsConfig = FsUtil.readJson(tsConfigFilePath) as ITsConfig;
42
- this._parsedTsConfig = ts.parseJsonConfigFileContent(
43
- tsConfig,
44
- ts.sys,
45
- _opt.pkgPath,
46
- {
47
- ...tsConfig.angularCompilerOptions ?? {},
48
- ..._opt.emitDts !== undefined ? {declaration: _opt.emitDts} : {}
43
+ constructor(pkgPath: string,
44
+ additionalOptions: CompilerOptions,
45
+ isDevMode: boolean,
46
+ globalStyleFilePath?: string) {
47
+ this.#pkgPath = pkgPath;
48
+ this.#globalStyleFilePath = globalStyleFilePath != null ? path.normalize(globalStyleFilePath) : undefined;
49
+
50
+ //-- isForAngular / parsedTsConfig
51
+
52
+ const tsconfigPath = path.resolve(pkgPath, "tsconfig.json");
53
+ const tsconfig = FsUtil.readJson(tsconfigPath);
54
+ this.#isForAngular = Boolean(tsconfig.angularCompilerOptions);
55
+ this.#parsedTsconfig = ts.parseJsonConfigFileContent(tsconfig, ts.sys, pkgPath, {
56
+ ...tsconfig.angularCompilerOptions,
57
+ ...additionalOptions
58
+ });
59
+
60
+ this.#distPath = this.#parsedTsconfig.options.outDir ?? path.resolve(pkgPath, "dist");
61
+
62
+ //-- compilerHost
63
+
64
+ this.#compilerHost = ts.createIncrementalCompilerHost(this.#parsedTsconfig.options);
65
+
66
+ const baseGetSourceFile = this.#compilerHost.getSourceFile;
67
+ this.#compilerHost.getSourceFile = (fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile, ...args) => {
68
+ if (!shouldCreateNewSourceFile && this.#sourceFileCacheMap.has(path.normalize(fileName))) {
69
+ return this.#sourceFileCacheMap.get(path.normalize(fileName));
49
70
  }
50
- );
51
71
 
52
- //-- vars
53
- this._isForAngular = Boolean(tsConfig.angularCompilerOptions);
72
+ const sf = baseGetSourceFile.call(
73
+ this.#compilerHost,
74
+ fileName,
75
+ languageVersionOrOptions,
76
+ onError,
77
+ true,
78
+ ...args,
79
+ );
80
+
81
+ if (sf) {
82
+ this.#sourceFileCacheMap.set(path.normalize(fileName), sf);
83
+ }
84
+ else {
85
+ this.#sourceFileCacheMap.delete(path.normalize(fileName));
86
+ }
87
+
88
+ return sf;
89
+ };
54
90
 
55
- //-- host
56
- this._compilerHost = ts.createIncrementalCompilerHost(this._parsedTsConfig.options);
57
- if (tsConfig.angularCompilerOptions) {
58
- this._compilerHost["readResource"] = (fileName: string) => {
59
- return this._compilerHost.readFile(fileName);
91
+ if (this.#isForAngular) {
92
+ //-- stylesheetBundler
93
+
94
+ const browserTarget = transformSupportedBrowsersToTargets(browserslist("defaults and fully supports es6-module"));
95
+ this.#stylesheetBundler = new ComponentStylesheetBundler(
96
+ {
97
+ workspaceRoot: pkgPath,
98
+ optimization: !isDevMode,
99
+ inlineFonts: true,
100
+ preserveSymlinks: false,
101
+ sourcemap: 'inline', //conf.dev ? 'inline' : false,
102
+ outputNames: {bundles: '[name]', media: 'media/[name]'},
103
+ includePaths: [],
104
+ externalDependencies: [],
105
+ target: browserTarget,
106
+ tailwindConfiguration: undefined,
107
+ cacheOptions: {
108
+ enabled: true,
109
+ path: ".cache/angular",
110
+ basePath: ".cache"
111
+ }
112
+ },
113
+ isDevMode
114
+ );
115
+
116
+ //-- compilerHost
117
+
118
+ (this.#compilerHost as AngularCompilerHost).readResource = (fileName: string) => {
119
+ return this.#compilerHost.readFile(fileName) ?? "";
60
120
  };
61
121
 
62
- this._compilerHost["transformResource"] = async (data: string, context: {
122
+ (this.#compilerHost as AngularCompilerHost).transformResource = async (data: string, context: {
63
123
  type: string,
64
124
  containingFile: string,
65
- resourceFile: any
125
+ resourceFile: string | null
66
126
  }) => {
67
- if (context.resourceFile != null || context.type !== "style") {
127
+ if (context.type !== "style") {
68
128
  return null;
69
129
  }
70
130
 
71
- try {
72
- const scssResult = await sass.compileStringAsync(data, {
73
- url: new URL((context.containingFile as string) + ".scss"),
74
- importer: {
75
- findFileUrl: (url) => pathToFileURL(url)
76
- },
77
- logger: sass.Logger.silent
78
- });
131
+ const contents = await this.#bundleStylesheetAsync(data, context.containingFile, context.resourceFile);
79
132
 
80
- const styleContent = scssResult.css.toString();
133
+ return StringUtil.isNullOrEmpty(contents) ? null : {content: contents};
134
+ };
81
135
 
82
- const deps = scssResult.loadedUrls.slice(1).map((item) => path.resolve(fileURLToPath(item.href)));
83
- for (const dep of deps) {
84
- const depCache = this._styleDepsCache.getOrCreate(dep, new Set<string>());
85
- depCache.add(path.resolve(context.containingFile));
86
- }
87
- return {content: styleContent};
88
- }
89
- catch (err) {
90
- this._logger.error("scss 파싱 에러", err);
91
- return null;
92
- }
136
+ (this.#compilerHost as AngularCompilerHost).getModifiedResourceFiles = () => {
137
+ return this.#modifiedFileSet;
93
138
  };
94
139
  }
95
140
  }
96
141
 
97
- public markChanges(changes: string[]): void {
98
- this._markedChanges.push(...changes);
142
+ async #bundleStylesheetAsync(data: string, containingFile: string, resourceFile: string | null = null) {
143
+ const stylesheetResult = resourceFile != null
144
+ ? await this.#stylesheetBundler!.bundleFile(resourceFile)
145
+ : await this.#stylesheetBundler!.bundleInline(
146
+ data,
147
+ containingFile,
148
+ "scss",
149
+ );
150
+
151
+ this.#watchFileSet.add(path.normalize(containingFile));
152
+ if (resourceFile != null) {
153
+ this.#watchFileSet.add(path.normalize(resourceFile));
154
+ }
155
+
156
+ if (stylesheetResult.referencedFiles) {
157
+ for (const referencedFile of stylesheetResult.referencedFiles) {
158
+ const referencingMapValSet = this.#resourceDependencyCacheMap.getOrCreate(path.normalize(referencedFile), new Set<string>());
159
+ referencingMapValSet.add(path.normalize(containingFile));
160
+ if (resourceFile != null) {
161
+ referencingMapValSet.add(path.normalize(resourceFile));
162
+ }
163
+ }
164
+
165
+ this.#watchFileSet.adds(...Array.from(stylesheetResult.referencedFiles.values()).map(item => path.normalize(item)));
166
+ }
167
+
168
+ this.#stylesheetResultMap.set(path.normalize(resourceFile ?? containingFile), {
169
+ outputFiles: stylesheetResult.outputFiles ?? [],
170
+ metafile: stylesheetResult.metafile,
171
+ errors: stylesheetResult.errors,
172
+ warnings: stylesheetResult.warnings
173
+ });
174
+
175
+ return stylesheetResult.contents;
99
176
  }
100
177
 
101
- public async buildAsync(): Promise<{
102
- watchFileSet: Set<string>;
103
- affectedFileSet: Set<string>;
104
- results: ISdCliPackageBuildResult[];
105
- }> {
106
- const markedChanges = this._markedChanges;
107
- this._markedChanges = [];
108
-
109
- const distPath = path.resolve(this._opt.pkgPath, "dist");
110
- const srcFilePaths = await FsUtil.globAsync(path.resolve(this._opt.pkgPath, "src/**/*.{ts,tsx}"));
111
- const srcFilePathSet = new Set<string>(srcFilePaths);
112
-
113
- if (this._isForAngular) {
114
- this._ngProgram = new NgtscProgram(
115
- srcFilePaths,
116
- this._parsedTsConfig.options,
117
- this._compilerHost,
118
- this._ngProgram
119
- );
120
- this._program = this._ngProgram.getTsProgram();
178
+ invalidate(modifiedFileSet: Set<string>) {
179
+ this.#stylesheetBundler?.invalidate(modifiedFileSet);
121
180
 
122
- const baseGetSourceFiles = this._program.getSourceFiles;
123
- this._program.getSourceFiles = function (...parameters) {
124
- const files: readonly (ts.SourceFile & { version?: string })[] = baseGetSourceFiles(...parameters);
181
+ for (const modifiedFile of modifiedFileSet) {
182
+ this.#stylesheetResultMap.delete(path.normalize(modifiedFile));
183
+ this.#sourceFileCacheMap.delete(path.normalize(modifiedFile));
184
+ this.#emittedFilesCacheMap.delete(path.normalize(modifiedFile));
125
185
 
126
- for (const file of files) {
127
- if (file.version === undefined) {
128
- file.version = createHash("sha256").update(file.text).digest("hex");
129
- }
186
+ if (this.#resourceDependencyCacheMap.has(path.normalize(modifiedFile))) {
187
+ for (const referencingFile of this.#resourceDependencyCacheMap.get(path.normalize(modifiedFile))!) {
188
+ this.#stylesheetResultMap.delete(path.normalize(referencingFile));
189
+ this.#sourceFileCacheMap.delete(path.normalize(referencingFile));
190
+ this.#emittedFilesCacheMap.delete(path.normalize(referencingFile));
130
191
  }
192
+ }
193
+ }
131
194
 
132
- return files;
133
- };
195
+ this.#modifiedFileSet.adds(...modifiedFileSet);
196
+ }
134
197
 
135
- this._builder = ts.createEmitAndSemanticDiagnosticsBuilderProgram(
136
- this._program,
137
- this._compilerHost,
138
- this._builder
198
+ async buildAsync(): Promise<ISdTsCompiler2Result> {
199
+ this.#resourceDependencyCacheMap.clear();
200
+ this.#watchFileSet.clear();
201
+ this.#stylesheetResultMap.clear();
202
+ this.#affectedFileSet.clear();
203
+
204
+ if (this.#isForAngular) {
205
+ this.#ngProgram = new NgtscProgram(
206
+ this.#parsedTsconfig.fileNames,
207
+ this.#parsedTsconfig.options,
208
+ this.#compilerHost,
209
+ this.#ngProgram
139
210
  );
211
+ this.#program = this.#ngProgram.getTsProgram();
140
212
  }
141
213
  else {
142
- /*this._program = ts.createProgram(
143
- srcFilePaths,
144
- this._parsedTsConfig.options,
145
- this._compilerHost,
146
- this._program
147
- );*/
148
-
149
- this._builder = ts.createIncrementalProgram({
150
- rootNames: srcFilePaths,
151
- host: this._compilerHost,
152
- options: this._parsedTsConfig.options,
153
- createProgram: ts.createEmitAndSemanticDiagnosticsBuilderProgram
154
- });
155
- this._program = this._builder.getProgram();
214
+ // noinspection UnnecessaryLocalVariableJS
215
+ this.#program = ts.createProgram(
216
+ this.#parsedTsconfig.fileNames,
217
+ this.#parsedTsconfig.options,
218
+ this.#compilerHost,
219
+ this.#program
220
+ );
156
221
  }
157
222
 
158
- const diagnostics: ts.Diagnostic[] = [];
159
- const affectedFileSet = new Set<string>();
223
+ const baseGetSourceFiles = this.#program.getSourceFiles;
224
+ this.#program.getSourceFiles = function (...parameters) {
225
+ const files: readonly (ts.SourceFile & { version?: string })[] = baseGetSourceFiles(...parameters);
160
226
 
161
- if (this._ngProgram) {
162
- diagnostics.push(...this._ngProgram.compiler.getOptionDiagnostics());
163
- }
227
+ for (const file of files) {
228
+ if (file.version === undefined) {
229
+ file.version = createHash("sha256").update(file.text).digest("hex");
230
+ }
231
+ }
164
232
 
165
- diagnostics.push(
166
- ...this._builder.getOptionsDiagnostics(),
167
- ...this._builder.getGlobalDiagnostics()
233
+ return files;
234
+ };
235
+
236
+ this.#builder = ts.createEmitAndSemanticDiagnosticsBuilderProgram(
237
+ this.#program,
238
+ this.#compilerHost,
239
+ this.#builder
168
240
  );
169
241
 
170
- if (this._ngProgram) {
171
- await this._ngProgram.compiler.analyzeAsync();
242
+ if (this.#ngProgram) {
243
+ await this.#ngProgram.compiler.analyzeAsync();
172
244
  }
173
245
 
174
- this._logger.debug(`[${path.basename(this._opt.pkgPath)}] 영향받는 파일 확인중...`);
246
+ //-- affectedFilePathSet
247
+
175
248
  while (true) {
176
- let affectedSourceFile: ts.SourceFile | undefined;
177
-
178
- const semanticResult = this._builder.getSemanticDiagnosticsOfNextAffectedFile(undefined, (sourceFile) => {
179
- //-- ngtypecheck의 org파일 포함 (ngtypecheck 파일는 무시)
180
- if (this._ngProgram?.compiler.ignoreForDiagnostics.has(sourceFile) && sourceFile.fileName.endsWith(".ngtypecheck.ts")) {
181
- const orgFileName = sourceFile.fileName.slice(0, -15) + ".ts";
182
- const orgSourceFile = this._builder!.getSourceFile(orgFileName);
183
- if (orgSourceFile) {
184
- affectedSourceFile = orgSourceFile;
249
+ const result = this.#builder.getSemanticDiagnosticsOfNextAffectedFile(undefined, (sourceFile) => {
250
+ if (
251
+ this.#ngProgram
252
+ && this.#ngProgram.compiler.ignoreForDiagnostics.has(sourceFile)
253
+ && sourceFile.fileName.endsWith('.ngtypecheck.ts')
254
+ ) {
255
+ const originalFilename = sourceFile.fileName.slice(0, -15) + '.ts';
256
+ const originalSourceFile = this.#sourceFileCacheMap.get(originalFilename);
257
+ if (originalSourceFile) {
258
+ this.#affectedFileSet.add(path.normalize(originalSourceFile.fileName));
185
259
  }
186
- return true;
187
- }
188
260
 
189
- //-- 소스폴더 파일 포함
190
- else if (srcFilePathSet.has(path.resolve(sourceFile.fileName))) {
191
- affectedSourceFile = sourceFile;
192
- return false;
193
- }
194
-
195
- //-- 나머지 무시
196
- else {
197
261
  return true;
198
262
  }
263
+
264
+ return false;
199
265
  });
200
- if (!semanticResult || !affectedSourceFile) break;
201
- diagnostics.push(...semanticResult.result);
202
266
 
203
- if ("fileName" in affectedSourceFile) {
204
- affectedFileSet.add(path.normalize(affectedSourceFile.fileName));
267
+ if (!result) {
268
+ break;
205
269
  }
206
- }
207
270
 
208
- if (this._isForAngular) {
209
- for (const markedChange of markedChanges) {
210
- const depsSet = this._styleDepsCache.get(markedChange);
211
- if (depsSet) {
212
- affectedFileSet.adds(...depsSet);
213
- }
214
- }
271
+ this.#affectedFileSet.add(path.normalize((result.affected as ts.SourceFile).fileName));
215
272
  }
216
273
 
217
- const globalStyleFilePath = path.resolve(this._opt.pkgPath, "src/styles.scss");
218
- if (this._opt.globalStyle && FsUtil.exists(globalStyleFilePath) && markedChanges.includes(globalStyleFilePath)) {
219
- affectedFileSet.add(globalStyleFilePath);
220
- }
274
+ // Deps -> refMap
221
275
 
222
- this._logger.debug(`[${path.basename(this._opt.pkgPath)}] 영향받는 파일 ${this._opt.emit ? "EMIT" : "CHECK"}...`);
223
-
224
- for (const affectedFilePath of affectedFileSet) {
225
- if (this._opt.globalStyle && affectedFilePath === globalStyleFilePath) {
226
- try {
227
- const content = await FsUtil.readFileAsync(affectedFilePath);
228
- const scssResult = await sass.compileStringAsync(content, {
229
- url: new URL(affectedFilePath),
230
- importer: {
231
- findFileUrl: (url) => pathToFileURL(url)
232
- },
233
- logger: sass.Logger.silent
234
- });
276
+ this.#builder.getSourceFiles().filter(sf => !this.#ngProgram || !this.#ngProgram.compiler.ignoreForEmit.has(sf))
277
+ .forEach(sf => {
278
+ this.#watchFileSet.add(path.normalize(sf.fileName));
235
279
 
236
- const deps = scssResult.loadedUrls.slice(1).map((item) => path.resolve(fileURLToPath(item.href)));
280
+ if (this.#ngProgram) {
281
+ const deps = this.#ngProgram.compiler.getResourceDependencies(sf);
237
282
  for (const dep of deps) {
238
- const depCache = this._styleDepsCache.getOrCreate(dep, new Set<string>());
239
- depCache.add(affectedFilePath);
240
- }
241
-
242
- if (this._opt.emit) {
243
- const outFilePath = path.resolve(this._opt.pkgPath, path.basename(affectedFilePath, path.extname(affectedFilePath)) + ".css");
283
+ const ref = this.#resourceDependencyCacheMap.getOrCreate(path.normalize(dep), new Set<string>());
284
+ ref.add(path.normalize(sf.fileName));
244
285
 
245
- this._writeFile(outFilePath, scssResult.css.toString());
286
+ this.#watchFileSet.add(path.normalize(dep));
246
287
  }
247
288
  }
248
- catch (err) {
249
- this._logger.error(err);
250
- }
289
+ });
290
+
291
+ //-- diagnostics
292
+
293
+ const diagnostics: ts.Diagnostic[] = [];
294
+
295
+ diagnostics.push(
296
+ ...this.#builder.getConfigFileParsingDiagnostics(),
297
+ ...this.#builder.getOptionsDiagnostics(),
298
+ ...this.#builder.getGlobalDiagnostics()
299
+ );
300
+
301
+ if (this.#ngProgram) {
302
+ diagnostics.push(...this.#ngProgram.compiler.getOptionDiagnostics());
303
+ }
304
+
305
+ for (const affectedFile of this.#affectedFileSet) {
306
+ const affectedSourceFile = this.#sourceFileCacheMap.get(affectedFile);
307
+ if (!affectedSourceFile || (this.#ngProgram && this.#ngProgram.compiler.ignoreForDiagnostics.has(affectedSourceFile))) {
308
+ continue;
251
309
  }
252
- else {
253
- const affectedSourceFile = this._builder.getSourceFile(affectedFilePath);
254
- if (!affectedSourceFile) continue;
255
-
256
- const emitResult = this._builder.emit(affectedSourceFile,
257
- (filePath, data) => {
258
- let realFilePath = filePath;
259
- let realData = data;
260
- if (PathUtil.isChildPath(realFilePath, path.resolve(distPath, path.basename(this._opt.pkgPath), "src"))) {
261
- realFilePath = path.resolve(distPath, path.relative(path.resolve(distPath, path.basename(this._opt.pkgPath), "src"), realFilePath));
262
-
263
- if (filePath.endsWith(".js.map")) {
264
- const sourceMapContents = JSON.parse(realData);
265
- // remove "../../"
266
- sourceMapContents.sources[0] = sourceMapContents.sources[0].slice(6);
267
- realData = JSON.stringify(sourceMapContents);
268
- }
269
- }
270
310
 
271
- this._ngProgram?.compiler.incrementalCompilation.recordSuccessfulEmit(affectedSourceFile);
272
- this._writeFile(realFilePath, realData);
273
- },
274
- undefined,
275
- !this._opt.emit,
276
- {...this._ngProgram?.compiler.prepareEmit().transformers ?? {}}
311
+ diagnostics.push(
312
+ ...this.#builder.getSyntacticDiagnostics(affectedSourceFile),
313
+ ...this.#builder.getSemanticDiagnostics(affectedSourceFile)
314
+ );
315
+
316
+ if (this.#ngProgram) {
317
+ if (affectedSourceFile.isDeclarationFile) {
318
+ continue;
319
+ }
320
+
321
+ diagnostics.push(
322
+ ...this.#ngProgram.compiler.getDiagnosticsForFile(affectedSourceFile, OptimizeFor.WholeProgram),
277
323
  );
324
+ }
325
+ }
278
326
 
279
- diagnostics.push(...emitResult.diagnostics);
327
+ //-- prepare emit cache
280
328
 
281
- diagnostics.push(...this._builder.getSyntacticDiagnostics(affectedSourceFile));
329
+ while (true) {
330
+ const affectedFileResult = this.#builder.emitNextAffectedFile((fileName, text, writeByteOrderMark, onError, sourceFiles, data) => {
331
+ if (!sourceFiles || sourceFiles.length === 0) {
332
+ this.#compilerHost.writeFile(fileName, text, writeByteOrderMark, onError, sourceFiles, data);
333
+ return;
334
+ }
282
335
 
283
- if (
284
- this._ngProgram &&
285
- !affectedSourceFile.isDeclarationFile &&
286
- !this._ngProgram.compiler.ignoreForEmit.has(affectedSourceFile) &&
287
- !this._ngProgram.compiler.incrementalCompilation.safeToSkipEmit(affectedSourceFile)
288
- ) {
289
- diagnostics.push(
290
- ...this._ngProgram.compiler.getDiagnosticsForFile(affectedSourceFile, OptimizeFor.WholeProgram)
291
- );
336
+ const sourceFile = ts.getOriginalNode(sourceFiles[0], ts.isSourceFile);
337
+
338
+ if (this.#ngProgram) {
339
+ if (this.#ngProgram.compiler.ignoreForEmit.has(sourceFile)) {
340
+ return;
341
+ }
342
+ this.#ngProgram.compiler.incrementalCompilation.recordSuccessfulEmit(sourceFile);
292
343
  }
344
+
345
+ const emittedFiles = this.#emittedFilesCacheMap.getOrCreate(path.normalize(sourceFile.fileName), []);
346
+ if (PathUtil.isChildPath(sourceFile.fileName, this.#pkgPath)) {
347
+ let realFilePath = fileName;
348
+ let realText = text;
349
+ if (PathUtil.isChildPath(realFilePath, path.resolve(this.#distPath, path.basename(this.#pkgPath), "src"))) {
350
+ realFilePath = path.resolve(this.#distPath, path.relative(path.resolve(this.#distPath, path.basename(this.#pkgPath), "src"), realFilePath));
351
+
352
+ if (fileName.endsWith(".js.map")) {
353
+ const sourceMapContents = JSON.parse(realText);
354
+ // remove "../../"
355
+ sourceMapContents.sources[0] = sourceMapContents.sources[0].slice(6);
356
+ realText = JSON.stringify(sourceMapContents);
357
+ }
358
+ }
359
+
360
+ emittedFiles.push({
361
+ outRelPath: path.relative(this.#distPath, realFilePath),
362
+ text: realText
363
+ });
364
+ }
365
+ else {
366
+ emittedFiles.push({text});
367
+ }
368
+ }, undefined, undefined, this.#ngProgram?.compiler.prepareEmit().transformers);
369
+
370
+ if (!affectedFileResult) {
371
+ break;
293
372
  }
373
+
374
+ diagnostics.push(...affectedFileResult.result.diagnostics);
375
+ }
376
+
377
+ //-- global style
378
+ if (
379
+ this.#globalStyleFilePath != null
380
+ && !this.#stylesheetResultMap.has(this.#globalStyleFilePath)
381
+ && FsUtil.exists(this.#globalStyleFilePath)
382
+ ) {
383
+ const data = await FsUtil.readFileAsync(this.#globalStyleFilePath);
384
+ const contents = await this.#bundleStylesheetAsync(data, this.#globalStyleFilePath, this.#globalStyleFilePath);
385
+ const emittedFiles = this.#emittedFilesCacheMap.getOrCreate(path.normalize(this.#globalStyleFilePath), []);
386
+ emittedFiles.push({
387
+ outRelPath: path.relative(path.resolve(this.#pkgPath, "src"), this.#globalStyleFilePath).replace(/\.scss$/, ".css"),
388
+ text: contents
389
+ });
390
+ this.#affectedFileSet.add(this.#globalStyleFilePath);
294
391
  }
295
392
 
296
- this._logger.debug(`[${path.basename(this._opt.pkgPath)}] 영향받는 파일 ${this._opt.emit ? "EMIT" : "CHECK"} 완료`, affectedFileSet);
393
+ //-- init
297
394
 
298
- const buildResults = diagnostics.map((item) => SdCliBuildResultUtil.convertFromTsDiag(item, this._opt.emit ? "build" : "check"));
395
+ this.#modifiedFileSet.clear();
396
+
397
+ //-- result
299
398
 
300
399
  return {
301
- watchFileSet: new Set([
302
- ...Array.from(this._styleDepsCache.keys()),
303
- ...this._builder.getSourceFiles().map(item => path.normalize(item.fileName))
304
- ]),
305
- affectedFileSet: affectedFileSet,
306
- results: buildResults
400
+ program: this.#program,
401
+ typescriptDiagnostics: diagnostics,
402
+ stylesheetResultMap: this.#stylesheetResultMap,
403
+ emittedFilesCacheMap: this.#emittedFilesCacheMap,
404
+ watchFileSet: this.#watchFileSet,
405
+ affectedFileSet: this.#affectedFileSet
307
406
  };
308
407
  }
408
+ }
309
409
 
310
- private _writeFile(filePath: string, data: string): void {
311
- if (this._writeFileCache.get(filePath) !== data) {
312
- this._compilerHost.writeFile(filePath, data, false);
313
- }
314
- this._writeFileCache.set(filePath, data);
315
- }
410
+ export interface ISdTsCompiler2Result {
411
+ program: ts.Program,
412
+ typescriptDiagnostics: ts.Diagnostic[],
413
+ stylesheetResultMap: Map<string, IStylesheetResult>,
414
+ emittedFilesCacheMap: Map<string, {
415
+ outRelPath?: string;
416
+ text: string;
417
+ }[]>,
418
+ watchFileSet: Set<string>,
419
+ affectedFileSet: Set<string>
316
420
  }
421
+
422
+ interface IStylesheetResult {
423
+ outputFiles: esbuild.OutputFile[];
424
+ metafile?: esbuild.Metafile;
425
+ errors?: esbuild.PartialMessage[];
426
+ warnings?: esbuild.PartialMessage[];
427
+ }