@simplysm/sd-cli 7.0.224 → 7.0.231
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.
- package/.eslintrc.cjs +21 -21
- package/dist/bin/sd-cli.mjs +2 -2
- package/dist/builder/SdCliClientBuilder.mjs +4 -3
- package/dist/builder/SdCliServerBuilder.mjs +2 -2
- package/dist/entry-points/SdCliNpm.mjs +2 -8
- package/dist/packages/SdCliPackage.mjs +2 -2
- package/package.json +37 -38
- package/src/bin/sd-cli.ts +274 -274
- package/src/build-tool/SdCliElectron.ts +81 -81
- package/src/builder/SdCliClientBuilder.ts +737 -736
- package/src/builder/SdCliServerBuilder.ts +483 -483
- package/src/commons.ts +138 -135
- package/src/entry-points/SdCliNpm.ts +19 -26
- package/src/packages/SdCliPackage.ts +242 -242
|
@@ -1,736 +1,737 @@
|
|
|
1
|
-
import { INpmConfig, ISdCliClientPackageConfig, ISdCliPackageBuildResult, ITsconfig } from "../commons";
|
|
2
|
-
import { EventEmitter } from "events";
|
|
3
|
-
import { FsUtil, Logger, PathUtil } from "@simplysm/sd-core-node";
|
|
4
|
-
import webpack from "webpack";
|
|
5
|
-
import path from "path";
|
|
6
|
-
import ts from "typescript";
|
|
7
|
-
import { SdCliBuildResultUtil } from "../utils/SdCliBuildResultUtil";
|
|
8
|
-
import { NamedChunksPlugin } from "@angular-devkit/build-angular/src/webpack/plugins/named-chunks-plugin";
|
|
9
|
-
import {
|
|
10
|
-
DedupeModuleResolvePlugin,
|
|
11
|
-
JavaScriptOptimizerPlugin,
|
|
12
|
-
SuppressExtractedTextChunksWebpackPlugin
|
|
13
|
-
} from "@angular-devkit/build-angular/src/webpack/plugins";
|
|
14
|
-
import CopyWebpackPlugin from "copy-webpack-plugin";
|
|
15
|
-
import MiniCssExtractPlugin from "mini-css-extract-plugin";
|
|
16
|
-
import { AngularWebpackPlugin } from "@ngtools/webpack";
|
|
17
|
-
import { IndexHtmlWebpackPlugin } from "@angular-devkit/build-angular/src/webpack/plugins/index-html-webpack-plugin";
|
|
18
|
-
import { SassWorkerImplementation } from "@angular-devkit/build-angular/src/sass/sass-service";
|
|
19
|
-
import { LicenseWebpackPlugin } from "license-webpack-plugin";
|
|
20
|
-
import NodePolyfillPlugin from "node-polyfill-webpack-plugin";
|
|
21
|
-
import { createHash } from "crypto";
|
|
22
|
-
import ESLintWebpackPlugin from "eslint-webpack-plugin";
|
|
23
|
-
import os from "os";
|
|
24
|
-
import { ESLint } from "eslint";
|
|
25
|
-
import { TransferSizePlugin } from "@angular-devkit/build-angular/src/webpack/plugins/transfer-size-plugin";
|
|
26
|
-
import { CssOptimizerPlugin } from "@angular-devkit/build-angular/src/webpack/plugins/css-optimizer-plugin";
|
|
27
|
-
import browserslist from "browserslist";
|
|
28
|
-
import { augmentAppWithServiceWorker } from "@angular-devkit/build-angular/src/utils/service-worker";
|
|
29
|
-
import { SdCliNgModuleGenerator } from "../ng-tools/SdCliNgModuleGenerator";
|
|
30
|
-
import { SdCliCordova } from "../build-tool/SdCliCordova";
|
|
31
|
-
import { SdCliNpmConfigUtil } from "../utils/SdCliNpmConfigUtil";
|
|
32
|
-
import electronBuilder from "electron-builder";
|
|
33
|
-
import LintResult = ESLint.LintResult;
|
|
34
|
-
|
|
35
|
-
export class SdCliClientBuilder extends EventEmitter {
|
|
36
|
-
private readonly _logger: Logger;
|
|
37
|
-
|
|
38
|
-
private readonly _tsconfigFilePath: string;
|
|
39
|
-
private readonly _parsedTsconfig: ts.ParsedCommandLine;
|
|
40
|
-
private readonly _npmConfigMap = new Map<string, INpmConfig>();
|
|
41
|
-
private readonly _ngModuleGenerator: SdCliNgModuleGenerator;
|
|
42
|
-
|
|
43
|
-
private readonly _cordova?: SdCliCordova;
|
|
44
|
-
|
|
45
|
-
private readonly _hasAngularRoute: boolean;
|
|
46
|
-
|
|
47
|
-
public constructor(private readonly _rootPath: string,
|
|
48
|
-
private readonly _config: ISdCliClientPackageConfig,
|
|
49
|
-
private readonly _workspaceRootPath: string) {
|
|
50
|
-
super();
|
|
51
|
-
|
|
52
|
-
const npmConfig = this._getNpmConfig(this._rootPath)!;
|
|
53
|
-
this._logger = Logger.get(["simplysm", "sd-cli", this.constructor.name, npmConfig.name]);
|
|
54
|
-
|
|
55
|
-
// tsconfig
|
|
56
|
-
this._tsconfigFilePath = path.resolve(this._rootPath, "tsconfig-build.json");
|
|
57
|
-
const tsconfig = FsUtil.readJson(this._tsconfigFilePath) as ITsconfig;
|
|
58
|
-
this._parsedTsconfig = ts.parseJsonConfigFileContent(tsconfig, ts.sys, this._rootPath, tsconfig.angularCompilerOptions);
|
|
59
|
-
|
|
60
|
-
// isAngular
|
|
61
|
-
this._hasAngularRoute = SdCliNpmConfigUtil.getDependencies(npmConfig).defaults.includes("@angular/router");
|
|
62
|
-
|
|
63
|
-
// NgModule 생성기 초기화
|
|
64
|
-
this._ngModuleGenerator = new SdCliNgModuleGenerator(this._rootPath, [
|
|
65
|
-
"controls",
|
|
66
|
-
"directives",
|
|
67
|
-
"guards",
|
|
68
|
-
"modals",
|
|
69
|
-
"providers",
|
|
70
|
-
"app",
|
|
71
|
-
"pages",
|
|
72
|
-
"print-templates",
|
|
73
|
-
"toasts",
|
|
74
|
-
"AppPage"
|
|
75
|
-
], this._hasAngularRoute ? {
|
|
76
|
-
glob: "**/*Page.ts",
|
|
77
|
-
fileEndsWith: "Page",
|
|
78
|
-
rootClassName: "AppPage"
|
|
79
|
-
} : undefined);
|
|
80
|
-
|
|
81
|
-
// CORDOVA
|
|
82
|
-
if (this._config.builder?.cordova) {
|
|
83
|
-
this._cordova = new SdCliCordova(this._rootPath, this._config.builder.cordova);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
public override on(event: "change", listener: () => void): this;
|
|
88
|
-
public override on(event: "complete", listener: (results: ISdCliPackageBuildResult[]) => void): this;
|
|
89
|
-
public override on(event: string | symbol, listener: (...args: any[]) => void): this {
|
|
90
|
-
return super.on(event, listener);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
public async watchAsync(): Promise<void> {
|
|
94
|
-
// DIST 비우기
|
|
95
|
-
await FsUtil.removeAsync(path.resolve(this._rootPath, ".electron"));
|
|
96
|
-
await FsUtil.removeAsync(this._parsedTsconfig.options.outDir!);
|
|
97
|
-
|
|
98
|
-
// NgModule 생성
|
|
99
|
-
await this._ngModuleGenerator.runAsync();
|
|
100
|
-
|
|
101
|
-
// CORDOVA 초기화
|
|
102
|
-
if (this._cordova) {
|
|
103
|
-
this._logger.debug("CORDOVA 구성...");
|
|
104
|
-
await this._cordova.initializeAsync();
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// 빌드 준비
|
|
108
|
-
const webpackConfigs = (Object.keys(this._config.builder ?? { web: {} }) as ("web" | "cordova" | "electron")[])
|
|
109
|
-
.map((builderType) => this._getWebpackConfig(true, builderType));
|
|
110
|
-
const multiCompiler = webpack(webpackConfigs);
|
|
111
|
-
await new Promise<void>((resolve, reject) => {
|
|
112
|
-
multiCompiler.hooks.invalid.tap(this.constructor.name, (fileName) => {
|
|
113
|
-
if (fileName != null) {
|
|
114
|
-
this._logger.debug("파일변경 감지", fileName);
|
|
115
|
-
// NgModule 캐시 삭제
|
|
116
|
-
this._ngModuleGenerator.removeCaches([path.resolve(fileName)]);
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
multiCompiler.hooks.watchRun.tapAsync(this.constructor.name, async (args, callback) => {
|
|
121
|
-
this.emit("change");
|
|
122
|
-
|
|
123
|
-
// NgModule 생성
|
|
124
|
-
await this._ngModuleGenerator.runAsync();
|
|
125
|
-
|
|
126
|
-
callback();
|
|
127
|
-
|
|
128
|
-
this._logger.debug("Webpack 빌드 수행...");
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
multiCompiler.watch({}, async (err, multiStats) => {
|
|
132
|
-
if (err != null || multiStats == null) {
|
|
133
|
-
this.emit("complete", [{
|
|
134
|
-
filePath: undefined,
|
|
135
|
-
line: undefined,
|
|
136
|
-
char: undefined,
|
|
137
|
-
code: undefined,
|
|
138
|
-
severity: "error",
|
|
139
|
-
message: err?.stack ?? "알 수 없는 오류 (multiStats=null)"
|
|
140
|
-
}]);
|
|
141
|
-
reject(err);
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// 결과 반환
|
|
146
|
-
const results = multiStats.stats.mapMany((stats) => SdCliBuildResultUtil.convertFromWebpackStats(stats));
|
|
147
|
-
|
|
148
|
-
// .config.json 파일 쓰기
|
|
149
|
-
const npmConfig = this._getNpmConfig(this._rootPath)!;
|
|
150
|
-
const packageKey = npmConfig.name.split("/").last()!;
|
|
151
|
-
|
|
152
|
-
const configDistPath = typeof this._config.server === "string"
|
|
153
|
-
? path.resolve(this._workspaceRootPath, "packages", this._config.server, "dist/www", packageKey, ".config.json")
|
|
154
|
-
: path.resolve(this._parsedTsconfig.options.outDir!, ".config.json");
|
|
155
|
-
await FsUtil.writeFileAsync(configDistPath, JSON.stringify(this._config.configs ?? {}, undefined, 2));
|
|
156
|
-
|
|
157
|
-
// 마무리
|
|
158
|
-
this._logger.debug("Webpack 빌드 완료");
|
|
159
|
-
resolve();
|
|
160
|
-
|
|
161
|
-
this.emit("complete", results);
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
public async buildAsync(): Promise<ISdCliPackageBuildResult[]> {
|
|
167
|
-
// DIST 비우기
|
|
168
|
-
await FsUtil.removeAsync(path.resolve(this._rootPath, ".electron"));
|
|
169
|
-
await FsUtil.removeAsync(this._parsedTsconfig.options.outDir!);
|
|
170
|
-
|
|
171
|
-
// NgModule 생성
|
|
172
|
-
await this._ngModuleGenerator.runAsync();
|
|
173
|
-
|
|
174
|
-
// CORDOVA 초기화
|
|
175
|
-
if (this._cordova) {
|
|
176
|
-
this._logger.debug("CORDOVA 구성...");
|
|
177
|
-
await this._cordova.initializeAsync();
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// 빌드
|
|
181
|
-
this._logger.debug("Webpack 빌드 수행...");
|
|
182
|
-
const builderTypes = (Object.keys(this._config.builder ?? { web: {} }) as ("web" | "cordova" | "electron")[]);
|
|
183
|
-
const webpackConfigs = builderTypes.map((builderType) => this._getWebpackConfig(false, builderType));
|
|
184
|
-
const multipleCompiler = webpack(webpackConfigs);
|
|
185
|
-
const buildResults = await new Promise<ISdCliPackageBuildResult[]>((resolve, reject) => {
|
|
186
|
-
multipleCompiler.run((err, multiStats) => {
|
|
187
|
-
if (err != null || multiStats == null) {
|
|
188
|
-
reject(err);
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// 결과 반환
|
|
193
|
-
const results = multiStats.stats.mapMany((stats) => SdCliBuildResultUtil.convertFromWebpackStats(stats));
|
|
194
|
-
resolve(results);
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
// .config.json 파일 쓰기
|
|
199
|
-
const targetPath = path.resolve(this._parsedTsconfig.options.outDir!, ".config.json");
|
|
200
|
-
await FsUtil.writeFileAsync(targetPath, JSON.stringify(this._config.configs ?? {}, undefined, 2));
|
|
201
|
-
|
|
202
|
-
// service-worker 처리
|
|
203
|
-
if (builderTypes.includes("web") && FsUtil.exists(path.resolve(this._rootPath, "ngsw-config.json"))) {
|
|
204
|
-
const packageKey = this._getNpmConfig(this._rootPath)!.name.split("/").last()!;
|
|
205
|
-
await augmentAppWithServiceWorker(
|
|
206
|
-
PathUtil.posix(path.relative(this._workspaceRootPath, this._rootPath)) as any,
|
|
207
|
-
PathUtil.posix(path.relative(this._workspaceRootPath, path.resolve(this._parsedTsconfig.options.outDir!))) as any,
|
|
208
|
-
`/${packageKey}/`,
|
|
209
|
-
PathUtil.posix(path.relative(this._workspaceRootPath, path.resolve(this._rootPath, "ngsw-config.json")))
|
|
210
|
-
);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// CORDOVA 빌드
|
|
214
|
-
if (this._cordova) {
|
|
215
|
-
this._logger.debug("CORDOVA 빌드...");
|
|
216
|
-
await this._cordova.buildAsync(path.resolve(this._parsedTsconfig.options.outDir!, "cordova"));
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// ELECTRON
|
|
220
|
-
if (this._config.builder?.electron) {
|
|
221
|
-
const npmConfig = this._getNpmConfig(this._rootPath)!;
|
|
222
|
-
|
|
223
|
-
const electronVersion = npmConfig.dependencies?.["electron"];
|
|
224
|
-
if (electronVersion === undefined) {
|
|
225
|
-
throw new Error("ELECTRON 빌드 패키지의 'dependencies'에는 'electron'이 반드시 포함되어야 합니다.");
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
const dotenvVersion = npmConfig.dependencies?.["dotenv"];
|
|
229
|
-
if (dotenvVersion === undefined) {
|
|
230
|
-
throw new Error("ELECTRON 빌드 패키지의 'dependencies'에는 'dotenv'가 반드시 포함되어야 합니다.");
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
const remoteVersion = npmConfig.dependencies?.["@electron/remote"];
|
|
234
|
-
|
|
235
|
-
const electronSrcPath = path.resolve(this._rootPath, `.electron/src`);
|
|
236
|
-
const electronDistPath = path.resolve(this._rootPath, `.electron/dist`);
|
|
237
|
-
|
|
238
|
-
await FsUtil.writeJsonAsync(path.resolve(electronSrcPath, `package.json`), {
|
|
239
|
-
name: npmConfig.name,
|
|
240
|
-
version: npmConfig.version,
|
|
241
|
-
description: npmConfig.description,
|
|
242
|
-
main: "electron.js",
|
|
243
|
-
author: npmConfig.author,
|
|
244
|
-
license: npmConfig.license,
|
|
245
|
-
devDependencies: {
|
|
246
|
-
"electron": electronVersion.replace("^", "")
|
|
247
|
-
},
|
|
248
|
-
dependencies: {
|
|
249
|
-
"dotenv": dotenvVersion,
|
|
250
|
-
...remoteVersion !== undefined ? {
|
|
251
|
-
"@electron/remote": remoteVersion
|
|
252
|
-
} : {}
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
electronTsFileContent =
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
path.resolve(this.
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
path.resolve(this.
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
...FsUtil.findAllParentChildDirPaths(
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
const
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
...FsUtil.exists(
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
/
|
|
382
|
-
/
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
.update(
|
|
395
|
-
.update(JSON.stringify(this.
|
|
396
|
-
.update(
|
|
397
|
-
.
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
new
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
"last
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
||
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
new
|
|
573
|
-
new
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
const
|
|
577
|
-
const
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
"
|
|
606
|
-
"
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
new
|
|
668
|
-
new
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
["
|
|
675
|
-
["
|
|
676
|
-
["
|
|
677
|
-
["
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
}
|
|
1
|
+
import { INpmConfig, ISdCliClientPackageConfig, ISdCliPackageBuildResult, ITsconfig } from "../commons";
|
|
2
|
+
import { EventEmitter } from "events";
|
|
3
|
+
import { FsUtil, Logger, PathUtil } from "@simplysm/sd-core-node";
|
|
4
|
+
import webpack from "webpack";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import ts from "typescript";
|
|
7
|
+
import { SdCliBuildResultUtil } from "../utils/SdCliBuildResultUtil";
|
|
8
|
+
import { NamedChunksPlugin } from "@angular-devkit/build-angular/src/webpack/plugins/named-chunks-plugin";
|
|
9
|
+
import {
|
|
10
|
+
DedupeModuleResolvePlugin,
|
|
11
|
+
JavaScriptOptimizerPlugin,
|
|
12
|
+
SuppressExtractedTextChunksWebpackPlugin
|
|
13
|
+
} from "@angular-devkit/build-angular/src/webpack/plugins";
|
|
14
|
+
import CopyWebpackPlugin from "copy-webpack-plugin";
|
|
15
|
+
import MiniCssExtractPlugin from "mini-css-extract-plugin";
|
|
16
|
+
import { AngularWebpackPlugin } from "@ngtools/webpack";
|
|
17
|
+
import { IndexHtmlWebpackPlugin } from "@angular-devkit/build-angular/src/webpack/plugins/index-html-webpack-plugin";
|
|
18
|
+
import { SassWorkerImplementation } from "@angular-devkit/build-angular/src/sass/sass-service";
|
|
19
|
+
import { LicenseWebpackPlugin } from "license-webpack-plugin";
|
|
20
|
+
import NodePolyfillPlugin from "node-polyfill-webpack-plugin";
|
|
21
|
+
import { createHash } from "crypto";
|
|
22
|
+
import ESLintWebpackPlugin from "eslint-webpack-plugin";
|
|
23
|
+
import os from "os";
|
|
24
|
+
import { ESLint } from "eslint";
|
|
25
|
+
import { TransferSizePlugin } from "@angular-devkit/build-angular/src/webpack/plugins/transfer-size-plugin";
|
|
26
|
+
import { CssOptimizerPlugin } from "@angular-devkit/build-angular/src/webpack/plugins/css-optimizer-plugin";
|
|
27
|
+
import browserslist from "browserslist";
|
|
28
|
+
import { augmentAppWithServiceWorker } from "@angular-devkit/build-angular/src/utils/service-worker";
|
|
29
|
+
import { SdCliNgModuleGenerator } from "../ng-tools/SdCliNgModuleGenerator";
|
|
30
|
+
import { SdCliCordova } from "../build-tool/SdCliCordova";
|
|
31
|
+
import { SdCliNpmConfigUtil } from "../utils/SdCliNpmConfigUtil";
|
|
32
|
+
import electronBuilder from "electron-builder";
|
|
33
|
+
import LintResult = ESLint.LintResult;
|
|
34
|
+
|
|
35
|
+
export class SdCliClientBuilder extends EventEmitter {
|
|
36
|
+
private readonly _logger: Logger;
|
|
37
|
+
|
|
38
|
+
private readonly _tsconfigFilePath: string;
|
|
39
|
+
private readonly _parsedTsconfig: ts.ParsedCommandLine;
|
|
40
|
+
private readonly _npmConfigMap = new Map<string, INpmConfig>();
|
|
41
|
+
private readonly _ngModuleGenerator: SdCliNgModuleGenerator;
|
|
42
|
+
|
|
43
|
+
private readonly _cordova?: SdCliCordova;
|
|
44
|
+
|
|
45
|
+
private readonly _hasAngularRoute: boolean;
|
|
46
|
+
|
|
47
|
+
public constructor(private readonly _rootPath: string,
|
|
48
|
+
private readonly _config: ISdCliClientPackageConfig,
|
|
49
|
+
private readonly _workspaceRootPath: string) {
|
|
50
|
+
super();
|
|
51
|
+
|
|
52
|
+
const npmConfig = this._getNpmConfig(this._rootPath)!;
|
|
53
|
+
this._logger = Logger.get(["simplysm", "sd-cli", this.constructor.name, npmConfig.name]);
|
|
54
|
+
|
|
55
|
+
// tsconfig
|
|
56
|
+
this._tsconfigFilePath = path.resolve(this._rootPath, "tsconfig-build.json");
|
|
57
|
+
const tsconfig = FsUtil.readJson(this._tsconfigFilePath) as ITsconfig;
|
|
58
|
+
this._parsedTsconfig = ts.parseJsonConfigFileContent(tsconfig, ts.sys, this._rootPath, tsconfig.angularCompilerOptions);
|
|
59
|
+
|
|
60
|
+
// isAngular
|
|
61
|
+
this._hasAngularRoute = SdCliNpmConfigUtil.getDependencies(npmConfig).defaults.includes("@angular/router");
|
|
62
|
+
|
|
63
|
+
// NgModule 생성기 초기화
|
|
64
|
+
this._ngModuleGenerator = new SdCliNgModuleGenerator(this._rootPath, [
|
|
65
|
+
"controls",
|
|
66
|
+
"directives",
|
|
67
|
+
"guards",
|
|
68
|
+
"modals",
|
|
69
|
+
"providers",
|
|
70
|
+
"app",
|
|
71
|
+
"pages",
|
|
72
|
+
"print-templates",
|
|
73
|
+
"toasts",
|
|
74
|
+
"AppPage"
|
|
75
|
+
], this._hasAngularRoute ? {
|
|
76
|
+
glob: "**/*Page.ts",
|
|
77
|
+
fileEndsWith: "Page",
|
|
78
|
+
rootClassName: "AppPage"
|
|
79
|
+
} : undefined);
|
|
80
|
+
|
|
81
|
+
// CORDOVA
|
|
82
|
+
if (this._config.builder?.cordova) {
|
|
83
|
+
this._cordova = new SdCliCordova(this._rootPath, this._config.builder.cordova);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public override on(event: "change", listener: () => void): this;
|
|
88
|
+
public override on(event: "complete", listener: (results: ISdCliPackageBuildResult[]) => void): this;
|
|
89
|
+
public override on(event: string | symbol, listener: (...args: any[]) => void): this {
|
|
90
|
+
return super.on(event, listener);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public async watchAsync(): Promise<void> {
|
|
94
|
+
// DIST 비우기
|
|
95
|
+
await FsUtil.removeAsync(path.resolve(this._rootPath, ".electron"));
|
|
96
|
+
await FsUtil.removeAsync(this._parsedTsconfig.options.outDir!);
|
|
97
|
+
|
|
98
|
+
// NgModule 생성
|
|
99
|
+
await this._ngModuleGenerator.runAsync();
|
|
100
|
+
|
|
101
|
+
// CORDOVA 초기화
|
|
102
|
+
if (this._cordova) {
|
|
103
|
+
this._logger.debug("CORDOVA 구성...");
|
|
104
|
+
await this._cordova.initializeAsync();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 빌드 준비
|
|
108
|
+
const webpackConfigs = (Object.keys(this._config.builder ?? { web: {} }) as ("web" | "cordova" | "electron")[])
|
|
109
|
+
.map((builderType) => this._getWebpackConfig(true, builderType));
|
|
110
|
+
const multiCompiler = webpack(webpackConfigs);
|
|
111
|
+
await new Promise<void>((resolve, reject) => {
|
|
112
|
+
multiCompiler.hooks.invalid.tap(this.constructor.name, (fileName) => {
|
|
113
|
+
if (fileName != null) {
|
|
114
|
+
this._logger.debug("파일변경 감지", fileName);
|
|
115
|
+
// NgModule 캐시 삭제
|
|
116
|
+
this._ngModuleGenerator.removeCaches([path.resolve(fileName)]);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
multiCompiler.hooks.watchRun.tapAsync(this.constructor.name, async (args, callback) => {
|
|
121
|
+
this.emit("change");
|
|
122
|
+
|
|
123
|
+
// NgModule 생성
|
|
124
|
+
await this._ngModuleGenerator.runAsync();
|
|
125
|
+
|
|
126
|
+
callback();
|
|
127
|
+
|
|
128
|
+
this._logger.debug("Webpack 빌드 수행...");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
multiCompiler.watch({}, async (err, multiStats) => {
|
|
132
|
+
if (err != null || multiStats == null) {
|
|
133
|
+
this.emit("complete", [{
|
|
134
|
+
filePath: undefined,
|
|
135
|
+
line: undefined,
|
|
136
|
+
char: undefined,
|
|
137
|
+
code: undefined,
|
|
138
|
+
severity: "error",
|
|
139
|
+
message: err?.stack ?? "알 수 없는 오류 (multiStats=null)"
|
|
140
|
+
}]);
|
|
141
|
+
reject(err);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// 결과 반환
|
|
146
|
+
const results = multiStats.stats.mapMany((stats) => SdCliBuildResultUtil.convertFromWebpackStats(stats));
|
|
147
|
+
|
|
148
|
+
// .config.json 파일 쓰기
|
|
149
|
+
const npmConfig = this._getNpmConfig(this._rootPath)!;
|
|
150
|
+
const packageKey = npmConfig.name.split("/").last()!;
|
|
151
|
+
|
|
152
|
+
const configDistPath = typeof this._config.server === "string"
|
|
153
|
+
? path.resolve(this._workspaceRootPath, "packages", this._config.server, "dist/www", packageKey, ".config.json")
|
|
154
|
+
: path.resolve(this._parsedTsconfig.options.outDir!, ".config.json");
|
|
155
|
+
await FsUtil.writeFileAsync(configDistPath, JSON.stringify(this._config.configs ?? {}, undefined, 2));
|
|
156
|
+
|
|
157
|
+
// 마무리
|
|
158
|
+
this._logger.debug("Webpack 빌드 완료");
|
|
159
|
+
resolve();
|
|
160
|
+
|
|
161
|
+
this.emit("complete", results);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
public async buildAsync(): Promise<ISdCliPackageBuildResult[]> {
|
|
167
|
+
// DIST 비우기
|
|
168
|
+
await FsUtil.removeAsync(path.resolve(this._rootPath, ".electron"));
|
|
169
|
+
await FsUtil.removeAsync(this._parsedTsconfig.options.outDir!);
|
|
170
|
+
|
|
171
|
+
// NgModule 생성
|
|
172
|
+
await this._ngModuleGenerator.runAsync();
|
|
173
|
+
|
|
174
|
+
// CORDOVA 초기화
|
|
175
|
+
if (this._cordova) {
|
|
176
|
+
this._logger.debug("CORDOVA 구성...");
|
|
177
|
+
await this._cordova.initializeAsync();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// 빌드
|
|
181
|
+
this._logger.debug("Webpack 빌드 수행...");
|
|
182
|
+
const builderTypes = (Object.keys(this._config.builder ?? { web: {} }) as ("web" | "cordova" | "electron")[]);
|
|
183
|
+
const webpackConfigs = builderTypes.map((builderType) => this._getWebpackConfig(false, builderType));
|
|
184
|
+
const multipleCompiler = webpack(webpackConfigs);
|
|
185
|
+
const buildResults = await new Promise<ISdCliPackageBuildResult[]>((resolve, reject) => {
|
|
186
|
+
multipleCompiler.run((err, multiStats) => {
|
|
187
|
+
if (err != null || multiStats == null) {
|
|
188
|
+
reject(err);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 결과 반환
|
|
193
|
+
const results = multiStats.stats.mapMany((stats) => SdCliBuildResultUtil.convertFromWebpackStats(stats));
|
|
194
|
+
resolve(results);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// .config.json 파일 쓰기
|
|
199
|
+
const targetPath = path.resolve(this._parsedTsconfig.options.outDir!, ".config.json");
|
|
200
|
+
await FsUtil.writeFileAsync(targetPath, JSON.stringify(this._config.configs ?? {}, undefined, 2));
|
|
201
|
+
|
|
202
|
+
// service-worker 처리
|
|
203
|
+
if (builderTypes.includes("web") && FsUtil.exists(path.resolve(this._rootPath, "ngsw-config.json"))) {
|
|
204
|
+
const packageKey = this._getNpmConfig(this._rootPath)!.name.split("/").last()!;
|
|
205
|
+
await augmentAppWithServiceWorker(
|
|
206
|
+
PathUtil.posix(path.relative(this._workspaceRootPath, this._rootPath)) as any,
|
|
207
|
+
PathUtil.posix(path.relative(this._workspaceRootPath, path.resolve(this._parsedTsconfig.options.outDir!))) as any,
|
|
208
|
+
`/${packageKey}/`,
|
|
209
|
+
PathUtil.posix(path.relative(this._workspaceRootPath, path.resolve(this._rootPath, "ngsw-config.json")))
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// CORDOVA 빌드
|
|
214
|
+
if (this._cordova) {
|
|
215
|
+
this._logger.debug("CORDOVA 빌드...");
|
|
216
|
+
await this._cordova.buildAsync(path.resolve(this._parsedTsconfig.options.outDir!, "cordova"));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ELECTRON
|
|
220
|
+
if (this._config.builder?.electron) {
|
|
221
|
+
const npmConfig = this._getNpmConfig(this._rootPath)!;
|
|
222
|
+
|
|
223
|
+
const electronVersion = npmConfig.dependencies?.["electron"];
|
|
224
|
+
if (electronVersion === undefined) {
|
|
225
|
+
throw new Error("ELECTRON 빌드 패키지의 'dependencies'에는 'electron'이 반드시 포함되어야 합니다.");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const dotenvVersion = npmConfig.dependencies?.["dotenv"];
|
|
229
|
+
if (dotenvVersion === undefined) {
|
|
230
|
+
throw new Error("ELECTRON 빌드 패키지의 'dependencies'에는 'dotenv'가 반드시 포함되어야 합니다.");
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const remoteVersion = npmConfig.dependencies?.["@electron/remote"];
|
|
234
|
+
|
|
235
|
+
const electronSrcPath = path.resolve(this._rootPath, `.electron/src`);
|
|
236
|
+
const electronDistPath = path.resolve(this._rootPath, `.electron/dist`);
|
|
237
|
+
|
|
238
|
+
await FsUtil.writeJsonAsync(path.resolve(electronSrcPath, `package.json`), {
|
|
239
|
+
name: npmConfig.name,
|
|
240
|
+
version: npmConfig.version,
|
|
241
|
+
description: npmConfig.description,
|
|
242
|
+
main: "electron.js",
|
|
243
|
+
author: npmConfig.author,
|
|
244
|
+
license: npmConfig.license,
|
|
245
|
+
devDependencies: {
|
|
246
|
+
"electron": electronVersion.replace("^", "")
|
|
247
|
+
},
|
|
248
|
+
dependencies: {
|
|
249
|
+
"dotenv": dotenvVersion,
|
|
250
|
+
...remoteVersion !== undefined ? {
|
|
251
|
+
"@electron/remote": remoteVersion
|
|
252
|
+
} : {}
|
|
253
|
+
}
|
|
254
|
+
}, { space: 2 });
|
|
255
|
+
await FsUtil.writeFileAsync(path.resolve(electronSrcPath, "yarn.lock"), "");
|
|
256
|
+
|
|
257
|
+
await FsUtil.writeFileAsync(path.resolve(electronSrcPath, `.env`), [
|
|
258
|
+
"NODE_ENV=production",
|
|
259
|
+
`SD_VERSION=${npmConfig.version}`,
|
|
260
|
+
(this._config.builder.electron.icon !== undefined) ? `SD_ELECTRON_ICON=${this._config.builder.electron.icon}` : `SD_ELECTRON_ICON=favicon.ico`,
|
|
261
|
+
...(this._config.env !== undefined) ? Object.keys(this._config.env).map((key) => `${key}=${this._config.env![key]}`) : []
|
|
262
|
+
].filterExists().join("\n"));
|
|
263
|
+
|
|
264
|
+
let electronTsFileContent = await FsUtil.readFileAsync(path.resolve(this._rootPath, `src/electron.ts`));
|
|
265
|
+
electronTsFileContent = "require(\"dotenv\").config({ path: `${__dirname}\\\\.env` });\n" + electronTsFileContent;
|
|
266
|
+
const result = ts.transpileModule(electronTsFileContent, { compilerOptions: { module: ts.ModuleKind.CommonJS } });
|
|
267
|
+
await FsUtil.writeFileAsync(path.resolve(electronSrcPath, "electron.js"), result.outputText);
|
|
268
|
+
|
|
269
|
+
await electronBuilder.build({
|
|
270
|
+
targets: electronBuilder.Platform.WINDOWS.createTarget(),
|
|
271
|
+
config: {
|
|
272
|
+
appId: this._config.builder.electron.appId,
|
|
273
|
+
productName: npmConfig.description,
|
|
274
|
+
// asar: false,
|
|
275
|
+
win: {
|
|
276
|
+
target: "nsis"
|
|
277
|
+
},
|
|
278
|
+
nsis: {},
|
|
279
|
+
directories: {
|
|
280
|
+
app: electronSrcPath,
|
|
281
|
+
output: electronDistPath
|
|
282
|
+
},
|
|
283
|
+
...this._config.builder.electron.installerIcon !== undefined ? {
|
|
284
|
+
icon: path.resolve(this._rootPath, "src", this._config.builder.electron.installerIcon)
|
|
285
|
+
} : {}
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
await FsUtil.copyAsync(
|
|
290
|
+
path.resolve(this._rootPath, `.electron/dist/${npmConfig.description} Setup ${npmConfig.version}.exe`),
|
|
291
|
+
path.resolve(this._parsedTsconfig.options.outDir!, `electron/${npmConfig.description}-latest.exe`)
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
await FsUtil.copyAsync(
|
|
295
|
+
path.resolve(this._rootPath, `.electron/dist/${npmConfig.description} Setup ${npmConfig.version}.exe`),
|
|
296
|
+
path.resolve(this._parsedTsconfig.options.outDir!, `electron/updates/${npmConfig.version}.exe`)
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// 마무리
|
|
301
|
+
this._logger.debug("Webpack 빌드 완료");
|
|
302
|
+
return buildResults;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
private _getInternalModuleCachePaths(workspaceName: string): string[] {
|
|
306
|
+
return [
|
|
307
|
+
...FsUtil.findAllParentChildDirPaths("node_modules/*/package.json", this._rootPath, this._workspaceRootPath),
|
|
308
|
+
...FsUtil.findAllParentChildDirPaths(`node_modules/!(@simplysm|@${workspaceName})/*/package.json`, this._rootPath, this._workspaceRootPath),
|
|
309
|
+
].map((p) => path.dirname(p));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
private _getWebpackConfig(watch: boolean, builderType: "web" | "cordova" | "electron"): webpack.Configuration {
|
|
313
|
+
const workspaceNpmConfig = this._getNpmConfig(this._workspaceRootPath)!;
|
|
314
|
+
const workspaceName = workspaceNpmConfig.name;
|
|
315
|
+
|
|
316
|
+
const internalModuleCachePaths = watch ? this._getInternalModuleCachePaths(workspaceName) : undefined;
|
|
317
|
+
|
|
318
|
+
const npmConfig = this._getNpmConfig(this._rootPath)!;
|
|
319
|
+
|
|
320
|
+
const workspacePkgLockContent = FsUtil.readFile(path.resolve(this._workspaceRootPath, "yarn.lock"));
|
|
321
|
+
|
|
322
|
+
const pkgKey = npmConfig.name.split("/").last()!;
|
|
323
|
+
const publicPath = builderType === "web" ? `/${pkgKey}/` : watch ? `/${pkgKey}/${builderType}/` : ``;
|
|
324
|
+
|
|
325
|
+
const cacheBasePath = path.resolve(this._rootPath, ".cache");
|
|
326
|
+
|
|
327
|
+
const distPath = (builderType === "cordova" && !watch) ? path.resolve(this._cordova!.cordovaPath, "www")
|
|
328
|
+
: (builderType === "electron" && !watch) ? path.resolve(this._rootPath, ".electron/src")
|
|
329
|
+
: builderType === "web" ? this._parsedTsconfig.options.outDir
|
|
330
|
+
: `${this._parsedTsconfig.options.outDir}/${builderType}`;
|
|
331
|
+
|
|
332
|
+
const sassImplementation = new SassWorkerImplementation();
|
|
333
|
+
|
|
334
|
+
const mainFilePath = path.resolve(this._rootPath, "src/main.ts");
|
|
335
|
+
const polyfillsFilePath = path.resolve(this._rootPath, "src/polyfills.ts");
|
|
336
|
+
const stylesFilePath = path.resolve(this._rootPath, "src/styles.scss");
|
|
337
|
+
|
|
338
|
+
let prevProgressMessage = "";
|
|
339
|
+
return {
|
|
340
|
+
mode: watch ? "development" : "production",
|
|
341
|
+
devtool: false,
|
|
342
|
+
target: builderType === "electron" ? ["electron-renderer", "es2015"] : ["web", "es2015"],
|
|
343
|
+
profile: false,
|
|
344
|
+
resolve: {
|
|
345
|
+
roots: [this._rootPath],
|
|
346
|
+
extensions: [".ts", ".tsx", ".mjs", ".cjs", ".js", ".jsx"],
|
|
347
|
+
symlinks: true,
|
|
348
|
+
modules: [this._workspaceRootPath, "node_modules"],
|
|
349
|
+
mainFields: ["es2015", "browser", "module", "main"],
|
|
350
|
+
conditionNames: ["es2015", "..."],
|
|
351
|
+
},
|
|
352
|
+
resolveLoader: {
|
|
353
|
+
symlinks: true
|
|
354
|
+
},
|
|
355
|
+
context: this._workspaceRootPath,
|
|
356
|
+
entry: {
|
|
357
|
+
main: [mainFilePath],
|
|
358
|
+
...FsUtil.exists(polyfillsFilePath) ? { polyfills: [polyfillsFilePath] } : {},
|
|
359
|
+
...FsUtil.exists(stylesFilePath) ? { styles: [stylesFilePath] } : {}
|
|
360
|
+
},
|
|
361
|
+
output: {
|
|
362
|
+
uniqueName: pkgKey,
|
|
363
|
+
hashFunction: "xxhash64",
|
|
364
|
+
clean: true,
|
|
365
|
+
path: distPath,
|
|
366
|
+
publicPath,
|
|
367
|
+
filename: "[name].js",
|
|
368
|
+
chunkFilename: "[name].js",
|
|
369
|
+
libraryTarget: undefined,
|
|
370
|
+
crossOriginLoading: false,
|
|
371
|
+
trustedTypes: "angular#bundler",
|
|
372
|
+
scriptType: "module"
|
|
373
|
+
},
|
|
374
|
+
watch: false,
|
|
375
|
+
watchOptions: {
|
|
376
|
+
poll: undefined,
|
|
377
|
+
ignored: undefined
|
|
378
|
+
},
|
|
379
|
+
performance: { hints: false },
|
|
380
|
+
ignoreWarnings: [
|
|
381
|
+
/Failed to parse source map from/,
|
|
382
|
+
/Add postcss as project dependency/,
|
|
383
|
+
/"@charset" must be the first rule in the file/
|
|
384
|
+
],
|
|
385
|
+
experiments: { backCompat: false, syncWebAssembly: true, asyncWebAssembly: true },
|
|
386
|
+
infrastructureLogging: { level: "error" },
|
|
387
|
+
stats: "errors-warnings",
|
|
388
|
+
cache: {
|
|
389
|
+
type: "filesystem",
|
|
390
|
+
profile: watch ? undefined : false,
|
|
391
|
+
cacheDirectory: path.resolve(cacheBasePath, "angular-webpack"),
|
|
392
|
+
maxMemoryGenerations: 1,
|
|
393
|
+
name: createHash("sha1")
|
|
394
|
+
.update(workspacePkgLockContent)
|
|
395
|
+
.update(JSON.stringify(this._parsedTsconfig.options))
|
|
396
|
+
.update(JSON.stringify(this._config))
|
|
397
|
+
.update(watch.toString())
|
|
398
|
+
.digest("hex")
|
|
399
|
+
},
|
|
400
|
+
...watch ? {
|
|
401
|
+
snapshot: {
|
|
402
|
+
immutablePaths: internalModuleCachePaths,
|
|
403
|
+
managedPaths: internalModuleCachePaths
|
|
404
|
+
}
|
|
405
|
+
} : {},
|
|
406
|
+
node: false,
|
|
407
|
+
optimization: {
|
|
408
|
+
minimizer: watch ? [] : [
|
|
409
|
+
new JavaScriptOptimizerPlugin({
|
|
410
|
+
define: {
|
|
411
|
+
ngDevMode: false,
|
|
412
|
+
ngI18nClosureMode: false,
|
|
413
|
+
ngJitMode: false
|
|
414
|
+
},
|
|
415
|
+
sourcemap: false,
|
|
416
|
+
target: ts.ScriptTarget.ES2017,
|
|
417
|
+
keepIdentifierNames: true,
|
|
418
|
+
keepNames: true,
|
|
419
|
+
removeLicenses: true,
|
|
420
|
+
advanced: true
|
|
421
|
+
}),
|
|
422
|
+
new TransferSizePlugin(),
|
|
423
|
+
new CssOptimizerPlugin({
|
|
424
|
+
supportedBrowsers: browserslist([
|
|
425
|
+
"last 1 Chrome versions",
|
|
426
|
+
"last 2 Edge major versions"
|
|
427
|
+
], { path: this._workspaceRootPath })
|
|
428
|
+
})
|
|
429
|
+
] as any[],
|
|
430
|
+
moduleIds: "deterministic",
|
|
431
|
+
chunkIds: watch ? "named" : "deterministic",
|
|
432
|
+
emitOnErrors: watch,
|
|
433
|
+
runtimeChunk: "single",
|
|
434
|
+
splitChunks: {
|
|
435
|
+
maxAsyncRequests: Infinity,
|
|
436
|
+
cacheGroups: {
|
|
437
|
+
default: {
|
|
438
|
+
chunks: "async",
|
|
439
|
+
minChunks: 2,
|
|
440
|
+
priority: 10
|
|
441
|
+
},
|
|
442
|
+
common: {
|
|
443
|
+
name: "common",
|
|
444
|
+
chunks: "async",
|
|
445
|
+
minChunks: 2,
|
|
446
|
+
enforce: true,
|
|
447
|
+
priority: 5
|
|
448
|
+
},
|
|
449
|
+
vendors: false,
|
|
450
|
+
defaultVendors: watch ? {
|
|
451
|
+
name: "vendor",
|
|
452
|
+
chunks: (chunk) => chunk.name === "main",
|
|
453
|
+
enforce: true,
|
|
454
|
+
test: /[\\/]node_modules[\\/]/
|
|
455
|
+
} : false
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
},
|
|
459
|
+
module: {
|
|
460
|
+
strictExportPresence: true,
|
|
461
|
+
parser: { javascript: { url: false, worker: false } },
|
|
462
|
+
rules: [
|
|
463
|
+
{
|
|
464
|
+
test: /\.?(svg|html)$/,
|
|
465
|
+
resourceQuery: /\?ngResource/,
|
|
466
|
+
type: "asset/source"
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
test: /[/\\]rxjs[/\\]add[/\\].+\.js$/,
|
|
470
|
+
sideEffects: true
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
test: /\.[cm]?[tj]sx?$/,
|
|
474
|
+
resolve: { fullySpecified: false },
|
|
475
|
+
exclude: [/[/\\](?:core-js|@babel|tslib|web-animations-js|web-streams-polyfill)[/\\]/],
|
|
476
|
+
use: [
|
|
477
|
+
{
|
|
478
|
+
loader: "@angular-devkit/build-angular/src/babel/webpack-loader",
|
|
479
|
+
options: {
|
|
480
|
+
cacheDirectory: path.resolve(cacheBasePath, "babel-webpack"),
|
|
481
|
+
scriptTarget: ts.ScriptTarget.ES2017,
|
|
482
|
+
aot: true,
|
|
483
|
+
optimize: !watch,
|
|
484
|
+
instrumentCode: undefined
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
]
|
|
488
|
+
},
|
|
489
|
+
...watch ? [
|
|
490
|
+
{
|
|
491
|
+
test: /\.[cm]?jsx?$/,
|
|
492
|
+
enforce: "pre" as const,
|
|
493
|
+
loader: "source-map-loader",
|
|
494
|
+
options: {
|
|
495
|
+
filterSourceMappingUrl: (mapUri: string, resourcePath: string) => {
|
|
496
|
+
const workspaceRegex = new RegExp(`node_modules[\\\\/]@${workspaceName}[\\\\/]`);
|
|
497
|
+
return !resourcePath.includes("node_modules")
|
|
498
|
+
|| (/node_modules[\\/]@simplysm[\\/]/).test(resourcePath)
|
|
499
|
+
|| workspaceRegex.test(resourcePath);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
] : [],
|
|
504
|
+
{
|
|
505
|
+
test: /\.[cm]?tsx?$/,
|
|
506
|
+
loader: "@ngtools/webpack",
|
|
507
|
+
exclude: [/[/\\](?:css-loader|mini-css-extract-plugin|webpack)[/\\]/]
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
test: /\.css$/i,
|
|
511
|
+
type: "asset/source"
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
test: /\.scss$/i,
|
|
515
|
+
rules: [
|
|
516
|
+
{
|
|
517
|
+
oneOf: [
|
|
518
|
+
{
|
|
519
|
+
use: [
|
|
520
|
+
{
|
|
521
|
+
loader: MiniCssExtractPlugin.loader
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
loader: "css-loader",
|
|
525
|
+
options: { url: false, sourceMap: watch }
|
|
526
|
+
}
|
|
527
|
+
],
|
|
528
|
+
include: [stylesFilePath],
|
|
529
|
+
resourceQuery: { not: [/\?ngResource/] }
|
|
530
|
+
},
|
|
531
|
+
{
|
|
532
|
+
type: "asset/source",
|
|
533
|
+
resourceQuery: /\?ngResource/
|
|
534
|
+
}
|
|
535
|
+
]
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
use: [
|
|
539
|
+
{
|
|
540
|
+
loader: "resolve-url-loader",
|
|
541
|
+
options: { sourceMap: watch }
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
loader: "sass-loader",
|
|
545
|
+
options: {
|
|
546
|
+
implementation: sassImplementation,
|
|
547
|
+
sourceMap: true,
|
|
548
|
+
sassOptions: {
|
|
549
|
+
fiber: false,
|
|
550
|
+
precision: 8,
|
|
551
|
+
includePaths: [],
|
|
552
|
+
outputStyle: "expanded",
|
|
553
|
+
quietDeps: true,
|
|
554
|
+
verbose: watch ? undefined : false
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
]
|
|
559
|
+
}
|
|
560
|
+
]
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico|otf|xlsx?|pptx?|docx?|zip|pfx|pkl)$/,
|
|
564
|
+
type: "asset/resource"
|
|
565
|
+
}
|
|
566
|
+
]
|
|
567
|
+
},
|
|
568
|
+
plugins: [
|
|
569
|
+
new NodePolyfillPlugin({
|
|
570
|
+
excludeAliases: builderType === "electron" ? ["process"] : []
|
|
571
|
+
}),
|
|
572
|
+
new NamedChunksPlugin(),
|
|
573
|
+
new DedupeModuleResolvePlugin(),
|
|
574
|
+
new webpack.ProgressPlugin({
|
|
575
|
+
handler: (per: number, msg: string, ...args: string[]) => {
|
|
576
|
+
const phaseText = msg ? ` - phase: ${msg}` : "";
|
|
577
|
+
const argsText = args.length > 0 ? ` - args: [${args.join(", ")}]` : "";
|
|
578
|
+
const progressMessage = `Webpack 빌드 수행중...(${Math.round(per * 100)}%)${phaseText}${argsText}`;
|
|
579
|
+
if (progressMessage !== prevProgressMessage) {
|
|
580
|
+
prevProgressMessage = progressMessage;
|
|
581
|
+
this._logger.debug(progressMessage);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}),
|
|
585
|
+
...watch ? [] : [
|
|
586
|
+
new LicenseWebpackPlugin({
|
|
587
|
+
stats: { warnings: false, errors: false },
|
|
588
|
+
perChunkOutput: false,
|
|
589
|
+
outputFilename: "3rdpartylicenses.txt",
|
|
590
|
+
skipChildCompilers: true
|
|
591
|
+
})
|
|
592
|
+
],
|
|
593
|
+
new CopyWebpackPlugin({
|
|
594
|
+
patterns: [
|
|
595
|
+
...["favicon.ico", "assets/", "manifest.json"].map((item) => ({
|
|
596
|
+
context: this._rootPath,
|
|
597
|
+
to: item,
|
|
598
|
+
from: `src/${item}`,
|
|
599
|
+
noErrorOnMissing: true,
|
|
600
|
+
force: true,
|
|
601
|
+
globOptions: {
|
|
602
|
+
dot: true,
|
|
603
|
+
followSymbolicLinks: false,
|
|
604
|
+
ignore: [
|
|
605
|
+
".gitkeep",
|
|
606
|
+
"**/.DS_Store",
|
|
607
|
+
"**/Thumbs.db"
|
|
608
|
+
].map((i) => PathUtil.posix(this._rootPath, i))
|
|
609
|
+
},
|
|
610
|
+
priority: 0
|
|
611
|
+
})),
|
|
612
|
+
...builderType === "cordova" && watch ? this._cordova!.platforms.mapMany((platform) => [
|
|
613
|
+
{
|
|
614
|
+
context: this._cordova!.cordovaPath,
|
|
615
|
+
to: `cordova-${platform}/plugins`,
|
|
616
|
+
from: `platforms/${platform}/platform_www/plugins`,
|
|
617
|
+
noErrorOnMissing: true
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
context: this._cordova!.cordovaPath,
|
|
621
|
+
to: `cordova-${platform}/cordova.js`,
|
|
622
|
+
from: `platforms/${platform}/platform_www/cordova.js`
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
context: this._cordova!.cordovaPath,
|
|
626
|
+
to: `cordova-${platform}/cordova_plugins.js`,
|
|
627
|
+
from: `platforms/${platform}/platform_www/cordova_plugins.js`,
|
|
628
|
+
noErrorOnMissing: true
|
|
629
|
+
},
|
|
630
|
+
{
|
|
631
|
+
context: this._cordova!.cordovaPath,
|
|
632
|
+
to: `cordova-${platform}/config.xml`,
|
|
633
|
+
from: `platforms/${platform}/www/config.xml`,
|
|
634
|
+
noErrorOnMissing: true
|
|
635
|
+
}
|
|
636
|
+
]) : []
|
|
637
|
+
]
|
|
638
|
+
}),
|
|
639
|
+
...watch ? [
|
|
640
|
+
new webpack.SourceMapDevToolPlugin({
|
|
641
|
+
filename: "[file].map",
|
|
642
|
+
include: [/js$/, /css$/],
|
|
643
|
+
sourceRoot: "webpack:///",
|
|
644
|
+
moduleFilenameTemplate: "[resource-path]",
|
|
645
|
+
append: undefined
|
|
646
|
+
})
|
|
647
|
+
] : [],
|
|
648
|
+
new AngularWebpackPlugin({
|
|
649
|
+
tsconfig: this._tsconfigFilePath,
|
|
650
|
+
compilerOptions: {
|
|
651
|
+
sourceMap: watch,
|
|
652
|
+
declaration: false,
|
|
653
|
+
declarationMap: false,
|
|
654
|
+
preserveSymlinks: false
|
|
655
|
+
},
|
|
656
|
+
jitMode: false,
|
|
657
|
+
emitNgModuleScope: watch,
|
|
658
|
+
inlineStyleFileExtension: "scss"
|
|
659
|
+
}),
|
|
660
|
+
{
|
|
661
|
+
apply: (compiler: webpack.Compiler) => {
|
|
662
|
+
compiler.hooks.shutdown.tap("sass-worker", () => {
|
|
663
|
+
sassImplementation.close();
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
},
|
|
667
|
+
new MiniCssExtractPlugin({ filename: "[name].css" }),
|
|
668
|
+
new SuppressExtractedTextChunksWebpackPlugin(),
|
|
669
|
+
new IndexHtmlWebpackPlugin({
|
|
670
|
+
indexPath: path.resolve(this._rootPath, "src/index.html"),
|
|
671
|
+
outputPath: "index.html",
|
|
672
|
+
baseHref: publicPath,
|
|
673
|
+
entrypoints: [
|
|
674
|
+
["runtime", !watch],
|
|
675
|
+
["polyfills", true],
|
|
676
|
+
["styles", false],
|
|
677
|
+
["vendor", true],
|
|
678
|
+
["main", true]
|
|
679
|
+
],
|
|
680
|
+
deployUrl: undefined,
|
|
681
|
+
sri: false,
|
|
682
|
+
cache: {
|
|
683
|
+
enabled: true,
|
|
684
|
+
basePath: cacheBasePath,
|
|
685
|
+
path: path.resolve(cacheBasePath, "index-webpack")
|
|
686
|
+
},
|
|
687
|
+
postTransform: undefined,
|
|
688
|
+
optimization: {
|
|
689
|
+
scripts: !watch,
|
|
690
|
+
styles: { minify: !watch, inlineCritical: !watch },
|
|
691
|
+
fonts: { inline: !watch }
|
|
692
|
+
},
|
|
693
|
+
crossOrigin: "none",
|
|
694
|
+
lang: undefined
|
|
695
|
+
}),
|
|
696
|
+
new webpack.EnvironmentPlugin({
|
|
697
|
+
SD_VERSION: this._getNpmConfig(this._rootPath)!.version,
|
|
698
|
+
...this._config.env
|
|
699
|
+
}),
|
|
700
|
+
new ESLintWebpackPlugin({
|
|
701
|
+
context: this._rootPath,
|
|
702
|
+
eslintPath: path.resolve(this._workspaceRootPath, "node_modules", "eslint"),
|
|
703
|
+
exclude: ["node_modules"],
|
|
704
|
+
extensions: ["ts", "js", "mjs", "cjs"],
|
|
705
|
+
fix: false,
|
|
706
|
+
threads: false,
|
|
707
|
+
formatter: (results: LintResult[]) => {
|
|
708
|
+
const resultMessages: string[] = [];
|
|
709
|
+
for (const result of results) {
|
|
710
|
+
for (const msg of result.messages) {
|
|
711
|
+
const severity = msg.severity === 1 ? "warning" : msg.severity === 2 ? "error" : undefined;
|
|
712
|
+
if (severity === undefined) continue;
|
|
713
|
+
|
|
714
|
+
resultMessages.push(SdCliBuildResultUtil.getMessage({
|
|
715
|
+
filePath: result.filePath,
|
|
716
|
+
line: msg.line,
|
|
717
|
+
char: msg.column,
|
|
718
|
+
code: msg.ruleId?.toString(),
|
|
719
|
+
severity,
|
|
720
|
+
message: msg.message
|
|
721
|
+
}));
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
return resultMessages.join(os.EOL);
|
|
725
|
+
}
|
|
726
|
+
})
|
|
727
|
+
] as any[]
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
private _getNpmConfig(pkgPath: string): INpmConfig | undefined {
|
|
732
|
+
if (!this._npmConfigMap.has(pkgPath)) {
|
|
733
|
+
this._npmConfigMap.set(pkgPath, FsUtil.readJson(path.resolve(pkgPath, "package.json")));
|
|
734
|
+
}
|
|
735
|
+
return this._npmConfigMap.get(pkgPath);
|
|
736
|
+
}
|
|
737
|
+
}
|