@simplysm/sd-cli 12.5.19 → 12.5.21

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 (58) hide show
  1. package/dist/build-tools/SdNgBundler.d.ts +1 -0
  2. package/dist/build-tools/SdNgBundler.js +70 -57
  3. package/dist/build-tools/SdNgBundler.js.map +1 -1
  4. package/dist/build-tools/SdReactBundler.d.ts +25 -0
  5. package/dist/build-tools/SdReactBundler.js +295 -0
  6. package/dist/build-tools/SdReactBundler.js.map +1 -0
  7. package/dist/build-tools/SdReactBundlerContext.d.ts +14 -0
  8. package/dist/build-tools/SdReactBundlerContext.js +59 -0
  9. package/dist/build-tools/SdReactBundlerContext.js.map +1 -0
  10. package/dist/build-tools/SdServerBundler.d.ts +1 -0
  11. package/dist/build-tools/SdServerBundler.js +8 -7
  12. package/dist/build-tools/SdServerBundler.js.map +1 -1
  13. package/dist/build-tools/SdTsCompiler.d.ts +1 -0
  14. package/dist/build-tools/SdTsCompiler.js +355 -327
  15. package/dist/build-tools/SdTsCompiler.js.map +1 -1
  16. package/dist/build-tools/SdTsLibBundler.d.ts +1 -1
  17. package/dist/build-tools/SdTsLibBundler.js +2 -1
  18. package/dist/build-tools/SdTsLibBundler.js.map +1 -1
  19. package/dist/builders/SdCliClientBuilder.js +42 -20
  20. package/dist/builders/SdCliClientBuilder.js.map +1 -1
  21. package/dist/builders/SdCliServerBuilder.js +2 -1
  22. package/dist/builders/SdCliServerBuilder.js.map +1 -1
  23. package/dist/builders/SdCliTsLibBuilder.js +4 -2
  24. package/dist/builders/SdCliTsLibBuilder.js.map +1 -1
  25. package/dist/bundle-plugins/sdNgPlugin.d.ts +1 -0
  26. package/dist/bundle-plugins/sdNgPlugin.js +38 -23
  27. package/dist/bundle-plugins/sdNgPlugin.js.map +1 -1
  28. package/dist/bundle-plugins/sdReactPlugin.d.ts +16 -0
  29. package/dist/bundle-plugins/sdReactPlugin.js +117 -0
  30. package/dist/bundle-plugins/sdReactPlugin.js.map +1 -0
  31. package/dist/bundle-plugins/sdServerPlugin.d.ts +1 -0
  32. package/dist/bundle-plugins/sdServerPlugin.js +1 -0
  33. package/dist/bundle-plugins/sdServerPlugin.js.map +1 -1
  34. package/dist/entry/SdCliProject.js +7 -4
  35. package/dist/entry/SdCliProject.js.map +1 -1
  36. package/dist/index.d.ts +4 -0
  37. package/dist/index.js +4 -0
  38. package/dist/index.js.map +1 -1
  39. package/dist/utils/SdCliPerformanceTime.d.ts +9 -0
  40. package/dist/utils/SdCliPerformanceTime.js +40 -0
  41. package/dist/utils/SdCliPerformanceTime.js.map +1 -0
  42. package/package.json +13 -10
  43. package/src/build-tools/SdNgBundler.ts +82 -68
  44. package/src/build-tools/SdReactBundler.ts +372 -0
  45. package/src/build-tools/SdReactBundlerContext.ts +71 -0
  46. package/src/build-tools/SdServerBundler.ts +22 -20
  47. package/src/build-tools/SdTsCompiler.ts +341 -357
  48. package/src/build-tools/SdTsLibBundler.ts +2 -1
  49. package/src/builders/SdCliClientBuilder.ts +53 -29
  50. package/src/builders/SdCliServerBuilder.ts +5 -4
  51. package/src/builders/SdCliTsLibBuilder.ts +7 -4
  52. package/src/bundle-plugins/sdNgPlugin.ts +44 -23
  53. package/src/bundle-plugins/sdReactPlugin.ts +164 -0
  54. package/src/bundle-plugins/sdServerPlugin.ts +2 -0
  55. package/src/entry/SdCliProject.ts +6 -4
  56. package/src/index.ts +4 -0
  57. package/src/utils/SdCliPerformanceTime.ts +42 -0
  58. package/tsconfig.json +1 -1
@@ -36,6 +36,7 @@ import { SassStylesheetLanguage } from "@angular/build/src/tools/esbuild/stylesh
36
36
  import { CssStylesheetLanguage } from "@angular/build/src/tools/esbuild/stylesheets/css-language";
37
37
  import { createCssResourcePlugin } from "@angular/build/src/tools/esbuild/stylesheets/css-resource-plugin";
38
38
  import { resolveAssets } from "@angular/build/src/utils/resolve-assets";
39
+ import { SdCliPerformanceTimer } from "../utils/SdCliPerformanceTime";
39
40
 
40
41
  export class SdNgBundler {
41
42
  readonly #logger = Logger.get(["simplysm", "sd-cli", "SdNgBundler"]);
@@ -95,19 +96,23 @@ export class SdNgBundler {
95
96
  affectedFileSet: Set<string>;
96
97
  results: ISdCliPackageBuildResult[];
97
98
  }> {
99
+ const perf = new SdCliPerformanceTimer("ng bundle");
100
+
98
101
  this.#debug(`get contexts...`);
99
102
 
100
103
  if (!this.#contexts) {
101
- this.#contexts = [
104
+ this.#contexts = perf.run("get contexts", () => [
102
105
  this._getAppContext(),
103
106
  this._getStyleContext(),
104
107
  ...(this.#opt.builderType === "electron" ? [this._getElectronMainContext()] : []),
105
- ];
108
+ ]);
106
109
  }
107
110
 
108
111
  this.#debug(`build...`);
109
112
 
110
- const bundlingResults = await this.#contexts.mapAsync(async (ctx, i) => await ctx.bundleAsync());
113
+ const bundlingResults = await perf.run("build", async () => {
114
+ return await this.#contexts!.mapAsync(async (ctx, i) => await ctx.bundleAsync());
115
+ });
111
116
 
112
117
  //-- results
113
118
  const results = bundlingResults.mapMany((bundlingResult) => bundlingResult.results);
@@ -138,88 +143,95 @@ export class SdNgBundler {
138
143
  }
139
144
 
140
145
  this.#debug(`create index.html...`);
141
-
142
- const genIndexHtmlResult = await this._genIndexHtmlAsync(outputFiles, initialFiles);
143
- for (const warning of genIndexHtmlResult.warnings) {
144
- results.push({
145
- filePath: undefined,
146
- line: undefined,
147
- char: undefined,
148
- code: undefined,
149
- severity: "warning",
150
- message: `(gen-index) ${warning}`,
151
- type: "build",
152
- });
153
- }
154
- for (const error of genIndexHtmlResult.errors) {
155
- results.push({
156
- filePath: undefined,
157
- line: undefined,
158
- char: undefined,
159
- code: undefined,
160
- severity: "error",
161
- message: `(gen-index) ${error}`,
162
- type: "build",
163
- });
164
- }
165
- outputFiles.push(createOutputFile("index.html", genIndexHtmlResult.csrContent, BuildOutputFileType.Root));
166
-
167
- //-- copy assets
168
- assetFiles.push(...(await this._copyAssetsAsync()));
169
-
170
- //-- extract 3rdpartylicenses
171
- if (!this.#opt.dev) {
172
- outputFiles.push(
173
- createOutputFile(
174
- "3rdpartylicenses.txt",
175
- await extractLicenses(metafile, this.#opt.pkgPath),
176
- BuildOutputFileType.Root,
177
- ),
178
- );
179
- }
180
-
181
- //-- service worker
182
- if (FsUtil.exists(this.#swConfFilePath)) {
183
- this.#debug(`prepare service worker...`);
184
-
185
- try {
186
- const serviceWorkerResult = await this._genServiceWorkerAsync(outputFiles, assetFiles);
187
- outputFiles.push(createOutputFile("ngsw.json", serviceWorkerResult.manifest, BuildOutputFileType.Root));
188
- assetFiles.push(...serviceWorkerResult.assetFiles);
189
- } catch (err) {
146
+ await perf.run("create index.html", async () => {
147
+ const genIndexHtmlResult = await this._genIndexHtmlAsync(outputFiles, initialFiles);
148
+ for (const warning of genIndexHtmlResult.warnings) {
149
+ results.push({
150
+ filePath: undefined,
151
+ line: undefined,
152
+ char: undefined,
153
+ code: undefined,
154
+ severity: "warning",
155
+ message: `(gen-index) ${warning}`,
156
+ type: "build",
157
+ });
158
+ }
159
+ for (const error of genIndexHtmlResult.errors) {
190
160
  results.push({
191
161
  filePath: undefined,
192
162
  line: undefined,
193
163
  char: undefined,
194
164
  code: undefined,
195
165
  severity: "error",
196
- message: `(gen-sw) ${err.toString()}`,
166
+ message: `(gen-index) ${error}`,
197
167
  type: "build",
198
168
  });
199
169
  }
170
+ outputFiles.push(createOutputFile("index.html", genIndexHtmlResult.csrContent, BuildOutputFileType.Root));
171
+ });
172
+
173
+ await perf.run("assets", async () => {
174
+ //-- copy assets
175
+ assetFiles.push(...(await this._copyAssetsAsync()));
176
+
177
+ //-- extract 3rdpartylicenses
178
+ if (!this.#opt.dev) {
179
+ outputFiles.push(
180
+ createOutputFile(
181
+ "3rdpartylicenses.txt",
182
+ await extractLicenses(metafile, this.#opt.pkgPath),
183
+ BuildOutputFileType.Root,
184
+ ),
185
+ );
186
+ }
187
+ });
188
+
189
+ //-- service worker
190
+ if (FsUtil.exists(this.#swConfFilePath)) {
191
+ this.#debug(`prepare service worker...`);
192
+
193
+ await perf.run("prepare service worker", async () => {
194
+ try {
195
+ const serviceWorkerResult = await this._genServiceWorkerAsync(outputFiles, assetFiles);
196
+ outputFiles.push(createOutputFile("ngsw.json", serviceWorkerResult.manifest, BuildOutputFileType.Root));
197
+ assetFiles.push(...serviceWorkerResult.assetFiles);
198
+ } catch (err) {
199
+ results.push({
200
+ filePath: undefined,
201
+ line: undefined,
202
+ char: undefined,
203
+ code: undefined,
204
+ severity: "error",
205
+ message: `(gen-sw) ${err.toString()}`,
206
+ type: "build",
207
+ });
208
+ }
209
+ });
200
210
  }
201
211
 
202
212
  //-- write
203
213
  this.#debug(`write output files...(${outputFiles.length})`);
204
214
 
205
- for (const outputFile of outputFiles) {
206
- const distFilePath = path.resolve(this.#opt.outputPath, outputFile.path);
207
- const prev = this.#outputCache.get(distFilePath);
208
- if (prev !== Buffer.from(outputFile.contents).toString("base64")) {
209
- await FsUtil.writeFileAsync(distFilePath, outputFile.contents);
210
- this.#outputCache.set(distFilePath, Buffer.from(outputFile.contents).toString("base64"));
215
+ await perf.run("write output file", async () => {
216
+ for (const outputFile of outputFiles) {
217
+ const distFilePath = path.resolve(this.#opt.outputPath, outputFile.path);
218
+ const prev = this.#outputCache.get(distFilePath);
219
+ if (prev !== Buffer.from(outputFile.contents).toString("base64")) {
220
+ await FsUtil.writeFileAsync(distFilePath, outputFile.contents);
221
+ this.#outputCache.set(distFilePath, Buffer.from(outputFile.contents).toString("base64"));
222
+ }
211
223
  }
212
- }
213
- for (const assetFile of assetFiles) {
214
- const prev = this.#outputCache.get(assetFile.source);
215
- const curr = FsUtil.lstat(assetFile.source).mtime.getTime();
216
- if (prev !== curr) {
217
- await FsUtil.copyAsync(assetFile.source, path.resolve(this.#opt.outputPath, assetFile.destination));
218
- this.#outputCache.set(assetFile.source, curr);
224
+ for (const assetFile of assetFiles) {
225
+ const prev = this.#outputCache.get(assetFile.source);
226
+ const curr = FsUtil.lstat(assetFile.source).mtime.getTime();
227
+ if (prev !== curr) {
228
+ await FsUtil.copyAsync(assetFile.source, path.resolve(this.#opt.outputPath, assetFile.destination));
229
+ this.#outputCache.set(assetFile.source, curr);
230
+ }
219
231
  }
220
- }
232
+ });
221
233
 
222
- this.#debug(`번들링중 영향받은 파일`, Array.from(this.#ngResultCache.affectedFileSet!));
234
+ this.#debug(perf.toString());
223
235
 
224
236
  return {
225
237
  program: this.#ngResultCache.program,
@@ -501,6 +513,7 @@ export class SdNgBundler {
501
513
  dev: this.#opt.dev,
502
514
  pkgPath: this.#opt.pkgPath,
503
515
  result: this.#ngResultCache,
516
+ watchScopePaths: this.#opt.watchScopePaths,
504
517
  }),
505
518
  // createCompilerPlugin({
506
519
  // sourcemap: this.#opt.dev,
@@ -634,4 +647,5 @@ interface IOptions {
634
647
  builderType: string;
635
648
  env: Record<string, string> | undefined;
636
649
  cordovaConfig: ISdCliClientBuilderCordovaConfig | undefined;
650
+ watchScopePaths: string[];
637
651
  }
@@ -0,0 +1,372 @@
1
+ import path from "path";
2
+ import { FsUtil, Logger, PathUtil } from "@simplysm/sd-core-node";
3
+ import { fileURLToPath } from "url";
4
+ import nodeStdLibBrowser from "node-stdlib-browser";
5
+ import nodeStdLibBrowserPlugin from "node-stdlib-browser/helpers/esbuild/plugin";
6
+ import { INpmConfig, ISdCliClientBuilderCordovaConfig, ISdCliPackageBuildResult } from "../commons";
7
+ import ts from "typescript";
8
+ import { SdReactBundlerContext } from "./SdReactBundlerContext";
9
+ import { IReactPluginResultCache, sdReactPlugin } from "../bundle-plugins/sdReactPlugin";
10
+ import { transformSupportedBrowsersToTargets } from "@angular/build/src/tools/esbuild/utils";
11
+ import browserslist from "browserslist";
12
+ import { glob } from "glob";
13
+ import { generateSW } from "workbox-build";
14
+
15
+ export class SdReactBundler {
16
+ readonly #logger = Logger.get(["simplysm", "sd-cli", "SdNgBundler"]);
17
+
18
+ readonly #modifiedFileSet = new Set<string>();
19
+ readonly #compileResultCache: IReactPluginResultCache = {
20
+ affectedFileSet: new Set<string>(),
21
+ watchFileSet: new Set<string>()
22
+ };
23
+ #contexts: SdReactBundlerContext[] | undefined;
24
+
25
+ readonly #outputCache = new Map<string, string | number>();
26
+
27
+ readonly #opt: IOptions;
28
+
29
+ readonly #pkgNpmConf: INpmConfig;
30
+ readonly #indexFilePath: string;
31
+ readonly #tsConfigFilePath: string;
32
+ readonly #browserTarget: string[];
33
+
34
+ public constructor(opt: IOptions) {
35
+ this.#opt = opt;
36
+ this.#pkgNpmConf = FsUtil.readJson(path.resolve(opt.pkgPath, "package.json"));
37
+ this.#indexFilePath = path.resolve(opt.pkgPath, "src/index.tsx");
38
+ this.#tsConfigFilePath = path.resolve(opt.pkgPath, "tsconfig.json");
39
+ this.#browserTarget = transformSupportedBrowsersToTargets(browserslist(["Chrome > 78"]));
40
+ }
41
+
42
+ public markForChanges(filePaths: string[]): void {
43
+ for (const filePath of filePaths) {
44
+ this.#modifiedFileSet.add(path.normalize(filePath));
45
+ }
46
+ }
47
+
48
+ public async bundleAsync(): Promise<{
49
+ program?: ts.Program;
50
+ watchFileSet: Set<string>;
51
+ affectedFileSet: Set<string>;
52
+ results: ISdCliPackageBuildResult[];
53
+ }> {
54
+ this.#debug(`get contexts...`);
55
+
56
+ if (!this.#contexts) {
57
+ this.#contexts = [
58
+ this._getAppContext(),
59
+ ...(this.#opt.builderType === "electron" ? [this._getElectronMainContext()] : [])
60
+ ];
61
+ }
62
+
63
+ this.#debug(`build...`);
64
+
65
+ const bundlingResults = await this.#contexts.mapAsync(async (ctx, i) => await ctx.bundleAsync());
66
+
67
+ //-- results
68
+ const results = bundlingResults.mapMany((bundlingResult) => bundlingResult.results);
69
+
70
+ this.#debug(`convert result...`);
71
+
72
+ const outputFiles: IReactOutputFile[] = bundlingResults
73
+ .mapMany((item) => item.outputFiles ?? [])
74
+ .map((item) => ({
75
+ relPath: path.relative(this.#opt.pkgPath, item.path),
76
+ contents: item.contents
77
+ }));
78
+
79
+ // cordova empty
80
+ if (this.#opt.builderType === "cordova" && this.#opt.cordovaConfig?.plugins) {
81
+ outputFiles.push({ relPath: "cordova-empty.js", contents: "export default {};" });
82
+ }
83
+
84
+ // index.html
85
+ let indexHtml = await FsUtil.readFileAsync(path.resolve(this.#opt.pkgPath, "src/index.html"));
86
+ indexHtml = indexHtml.replace(/<\/head>/, "<script type=\"module\" src=\"index.js\"></script></head>");
87
+ if (!this.#opt.dev) {
88
+ indexHtml = indexHtml.replace(/<\/head>/, "<script src=\"registerSW.js\"></script></head>");
89
+ }
90
+ if (this.#opt.builderType === "cordova") {
91
+ indexHtml = indexHtml.replace(
92
+ /(.*)<\/head>/,
93
+ "<script type=\"module\" src=\"cordova-entry.js\"></script>\n$1</head>"
94
+ );
95
+ }
96
+ if (outputFiles.some((item) => item.relPath === "index.css")) {
97
+ indexHtml = indexHtml.replace(/(.*)<\/head>/, "<link rel=\"stylesheet\" href=\"index.css\">\n$1</head>");
98
+ }
99
+
100
+ outputFiles.push({
101
+ relPath: "index.html",
102
+ contents: indexHtml
103
+ });
104
+
105
+ // copy assets
106
+ outputFiles.push(
107
+ ...this.#copyAssets("src", "favicon.ico"),
108
+ ...this.#copyAssets("src", "manifest.webmanifest"),
109
+ ...this.#copyAssets("src/assets", "**/*", "assets"),
110
+ ...(this.#opt.dev && this.#opt.builderType === "cordova"
111
+ ? Object.keys(this.#opt.cordovaConfig?.platform ?? { browser: {} }).mapMany((platform) => [
112
+ ...this.#copyAssets(
113
+ `.cordova/platforms/${platform}/platform_www/plugins`,
114
+ "**/*",
115
+ `cordova-${platform}/plugins`
116
+ ),
117
+ ...this.#copyAssets(`.cordova/platforms/${platform}/platform_www`, "cordova.js", `cordova-${platform}`),
118
+ ...this.#copyAssets(
119
+ `.cordova/platforms/${platform}/platform_www`,
120
+ "cordova_plugins.js",
121
+ `cordova-${platform}`
122
+ ),
123
+ ...this.#copyAssets(`.cordova/platforms/${platform}/www`, "config.xml", `cordova-${platform}`)
124
+ ])
125
+ : [])
126
+ );
127
+
128
+ // TODO: extract 3rdpartylicenses
129
+
130
+ //-- write
131
+ this.#debug(`write output files...(${outputFiles.length})`);
132
+
133
+ for (const outputFile of outputFiles) {
134
+ const distFilePath = path.resolve(this.#opt.outputPath, outputFile.relPath);
135
+ const prev = this.#outputCache.get(distFilePath);
136
+ if (prev !== Buffer.from(outputFile.contents).toString("base64")) {
137
+ await FsUtil.writeFileAsync(distFilePath, outputFile.contents);
138
+ this.#outputCache.set(distFilePath, Buffer.from(outputFile.contents).toString("base64"));
139
+ }
140
+ }
141
+
142
+ // service worker
143
+ if (!this.#opt.dev) {
144
+ try {
145
+ const swResult = await generateSW({
146
+ globDirectory: path.resolve(this.#opt.pkgPath, "dist"),
147
+ globPatterns: ["**/*"],
148
+ globIgnores: ["3rdpartylicenses.txt", "*.js.map"],
149
+ globStrict: true,
150
+ swDest: path.resolve(this.#opt.pkgPath, "dist", "sw.js")
151
+ });
152
+ swResult.warnings.map((msg) => {
153
+ results.push({
154
+ filePath: undefined,
155
+ line: undefined,
156
+ char: undefined,
157
+ code: undefined,
158
+ severity: "warning",
159
+ message: `(gen-sw) ${msg}`,
160
+ type: "build"
161
+ });
162
+ });
163
+
164
+ await FsUtil.writeFileAsync(
165
+ path.resolve(this.#opt.pkgPath, "dist", "registerSW.js"),
166
+ `
167
+ if ('serviceWorker' in navigator) {
168
+ window.addEventListener('load', () => {
169
+ navigator.serviceWorker.register('sw.js')
170
+ })
171
+ }`.trim()
172
+ );
173
+ }
174
+ catch (err) {
175
+ results.push({
176
+ filePath: undefined,
177
+ line: undefined,
178
+ char: undefined,
179
+ code: undefined,
180
+ severity: "error",
181
+ message: `(gen-sw) ${err.toString()}`,
182
+ type: "build"
183
+ });
184
+ }
185
+ }
186
+
187
+ this.#debug(`번들링중 영향받은 파일`, Array.from(this.#compileResultCache.affectedFileSet!));
188
+
189
+ return {
190
+ program: this.#compileResultCache.program,
191
+ watchFileSet: new Set(this.#compileResultCache.watchFileSet),
192
+ affectedFileSet: this.#compileResultCache.affectedFileSet!,
193
+ results
194
+ };
195
+ }
196
+
197
+ #copyAssets(srcDir: string, globPath: string, distDir?: string): IReactOutputFile[] {
198
+ const result: IReactOutputFile[] = [];
199
+ const srcGlobPath = path.resolve(this.#opt.pkgPath, srcDir, globPath);
200
+ const srcPaths = glob.sync(srcGlobPath);
201
+ for (const srcPath of srcPaths) {
202
+ if (!FsUtil.exists(srcPath)) continue;
203
+ result.push({
204
+ relPath: path.join(
205
+ ...[distDir, path.relative(path.resolve(this.#opt.pkgPath, srcDir), srcPath)].filterExists()
206
+ ),
207
+ contents: FsUtil.readFile(srcPath)
208
+ });
209
+ }
210
+
211
+ return result;
212
+ }
213
+
214
+ private _getAppContext() {
215
+ return new SdReactBundlerContext(this.#opt.pkgPath, {
216
+ absWorkingDir: this.#opt.pkgPath,
217
+ bundle: true,
218
+ keepNames: true,
219
+ format: "esm",
220
+ assetNames: "media/[name]",
221
+ conditions: ["es2020", "es2015", "module"],
222
+ resolveExtensions: [".js", ".mjs", ".cjs", ".ts", ".tsx"],
223
+ metafile: true,
224
+ legalComments: this.#opt.dev ? "eof" : "none",
225
+ logLevel: "silent",
226
+ minifyIdentifiers: !this.#opt.dev,
227
+ minifySyntax: !this.#opt.dev,
228
+ minifyWhitespace: !this.#opt.dev,
229
+ pure: ["forwardRef"],
230
+ outdir: this.#opt.pkgPath,
231
+ outExtension: undefined,
232
+ sourcemap: true, //this.#opt.dev,
233
+ splitting: true,
234
+ chunkNames: "[name]-[hash]",
235
+ tsconfig: this.#tsConfigFilePath,
236
+ write: false,
237
+ preserveSymlinks: false,
238
+ define: {
239
+ ...(!this.#opt.dev ? { ngDevMode: "false" } : {}),
240
+ "ngJitMode": "false",
241
+ "global": "global",
242
+ "process": "process",
243
+ "Buffer": "Buffer",
244
+ "process.env.SD_VERSION": JSON.stringify(this.#pkgNpmConf.version),
245
+ "process.env.NODE_ENV": JSON.stringify(this.#opt.dev ? "development" : "production"),
246
+ ...(this.#opt.env
247
+ ? Object.keys(this.#opt.env).toObject(
248
+ (key) => `process.env.${key}`,
249
+ (key) => JSON.stringify(this.#opt.env![key])
250
+ )
251
+ : {})
252
+ },
253
+ platform: "browser",
254
+ mainFields: ["es2020", "es2015", "browser", "module", "main"],
255
+ entryNames: "[name]",
256
+ entryPoints: {
257
+ index: this.#indexFilePath
258
+ },
259
+ external: ["electron"],
260
+ target: this.#browserTarget,
261
+ supported: { "async-await": false, "object-rest-spread": false },
262
+ loader: {
263
+ ".png": "file",
264
+ ".jpeg": "file",
265
+ ".jpg": "file",
266
+ ".jfif": "file",
267
+ ".gif": "file",
268
+ ".svg": "file",
269
+ ".woff": "file",
270
+ ".woff2": "file",
271
+ ".ttf": "file",
272
+ ".ttc": "file",
273
+ ".eot": "file",
274
+ ".ico": "file",
275
+ ".otf": "file",
276
+ ".csv": "file",
277
+ ".xlsx": "file",
278
+ ".xls": "file",
279
+ ".pptx": "file",
280
+ ".ppt": "file",
281
+ ".docx": "file",
282
+ ".doc": "file",
283
+ ".zip": "file",
284
+ ".pfx": "file",
285
+ ".pkl": "file",
286
+ ".mp3": "file",
287
+ ".ogg": "file"
288
+ },
289
+ inject: [PathUtil.posix(fileURLToPath(import.meta.resolve("node-stdlib-browser/helpers/esbuild/shim")))],
290
+ plugins: [
291
+ ...(this.#opt.builderType === "cordova" && this.#opt.cordovaConfig?.plugins
292
+ ? [
293
+ {
294
+ name: "cordova:plugin-empty",
295
+ setup: ({ onResolve }) => {
296
+ onResolve({ filter: new RegExp("(" + this.#opt.cordovaConfig!.plugins!.join("|") + ")") }, () => {
297
+ return {
298
+ path: `./cordova-empty.js`,
299
+ external: true
300
+ };
301
+ });
302
+ }
303
+ }
304
+ ]
305
+ : []),
306
+ sdReactPlugin({
307
+ modifiedFileSet: this.#modifiedFileSet,
308
+ dev: this.#opt.dev,
309
+ pkgPath: this.#opt.pkgPath,
310
+ result: this.#compileResultCache,
311
+ watchScopePaths: this.#opt.watchScopePaths,
312
+ }),
313
+ nodeStdLibBrowserPlugin(nodeStdLibBrowser)
314
+ ]
315
+ });
316
+ }
317
+
318
+ private _getElectronMainContext() {
319
+ return new SdReactBundlerContext(this.#opt.pkgPath, {
320
+ absWorkingDir: this.#opt.pkgPath,
321
+ bundle: true,
322
+ entryNames: "[name]",
323
+ assetNames: "media/[name]",
324
+ conditions: ["es2020", "es2015", "module"],
325
+ resolveExtensions: [".js", ".mjs", ".cjs", ".ts"],
326
+ metafile: true,
327
+ legalComments: this.#opt.dev ? "eof" : "none",
328
+ logLevel: "silent",
329
+ minify: !this.#opt.dev,
330
+ outdir: this.#opt.pkgPath,
331
+ sourcemap: true, //this.#opt.dev,
332
+ tsconfig: this.#tsConfigFilePath,
333
+ write: false,
334
+ preserveSymlinks: false,
335
+ external: ["electron"],
336
+ define: {
337
+ ...(!this.#opt.dev ? { ngDevMode: "false" } : {}),
338
+ "process.env.SD_VERSION": JSON.stringify(this.#pkgNpmConf.version),
339
+ "process.env.NODE_ENV": JSON.stringify(this.#opt.dev ? "development" : "production"),
340
+ ...(this.#opt.env
341
+ ? Object.keys(this.#opt.env).toObject(
342
+ (key) => `process.env.${key}`,
343
+ (key) => JSON.stringify(this.#opt.env![key])
344
+ )
345
+ : {})
346
+ },
347
+ platform: "node",
348
+ entryPoints: {
349
+ "electron-main": path.resolve(this.#opt.pkgPath, "src/electron-main.ts")
350
+ }
351
+ });
352
+ }
353
+
354
+ #debug(...msg: any[]): void {
355
+ this.#logger.debug(`[${path.basename(this.#opt.pkgPath)}]`, ...msg);
356
+ }
357
+ }
358
+
359
+ interface IOptions {
360
+ dev: boolean;
361
+ outputPath: string;
362
+ pkgPath: string;
363
+ builderType: string;
364
+ env: Record<string, string> | undefined;
365
+ cordovaConfig: ISdCliClientBuilderCordovaConfig | undefined;
366
+ watchScopePaths: string[];
367
+ }
368
+
369
+ interface IReactOutputFile {
370
+ relPath: string;
371
+ contents: Uint8Array | string;
372
+ }
@@ -0,0 +1,71 @@
1
+ import esbuild from "esbuild";
2
+ import path from "path";
3
+ import { ISdCliPackageBuildResult } from "../commons";
4
+ import { Logger } from "@simplysm/sd-core-node";
5
+
6
+ export class SdReactBundlerContext {
7
+ readonly #logger = Logger.get(["simplysm", "sd-cli", "SdReactBundlerContext"]);
8
+
9
+ private _context?: esbuild.BuildContext;
10
+
11
+ public constructor(
12
+ private readonly _pkgPath: string,
13
+ private readonly _esbuildOptions: esbuild.BuildOptions,
14
+ ) {}
15
+
16
+ public async bundleAsync() {
17
+ if (this._context == null) {
18
+ this._context = await esbuild.context(this._esbuildOptions);
19
+ }
20
+
21
+ let buildResult: esbuild.BuildResult;
22
+
23
+ try {
24
+ this.#debug(`rebuild...`);
25
+ buildResult = await this._context.rebuild();
26
+ this.#debug(`rebuild completed`);
27
+ } catch (err) {
28
+ if ("warnings" in err || "errors" in err) {
29
+ buildResult = err;
30
+ } else {
31
+ throw err;
32
+ }
33
+ }
34
+
35
+ this.#debug(`convert results...`);
36
+
37
+ const results = [
38
+ ...buildResult.warnings.map((warn) => ({
39
+ filePath: warn.location?.file !== undefined ? path.resolve(this._pkgPath, warn.location.file) : undefined,
40
+ line: warn.location?.line,
41
+ char: warn.location?.column,
42
+ code: warn.text.slice(0, warn.text.indexOf(":")),
43
+ severity: "warning",
44
+ message: `${warn.pluginName ? `(${warn.pluginName}) ` : ""} ${warn.text.slice(warn.text.indexOf(":") + 1)}`,
45
+ type: "build",
46
+ })),
47
+ ...buildResult.errors.map((err) => ({
48
+ filePath: err.location?.file !== undefined ? path.resolve(this._pkgPath, err.location.file) : undefined,
49
+ line: err.location?.line,
50
+ char: err.location?.column !== undefined ? err.location.column + 1 : undefined,
51
+ code: err.text.slice(0, err.text.indexOf(":")),
52
+ severity: "error",
53
+ message: `${err.pluginName ? `(${err.pluginName}) ` : ""} ${err.text.slice(err.text.indexOf(":") + 1)}`,
54
+ type: "build",
55
+ })),
56
+ ] as ISdCliPackageBuildResult[];
57
+
58
+ return {
59
+ results,
60
+ outputFiles: buildResult.outputFiles,
61
+ metafile: buildResult.metafile,
62
+ };
63
+ }
64
+
65
+ #debug(...msg: any[]): void {
66
+ this.#logger.debug(
67
+ `[${path.basename(this._pkgPath)}] (${Object.keys(this._esbuildOptions.entryPoints as Record<string, any>).join(", ")})`,
68
+ ...msg,
69
+ );
70
+ }
71
+ }