Skip to content

Commit 18dcd4c

Browse files
chore: more refactoring
Signed-off-by: Victor Adossi <vadossi@cosmonic.com>
1 parent 7f17009 commit 18dcd4c

File tree

1 file changed

+140
-75
lines changed

1 file changed

+140
-75
lines changed

src/componentize.js

Lines changed: 140 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,46 @@
1+
import { fileURLToPath } from 'node:url';
2+
import { cwd, stdout, platform } from 'node:process';
3+
import { freemem } from 'node:os';
4+
import { spawnSync } from 'node:child_process';
5+
import { tmpdir } from 'node:os';
6+
import { resolve, join, dirname } from 'node:path';
7+
import { readFile, writeFile, mkdir, rm } from 'node:fs/promises';
8+
import { rmSync, existsSync } from 'node:fs';
9+
import { createHash } from 'node:crypto';
10+
111
import wizer from '@bytecodealliance/wizer';
212
import getWeval from '@bytecodealliance/weval';
313
import {
414
componentNew,
515
metadataAdd,
616
preview1AdapterReactorPath,
717
} from '@bytecodealliance/jco';
8-
import { spawnSync } from 'node:child_process';
9-
import { tmpdir } from 'node:os';
10-
import { resolve, join, dirname } from 'node:path';
11-
import { readFile, writeFile, mkdir, rm } from 'node:fs/promises';
12-
import { rmSync, existsSync } from 'node:fs';
13-
import { createHash } from 'node:crypto';
18+
1419
import {
1520
spliceBindings,
1621
stubWasi,
1722
} from '../lib/spidermonkey-embedding-splicer.js';
18-
import { fileURLToPath } from 'node:url';
19-
import { cwd, stdout, platform } from 'node:process';
23+
2024
export const { version } = JSON.parse(
2125
await readFile(new URL('../package.json', import.meta.url), 'utf8')
2226
);
23-
const isWindows = platform === 'win32';
2427

25-
function maybeWindowsPath(path) {
26-
if (!path) return path;
27-
if (!isWindows) return resolve(path);
28-
return '//?/' + resolve(path).replace(/\\/g, '/');
29-
}
30-
31-
/**
32-
* Clean up the given input string by removing the given patterns if
33-
* found as line prefixes.
34-
*/
35-
function stripLinesPrefixes(input, prefixPatterns) {
36-
return input.split('\n')
37-
.map(line => prefixPatterns.reduce((line, n) => line.replace(n, ''), line))
38-
.join('\n').trim();
39-
}
28+
/** Whether the current platform is windows */
29+
const IS_WINDOWS = platform === 'win32';
4030

41-
const WizerErrorCause = `Error: the \`componentize.wizer\` function trapped
31+
/** Prefix into wizer error output that indicates a error/trap */
32+
const WIZER_ERROR_CAUSE_PREFIX = `Error: the \`componentize.wizer\` function trapped
4233
4334
Caused by:`;
4435

45-
const WizerExitCode = "Exited with i32 exit status";
46-
47-
function parseWizerStderr(stderr) {
48-
let output = `${stderr}`;
49-
let causeStart = output.indexOf(WizerErrorCause);
50-
let exitCodeStart = output.indexOf(WizerExitCode);
51-
if (causeStart === -1 || exitCodeStart === -1) {
52-
return output;
53-
}
54-
55-
let causeEnd = output.indexOf('\n', exitCodeStart + 1);
56-
return `${output.substring(0, causeStart)}${output.substring(causeEnd)}`.trim();
57-
}
36+
/** Prefix into wizer error output that indicates exit status */
37+
const WIZER_EXIT_CODE_PREFIX = 'Exited with i32 exit status';
5838

59-
export async function componentize(opts,
60-
_deprecatedWitWorldOrOpts = undefined,
61-
_deprecatedOpts = undefined) {
39+
export async function componentize(
40+
opts,
41+
_deprecatedWitWorldOrOpts = undefined,
42+
_deprecatedOpts = undefined,
43+
) {
6244
let useOriginalSourceFile = true;
6345
let jsSource;
6446

@@ -74,27 +56,19 @@ export async function componentize(opts,
7456
} else {
7557
if (typeof _deprecatedWitWorldOrOpts !== 'object') {
7658
throw new Error(
77-
`componentize: second argument must be an object or a string, but is ${typeof _deprecatedWitWorldOrOpts}`
59+
`componentize: second argument must be an object or a string, but is ${typeof _deprecatedWitWorldOrOpts}`,
7860
);
7961
}
8062
opts = _deprecatedWitWorldOrOpts;
8163
}
8264
}
8365

84-
const tmpDir = join(
85-
tmpdir(),
86-
createHash('sha256')
87-
.update(Math.random().toString())
88-
.digest('hex')
89-
.slice(0, 12)
90-
);
91-
await mkdir(tmpDir);
92-
const sourceDir = join(tmpDir, 'sources');
93-
await mkdir(sourceDir);
66+
// Prepare a working diretory for use during componentization
67+
const { sourcesDir, baseDir: tmpDir } = prepWorkDir();
9468

9569
let {
9670
sourceName = 'source.js',
97-
sourcePath = join(sourceDir, sourceName),
71+
sourcePath = join(sourcesDir, sourceName),
9872
preview2Adapter = preview1AdapterReactorPath(),
9973
witPath,
10074
witWorld,
@@ -110,40 +84,30 @@ export async function componentize(opts,
11084
),
11185
} = opts;
11286

113-
const engine =
114-
opts.engine ||
115-
fileURLToPath(
116-
new URL(
117-
opts.enableAot
118-
? `../lib/starlingmonkey_embedding_weval.wasm`
119-
: `../lib/starlingmonkey_embedding${
120-
debugBuild ? '.debug' : ''
121-
}.wasm`,
122-
import.meta.url
123-
)
124-
);
87+
// Determine the path to the StarlingMonkey binary
88+
const engine = getEnginePath(opts);
12589

12690
let { wasm, jsBindings, exports, imports } = spliceBindings(
12791
await readFile(engine),
12892
witWorld,
12993
maybeWindowsPath(witPath),
13094
worldName,
131-
false
95+
false,
13296
);
13397

13498
const input = join(tmpDir, 'in.wasm');
13599
const output = join(tmpDir, 'out.wasm');
136100

137101
await writeFile(input, Buffer.from(wasm));
138-
await writeFile(join(sourceDir, "initializer.js"), jsBindings);
102+
await writeFile(join(sourcesDir, 'initializer.js'), jsBindings);
139103

140104
if (debugBindings) {
141105
console.log('--- JS Bindings ---');
142106
console.log(
143107
jsBindings
144108
.split('\n')
145109
.map((ln, idx) => `${(idx + 1).toString().padStart(4, ' ')} | ${ln}`)
146-
.join('\n')
110+
.join('\n'),
147111
);
148112
console.log('--- JS Imports ---');
149113
console.log(imports);
@@ -206,7 +170,7 @@ export async function componentize(opts,
206170
console.log(env);
207171
}
208172

209-
let initializerPath = join(sourceDir, 'initializer.js');
173+
let initializerPath = join(sourcesDir, 'initializer.js');
210174
sourcePath = maybeWindowsPath(sourcePath);
211175
let workspacePrefix = dirname(sourcePath);
212176

@@ -221,7 +185,7 @@ export async function componentize(opts,
221185
}
222186
let args = `--initializer-script-path ${initializerPath} ${sourcePath}`;
223187
runtimeArgs = runtimeArgs ? `${runtimeArgs} ${args}` : args;
224-
let preopens = [`--dir ${sourceDir}`];
188+
let preopens = [`--dir ${sourcesDir}`];
225189
if (opts.enableAot) {
226190
preopens.push(`--dir ${workspacePrefix}`);
227191
} else {
@@ -252,12 +216,12 @@ export async function componentize(opts,
252216
`-o ${output}`,
253217
],
254218
{
255-
stdio: [null, stdout, "pipe"],
219+
stdio: [null, stdout, 'pipe'],
256220
env,
257221
input: runtimeArgs,
258222
shell: true,
259223
encoding: 'utf-8',
260-
}
224+
},
261225
);
262226
} else {
263227
wizerProcess = spawnSync(
@@ -273,15 +237,16 @@ export async function componentize(opts,
273237
input,
274238
],
275239
{
276-
stdio: [null, stdout, "pipe"],
240+
stdio: [null, stdout, 'pipe'],
277241
env,
278242
input: runtimeArgs,
279243
shell: true,
280244
encoding: 'utf-8',
281-
}
245+
},
282246
);
283247
}
284248

249+
// If the wizer (or weval) process failed, parse the output and display to the user
285250
if (wizerProcess.status !== 0) {
286251
let wizerErr = parseWizerStderr(wizerProcess.stderr);
287252
let err = `Failed to initialize component:\n${wizerErr}`;
@@ -413,11 +378,111 @@ export async function componentize(opts,
413378

414379
// convert CABI import conventions to ESM import conventions
415380
imports = imports.map(([specifier, impt]) =>
416-
specifier === '$root' ? [impt, 'default'] : [specifier, impt]
381+
specifier === '$root' ? [impt, 'default'] : [specifier, impt],
417382
);
418383

419384
return {
420385
component,
421386
imports,
422387
};
423388
}
389+
390+
/**
391+
* Calculate the min stack size depending on free memory
392+
*
393+
* @param {number} freeMemoryBytes - Amount of free memory in the system, in bytes (if not provided, os.freemem() is used)
394+
* @returns {number} The minimum stack size that should be used as a default.
395+
*/
396+
function defaultMinStackSize(freeMemoryBytes) {
397+
freeMemoryBytes = freeMemoryBytes ?? freemem();
398+
return Math.max(8 * 1024 * 1024, Math.floor(freeMemoryBytes * 0.1));
399+
}
400+
401+
/** Determine the correct path for the engine */
402+
function getEnginePath(opts) {
403+
if (opts.engine) {
404+
return opts.engine;
405+
}
406+
let engineBinaryRelPath = '../lib/starlingmonkey_embedding_weval.wasm';
407+
if (opts.enableAot) {
408+
engineBinaryRelPath = `../lib/starlingmonkey_embedding${debugBuild ? '.debug' : ''}.wasm`;
409+
}
410+
return fileURLToPath(new URL(engineBinaryRelPath, import.meta.url));
411+
}
412+
413+
/**
414+
* Parse output of post-processing step (whether Wizer or Weval)
415+
*
416+
* @param {Stream} stderr
417+
* @returns {string} String that can be printed to describe error output
418+
*/
419+
function parseWizerStderr(stderr) {
420+
let output = `${stderr}`;
421+
let causeStart = output.indexOf(WIZER_ERROR_CAUSE_PREFIX);
422+
let exitCodeStart = output.indexOf(WIZER_EXIT_CODE_PREFIX);
423+
if (causeStart === -1 || exitCodeStart === -1) {
424+
return output;
425+
}
426+
427+
let causeEnd = output.indexOf('\n', exitCodeStart + 1);
428+
return `${output.substring(0, causeStart)}${output.substring(causeEnd)}`.trim();
429+
}
430+
431+
/**
432+
* Check whether a value is numeric (including BigInt)
433+
*
434+
* @param {any} n
435+
* @returns {boolean} whether the value is numeric
436+
*/
437+
function isNumeric(n) {
438+
switch (typeof n) {
439+
case 'bigint':
440+
case 'number':
441+
return true;
442+
case 'object':
443+
return n.constructor == BigInt || n.constructor == Number;
444+
default:
445+
return false;
446+
}
447+
}
448+
449+
/**
450+
* Convert a given path to one that also works on windows
451+
*
452+
* @param {string} path - A path that may or may not be windows-compatible
453+
* @returns {string} The Windows-compatible (if necessary) path
454+
*/
455+
function maybeWindowsPath(path) {
456+
if (!path) return path;
457+
if (!IS_WINDOWS) return resolve(path);
458+
return '//?/' + resolve(path).replace(/\\/g, '/');
459+
}
460+
461+
/**
462+
* Clean up the given input string by removing the given patterns if
463+
* found as line prefixes.
464+
*/
465+
function stripLinesPrefixes(input, prefixPatterns) {
466+
return input
467+
.split('\n')
468+
.map((line) =>
469+
prefixPatterns.reduce((line, n) => line.replace(n, ''), line),
470+
)
471+
.join('\n')
472+
.trim();
473+
}
474+
475+
/** Prepare a work directory for use with componentization */
476+
async function prepWorkDir() {
477+
const tmpDir = join(
478+
tmpdir(),
479+
createHash('sha256')
480+
.update(Math.random().toString())
481+
.digest('hex')
482+
.slice(0, 12),
483+
);
484+
await mkdir(tmpDir);
485+
const sourcesDir = join(tmpDir, 'sources');
486+
await mkdir(sourceDir);
487+
return { baseDir, sourcesDir };
488+
}

0 commit comments

Comments
 (0)