@simplysm/sd-cli 11.1.15 → 11.1.17

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 (50) hide show
  1. package/dist/build-tools/SdLinter.d.ts +2 -2
  2. package/dist/build-tools/SdLinter.js +8 -17
  3. package/dist/build-tools/SdLinter.js.map +1 -1
  4. package/dist/build-tools/SdNgBundler.d.ts +2 -2
  5. package/dist/build-tools/SdNgBundler.js +63 -45
  6. package/dist/build-tools/SdNgBundler.js.map +1 -1
  7. package/dist/build-tools/SdTsBundler.js +19 -4
  8. package/dist/build-tools/SdTsBundler.js.map +1 -1
  9. package/dist/builders/SdCliClientBuilder.d.ts +1 -0
  10. package/dist/builders/SdCliClientBuilder.js +16 -19
  11. package/dist/builders/SdCliClientBuilder.js.map +1 -1
  12. package/dist/builders/SdCliJsLibLinter.js +3 -3
  13. package/dist/builders/SdCliJsLibLinter.js.map +1 -1
  14. package/dist/builders/SdCliServerBuilder.d.ts +1 -5
  15. package/dist/builders/SdCliServerBuilder.js +37 -30
  16. package/dist/builders/SdCliServerBuilder.js.map +1 -1
  17. package/dist/builders/SdCliTsLibBuilder.js +1 -2
  18. package/dist/builders/SdCliTsLibBuilder.js.map +1 -1
  19. package/dist/bundle-plugins/sdLoadedFilesPlugin.d.ts +4 -0
  20. package/dist/bundle-plugins/sdLoadedFilesPlugin.js +15 -0
  21. package/dist/bundle-plugins/sdLoadedFilesPlugin.js.map +1 -0
  22. package/dist/bundle-plugins/sdNgPlugin.d.ts +13 -0
  23. package/dist/bundle-plugins/sdNgPlugin.js +272 -0
  24. package/dist/bundle-plugins/sdNgPlugin.js.map +1 -0
  25. package/dist/bundle-plugins/sdTscPlugin.d.ts +5 -0
  26. package/dist/bundle-plugins/sdTscPlugin.js +21 -0
  27. package/dist/bundle-plugins/sdTscPlugin.js.map +1 -0
  28. package/dist/commons.d.ts +1 -1
  29. package/dist/entry/SdCliProject.js +5 -1
  30. package/dist/entry/SdCliProject.js.map +1 -1
  31. package/dist/index.d.ts +3 -0
  32. package/dist/index.js +3 -0
  33. package/dist/index.js.map +1 -1
  34. package/dist/utils/SdCliBuildResultUtil.js +1 -1
  35. package/dist/utils/SdCliBuildResultUtil.js.map +1 -1
  36. package/package.json +17 -16
  37. package/src/build-tools/SdLinter.ts +10 -18
  38. package/src/build-tools/SdNgBundler.ts +68 -53
  39. package/src/build-tools/SdTsBundler.ts +24 -4
  40. package/src/builders/SdCliClientBuilder.ts +25 -24
  41. package/src/builders/SdCliJsLibLinter.ts +3 -3
  42. package/src/builders/SdCliServerBuilder.ts +40 -35
  43. package/src/builders/SdCliTsLibBuilder.ts +1 -3
  44. package/src/bundle-plugins/sdLoadedFilesPlugin.ts +19 -0
  45. package/src/bundle-plugins/sdNgPlugin.ts +429 -0
  46. package/src/bundle-plugins/sdTscPlugin.ts +25 -0
  47. package/src/commons.ts +1 -1
  48. package/src/entry/SdCliProject.ts +5 -2
  49. package/src/index.ts +3 -0
  50. package/src/utils/SdCliBuildResultUtil.ts +1 -1
@@ -0,0 +1,429 @@
1
+ import esbuild from "esbuild";
2
+ import {FsUtil} from "@simplysm/sd-core-node";
3
+ import ts from "typescript";
4
+ import path from "path";
5
+ import {convertTypeScriptDiagnostic} from "@angular-devkit/build-angular/src/tools/esbuild/angular/diagnostics";
6
+ import {AngularCompilerHost} from "@angular-devkit/build-angular/src/tools/esbuild/angular/angular-host";
7
+ import {
8
+ ComponentStylesheetBundler
9
+ } from "@angular-devkit/build-angular/src/tools/esbuild/angular/component-stylesheets";
10
+ import {transformSupportedBrowsersToTargets} from "@angular-devkit/build-angular/src/tools/esbuild/utils";
11
+ import browserslist from "browserslist";
12
+ import {StringUtil} from "@simplysm/sd-core-common";
13
+ import {NgtscProgram, OptimizeFor} from "@angular/compiler-cli";
14
+ import {createHash} from "crypto";
15
+ import {JavaScriptTransformer} from "@angular-devkit/build-angular/src/tools/esbuild/javascript-transformer";
16
+ import os from "os";
17
+
18
+ export function sdNgPlugin(conf: {
19
+ pkgPath: string;
20
+ dev: boolean;
21
+ modifiedFileSet: Set<string>;
22
+ result: INgResultCache;
23
+ }): esbuild.Plugin {
24
+ const tsConfigPath = path.resolve(conf.pkgPath, "tsconfig.json");
25
+ const tsConfig = FsUtil.readJson(tsConfigPath);
26
+ const parsedTsConfig = ts.parseJsonConfigFileContent(tsConfig, ts.sys, conf.pkgPath, {
27
+ ...tsConfig.angularCompilerOptions,
28
+ declaration: false
29
+ });
30
+
31
+ const sourceFileCache = new Map<string, ts.SourceFile>();
32
+ const referencingMap = new Map<string, Set<string>>();
33
+
34
+ let ngProgram: NgtscProgram | undefined;
35
+ let builder: ts.EmitAndSemanticDiagnosticsBuilderProgram | undefined;
36
+
37
+ let resultCache: IResultCache = {
38
+ watchFileSet: new Set<string>(),
39
+ affectedFileSet: new Set<string>(),
40
+ additionalResultMap: new Map<string, IAdditionalResult>()
41
+ };
42
+ const tscPrepareMap = new Map<string, string>();
43
+ const outputCacheMap = new Map<string, Uint8Array>();
44
+
45
+ let stylesheetBundler: ComponentStylesheetBundler | undefined;
46
+
47
+ function createCompilerHost() {
48
+ const compilerHost: AngularCompilerHost = ts.createIncrementalCompilerHost(parsedTsConfig.options);
49
+ compilerHost.readResource = (fileName: string) => {
50
+ return compilerHost.readFile(fileName) ?? "";
51
+ };
52
+
53
+ compilerHost.transformResource = async (data: string, context: {
54
+ type: string,
55
+ containingFile: string,
56
+ resourceFile: any
57
+ }) => {
58
+ if (context.type !== "style") {
59
+ return null;
60
+ }
61
+
62
+ const stylesheetResult = context.resourceFile != null
63
+ ? await stylesheetBundler!.bundleFile(context.resourceFile)
64
+ : await stylesheetBundler!.bundleInline(
65
+ data,
66
+ context.containingFile,
67
+ "scss",
68
+ );
69
+
70
+ resultCache.watchFileSet.add(path.normalize(context.containingFile));
71
+
72
+ if (stylesheetResult.referencedFiles) {
73
+ for (const referencedFile of stylesheetResult.referencedFiles) {
74
+ const referencingMapValSet = referencingMap.getOrCreate(path.normalize(referencedFile), new Set<string>());
75
+ referencingMapValSet.add(path.normalize(context.containingFile));
76
+ }
77
+
78
+ resultCache.watchFileSet.adds(...Array.from(stylesheetResult.referencedFiles.values()).map(item => path.normalize(item)));
79
+ }
80
+
81
+ resultCache.additionalResultMap.set(path.normalize(context.resourceFile ?? context.containingFile), {
82
+ outputFiles: stylesheetResult.resourceFiles,
83
+ metafile: stylesheetResult.metafile,
84
+ errors: stylesheetResult.errors,
85
+ warnings: stylesheetResult.warnings
86
+ });
87
+
88
+ return StringUtil.isNullOrEmpty(stylesheetResult.contents) ? null : {content: stylesheetResult.contents};
89
+ };
90
+
91
+ compilerHost.getModifiedResourceFiles = () => {
92
+ return conf.modifiedFileSet;
93
+ };
94
+
95
+ const baseGetSourceFile = compilerHost.getSourceFile;
96
+ compilerHost.getSourceFile = (fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile, ...args) => {
97
+ if (!shouldCreateNewSourceFile && sourceFileCache.has(path.normalize(fileName))) {
98
+ return sourceFileCache.get(path.normalize(fileName));
99
+ }
100
+
101
+ const file = baseGetSourceFile.call(
102
+ compilerHost,
103
+ fileName,
104
+ languageVersionOrOptions,
105
+ onError,
106
+ true,
107
+ ...args,
108
+ );
109
+
110
+ if (file) {
111
+ sourceFileCache.set(path.normalize(fileName), file);
112
+ }
113
+
114
+ return file;
115
+ };
116
+
117
+ return compilerHost;
118
+ }
119
+
120
+ function findAffectedFileSet() {
121
+ const affectedFileSet = new Set<string>();
122
+
123
+ while (true) {
124
+ const result = builder!.getSemanticDiagnosticsOfNextAffectedFile(undefined, (sourceFile) => {
125
+ if (ngProgram?.compiler.ignoreForDiagnostics.has(sourceFile) && sourceFile.fileName.endsWith('.ngtypecheck.ts')) {
126
+ const originalFilename = sourceFile.fileName.slice(0, -15) + '.ts';
127
+ const originalSourceFile = sourceFileCache.get(originalFilename);
128
+ if (originalSourceFile) {
129
+ affectedFileSet.add(path.normalize(originalSourceFile.fileName));
130
+ }
131
+
132
+ return true;
133
+ }
134
+
135
+ return false;
136
+ });
137
+
138
+ if (!result) {
139
+ break;
140
+ }
141
+
142
+ affectedFileSet.add(path.normalize((result.affected as ts.SourceFile).fileName));
143
+ }
144
+
145
+ return affectedFileSet;
146
+ }
147
+
148
+ return {
149
+ name: "sd-ng",
150
+ setup: (build: esbuild.PluginBuild) => {
151
+ //-- stylesheetBundler
152
+ const browserTarget = transformSupportedBrowsersToTargets(browserslist("defaults and fully supports es6-module"));
153
+ stylesheetBundler = new ComponentStylesheetBundler(
154
+ {
155
+ workspaceRoot: conf.pkgPath,
156
+ optimization: !conf.dev,
157
+ sourcemap: conf.dev ? 'inline' : false,
158
+ outputNames: {bundles: '[name]', media: 'media/[name]'},
159
+ includePaths: [],
160
+ externalDependencies: [],
161
+ target: browserTarget,
162
+ preserveSymlinks: false,
163
+ tailwindConfiguration: undefined
164
+ },
165
+ conf.dev
166
+ );
167
+
168
+ //-- compilerHost
169
+ const compilerHost = createCompilerHost();
170
+
171
+ //-- js babel transformer
172
+ const javascriptTransformer = new JavaScriptTransformer({
173
+ thirdPartySourcemaps: conf.dev,
174
+ sourcemap: conf.dev,
175
+ jit: false,
176
+ advancedOptimizations: true
177
+ }, os.cpus().length);
178
+
179
+ //-- vars
180
+
181
+ //---------------------------
182
+
183
+ build.onStart(async () => {
184
+ //-- modified
185
+ stylesheetBundler!.invalidate(conf.modifiedFileSet);
186
+ for (const modifiedFile of conf.modifiedFileSet) {
187
+ sourceFileCache.delete(modifiedFile);
188
+ outputCacheMap.delete(modifiedFile);
189
+
190
+ if (referencingMap.has(modifiedFile)) {
191
+ for (const referencingFile of referencingMap.get(modifiedFile)!) {
192
+ sourceFileCache.delete(referencingFile);
193
+ outputCacheMap.delete(modifiedFile);
194
+ }
195
+ }
196
+ }
197
+ referencingMap.clear();
198
+
199
+ //-- init resultCache
200
+
201
+ resultCache = {
202
+ watchFileSet: new Set<string>(),
203
+ affectedFileSet: new Set<string>(),
204
+ additionalResultMap: new Map<string, IAdditionalResult>()
205
+ };
206
+
207
+ //-- createBuilder
208
+
209
+ ngProgram = new NgtscProgram(
210
+ parsedTsConfig.fileNames,
211
+ parsedTsConfig.options,
212
+ compilerHost,
213
+ ngProgram
214
+ );
215
+ const ngCompiler = ngProgram.compiler;
216
+ const program = ngProgram.getTsProgram();
217
+
218
+ const baseGetSourceFiles = program.getSourceFiles;
219
+ program.getSourceFiles = function (...parameters) {
220
+ const files: readonly (ts.SourceFile & { version?: string })[] = baseGetSourceFiles(...parameters);
221
+
222
+ for (const file of files) {
223
+ if (file.version === undefined) {
224
+ file.version = createHash("sha256").update(file.text).digest("hex");
225
+ }
226
+ }
227
+
228
+ return files;
229
+ };
230
+
231
+ builder = ts.createEmitAndSemanticDiagnosticsBuilderProgram(
232
+ program,
233
+ compilerHost,
234
+ builder
235
+ );
236
+
237
+ await ngCompiler.analyzeAsync();
238
+
239
+ //-- affectedFilePathSet
240
+
241
+ resultCache.affectedFileSet.adds(...findAffectedFileSet());
242
+
243
+ // Deps -> refMap
244
+ builder.getSourceFiles().filter(sf => !ngCompiler.ignoreForEmit.has(sf))
245
+ .forEach(sf => {
246
+ resultCache.watchFileSet.add(path.normalize(sf.fileName));
247
+
248
+ const deps = ngCompiler.getResourceDependencies(sf);
249
+ for (const dep of deps) {
250
+ const ref = referencingMap.getOrCreate(dep, new Set<string>());
251
+ ref.add(dep);
252
+
253
+ resultCache.watchFileSet.add(path.normalize(dep));
254
+ }
255
+ });
256
+
257
+ // refMap, modFile -> affectedFileSet
258
+ for (const modifiedFile of conf.modifiedFileSet) {
259
+ resultCache.affectedFileSet.adds(...referencingMap.get(modifiedFile) ?? []);
260
+ }
261
+
262
+ //-- diagnostics / build
263
+
264
+ const diagnostics: ts.Diagnostic[] = [];
265
+
266
+ diagnostics.push(
267
+ ...builder.getConfigFileParsingDiagnostics(),
268
+ ...ngCompiler.getOptionDiagnostics(),
269
+ ...builder.getOptionsDiagnostics(),
270
+ ...builder.getGlobalDiagnostics()
271
+ );
272
+
273
+ for (const affectedFile of resultCache.affectedFileSet) {
274
+ const affectedSourceFile = sourceFileCache.get(path.normalize(affectedFile));
275
+ if (!affectedSourceFile || ngCompiler.ignoreForDiagnostics.has(affectedSourceFile)) {
276
+ continue;
277
+ }
278
+
279
+ diagnostics.push(
280
+ ...builder.getSyntacticDiagnostics(affectedSourceFile),
281
+ ...builder.getSemanticDiagnostics(affectedSourceFile)
282
+ );
283
+
284
+ if (affectedSourceFile.isDeclarationFile) {
285
+ continue;
286
+ }
287
+
288
+ diagnostics.push(
289
+ ...ngCompiler.getDiagnosticsForFile(affectedSourceFile, OptimizeFor.WholeProgram),
290
+ );
291
+ }
292
+
293
+ //-- prepare emit cache
294
+ while (true) {
295
+ const affectedFileResult = builder.emitNextAffectedFile((fileName, text, writeByteOrderMark, onError, sourceFiles, data) => {
296
+ if (!sourceFiles || sourceFiles.length === 0) {
297
+ compilerHost.writeFile(fileName, text, writeByteOrderMark, onError, sourceFiles, data);
298
+ return;
299
+ }
300
+
301
+ const sourceFile = ts.getOriginalNode(sourceFiles[0], ts.isSourceFile);
302
+ if (ngCompiler.ignoreForEmit.has(sourceFile)) {
303
+ return;
304
+ }
305
+
306
+ ngCompiler.incrementalCompilation.recordSuccessfulEmit(sourceFile);
307
+ tscPrepareMap.set(path.normalize(sourceFile.fileName), text);
308
+ }, undefined, undefined, ngProgram.compiler.prepareEmit().transformers);
309
+
310
+ if (!affectedFileResult) {
311
+ break;
312
+ }
313
+
314
+ diagnostics.push(...affectedFileResult.result.diagnostics);
315
+ }
316
+
317
+ //-- return err/warn
318
+
319
+ return {
320
+ errors: [
321
+ ...diagnostics.filter(item => item.category === ts.DiagnosticCategory.Error).map(item => convertTypeScriptDiagnostic(ts, item)),
322
+ ...Array.from(resultCache.additionalResultMap.values()).flatMap(item => item.errors)
323
+ ].filterExists(),
324
+ warnings: [
325
+ ...diagnostics.filter(item => item.category !== ts.DiagnosticCategory.Error).map(item => convertTypeScriptDiagnostic(ts, item)),
326
+ ...Array.from(resultCache.additionalResultMap.values()).flatMap(item => item.warnings)
327
+ ],
328
+ };
329
+ });
330
+
331
+ build.onLoad({filter: /\.ts$/}, async (args) => {
332
+ resultCache.watchFileSet.add(path.normalize(args.path));
333
+
334
+ const output = outputCacheMap.get(path.normalize(args.path));
335
+ if (output != null) {
336
+ return {contents: output, loader: "js"};
337
+ }
338
+
339
+ const contents = tscPrepareMap.get(path.normalize(args.path));
340
+
341
+ const {sideEffects} = await build.resolve(args.path, {
342
+ kind: 'import-statement',
343
+ resolveDir: build.initialOptions.absWorkingDir ?? '',
344
+ });
345
+
346
+ const newContents = await javascriptTransformer.transformData(
347
+ args.path,
348
+ contents!,
349
+ true,
350
+ sideEffects
351
+ );
352
+
353
+ outputCacheMap.set(path.normalize(args.path), newContents);
354
+
355
+ return {contents: newContents, loader: "js"};
356
+ });
357
+
358
+ build.onLoad(
359
+ {filter: /\.[cm]?js$/},
360
+ async (args) => {
361
+ resultCache.watchFileSet.add(path.normalize(args.path));
362
+
363
+ const output = outputCacheMap.get(path.normalize(args.path));
364
+ if (output != null) {
365
+ return {contents: output, loader: "js"};
366
+ }
367
+
368
+ const {sideEffects} = await build.resolve(args.path, {
369
+ kind: 'import-statement',
370
+ resolveDir: build.initialOptions.absWorkingDir ?? '',
371
+ });
372
+
373
+ // const contents = await FsUtil.readFileAsync(args.path);
374
+
375
+ const newContents = await javascriptTransformer.transformFile(
376
+ args.path,
377
+ false,
378
+ sideEffects
379
+ );
380
+
381
+ outputCacheMap.set(path.normalize(args.path), newContents);
382
+
383
+ return {
384
+ contents: newContents,
385
+ loader: 'js',
386
+ };
387
+ }
388
+ );
389
+
390
+ build.onEnd((result) => {
391
+ for (const {outputFiles, metafile} of resultCache.additionalResultMap.values()) {
392
+ result.outputFiles?.push(...outputFiles);
393
+
394
+ if (result.metafile && metafile) {
395
+ result.metafile.inputs = {...result.metafile.inputs, ...metafile.inputs};
396
+ result.metafile.outputs = {...result.metafile.outputs, ...metafile.outputs};
397
+ }
398
+ }
399
+
400
+ conf.result.watchFileSet = resultCache.watchFileSet;
401
+ conf.result.affectedFileSet = resultCache.affectedFileSet;
402
+ conf.result.outputFiles = result.outputFiles;
403
+ conf.result.metafile = result.metafile;
404
+
405
+ conf.modifiedFileSet.clear();
406
+ });
407
+ }
408
+ };
409
+ }
410
+
411
+ interface IResultCache {
412
+ watchFileSet: Set<string>;
413
+ affectedFileSet: Set<string>;
414
+ additionalResultMap: Map<string, IAdditionalResult>;
415
+ }
416
+
417
+ interface IAdditionalResult {
418
+ outputFiles: esbuild.OutputFile[];
419
+ metafile?: esbuild.Metafile;
420
+ errors?: esbuild.PartialMessage[];
421
+ warnings: esbuild.PartialMessage[];
422
+ }
423
+
424
+ export interface INgResultCache {
425
+ watchFileSet?: Set<string>;
426
+ affectedFileSet?: Set<string>;
427
+ outputFiles?: esbuild.OutputFile[];
428
+ metafile?: esbuild.Metafile;
429
+ }
@@ -0,0 +1,25 @@
1
+ import esbuild from "esbuild";
2
+ import {FsUtil} from "@simplysm/sd-core-node";
3
+ import ts from "typescript";
4
+ import path from "path";
5
+
6
+ export function sdTscPlugin(conf: {
7
+ parsedTsconfig: ts.ParsedCommandLine;
8
+ }): esbuild.Plugin {
9
+ return {
10
+ name: "sd-tsc",
11
+ setup: (build: esbuild.PluginBuild) => {
12
+ if (conf.parsedTsconfig.options.emitDecoratorMetadata) {
13
+ build.onLoad({filter: /\.ts$/}, async (args) => {
14
+
15
+ const fileContent = await FsUtil.readFileAsync(args.path);
16
+ const program = ts.transpileModule(fileContent, {
17
+ compilerOptions: conf.parsedTsconfig.options,
18
+ fileName: path.basename(args.path),
19
+ });
20
+ return {contents: program.outputText};
21
+ });
22
+ }
23
+ }
24
+ };
25
+ }
package/src/commons.ts CHANGED
@@ -49,7 +49,7 @@ export interface ISdCliPackageBuildResult {
49
49
  code: string | undefined;
50
50
  severity: "error" | "warning" | "suggestion" | "message";
51
51
  message: string;
52
- type: "build" | "lint" | "style" | undefined;
52
+ type: "build" | "lint" | "style" | "check" | undefined;
53
53
  }
54
54
 
55
55
  export interface ISdCliConfig {
@@ -485,16 +485,19 @@ export class SdCliProject {
485
485
  logger.info("모든 빌드가 완료되었습니다.");
486
486
  }
487
487
 
488
+ // TODO: npm:piscina??
488
489
  private static async _prepareClusterAsync(): Promise<cp.ChildProcess> {
489
490
  const logger = Logger.get(["simplysm", "sd-cli", "SdCliProject", "_runBuildClusterAsync"]);
490
-
491
491
  return await new Promise<cp.ChildProcess>(async (resolve, reject) => {
492
492
  const cluster = cp.fork(
493
493
  fileURLToPath(await import.meta.resolve!("../build-cluster")),
494
494
  [],
495
495
  {
496
496
  stdio: ["pipe", "pipe", "pipe", "ipc"],
497
- env: process.env
497
+ env: {
498
+ ...process.env,
499
+ // "NG_BUILD_PARALLEL_TS": "0"
500
+ }
498
501
  }
499
502
  );
500
503
 
package/src/index.ts CHANGED
@@ -10,6 +10,9 @@ export * from "./builders/SdCliClientBuilder";
10
10
  export * from "./builders/SdCliJsLibLinter";
11
11
  export * from "./builders/SdCliServerBuilder";
12
12
  export * from "./builders/SdCliTsLibBuilder";
13
+ export * from "./bundle-plugins/sdLoadedFilesPlugin";
14
+ export * from "./bundle-plugins/sdNgPlugin";
15
+ export * from "./bundle-plugins/sdTscPlugin";
13
16
  export * from "./commons";
14
17
  export * from "./entry/SdCliElectron";
15
18
  export * from "./entry/SdCliLocalUpdate";
@@ -25,7 +25,7 @@ export class SdCliBuildResultUtil {
25
25
  code,
26
26
  severity,
27
27
  message,
28
- type: "build" as const
28
+ type
29
29
  };
30
30
  }
31
31