@git.zone/tsbundle 2.11.4 → 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 +7 -5
  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
@@ -2,13 +2,20 @@ 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 { workerPlugin } from './workerplugin.js';
6
+ import {
7
+ cleanupBuildStage,
8
+ createBuildStage,
9
+ getChunkNamespace,
10
+ publishGeneratedArtifacts,
11
+ } from '../mod_output/artifactpublisher.js';
5
12
 
6
13
  export class TsBundleProcess {
7
14
  constructor() {
8
15
  // Nothing here
9
16
  }
10
17
 
11
- public async getAliases() {
18
+ public async getAliases(): Promise<Record<string, string>> {
12
19
  try {
13
20
  const aliasObject: Record<string, string> = {};
14
21
  const tsconfigPath = plugins.path.join(paths.cwd, 'tsconfig.json');
@@ -27,56 +34,71 @@ export class TsBundleProcess {
27
34
  }
28
35
  }
29
36
  return aliasObject;
30
- } catch (error) {
37
+ } catch {
31
38
  return {};
32
39
  }
33
40
  }
34
41
 
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 esbuild = await plugins.esbuild.build({
41
- entryPoints: [fromArg],
42
- bundle: true,
43
- sourcemap: argvArg.sourcemap !== false,
44
- format: 'esm',
45
- target: 'es2022',
46
- keepNames: true,
47
- entryNames: plugins.path.parse(toArg).name,
48
- outdir: plugins.path.parse(toArg).dir,
49
- splitting: false,
50
- treeShaking: false,
51
- tsconfig: paths.tsconfigPath,
52
- alias: await this.getAliases(),
53
- });
42
+ private async build(
43
+ fromArg: string,
44
+ toArg: string,
45
+ argvArg: interfaces.ICliOptions,
46
+ productionArg: boolean,
47
+ ): Promise<void> {
48
+ const logicalOutputName = plugins.path.basename(argvArg.chunkOwner || toArg);
49
+ const chunkNamespace = getChunkNamespace(logicalOutputName);
50
+ const outputDirectory = plugins.path.dirname(toArg);
51
+ const stage = createBuildStage(outputDirectory);
52
+ const stageMainPath = plugins.path.join(stage.stageDirectory, plugins.path.basename(toArg));
53
+ try {
54
+ await plugins.esbuild.build({
55
+ absWorkingDir: process.cwd(),
56
+ entryPoints: [fromArg],
57
+ bundle: true,
58
+ sourcemap: argvArg.sourcemap !== false,
59
+ format: 'esm',
60
+ target: 'es2022',
61
+ minify: productionArg,
62
+ keepNames: true,
63
+ entryNames: plugins.path.parse(toArg).name,
64
+ outdir: stage.stageDirectory,
65
+ tsconfig: paths.tsconfigPath,
66
+ splitting: false,
67
+ treeShaking: false,
68
+ chunkNames: `chunks/${chunkNamespace}/[name]-[hash]`,
69
+ alias: await this.getAliases(),
70
+ plugins: [workerPlugin()],
71
+ });
72
+ await publishGeneratedArtifacts({
73
+ sourceDirectory: stage.stageDirectory,
74
+ sourceMainPath: stageMainPath,
75
+ targetPath: toArg,
76
+ logicalOutputName,
77
+ chunkNamespace,
78
+ sourceMapsEnabled: argvArg.sourcemap !== false,
79
+ });
80
+ } finally {
81
+ cleanupBuildStage(stage.stageDirectory, outputDirectory, stage.marker);
82
+ }
54
83
  }
55
84
 
56
- /**
57
- * creates a bundle for the production environment
58
- */
59
- public async buildProduction(fromArg: string, toArg: string, argvArg: any) {
60
- // create a bundle
85
+ public async buildTest(
86
+ fromArg: string,
87
+ toArg: string,
88
+ argvArg: interfaces.ICliOptions,
89
+ ): Promise<void> {
90
+ await this.build(fromArg, toArg, argvArg, false);
91
+ }
92
+
93
+ public async buildProduction(
94
+ fromArg: string,
95
+ toArg: string,
96
+ argvArg: interfaces.ICliOptions,
97
+ ): Promise<void> {
61
98
  console.log('esbuild specific:');
62
99
  console.log(`from: ${fromArg}`);
63
100
  console.log(`to: ${toArg}`);
64
- const esbuild = await plugins.esbuild.build({
65
- entryPoints: [fromArg],
66
- bundle: true,
67
- sourcemap: argvArg.sourcemap !== false,
68
- format: 'esm',
69
- target: 'es2022',
70
- minify: true,
71
- keepNames: true,
72
- entryNames: plugins.path.parse(toArg).name,
73
- outdir: plugins.path.parse(toArg).dir,
74
- tsconfig: paths.tsconfigPath,
75
- splitting: false,
76
- treeShaking: false,
77
- chunkNames: 'chunks/[name]-[hash]',
78
- alias: await this.getAliases(),
79
- });
101
+ await this.build(fromArg, toArg, argvArg, true);
80
102
  }
81
103
  }
82
104
 
@@ -121,15 +143,24 @@ const run = async () => {
121
143
  );
122
144
  }
123
145
  process.exit(0);
124
- } catch (error: any) {
146
+ } catch (error: unknown) {
125
147
  console.error('\n\x1b[31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m');
126
148
  console.error('\x1b[31m❌ BUILD FAILED\x1b[0m');
127
149
  console.error('\x1b[31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m\n');
128
150
 
129
- if (error.errors && Array.isArray(error.errors)) {
151
+ const errorData = typeof error === 'object' && error !== null
152
+ ? error as {
153
+ errors?: Array<{
154
+ location?: { file?: string; line?: number; column?: number };
155
+ text: string;
156
+ }>;
157
+ message?: string;
158
+ }
159
+ : undefined;
160
+ if (Array.isArray(errorData?.errors)) {
130
161
  // esbuild errors - format them nicely
131
- console.error(`Found ${error.errors.length} error(s):\n`);
132
- for (const err of error.errors) {
162
+ console.error(`Found ${errorData.errors.length} error(s):\n`);
163
+ for (const err of errorData.errors) {
133
164
  const file = err.location?.file || 'unknown';
134
165
  const line = err.location?.line || '?';
135
166
  const column = err.location?.column || '?';
@@ -137,7 +168,7 @@ const run = async () => {
137
168
  console.error(` ${err.text}\n`);
138
169
  }
139
170
  } else {
140
- console.error(error.message || error);
171
+ console.error(errorData?.message || error);
141
172
  }
142
173
 
143
174
  console.error('\x1b[31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m\n');
@@ -1,5 +1,12 @@
1
1
  export * from '../plugins.js';
2
2
 
3
- import esbuild from 'esbuild';
3
+ import esbuild, {
4
+ type BuildOptions,
5
+ type Loader,
6
+ type Plugin,
7
+ } from 'esbuild';
8
+ import MagicString from 'magic-string';
9
+ import ts from 'typescript';
4
10
 
5
- export { esbuild };
11
+ export { esbuild, MagicString, ts };
12
+ export type { BuildOptions, Loader, Plugin };
@@ -0,0 +1,328 @@
1
+ /*
2
+ * Derived from @chialab/esbuild-plugin-worker 0.19.2.
3
+ * Copyright (c) 2021 Chialab. Licensed under the MIT License.
4
+ * See third-party-notices.md in the published package.
5
+ */
6
+
7
+ import * as plugins from './plugins.js';
8
+
9
+ interface IWorkerPluginOptions {
10
+ constructors?: string[];
11
+ }
12
+
13
+ interface IWorkerReference {
14
+ argument: plugins.ts.NewExpression;
15
+ module: boolean;
16
+ value: string;
17
+ }
18
+
19
+ interface IWorkerBuildState {
20
+ dependencyGraph: Map<string, Set<string>>;
21
+ emittedWorkers: Map<string, Promise<string>>;
22
+ parentKey: string;
23
+ parentOutputDirectory: string;
24
+ }
25
+
26
+ const rootWorkerKey = 'tsbundle:main';
27
+
28
+ const hasDependencyPath = (
29
+ graphArg: Map<string, Set<string>>,
30
+ fromKeyArg: string,
31
+ targetKeyArg: string,
32
+ visitedArg = new Set<string>(),
33
+ ): boolean => {
34
+ if (fromKeyArg === targetKeyArg) {
35
+ return true;
36
+ }
37
+ if (visitedArg.has(fromKeyArg)) {
38
+ return false;
39
+ }
40
+ visitedArg.add(fromKeyArg);
41
+ for (const dependencyKey of graphArg.get(fromKeyArg) || []) {
42
+ if (hasDependencyPath(graphArg, dependencyKey, targetKeyArg, visitedArg)) {
43
+ return true;
44
+ }
45
+ }
46
+ return false;
47
+ };
48
+
49
+ const addWorkerDependency = (
50
+ graphArg: Map<string, Set<string>>,
51
+ parentKeyArg: string,
52
+ childKeyArg: string,
53
+ ): void => {
54
+ if (hasDependencyPath(graphArg, childKeyArg, parentKeyArg)) {
55
+ throw new Error(`Cyclic module-worker dependency detected: ${parentKeyArg} -> ${childKeyArg}`);
56
+ }
57
+ const dependencies = graphArg.get(parentKeyArg) || new Set<string>();
58
+ dependencies.add(childKeyArg);
59
+ graphArg.set(parentKeyArg, dependencies);
60
+ };
61
+
62
+ const getLoader = (filePathArg: string): plugins.Loader => {
63
+ switch (plugins.path.extname(filePathArg)) {
64
+ case '.tsx':
65
+ return 'tsx';
66
+ case '.jsx':
67
+ return 'jsx';
68
+ case '.ts':
69
+ case '.mts':
70
+ case '.cts':
71
+ return 'ts';
72
+ default:
73
+ return 'js';
74
+ }
75
+ };
76
+
77
+ const getScriptKind = (filePathArg: string): plugins.ts.ScriptKind => {
78
+ switch (plugins.path.extname(filePathArg)) {
79
+ case '.tsx':
80
+ return plugins.ts.ScriptKind.TSX;
81
+ case '.jsx':
82
+ return plugins.ts.ScriptKind.JSX;
83
+ case '.ts':
84
+ case '.mts':
85
+ case '.cts':
86
+ return plugins.ts.ScriptKind.TS;
87
+ default:
88
+ return plugins.ts.ScriptKind.JS;
89
+ }
90
+ };
91
+
92
+ const isImportMetaUrl = (nodeArg: plugins.ts.Node): boolean => {
93
+ return plugins.ts.isPropertyAccessExpression(nodeArg)
94
+ && nodeArg.name.text === 'url'
95
+ && plugins.ts.isMetaProperty(nodeArg.expression)
96
+ && nodeArg.expression.keywordToken === plugins.ts.SyntaxKind.ImportKeyword
97
+ && nodeArg.expression.name.text === 'meta';
98
+ };
99
+
100
+ const getWorkerConstructorName = (nodeArg: plugins.ts.Expression): string | undefined => {
101
+ if (plugins.ts.isIdentifier(nodeArg)) {
102
+ return nodeArg.text;
103
+ }
104
+ if (
105
+ plugins.ts.isPropertyAccessExpression(nodeArg)
106
+ && plugins.ts.isIdentifier(nodeArg.expression)
107
+ && ['window', 'globalThis', 'self', 'global'].includes(nodeArg.expression.text)
108
+ ) {
109
+ return nodeArg.name.text;
110
+ }
111
+ return undefined;
112
+ };
113
+
114
+ const isModuleWorker = (optionsArg: plugins.ts.Expression | undefined): boolean => {
115
+ if (!optionsArg || !plugins.ts.isObjectLiteralExpression(optionsArg)) {
116
+ return false;
117
+ }
118
+ return optionsArg.properties.some((property) => (
119
+ plugins.ts.isPropertyAssignment(property)
120
+ && property.name.getText() === 'type'
121
+ && plugins.ts.isStringLiteral(property.initializer)
122
+ && property.initializer.text === 'module'
123
+ ));
124
+ };
125
+
126
+ export const workerPlugin = ({
127
+ constructors = ['Worker', 'SharedWorker'],
128
+ }: IWorkerPluginOptions = {}, stateArg?: IWorkerBuildState): plugins.Plugin => {
129
+ const state: IWorkerBuildState = stateArg || {
130
+ dependencyGraph: new Map<string, Set<string>>(),
131
+ emittedWorkers: new Map<string, Promise<string>>(),
132
+ parentKey: rootWorkerKey,
133
+ parentOutputDirectory: '',
134
+ };
135
+
136
+ const plugin: plugins.Plugin = {
137
+ name: 'tsbundle-worker',
138
+ setup(pluginBuild) {
139
+ pluginBuild.onLoad({
140
+ filter: /\.[cm]?[jt]sx?$/,
141
+ namespace: 'file',
142
+ }, async (args) => {
143
+ const code = await plugins.fsPromises.readFile(args.path, 'utf8');
144
+ if (!constructors.some((constructorName) => code.includes(`new ${constructorName}`))) {
145
+ return undefined;
146
+ }
147
+
148
+ const sourceFile = plugins.ts.createSourceFile(
149
+ args.path,
150
+ code,
151
+ plugins.ts.ScriptTarget.Latest,
152
+ true,
153
+ getScriptKind(args.path),
154
+ );
155
+ const localClasses = new Set<string>();
156
+ const symbols = new Map<string, string>();
157
+ const references: IWorkerReference[] = [];
158
+
159
+ const collectDeclarations = (node: plugins.ts.Node): void => {
160
+ if (plugins.ts.isClassDeclaration(node) && node.name) {
161
+ localClasses.add(node.name.text);
162
+ } else if (
163
+ plugins.ts.isVariableDeclaration(node)
164
+ && plugins.ts.isIdentifier(node.name)
165
+ && node.initializer
166
+ && plugins.ts.isStringLiteral(node.initializer)
167
+ ) {
168
+ symbols.set(node.name.text, node.initializer.text);
169
+ }
170
+ plugins.ts.forEachChild(node, collectDeclarations);
171
+ };
172
+ collectDeclarations(sourceFile);
173
+
174
+ const collectReferences = (node: plugins.ts.Node): void => {
175
+ if (plugins.ts.isNewExpression(node)) {
176
+ const constructorName = getWorkerConstructorName(node.expression);
177
+ const argument = node.arguments?.[0];
178
+ if (
179
+ constructorName
180
+ && constructors.includes(constructorName)
181
+ && !localClasses.has(constructorName)
182
+ && argument
183
+ && plugins.ts.isNewExpression(argument)
184
+ && plugins.ts.isIdentifier(argument.expression)
185
+ && argument.expression.text === 'URL'
186
+ && argument.arguments?.length === 2
187
+ && isImportMetaUrl(argument.arguments[1])
188
+ ) {
189
+ const reference = argument.arguments[0];
190
+ const value = plugins.ts.isStringLiteral(reference)
191
+ ? reference.text
192
+ : plugins.ts.isIdentifier(reference)
193
+ ? symbols.get(reference.text)
194
+ : undefined;
195
+ if (value) {
196
+ references.push({
197
+ argument,
198
+ module: isModuleWorker(node.arguments?.[1]),
199
+ value,
200
+ });
201
+ }
202
+ }
203
+ }
204
+ plugins.ts.forEachChild(node, collectReferences);
205
+ };
206
+ collectReferences(sourceFile);
207
+ if (references.length === 0) {
208
+ return undefined;
209
+ }
210
+
211
+ const outdir = pluginBuild.initialOptions.outdir;
212
+ const chunkNames = pluginBuild.initialOptions.chunkNames;
213
+ if (!outdir || !chunkNames) {
214
+ throw new Error('Module workers require esbuild outdir and chunkNames options.');
215
+ }
216
+ const workerEntryPattern = chunkNames.replace('[name]', 'worker');
217
+ const workerOutputDirectoryRaw = plugins.path.dirname(workerEntryPattern);
218
+ const workerOutputDirectory = workerOutputDirectoryRaw === '.'
219
+ ? ''
220
+ : workerOutputDirectoryRaw;
221
+ if (
222
+ plugins.path.isAbsolute(workerOutputDirectory)
223
+ || workerOutputDirectory === '..'
224
+ || workerOutputDirectory.startsWith(`..${plugins.path.sep}`)
225
+ || /\[[^\]]+\]/.test(workerOutputDirectory)
226
+ ) {
227
+ throw new Error(`Module-worker output directory must stay fixed within esbuild outdir: ${chunkNames}`);
228
+ }
229
+
230
+ const magicString = new plugins.MagicString(code);
231
+ const watchFiles = new Set<string>([args.path]);
232
+ for (const reference of references) {
233
+ const resolved = await pluginBuild.resolve(reference.value, {
234
+ kind: 'dynamic-import',
235
+ importer: args.path,
236
+ namespace: 'file',
237
+ resolveDir: plugins.path.dirname(args.path),
238
+ });
239
+ if (resolved.external) {
240
+ continue;
241
+ }
242
+ if (!resolved.path) {
243
+ throw new Error(`Unable to resolve worker entry '${reference.value}' from ${args.path}.`);
244
+ }
245
+ watchFiles.add(resolved.path);
246
+
247
+ const canonicalWorkerPath = await plugins.fsPromises.realpath(resolved.path);
248
+ const cacheKey = `${reference.module ? 'module' : 'classic'}:${canonicalWorkerPath}`;
249
+ addWorkerDependency(state.dependencyGraph, state.parentKey, cacheKey);
250
+ let emittedWorkerPromise = state.emittedWorkers.get(cacheKey);
251
+ if (!emittedWorkerPromise) {
252
+ emittedWorkerPromise = (async () => {
253
+ const initialOptions = pluginBuild.initialOptions;
254
+ const workerResult = await plugins.esbuild.build({
255
+ ...initialOptions,
256
+ entryPoints: [resolved.path],
257
+ stdin: undefined,
258
+ outfile: undefined,
259
+ outdir,
260
+ entryNames: workerEntryPattern,
261
+ bundle: true,
262
+ format: reference.module ? 'esm' : 'iife',
263
+ platform: initialOptions.platform || 'browser',
264
+ splitting: false,
265
+ metafile: true,
266
+ write: true,
267
+ plugins: [workerPlugin({ constructors }, {
268
+ dependencyGraph: state.dependencyGraph,
269
+ emittedWorkers: state.emittedWorkers,
270
+ parentKey: cacheKey,
271
+ parentOutputDirectory: workerOutputDirectory,
272
+ })],
273
+ });
274
+ const outputEntry = Object.entries(workerResult.metafile.outputs).find(([, output]) => (
275
+ !!output.entryPoint && !output.cssBundle
276
+ ));
277
+ if (!outputEntry) {
278
+ throw new Error(`Worker build produced no JavaScript output for ${resolved.path}.`);
279
+ }
280
+
281
+ const outputPath = plugins.path.isAbsolute(outputEntry[0])
282
+ ? outputEntry[0]
283
+ : plugins.path.resolve(initialOptions.absWorkingDir || process.cwd(), outputEntry[0]);
284
+ const relativeOutputPath = plugins.path.relative(outdir, outputPath);
285
+ if (
286
+ relativeOutputPath === '..'
287
+ || relativeOutputPath.startsWith(`..${plugins.path.sep}`)
288
+ || plugins.path.isAbsolute(relativeOutputPath)
289
+ ) {
290
+ throw new Error(`Worker output escaped esbuild outdir: ${outputPath}`);
291
+ }
292
+ return relativeOutputPath;
293
+ })();
294
+ state.emittedWorkers.set(cacheKey, emittedWorkerPromise);
295
+ }
296
+
297
+ const emittedWorker = await emittedWorkerPromise;
298
+ const relativeWorkerPath = plugins.path.relative(
299
+ state.parentOutputDirectory || '.',
300
+ emittedWorker,
301
+ ).split(plugins.path.sep).join('/');
302
+ const workerUrl = relativeWorkerPath.startsWith('.')
303
+ ? relativeWorkerPath
304
+ : `./${relativeWorkerPath}`;
305
+ magicString.overwrite(
306
+ reference.argument.getStart(sourceFile),
307
+ reference.argument.end,
308
+ `new URL('${workerUrl}', import.meta.url).href`,
309
+ );
310
+ }
311
+
312
+ const sourceMap = magicString.generateMap({
313
+ source: plugins.path.basename(args.path),
314
+ includeContent: true,
315
+ hires: true,
316
+ });
317
+ return {
318
+ contents: `${magicString.toString()}\n//# sourceMappingURL=${sourceMap.toUrl()}`,
319
+ loader: getLoader(args.path),
320
+ resolveDir: plugins.path.dirname(args.path),
321
+ watchFiles: [...watchFiles],
322
+ };
323
+ });
324
+ },
325
+ };
326
+
327
+ return plugin;
328
+ };