@lakutata/builder 3.1.1 → 3.1.3
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/dist/esm/lib/Bundle.js +10 -4
- package/dist/esm/lib/Files.js +44 -5
- package/dist/esm/lib/Native.js +58 -4
- package/dist/types/lib/Files.d.ts +11 -1
- package/dist/types/lib/Native.d.ts +23 -1
- package/package.json +1 -1
package/dist/esm/lib/Bundle.js
CHANGED
|
@@ -98,7 +98,7 @@ export async function bundleProject(options) {
|
|
|
98
98
|
throw new Error(`The entry ${path.relative(process.cwd(), entry) || entry} does not exist: build the project first`);
|
|
99
99
|
const outfile = path.resolve(project, options.outfile ?? path.join('dist', `${name}.cjs`));
|
|
100
100
|
const collector = new NativeCollector(project, options.runtime);
|
|
101
|
-
const files = new FileCollector(project);
|
|
101
|
+
const files = new FileCollector(project, { packageOf: (file) => collector.addonPackageOf(file), nativeFilesOf: (root, directory) => collector.nativeFilesOf(root, directory) });
|
|
102
102
|
//esbuild is loaded by the commands which need it
|
|
103
103
|
const { build } = await import('esbuild');
|
|
104
104
|
//The application, and each script of its workers (the bundler does not follow new Worker()): their native packages
|
|
@@ -147,9 +147,15 @@ export async function bundleProject(options) {
|
|
|
147
147
|
const resourcesDirectory = path.resolve(path.dirname(outfile), options.directories?.resources ?? `${base}.resources`);
|
|
148
148
|
//The scripts of the workers, until the ones they start are bundled too
|
|
149
149
|
const scripts = new Map();
|
|
150
|
+
//The files of every bundle, the application's and its workers'
|
|
151
|
+
const inputs = new Set(application.inputs);
|
|
150
152
|
for (let pending = [...files.scripts]; pending.length; pending = [...files.scripts].filter(([at]) => !scripts.has(at))) {
|
|
151
|
-
for (const [at, script] of pending)
|
|
152
|
-
|
|
153
|
+
for (const [at, script] of pending) {
|
|
154
|
+
const bundled = await bundle(script, path.join(nativeDirectory, ...at.split('/')));
|
|
155
|
+
scripts.set(at, bundled.code);
|
|
156
|
+
for (const input of bundled.inputs)
|
|
157
|
+
inputs.add(input);
|
|
158
|
+
}
|
|
153
159
|
}
|
|
154
160
|
files.warnBundled(application.inputs);
|
|
155
161
|
const embedded = options.native === 'embedded';
|
|
@@ -221,6 +227,6 @@ export async function bundleProject(options) {
|
|
|
221
227
|
resourcesDirectory: resources.files.length && !embedded ? resourcesDirectory : undefined,
|
|
222
228
|
workers: [...new Set([...scripts.keys()].map((at) => files.nameOf(files.scripts.get(at))))].sort(),
|
|
223
229
|
files: [...files.roots].filter((root) => !files.scriptPaths().has(root)).map((root) => files.nameOf(root)).sort(),
|
|
224
|
-
warnings: files.warnings
|
|
230
|
+
warnings: [...files.warnings, ...collector.unshippedAddons(inputs, files.files.values())]
|
|
225
231
|
};
|
|
226
232
|
}
|
package/dist/esm/lib/Files.js
CHANGED
|
@@ -291,8 +291,10 @@ export class FileCollector {
|
|
|
291
291
|
/**
|
|
292
292
|
* @param project The directory of the project: the scripts are named from it (as the modules the bundler gives, its
|
|
293
293
|
* real path)
|
|
294
|
+
* @param addons The packages sharing the framework and holding addons
|
|
294
295
|
*/
|
|
295
|
-
constructor(project) {
|
|
296
|
+
constructor(project, addons) {
|
|
297
|
+
this.addons = addons;
|
|
296
298
|
/**
|
|
297
299
|
* The scripts: their paths in the native tree, and their files
|
|
298
300
|
*/
|
|
@@ -354,6 +356,7 @@ export class FileCollector {
|
|
|
354
356
|
const directory = path.resolve(path.join(found.base ?? path.dirname(file), prefix.slice(0, cut + 1)));
|
|
355
357
|
const rest = [...prefix.slice(cut + 1) ? [{ text: prefix.slice(cut + 1) }] : [], ...found.parts.slice(leading.length)];
|
|
356
358
|
const targets = { directory: directory, rest: rest, scripts: [], files: [], roots: [] };
|
|
359
|
+
const addons = this.addons?.packageOf(file);
|
|
357
360
|
//A file: bundled when it is a script, copied otherwise; a directory of data: its files copied
|
|
358
361
|
const take = (at, strict) => {
|
|
359
362
|
const stats = statSync(at);
|
|
@@ -362,8 +365,19 @@ export class FileCollector {
|
|
|
362
365
|
targets.roots.push(at);
|
|
363
366
|
}
|
|
364
367
|
else if (stats.isDirectory()) {
|
|
365
|
-
//A directory
|
|
366
|
-
|
|
368
|
+
//A directory of the package of addons of the module, bundled with the application: its native files
|
|
369
|
+
//(its addons found from it, as node-gyp-build finds them)
|
|
370
|
+
if (addons && (at + path.sep).startsWith(addons + path.sep)) {
|
|
371
|
+
targets.files.push(...this.addons.nativeFilesOf(addons, at));
|
|
372
|
+
targets.roots.push(at);
|
|
373
|
+
//The directory named exactly (the package above the module): the files are named from it
|
|
374
|
+
if (strict) {
|
|
375
|
+
targets.directory = at;
|
|
376
|
+
targets.rest = [];
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
else if ((file + path.sep).startsWith(at + path.sep)) {
|
|
380
|
+
//A directory holding the module (the application, its packages), or holding code: not shipped
|
|
367
381
|
if (strict)
|
|
368
382
|
targets.refused = 'ancestor';
|
|
369
383
|
}
|
|
@@ -452,8 +466,29 @@ export class FileCollector {
|
|
|
452
466
|
const members = new Map();
|
|
453
467
|
const constructors = new Set();
|
|
454
468
|
const namespaces = new Set();
|
|
469
|
+
//The names of the properties (a.__dirname, {__dirname: a}, {__dirname}), which are not to be rewritten; whether the
|
|
470
|
+
//module declares a __dirname of its own (a variable, a parameter: the name is not the directory of the module)
|
|
471
|
+
const properties = new Set();
|
|
472
|
+
let declaresDirname = false;
|
|
473
|
+
const names = (pattern) => {
|
|
474
|
+
let found = false;
|
|
475
|
+
walk(pattern, (node) => {
|
|
476
|
+
if (node.type === 'Identifier' && node.name === '__dirname')
|
|
477
|
+
found = true;
|
|
478
|
+
});
|
|
479
|
+
return found;
|
|
480
|
+
};
|
|
455
481
|
const remember = (values, name, value) => values.set(name, values.has(name) ? null : value);
|
|
456
482
|
walk(program, (node) => {
|
|
483
|
+
if (node.type === 'MemberExpression' && !node.computed)
|
|
484
|
+
properties.add(node.property);
|
|
485
|
+
if (node.type === 'Property' && !node.computed) {
|
|
486
|
+
properties.add(node.key);
|
|
487
|
+
if (node.shorthand)
|
|
488
|
+
properties.add(node.value);
|
|
489
|
+
}
|
|
490
|
+
if ((node.type === 'VariableDeclarator' && names(node.id)) || (node.type === 'CatchClause' && node.param && names(node.param)) || (/Function/.test(node.type) && node.params.some(names)))
|
|
491
|
+
declaresDirname = true;
|
|
457
492
|
if (node.type === 'ImportDeclaration' && WORKER_THREADS.has(node.source.value)) {
|
|
458
493
|
for (const specifier of node.specifiers) {
|
|
459
494
|
if (specifier.type === 'ImportSpecifier' && (specifier.imported.name ?? specifier.imported.value) === 'Worker')
|
|
@@ -484,11 +519,15 @@ export class FileCollector {
|
|
|
484
519
|
if (node.type === 'AssignmentExpression' && node.operator === '=' && node.left.type === 'MemberExpression' && node.left.object.type === 'ThisExpression' && !node.left.computed)
|
|
485
520
|
remember(members, node.left.property.name, node.right);
|
|
486
521
|
});
|
|
487
|
-
//The paths to files, wherever they are written
|
|
522
|
+
//The paths to files, wherever they are written. In a module of a package of addons bundled with the application,
|
|
523
|
+
//the directory of the module itself too (given to node-gyp-build): the native files of the package are there, in
|
|
524
|
+
//the tree (a __dirname the module declares is its own: the value it is given, rewritten when it is the directory)
|
|
525
|
+
const ownDirectory = !!this.addons?.packageOf(file);
|
|
526
|
+
const isOwnDirectory = (node) => ownDirectory && isDirname(node) && !properties.has(node) && !(node.type === 'Identifier' && declaresDirname);
|
|
488
527
|
const edits = [];
|
|
489
528
|
const followed = new Set();
|
|
490
529
|
walk(program, (node) => {
|
|
491
|
-
const found = scriptPathOf(node, source, file);
|
|
530
|
+
const found = scriptPathOf(node, source, file) ?? (isOwnDirectory(node) ? { kind: 'path', parts: [] } : undefined);
|
|
492
531
|
if (!found)
|
|
493
532
|
return true;
|
|
494
533
|
const targets = this.targetsOf(found, file);
|
package/dist/esm/lib/Native.js
CHANGED
|
@@ -227,7 +227,7 @@ export class NativeCollector {
|
|
|
227
227
|
}
|
|
228
228
|
/**
|
|
229
229
|
* Whether a package is left out of the bundle: it holds addons, or one of its dependencies does (it loads them,
|
|
230
|
-
* with computed paths often), and it is
|
|
230
|
+
* with computed paths often), and it is neither the project nor a package sharing the framework
|
|
231
231
|
* @param root
|
|
232
232
|
*/
|
|
233
233
|
isNative(root) {
|
|
@@ -236,14 +236,68 @@ export class NativeCollector {
|
|
|
236
236
|
const info = this.package(root);
|
|
237
237
|
if (!info)
|
|
238
238
|
return false;
|
|
239
|
-
if (info.addons)
|
|
240
|
-
return true;
|
|
241
239
|
//A package of the framework, or built on it, shares it with the application: bundled with it (one copy of its
|
|
242
|
-
//classes, its container), its native dependencies loaded by their names, which the bundler follows
|
|
240
|
+
//classes, its container), its native dependencies loaded by their names, which the bundler follows, and its own
|
|
241
|
+
//addons loaded from the tree (a require of the addon, or a path from its module: the bundler follows both)
|
|
243
242
|
if (sharesFramework(info.manifest))
|
|
244
243
|
return false;
|
|
244
|
+
if (info.addons)
|
|
245
|
+
return true;
|
|
245
246
|
return this.dependencies(root).some(({ root: dependency }) => !!this.package(dependency)?.addons);
|
|
246
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* The package sharing the framework and holding addons a file belongs to: bundled with the application, the paths
|
|
250
|
+
* to its own directories ship their native files (its addons, its manifest, its data)
|
|
251
|
+
* @param file
|
|
252
|
+
* @returns its directory, undefined for a file of another package
|
|
253
|
+
*/
|
|
254
|
+
addonPackageOf(file) {
|
|
255
|
+
const root = this.packageRoot(file);
|
|
256
|
+
if (!root || root === this.project)
|
|
257
|
+
return undefined;
|
|
258
|
+
const info = this.package(root);
|
|
259
|
+
return info?.addons && sharesFramework(info.manifest) ? root : undefined;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* The native files of a directory of a package holding addons: the files node loads at run time (its addons, its
|
|
263
|
+
* libraries, its manifest, its data), without its scripts (bundled) and what node never loads
|
|
264
|
+
* @param root the directory of the package
|
|
265
|
+
* @param directory
|
|
266
|
+
*/
|
|
267
|
+
nativeFilesOf(root, directory) {
|
|
268
|
+
const cargo = existsSync(path.join(root, 'Cargo.toml'));
|
|
269
|
+
return packageFiles(directory).filter((file) => {
|
|
270
|
+
const relative = path.relative(root, file).split(path.sep).join('/');
|
|
271
|
+
return !SCRIPT.test(file) && !UNUSED.test(file) && !BUILD_FILES.test(relative) && !(cargo && relative.startsWith('target/'));
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* The addons of the packages sharing the framework bundled with the application which are not shipped: none of
|
|
276
|
+
* their addons is loaded by a require or a path the bundler follows (they would not be found at run time)
|
|
277
|
+
* @param inputs the files bundled
|
|
278
|
+
* @param shipped the files of the tree (the addons loaded by a require, the files read by a path from a module)
|
|
279
|
+
* @returns the warnings, one by package
|
|
280
|
+
*/
|
|
281
|
+
unshippedAddons(inputs, shipped) {
|
|
282
|
+
const files = new Set([...shipped, ...this.addons.values()]);
|
|
283
|
+
const roots = new Set();
|
|
284
|
+
for (const input of inputs) {
|
|
285
|
+
const root = this.packageRoot(input);
|
|
286
|
+
if (root && root !== this.project)
|
|
287
|
+
roots.add(root);
|
|
288
|
+
}
|
|
289
|
+
const warnings = [];
|
|
290
|
+
for (const root of [...roots].sort()) {
|
|
291
|
+
const info = this.package(root);
|
|
292
|
+
if (!info?.addons || !sharesFramework(info.manifest))
|
|
293
|
+
continue;
|
|
294
|
+
const addons = packageFiles(root).filter((file) => ADDON.test(file));
|
|
295
|
+
if (addons.some((addon) => files.has(addon)))
|
|
296
|
+
continue;
|
|
297
|
+
warnings.push(`${info.name}: its native addon (${addons.map((addon) => path.relative(root, addon).split(path.sep).join('/')).join(', ')}) is not shipped: the bundler does not follow how it is loaded. A package built on the framework is bundled with the application: load its addon by a require (require('./build/Release/addon.node')) or by a path from its module (path.join(__dirname, 'prebuilds', \`\${process.platform}-\${process.arch}\`, 'addon.node'), new URL('./addon.node', import.meta.url))`);
|
|
298
|
+
}
|
|
299
|
+
return warnings;
|
|
300
|
+
}
|
|
247
301
|
/**
|
|
248
302
|
* Resolve an import as esbuild does
|
|
249
303
|
* @param build
|
|
@@ -27,6 +27,14 @@ type ScriptPath = {
|
|
|
27
27
|
*/
|
|
28
28
|
base?: string;
|
|
29
29
|
};
|
|
30
|
+
/**
|
|
31
|
+
* The packages sharing the framework and holding addons, bundled with the application: the package of a file, the
|
|
32
|
+
* native files of one of its directories (shipped by the paths to its own directories)
|
|
33
|
+
*/
|
|
34
|
+
export type AddonPackages = {
|
|
35
|
+
packageOf(file: string): string | undefined;
|
|
36
|
+
nativeFilesOf(root: string, directory: string): string[];
|
|
37
|
+
};
|
|
30
38
|
/**
|
|
31
39
|
* What a path from a module gives: the directory of its pattern, the parts from it, the scripts to bundle and the files
|
|
32
40
|
* to copy (the files of its directories of data), or why it cannot be shipped
|
|
@@ -54,6 +62,7 @@ type Targets = {
|
|
|
54
62
|
* paths their users give)
|
|
55
63
|
*/
|
|
56
64
|
export declare class FileCollector {
|
|
65
|
+
protected readonly addons?: AddonPackages | undefined;
|
|
57
66
|
/**
|
|
58
67
|
* The scripts: their paths in the native tree, and their files
|
|
59
68
|
*/
|
|
@@ -71,8 +80,9 @@ export declare class FileCollector {
|
|
|
71
80
|
/**
|
|
72
81
|
* @param project The directory of the project: the scripts are named from it (as the modules the bundler gives, its
|
|
73
82
|
* real path)
|
|
83
|
+
* @param addons The packages sharing the framework and holding addons
|
|
74
84
|
*/
|
|
75
|
-
constructor(project: string);
|
|
85
|
+
constructor(project: string, addons?: AddonPackages | undefined);
|
|
76
86
|
/**
|
|
77
87
|
* The key of a directory in the tree ("/" separated): its path from the project; outside of the project (the
|
|
78
88
|
* packages of a workspace, linked), its path from the node_modules holding it, else a hash of its path (the paths of
|
|
@@ -121,10 +121,32 @@ export declare class NativeCollector {
|
|
|
121
121
|
}[];
|
|
122
122
|
/**
|
|
123
123
|
* Whether a package is left out of the bundle: it holds addons, or one of its dependencies does (it loads them,
|
|
124
|
-
* with computed paths often), and it is
|
|
124
|
+
* with computed paths often), and it is neither the project nor a package sharing the framework
|
|
125
125
|
* @param root
|
|
126
126
|
*/
|
|
127
127
|
protected isNative(root: string | undefined): root is string;
|
|
128
|
+
/**
|
|
129
|
+
* The package sharing the framework and holding addons a file belongs to: bundled with the application, the paths
|
|
130
|
+
* to its own directories ship their native files (its addons, its manifest, its data)
|
|
131
|
+
* @param file
|
|
132
|
+
* @returns its directory, undefined for a file of another package
|
|
133
|
+
*/
|
|
134
|
+
addonPackageOf(file: string): string | undefined;
|
|
135
|
+
/**
|
|
136
|
+
* The native files of a directory of a package holding addons: the files node loads at run time (its addons, its
|
|
137
|
+
* libraries, its manifest, its data), without its scripts (bundled) and what node never loads
|
|
138
|
+
* @param root the directory of the package
|
|
139
|
+
* @param directory
|
|
140
|
+
*/
|
|
141
|
+
nativeFilesOf(root: string, directory: string): string[];
|
|
142
|
+
/**
|
|
143
|
+
* The addons of the packages sharing the framework bundled with the application which are not shipped: none of
|
|
144
|
+
* their addons is loaded by a require or a path the bundler follows (they would not be found at run time)
|
|
145
|
+
* @param inputs the files bundled
|
|
146
|
+
* @param shipped the files of the tree (the addons loaded by a require, the files read by a path from a module)
|
|
147
|
+
* @returns the warnings, one by package
|
|
148
|
+
*/
|
|
149
|
+
unshippedAddons(inputs: Iterable<string>, shipped: Iterable<string>): string[];
|
|
128
150
|
/**
|
|
129
151
|
* Resolve an import as esbuild does
|
|
130
152
|
* @param build
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lakutata/builder",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.3",
|
|
4
4
|
"description": "The builder of the applications of the lakutata framework: their bundles, their executables (Node.js, Bun) with their native addons and their resources.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"lakutata",
|