@git.zone/tsbundle 2.11.3 → 2.12.0

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 (37) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/interfaces/index.d.ts +1 -0
  3. package/dist_ts/mod_custom/index.d.ts +10 -3
  4. package/dist_ts/mod_custom/index.js +277 -86
  5. package/dist_ts/mod_esbuild/index.child.d.ts +4 -8
  6. package/dist_ts/mod_esbuild/index.child.js +51 -44
  7. package/dist_ts/mod_esbuild/plugins.d.ts +5 -2
  8. package/dist_ts/mod_esbuild/plugins.js +5 -3
  9. package/dist_ts/mod_esbuild/workerplugin.d.ts +12 -0
  10. package/dist_ts/mod_esbuild/workerplugin.js +254 -0
  11. package/dist_ts/mod_output/artifactpublisher.d.ts +43 -0
  12. package/dist_ts/mod_output/artifactpublisher.js +925 -0
  13. package/dist_ts/mod_output/index.d.ts +1 -0
  14. package/dist_ts/mod_output/index.js +11 -3
  15. package/dist_ts/mod_rolldown/index.child.d.ts +4 -2
  16. package/dist_ts/mod_rolldown/index.child.js +47 -36
  17. package/dist_ts/mod_rspack/index.child.d.ts +4 -2
  18. package/dist_ts/mod_rspack/index.child.js +73 -115
  19. package/dist_ts/plugins.d.ts +3 -1
  20. package/dist_ts/plugins.js +4 -2
  21. package/dist_ts/tsbundle.class.tsbundle.js +98 -16
  22. package/package.json +10 -9
  23. package/readme.hints.md +20 -4
  24. package/readme.md +23 -6
  25. package/third-party-notices.md +29 -0
  26. package/ts/00_commitinfo_data.ts +1 -1
  27. package/ts/interfaces/index.ts +1 -0
  28. package/ts/mod_custom/index.ts +347 -96
  29. package/ts/mod_esbuild/index.child.ts +78 -47
  30. package/ts/mod_esbuild/plugins.ts +9 -2
  31. package/ts/mod_esbuild/workerplugin.ts +328 -0
  32. package/ts/mod_output/artifactpublisher.ts +1185 -0
  33. package/ts/mod_output/index.ts +10 -2
  34. package/ts/mod_rolldown/index.child.ts +65 -42
  35. package/ts/mod_rspack/index.child.ts +95 -127
  36. package/ts/plugins.ts +3 -1
  37. package/ts/tsbundle.class.tsbundle.ts +102 -14
@@ -1,9 +1,11 @@
1
1
  import * as plugins from './plugins.js';
2
2
  import * as paths from '../paths.js';
3
3
  import * as interfaces from '../interfaces/index.js';
4
+ import { writeFileAtomically } from './artifactpublisher.js';
4
5
 
5
6
  export class Base64TsOutput {
6
7
  private files: interfaces.IBase64File[] = [];
8
+ private filePaths = new Set<string>();
7
9
  private cwd: string;
8
10
 
9
11
  constructor(cwd: string = paths.cwd) {
@@ -14,10 +16,15 @@ export class Base64TsOutput {
14
16
  * Add a file with its content to the output
15
17
  */
16
18
  public addFile(filePath: string, content: Buffer | string): void {
19
+ const normalizedPath = plugins.path.posix.normalize(filePath.replace(/\\/g, '/'));
20
+ if (this.filePaths.has(normalizedPath)) {
21
+ throw new Error(`Duplicate base64ts output path: ${normalizedPath}`);
22
+ }
17
23
  const contentBuffer = typeof content === 'string' ? Buffer.from(content, 'utf-8') : content;
18
24
  const contentBase64 = contentBuffer.toString('base64');
25
+ this.filePaths.add(normalizedPath);
19
26
  this.files.push({
20
- path: filePath,
27
+ path: normalizedPath,
21
28
  contentBase64,
22
29
  });
23
30
  }
@@ -144,7 +151,7 @@ ${filesFormatted}
144
151
  const outputDir = plugins.path.dirname(absolutePath);
145
152
  await plugins.fs.directory(outputDir).create();
146
153
  const content = this.generateTypeScript(maxLineLength);
147
- await plugins.fs.file(absolutePath).encoding('utf8').write(content);
154
+ await writeFileAtomically(absolutePath, content);
148
155
  console.log(`Generated base64ts output: ${outputPath}`);
149
156
  }
150
157
 
@@ -160,5 +167,6 @@ ${filesFormatted}
160
167
  */
161
168
  public clear(): void {
162
169
  this.files = [];
170
+ this.filePaths.clear();
163
171
  }
164
172
  }
@@ -2,6 +2,12 @@ import * as plugins from './plugins.js';
2
2
  import * as paths from '../paths.js';
3
3
  import * as interfaces from '../interfaces/index.js';
4
4
  import { logger } from '../tsbundle.logging.js';
5
+ import {
6
+ cleanupBuildStage,
7
+ createBuildStage,
8
+ getChunkNamespace,
9
+ publishGeneratedArtifacts,
10
+ } from '../mod_output/artifactpublisher.js';
5
11
 
6
12
  export class TsBundleProcess {
7
13
  constructor() {
@@ -32,59 +38,76 @@ export class TsBundleProcess {
32
38
  }
33
39
  }
34
40
 
35
- /**
36
- * creates a bundle for the test enviroment
37
- */
38
- public async buildTest(fromArg: string, toArg: string, argvArg: any) {
39
- // create a bundle
40
- const result = await plugins.rolldown({
41
- input: fromArg,
42
- resolve: {
43
- alias: await this.getAliases(),
44
- tsconfigFilename: paths.tsconfigPath,
45
- },
46
- });
47
-
41
+ private async build(
42
+ fromArg: string,
43
+ toArg: string,
44
+ argvArg: interfaces.ICliOptions,
45
+ productionArg: boolean,
46
+ ): Promise<void> {
47
+ const logicalOutputName = plugins.path.basename(argvArg.chunkOwner || toArg);
48
+ const chunkNamespace = getChunkNamespace(logicalOutputName);
48
49
  const outputDir = plugins.path.dirname(toArg);
49
50
  const outputFilename = plugins.path.basename(toArg);
51
+ const stage = createBuildStage(outputDir);
52
+ const stageMainPath = plugins.path.join(stage.stageDirectory, outputFilename);
53
+ try {
54
+ const result = await plugins.rolldown({
55
+ input: fromArg,
56
+ resolve: {
57
+ alias: await this.getAliases(),
58
+ tsconfigFilename: paths.tsconfigPath,
59
+ },
60
+ });
61
+ try {
62
+ await result.write({
63
+ dir: stage.stageDirectory,
64
+ entryFileNames: outputFilename,
65
+ chunkFileNames: `chunks/${chunkNamespace}/[name]-[hash].js`,
66
+ assetFileNames: `chunks/${chunkNamespace}/[name]-[hash][extname]`,
67
+ format: 'es',
68
+ sourcemap: argvArg.sourcemap !== false,
69
+ minify: productionArg,
70
+ codeSplitting: false,
71
+ });
72
+ } finally {
73
+ await result.close();
74
+ }
75
+ await publishGeneratedArtifacts({
76
+ sourceDirectory: stage.stageDirectory,
77
+ sourceMainPath: stageMainPath,
78
+ targetPath: toArg,
79
+ logicalOutputName,
80
+ chunkNamespace,
81
+ sourceMapsEnabled: argvArg.sourcemap !== false,
82
+ });
83
+ } finally {
84
+ cleanupBuildStage(stage.stageDirectory, outputDir, stage.marker);
85
+ }
86
+ }
50
87
 
51
- await result.write({
52
- dir: outputDir,
53
- entryFileNames: outputFilename,
54
- format: 'es',
55
- sourcemap: argvArg.sourcemap !== false,
56
- codeSplitting: false,
57
- });
88
+ /**
89
+ * creates a bundle for the test enviroment
90
+ */
91
+ public async buildTest(
92
+ fromArg: string,
93
+ toArg: string,
94
+ argvArg: interfaces.ICliOptions,
95
+ ): Promise<void> {
96
+ await this.build(fromArg, toArg, argvArg, false);
58
97
  }
59
98
 
60
99
  /**
61
100
  * creates a bundle for the production environment
62
101
  */
63
- public async buildProduction(fromArg: string, toArg: string, argvArg: any) {
64
- // create a bundle
102
+ public async buildProduction(
103
+ fromArg: string,
104
+ toArg: string,
105
+ argvArg: interfaces.ICliOptions,
106
+ ): Promise<void> {
65
107
  console.log('rolldown specific:');
66
108
  console.log(`from: ${fromArg}`);
67
109
  console.log(`to: ${toArg}`);
68
-
69
- const result = await plugins.rolldown({
70
- input: fromArg,
71
- resolve: {
72
- alias: await this.getAliases(),
73
- tsconfigFilename: paths.tsconfigPath,
74
- },
75
- });
76
-
77
- const outputDir = plugins.path.dirname(toArg);
78
- const outputFilename = plugins.path.basename(toArg);
79
-
80
- await result.write({
81
- dir: outputDir,
82
- entryFileNames: outputFilename,
83
- format: 'es',
84
- sourcemap: argvArg.sourcemap !== false,
85
- minify: true,
86
- codeSplitting: false,
87
- });
110
+ await this.build(fromArg, toArg, argvArg, true);
88
111
  }
89
112
  }
90
113
 
@@ -2,6 +2,12 @@ import * as plugins from './plugins.js';
2
2
  import * as paths from '../paths.js';
3
3
  import * as interfaces from '../interfaces/index.js';
4
4
  import { logger } from '../tsbundle.logging.js';
5
+ import {
6
+ cleanupBuildStage,
7
+ createBuildStage,
8
+ getChunkNamespace,
9
+ publishGeneratedArtifacts,
10
+ } from '../mod_output/artifactpublisher.js';
5
11
 
6
12
  export class TsBundleProcess {
7
13
  constructor() {
@@ -36,22 +42,33 @@ export class TsBundleProcess {
36
42
  }
37
43
  }
38
44
 
39
- /**
40
- * creates a bundle for the test enviroment
41
- */
42
- public async buildTest(fromArg: string, toArg: string, argvArg: any) {
45
+ private async build(
46
+ fromArg: string,
47
+ toArg: string,
48
+ argvArg: interfaces.ICliOptions,
49
+ productionArg: boolean,
50
+ ): Promise<void> {
43
51
  const aliases = await this.getAliases();
52
+ const logicalOutputName = plugins.path.basename(argvArg.chunkOwner || toArg);
53
+ const chunkNamespace = getChunkNamespace(logicalOutputName);
44
54
  const outputDir = plugins.path.dirname(toArg);
45
55
  const outputFilename = plugins.path.basename(toArg);
56
+ const stage = createBuildStage(outputDir);
57
+ const stageMainPath = plugins.path.join(stage.stageDirectory, outputFilename);
46
58
 
47
59
  const config = {
48
- mode: 'development' as const,
60
+ mode: productionArg ? 'production' as const : 'development' as const,
49
61
  entry: {
50
62
  main: fromArg,
51
63
  },
52
64
  output: {
53
- path: outputDir,
65
+ path: stage.stageDirectory,
54
66
  filename: outputFilename,
67
+ chunkFilename: `chunks/${chunkNamespace}/[name]-[contenthash].js`,
68
+ assetModuleFilename: `chunks/${chunkNamespace}/[name]-[contenthash][ext]`,
69
+ cssFilename: `chunks/${chunkNamespace}/[name]-[contenthash].css`,
70
+ cssChunkFilename: `chunks/${chunkNamespace}/[name]-[contenthash].css`,
71
+ webassemblyModuleFilename: `chunks/${chunkNamespace}/[hash].module.wasm`,
55
72
  module: true,
56
73
  library: {
57
74
  type: 'module' as const,
@@ -80,6 +97,12 @@ export class TsBundleProcess {
80
97
  transform: {
81
98
  decoratorVersion: '2022-03',
82
99
  },
100
+ ...(productionArg ? {
101
+ minify: {
102
+ compress: true,
103
+ mangle: true,
104
+ },
105
+ } : {}),
83
106
  },
84
107
  },
85
108
  },
@@ -87,139 +110,84 @@ export class TsBundleProcess {
87
110
  },
88
111
  ],
89
112
  },
113
+ ...(productionArg ? {
114
+ optimization: {
115
+ minimize: true,
116
+ concatenateModules: true,
117
+ usedExports: true,
118
+ sideEffects: true,
119
+ },
120
+ } : {}),
90
121
  };
91
122
 
92
- return new Promise((resolve, reject) => {
93
- plugins.rspack(config, (err, stats) => {
94
- if (err) {
95
- console.error(err.stack || err);
96
- reject(err);
97
- return;
98
- }
99
- if (!stats) {
100
- reject(new Error('Rspack did not return stats'));
101
- return;
102
- }
103
-
104
- if (stats.hasErrors()) {
105
- console.error(stats.toString());
106
- reject(new Error('Build failed with errors'));
107
- return;
108
- }
109
-
110
- console.log(
111
- stats.toString({
112
- colors: true,
113
- modules: false,
114
- children: false,
115
- chunks: false,
116
- chunkModules: false,
117
- }),
118
- );
123
+ try {
124
+ await new Promise<void>((resolve, reject) => {
125
+ plugins.rspack(config, (err, stats) => {
126
+ if (err) {
127
+ console.error(err.stack || err);
128
+ reject(err);
129
+ return;
130
+ }
131
+ if (!stats) {
132
+ reject(new Error('Rspack did not return stats'));
133
+ return;
134
+ }
135
+
136
+ if (stats.hasErrors()) {
137
+ console.error(stats.toString());
138
+ reject(new Error('Build failed with errors'));
139
+ return;
140
+ }
141
+
142
+ console.log(
143
+ stats.toString({
144
+ colors: true,
145
+ modules: false,
146
+ children: false,
147
+ chunks: false,
148
+ chunkModules: false,
149
+ }),
150
+ );
119
151
 
120
- resolve(undefined);
152
+ resolve();
153
+ });
154
+ });
155
+ await publishGeneratedArtifacts({
156
+ sourceDirectory: stage.stageDirectory,
157
+ sourceMainPath: stageMainPath,
158
+ targetPath: toArg,
159
+ logicalOutputName,
160
+ chunkNamespace,
161
+ sourceMapsEnabled: argvArg.sourcemap !== false,
121
162
  });
122
- });
163
+ } finally {
164
+ cleanupBuildStage(stage.stageDirectory, outputDir, stage.marker);
165
+ }
166
+ }
167
+
168
+ /**
169
+ * creates a bundle for the test enviroment
170
+ */
171
+ public async buildTest(
172
+ fromArg: string,
173
+ toArg: string,
174
+ argvArg: interfaces.ICliOptions,
175
+ ): Promise<void> {
176
+ await this.build(fromArg, toArg, argvArg, false);
123
177
  }
124
178
 
125
179
  /**
126
180
  * creates a bundle for the production environment
127
181
  */
128
- public async buildProduction(fromArg: string, toArg: string, argvArg: any) {
182
+ public async buildProduction(
183
+ fromArg: string,
184
+ toArg: string,
185
+ argvArg: interfaces.ICliOptions,
186
+ ): Promise<void> {
129
187
  console.log('rspack specific:');
130
188
  console.log(`from: ${fromArg}`);
131
189
  console.log(`to: ${toArg}`);
132
-
133
- const aliases = await this.getAliases();
134
- const outputDir = plugins.path.dirname(toArg);
135
- const outputFilename = plugins.path.basename(toArg);
136
-
137
- const config = {
138
- mode: 'production' as const,
139
- entry: {
140
- main: fromArg,
141
- },
142
- output: {
143
- path: outputDir,
144
- filename: outputFilename,
145
- module: true,
146
- library: {
147
- type: 'module' as const,
148
- },
149
- },
150
- devtool: (argvArg.sourcemap === false ? false : 'source-map') as false | 'source-map',
151
- resolve: {
152
- alias: aliases,
153
- extensions: ['.ts', '.tsx', '.js', '.jsx'],
154
- },
155
- module: {
156
- rules: [
157
- {
158
- test: /\.tsx?$/,
159
- exclude: /node_modules/,
160
- use: {
161
- loader: 'builtin:swc-loader',
162
- options: {
163
- jsc: {
164
- parser: {
165
- syntax: 'typescript',
166
- tsx: true,
167
- decorators: true,
168
- },
169
- target: 'es2022',
170
- transform: {
171
- decoratorVersion: '2022-03',
172
- },
173
- minify: {
174
- compress: true,
175
- mangle: true,
176
- },
177
- },
178
- },
179
- },
180
- type: 'javascript/auto',
181
- },
182
- ],
183
- },
184
- optimization: {
185
- minimize: true,
186
- concatenateModules: true,
187
- usedExports: true,
188
- sideEffects: true,
189
- },
190
- };
191
-
192
- return new Promise((resolve, reject) => {
193
- plugins.rspack(config, (err, stats) => {
194
- if (err) {
195
- console.error(err.stack || err);
196
- reject(err);
197
- return;
198
- }
199
- if (!stats) {
200
- reject(new Error('Rspack did not return stats'));
201
- return;
202
- }
203
-
204
- if (stats.hasErrors()) {
205
- console.error(stats.toString());
206
- reject(new Error('Build failed with errors'));
207
- return;
208
- }
209
-
210
- console.log(
211
- stats.toString({
212
- colors: true,
213
- modules: false,
214
- children: false,
215
- chunks: false,
216
- chunkModules: false,
217
- }),
218
- );
219
-
220
- resolve(undefined);
221
- });
222
- });
190
+ await this.build(fromArg, toArg, argvArg, true);
223
191
  }
224
192
  }
225
193
 
package/ts/plugins.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  // node native
2
2
  import * as path from 'path';
3
+ import * as crypto from 'node:crypto';
3
4
  import * as fsSync from 'node:fs';
4
5
  import * as fsPromises from 'node:fs/promises';
5
6
  import * as os from 'node:os';
7
+ import { setTimeout as delay } from 'node:timers/promises';
6
8
 
7
- export { fsPromises, fsSync, os, path };
9
+ export { crypto, delay, fsPromises, fsSync, os, path };
8
10
 
9
11
  // pushrocks scope
10
12
  import * as smartconfig from '@push.rocks/smartconfig';
@@ -9,7 +9,6 @@ export class TsBundle {
9
9
  toArg: string = './dist_bundle/bundle.js',
10
10
  argvArg: interfaces.ICliOptions,
11
11
  ) {
12
- const done = plugins.smartpromise.defer();
13
12
  const getBundlerPath = () => {
14
13
  switch (argvArg.bundler) {
15
14
  case 'rolldown':
@@ -49,20 +48,109 @@ export class TsBundle {
49
48
  },
50
49
  },
51
50
  );
52
- const childProcess = await threadsimple.start();
53
- childProcess.on('exit', (status) => {
54
- if (status !== 0) {
55
- done.reject(new Error(`Bundle build failed with exit code ${status}`));
56
- } else {
57
- done.resolve();
51
+ const startPromise = threadsimple.start();
52
+ const childProcess = threadsimple.threadChildProcess;
53
+ if (!childProcess) {
54
+ await startPromise;
55
+ throw new Error('ThreadSimple.start() did not expose its child process synchronously');
56
+ }
57
+ await new Promise<void>((resolve, reject) => {
58
+ let settled = false;
59
+ let startSettled = false;
60
+ let terminal = false;
61
+ let firstError: Error | undefined;
62
+ let terminationRequested = false;
63
+ let forceKillTimer: ReturnType<typeof setTimeout> | undefined;
64
+ const cleanupListeners = (): void => {
65
+ childProcess.removeListener('error', handleError);
66
+ childProcess.removeListener('exit', handleExit);
67
+ childProcess.removeListener('close', handleClose);
68
+ };
69
+ const clearForceKillTimer = (): void => {
70
+ if (forceKillTimer) {
71
+ clearTimeout(forceKillTimer);
72
+ forceKillTimer = undefined;
73
+ }
74
+ };
75
+ const settleIfComplete = (): void => {
76
+ if (settled || !startSettled || !terminal) {
77
+ return;
78
+ }
79
+ settled = true;
80
+ clearForceKillTimer();
81
+ cleanupListeners();
82
+ if (firstError) {
83
+ reject(firstError);
84
+ } else {
85
+ resolve();
86
+ }
87
+ };
88
+ const isChildLive = (): boolean => (
89
+ childProcess.exitCode === null && childProcess.signalCode === null
90
+ );
91
+ const requestTermination = (): void => {
92
+ if (terminationRequested || !childProcess.pid || !isChildLive()) {
93
+ return;
94
+ }
95
+ terminationRequested = true;
96
+ forceKillTimer = setTimeout(() => {
97
+ if (isChildLive()) {
98
+ try {
99
+ childProcess.kill('SIGKILL');
100
+ } catch {
101
+ // The first child-process failure remains the actionable error.
102
+ }
103
+ }
104
+ }, 5_000);
105
+ try {
106
+ childProcess.kill('SIGTERM');
107
+ } catch {
108
+ // The first child-process failure remains the actionable error.
109
+ }
110
+ };
111
+ const handleError = (errorArg: Error): void => {
112
+ firstError ??= errorArg;
113
+ requestTermination();
114
+ };
115
+ const handleTerminal = (
116
+ statusArg: number | null,
117
+ signalArg: NodeJS.Signals | null,
118
+ ): void => {
119
+ if (terminal) {
120
+ return;
121
+ }
122
+ terminal = true;
123
+ clearForceKillTimer();
124
+ if (!firstError && statusArg !== 0) {
125
+ const exitDetail = statusArg === null ? `signal ${signalArg}` : `exit code ${statusArg}`;
126
+ firstError = new Error(`Bundle build failed with ${exitDetail}`);
127
+ }
128
+ settleIfComplete();
129
+ };
130
+ const handleExit = (statusArg: number | null, signalArg: NodeJS.Signals | null): void => {
131
+ handleTerminal(statusArg, signalArg);
132
+ };
133
+ const handleClose = (statusArg: number | null, signalArg: NodeJS.Signals | null): void => {
134
+ handleTerminal(statusArg, signalArg);
135
+ };
136
+ childProcess.on('error', handleError);
137
+ childProcess.once('exit', handleExit);
138
+ childProcess.once('close', handleClose);
139
+ void startPromise.then(
140
+ () => {
141
+ startSettled = true;
142
+ settleIfComplete();
143
+ },
144
+ (errorArg: unknown) => {
145
+ startSettled = true;
146
+ firstError ??= errorArg instanceof Error ? errorArg : new Error(String(errorArg));
147
+ requestTermination();
148
+ settleIfComplete();
149
+ },
150
+ );
151
+ if (childProcess.exitCode !== null || childProcess.signalCode !== null) {
152
+ handleTerminal(childProcess.exitCode, childProcess.signalCode);
58
153
  }
59
154
  });
60
- await done.promise;
61
- if (argvArg.sourcemap === false) {
62
- const outputPath = plugins.path.isAbsolute(toArg)
63
- ? toArg
64
- : plugins.path.resolve(cwdArg, toArg);
65
- await plugins.fsPromises.rm(`${outputPath}.map`, { force: true });
66
- }
67
155
  }
68
156
  }