@bytecodealliance/jco 0.8.0 → 0.9.1

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 (36) hide show
  1. package/LICENSE +220 -0
  2. package/README.md +38 -14
  3. package/lib/console.js +7 -0
  4. package/obj/imports/environment.d.ts +3 -0
  5. package/obj/imports/exit.d.ts +4 -0
  6. package/obj/imports/filesystem.d.ts +152 -0
  7. package/obj/imports/preopens.d.ts +5 -0
  8. package/obj/imports/random.d.ts +3 -0
  9. package/obj/imports/stderr.d.ts +5 -0
  10. package/obj/imports/stdin.d.ts +5 -0
  11. package/obj/imports/stdout.d.ts +5 -0
  12. package/obj/imports/streams.d.ts +12 -0
  13. package/obj/imports/wall-clock.d.ts +6 -0
  14. package/{js-component-bindgen-component.core.wasm → obj/js-component-bindgen-component.core.wasm} +0 -0
  15. package/obj/js-component-bindgen-component.d.ts +60 -0
  16. package/obj/js-component-bindgen-component.js +2036 -0
  17. package/obj/wasm-tools.d.ts +53 -0
  18. package/obj/wasm-tools.js +2236 -0
  19. package/package.json +36 -6
  20. package/src/api.js +68 -0
  21. package/src/cmd/componentize.js +21 -0
  22. package/src/cmd/opt.js +151 -0
  23. package/src/cmd/transpile.js +366 -0
  24. package/src/cmd/wasm-tools.js +114 -0
  25. package/src/common.js +104 -0
  26. package/src/jco.js +129 -0
  27. package/api.d.ts +0 -109
  28. package/api.mjs +0 -46035
  29. package/cli.mjs +0 -49700
  30. package/wasm-opt +0 -2
  31. package/wasm2js +0 -2
  32. /package/{wasi_snapshot_preview1.command.wasm → lib/wasi_snapshot_preview1.command.wasm} +0 -0
  33. /package/{wasi_snapshot_preview1.reactor.wasm → lib/wasi_snapshot_preview1.reactor.wasm} +0 -0
  34. /package/{js-component-bindgen-component.core2.wasm → obj/js-component-bindgen-component.core2.wasm} +0 -0
  35. /package/{wasm-tools.core.wasm → obj/wasm-tools.core.wasm} +0 -0
  36. /package/{wasm-tools.core2.wasm → obj/wasm-tools.core2.wasm} +0 -0
package/package.json CHANGED
@@ -1,15 +1,31 @@
1
1
  {
2
2
  "name": "@bytecodealliance/jco",
3
+ "version": "0.9.1",
3
4
  "description": "JavaScript tooling for working with WebAssembly Components",
4
- "version": "0.8.0",
5
- "exports": "./api.mjs",
6
- "types": "api.d.ts",
7
5
  "author": "Guy Bedford",
8
6
  "bin": {
9
- "jco": "cli.mjs"
7
+ "jco": "src/jco.js"
10
8
  },
9
+ "exports": "./src/api.js",
10
+ "type": "module",
11
11
  "dependencies": {
12
- "@bytecodealliance/preview2-shim": "0.0.9"
12
+ "@bytecodealliance/preview2-shim": "0.0.9",
13
+ "binaryen": "^111.0.0",
14
+ "chalk-template": "^0.4.0",
15
+ "commander": "^9.4.1",
16
+ "mkdirp": "^1.0.4",
17
+ "ora": "^6.1.2",
18
+ "terser": "^5.16.1"
19
+ },
20
+ "devDependencies": {
21
+ "@bytecodealliance/componentize-js": "0.0.7",
22
+ "@types/node": "^18.11.17",
23
+ "@typescript-eslint/eslint-plugin": "^5.41.0",
24
+ "@typescript-eslint/parser": "^5.41.0",
25
+ "eslint": "^8.30.0",
26
+ "mocha": "^10.2.0",
27
+ "terser": "^5.16.1",
28
+ "typescript": "^4.3.2"
13
29
  },
14
30
  "repository": {
15
31
  "type": "git",
@@ -24,5 +40,19 @@
24
40
  "bugs": {
25
41
  "url": "https://github.com/bytecodealliance/jco/issues"
26
42
  },
27
- "homepage": "https://github.com/bytecodealliance/jco#readme"
43
+ "homepage": "https://github.com/bytecodealliance/jco#readme",
44
+ "scripts": {
45
+ "build": "cargo build --workspace --target wasm32-wasi --release && cargo run",
46
+ "build:types:preview2-shim": "PREVIEW2_SHIM_TYPES=* cargo build -p jco",
47
+ "lint": "eslint -c eslintrc.cjs lib/**/*.js packages/*/lib/**/*.js",
48
+ "test": "mocha -u tdd test/test.js --timeout 120000"
49
+ },
50
+ "files": [
51
+ "lib",
52
+ "src",
53
+ "obj"
54
+ ],
55
+ "workspaces": [
56
+ "packages/preview2-shim"
57
+ ]
28
58
  }
package/src/api.js ADDED
@@ -0,0 +1,68 @@
1
+ export { optimizeComponent as opt } from './cmd/opt.js';
2
+ export { transpileComponent as transpile } from './cmd/transpile.js';
3
+ import { $init, print as printFn, parse as parseFn, componentWit as componentWitFn, componentNew as componentNewFn, componentEmbed as componentEmbedFn, metadataAdd as metadataAddFn, metadataShow as metadataShowFn } from "../obj/wasm-tools.js";
4
+
5
+ /**
6
+ * @param {Parameters<import('../obj/wasm-tools.js').print>[0]} binary
7
+ * @return {Promise<ReturnType<import('../obj/wasm-tools.js').print>>}
8
+ */
9
+ export async function print (binary) {
10
+ await $init;
11
+ return printFn(binary);
12
+ }
13
+ /**
14
+ * @param {Parameters<import('../obj/wasm-tools.js').parse>[0]} wat
15
+ * @return {Promise<ReturnType<import('../obj/wasm-tools.js').parse>>}
16
+ */
17
+ export async function parse (wat) {
18
+ await $init;
19
+ return parseFn(wat);
20
+ }
21
+ /**
22
+ * @param {Parameters<import('../obj/wasm-tools.js').componentWit>[0]} binary
23
+ * @return {Promise<ReturnType<import('../obj/wasm-tools.js').componentWit>>}
24
+ */
25
+ export async function componentWit (binary) {
26
+ await $init;
27
+ return componentWitFn(binary);
28
+ }
29
+ /**
30
+ * @param {Parameters<import('../obj/wasm-tools.js').componentNew>[0]} binary
31
+ * @param {Parameters<import('../obj/wasm-tools.js').componentNew>[1]} adapters
32
+ * @return {Promise<ReturnType<import('../obj/wasm-tools.js').componentNew>>}
33
+ */
34
+ export async function componentNew (binary, adapters) {
35
+ await $init;
36
+ return componentNewFn(binary, adapters);
37
+ }
38
+ /**
39
+ * @param {Parameters<import('../obj/wasm-tools.js').componentEmbed>[0]} embedOpts
40
+ * @return {Promise<ReturnType<import('../obj/wasm-tools.js').componentEmbed>>}
41
+ */
42
+ export async function componentEmbed (embedOpts) {
43
+ await $init;
44
+ return componentEmbedFn(embedOpts);
45
+ }
46
+ /**
47
+ * @param {Parameters<import('../obj/wasm-tools.js').metadataAdd>[0]} binary
48
+ * @param {Parameters<import('../obj/wasm-tools.js').metadataAdd>[1]} metadata
49
+ * @return {Promise<ReturnType<import('../obj/wasm-tools.js').metadataAdd>>}
50
+ */
51
+ export async function metadataAdd (binary, metadata) {
52
+ await $init;
53
+ return metadataAddFn(binary, metadata);
54
+ }
55
+ /**
56
+ * @param {Parameters<import('../obj/wasm-tools.js').metadataShow>[0]} binary
57
+ * @return {Promise<ReturnType<import('../obj/wasm-tools.js').metadataShow>>}
58
+ */
59
+ export async function metadataShow (binary) {
60
+ await $init;
61
+ return metadataShowFn(binary);
62
+ }
63
+ export function preview1AdapterCommandPath () {
64
+ return new URL('../lib/wasi_snapshot_preview1.command.wasm', import.meta.url);
65
+ }
66
+ export function preview1AdapterReactorPath () {
67
+ return new URL('../lib/wasi_snapshot_preview1.reactor.wasm', import.meta.url);
68
+ }
@@ -0,0 +1,21 @@
1
+ import { readFile, writeFile } from 'node:fs/promises';
2
+ import { resolve } from 'node:path';
3
+ import c from 'chalk-template';
4
+
5
+ export async function componentize (jsSource, opts) {
6
+ let componentizeFn;
7
+ try {
8
+ ({ componentize: componentizeFn } = await eval('import("@bytecodealliance/componentize-js")'));
9
+ } catch (e) {
10
+ if (e?.code === 'ERR_MODULE_NOT_FOUND' && e?.message?.includes('\'@bytecodealliance/componentize-js\''))
11
+ throw new Error(`componentize-js must first be installed separately via "npm install @bytecodealliance/componentize-js".`);
12
+ throw e;
13
+ }
14
+ const source = await readFile(jsSource, 'utf8');
15
+ const { component, imports } = await componentizeFn(source, {
16
+ witPath: resolve(opts.wit),
17
+ worldName: opts.worldName
18
+ });
19
+ await writeFile(opts.out, component);
20
+ console.log(c`{green OK} Successfully written {bold ${opts.out}} with imports (${imports.join(', ')}).`);
21
+ }
package/src/cmd/opt.js ADDED
@@ -0,0 +1,151 @@
1
+ import { $init, metadataShow, print } from '../../obj/wasm-tools.js';
2
+ import { writeFile } from 'fs/promises';
3
+ import { fileURLToPath } from 'url';
4
+ import c from 'chalk-template';
5
+ import { readFile, sizeStr, fixedDigitDisplay, table, spawnIOTmp, setShowSpinner, getShowSpinner } from '../common.js';
6
+ import ora from 'ora';
7
+
8
+ let WASM_OPT;
9
+ try {
10
+ WASM_OPT = fileURLToPath(new URL('../../node_modules/binaryen/bin/wasm-opt', import.meta.url));
11
+ } catch (e) {
12
+ WASM_OPT = new URL('../../node_modules/binaryen/bin/wasm-opt', import.meta.url);
13
+ }
14
+
15
+ export async function opt (componentPath, opts, program) {
16
+ await $init;
17
+ const varIdx = program.parent.rawArgs.indexOf('--');
18
+ if (varIdx !== -1)
19
+ opts.optArgs = program.parent.rawArgs.slice(varIdx + 1);
20
+ const componentBytes = await readFile(componentPath);
21
+
22
+ if (!opts.quiet) setShowSpinner(true);
23
+ const optPromise = optimizeComponent(componentBytes, opts);
24
+ const { component, compressionInfo } = await optPromise;
25
+
26
+ await writeFile(opts.output, component);
27
+
28
+ let totalBeforeBytes = 0, totalAfterBytes = 0;
29
+
30
+ if (!opts.quiet)
31
+ console.log(c`
32
+ {bold Optimized WebAssembly Component Internal Core Modules:}
33
+
34
+ ${table([...compressionInfo.map(({ beforeBytes, afterBytes }, i) => {
35
+ totalBeforeBytes += beforeBytes;
36
+ totalAfterBytes += afterBytes;
37
+ return [
38
+ ` - Core Module ${i + 1}: `,
39
+ sizeStr(beforeBytes),
40
+ ' -> ',
41
+ c`{cyan ${sizeStr(afterBytes)}} `,
42
+ `(${fixedDigitDisplay(afterBytes / beforeBytes * 100, 2)}%)`
43
+ ];
44
+ }), ['', '', '', '', ''], [
45
+ ` = Total: `,
46
+ `${sizeStr(totalBeforeBytes)}`,
47
+ ` => `,
48
+ c`{cyan ${sizeStr(totalAfterBytes)}} `,
49
+ `(${fixedDigitDisplay(totalAfterBytes / totalBeforeBytes * 100, 2)}%)`
50
+ ]], [,,,,'right'])}`);
51
+ }
52
+
53
+ /**
54
+ *
55
+ * @param {Uint8Array} componentBytes
56
+ * @param {{ quiet: boolean, optArgs?: string[] }} options?
57
+ * @returns {Promise<{ component: Uint8Array, compressionInfo: { beforeBytes: number, afterBytes: number }[] >}
58
+ */
59
+ export async function optimizeComponent (componentBytes, opts) {
60
+ await $init;
61
+ const showSpinner = getShowSpinner();
62
+ let spinner;
63
+ try {
64
+ const coreModules = metadataShow(componentBytes).slice(1, -1).map(({ range }) => range);
65
+
66
+ let completed = 0;
67
+ const spinnerText = () => c`{cyan ${completed} / ${coreModules.length}} Running Binaryen on WebAssembly Component Internal Core Modules \n`;
68
+ if (showSpinner) {
69
+ spinner = ora({
70
+ color: 'cyan',
71
+ spinner: 'bouncingBar'
72
+ }).start();
73
+ spinner.text = spinnerText();
74
+ }
75
+
76
+ const optimizedCoreModules = await Promise.all(coreModules.map(async ([coreModuleStart, coreModuleEnd]) => {
77
+ const optimized = wasmOpt(componentBytes.subarray(coreModuleStart, coreModuleEnd), opts?.optArgs);
78
+ if (spinner) {
79
+ completed++;
80
+ spinner.text = spinnerText();
81
+ }
82
+ return optimized;
83
+ }));
84
+
85
+ let outComponentBytes = new Uint8Array(componentBytes.byteLength);
86
+ let nextReadPos = 0, nextWritePos = 0;
87
+ for (let i = 0; i < coreModules.length; i++) {
88
+ const [coreModuleStart, coreModuleEnd] = coreModules[i];
89
+ const optimizedCoreModule = optimizedCoreModules[i];
90
+
91
+ let lebByteLen = 1;
92
+ while (componentBytes[coreModuleStart - 1 - lebByteLen] & 0x80) lebByteLen++;
93
+
94
+ // Write from the last read to the LEB byte start of the core module
95
+ outComponentBytes.set(componentBytes.subarray(nextReadPos, coreModuleStart - lebByteLen), nextWritePos);
96
+ nextWritePos += coreModuleStart - lebByteLen - nextReadPos;
97
+
98
+ // Write the new LEB bytes
99
+ let val = optimizedCoreModule.byteLength;
100
+ do {
101
+ const byte = val & 0x7F;
102
+ val >>>= 7;
103
+ outComponentBytes[nextWritePos++] = val === 0 ? byte : byte | 0x80;
104
+ } while (val !== 0);
105
+
106
+ // Write the core module
107
+ outComponentBytes.set(optimizedCoreModule, nextWritePos);
108
+ nextWritePos += optimizedCoreModule.byteLength;
109
+
110
+ nextReadPos = coreModuleEnd;
111
+ }
112
+
113
+ outComponentBytes.set(componentBytes.subarray(nextReadPos, componentBytes.byteLength), nextWritePos);
114
+ nextWritePos += componentBytes.byteLength - nextReadPos;
115
+ nextReadPos += componentBytes.byteLength - nextReadPos;
116
+
117
+ outComponentBytes = outComponentBytes.subarray(0, outComponentBytes.length + nextWritePos - nextReadPos);
118
+
119
+ // verify it still parses ok
120
+ try {
121
+ await print(outComponentBytes);
122
+ } catch (e) {
123
+ throw new Error(`Internal error performing optimization.\n${e.message}`)
124
+ }
125
+
126
+ return {
127
+ component: outComponentBytes,
128
+ compressionInfo: coreModules.map(([s, e], i) => ({ beforeBytes: e - s, afterBytes: optimizedCoreModules[i].byteLength }))
129
+ };
130
+ }
131
+ finally {
132
+ if (spinner)
133
+ spinner.stop();
134
+ }
135
+ }
136
+
137
+ /**
138
+ * @param {Uint8Array} source
139
+ * @returns {Promise<Uint8Array>}
140
+ */
141
+ async function wasmOpt (source, args = ['-O1', '--low-memory-unused', '--enable-bulk-memory']) {
142
+ try {
143
+ return await spawnIOTmp(WASM_OPT, source, [
144
+ ...args, '-o'
145
+ ]);
146
+ } catch (e) {
147
+ if (e.toString().includes('BasicBlock requested'))
148
+ return wasmOpt(source, args);
149
+ throw e;
150
+ }
151
+ }
@@ -0,0 +1,366 @@
1
+ import { $init, generate } from '../../obj/js-component-bindgen-component.js';
2
+ import { writeFile } from 'fs/promises';
3
+ import { mkdir } from 'fs/promises';
4
+ import { dirname, extname, basename } from 'path';
5
+ import c from 'chalk-template';
6
+ import { readFile, sizeStr, table, spawnIOTmp, setShowSpinner, getShowSpinner } from '../common.js';
7
+ import { optimizeComponent } from './opt.js';
8
+ import { minify } from 'terser';
9
+ import { fileURLToPath } from 'url';
10
+ import ora from 'ora';
11
+
12
+ export async function transpile (componentPath, opts, program) {
13
+ const varIdx = program.parent.rawArgs.indexOf('--');
14
+ if (varIdx !== -1)
15
+ opts.optArgs = program.parent.rawArgs.slice(varIdx + 1);
16
+ const component = await readFile(componentPath);
17
+
18
+ if (!opts.quiet)
19
+ setShowSpinner(true);
20
+ if (!opts.name)
21
+ opts.name = basename(componentPath.slice(0, -extname(componentPath).length || Infinity));
22
+ if (opts.map)
23
+ opts.map = Object.fromEntries(opts.map.map(mapping => mapping.split('=')));
24
+ const { files } = await transpileComponent(component, opts);
25
+
26
+ await Promise.all(Object.entries(files).map(async ([name, file]) => {
27
+ await mkdir(dirname(name), { recursive: true });
28
+ await writeFile(name, file);
29
+ }));
30
+
31
+ if (!opts.quiet)
32
+ console.log(c`
33
+ {bold Transpiled JS Component Files:}
34
+
35
+ ${table(Object.entries(files).map(([name, source]) => [
36
+ c` - {italic ${name}} `,
37
+ c`{black.italic ${sizeStr(source.length)}}`
38
+ ]))}`);
39
+ }
40
+
41
+ let WASM_2_JS;
42
+ try {
43
+ WASM_2_JS = fileURLToPath(new URL('../../node_modules/binaryen/bin/wasm2js', import.meta.url));
44
+ } catch {
45
+ WASM_2_JS = new URL('../../node_modules/binaryen/bin/wasm2js', import.meta.url);
46
+ }
47
+
48
+ /**
49
+ * @param {Uint8Array} source
50
+ * @returns {Promise<Uint8Array>}
51
+ */
52
+ async function wasm2Js (source) {
53
+ try {
54
+ return await spawnIOTmp(WASM_2_JS, source, ['-Oz', '-o']);
55
+ } catch (e) {
56
+ if (e.toString().includes('BasicBlock requested'))
57
+ return wasm2Js(source);
58
+ throw e;
59
+ }
60
+ }
61
+
62
+ /**
63
+ *
64
+ * @param {Uint8Array} component
65
+ * @param {{
66
+ * name: string,
67
+ * instantiation?: bool,
68
+ * map?: Record<string, string>,
69
+ * validLiftingOptimization?: bool,
70
+ * noNodejsCompat?: bool,
71
+ * tlaCompat?: bool,
72
+ * base64Cutoff?: bool,
73
+ * js?: bool,
74
+ * minify?: bool,
75
+ * optimize?: bool,
76
+ * optArgs?: string[],
77
+ * }} opts
78
+ * @returns {Promise<{ files: { [filename: string]: Uint8Array }, imports: string[], exports: [string, 'function' | 'instance'][] }>}
79
+ */
80
+ export async function transpileComponent (component, opts = {}) {
81
+ await $init;
82
+ if (opts.noWasiShim) opts.wasiShim = false;
83
+
84
+ let spinner;
85
+ const showSpinner = getShowSpinner();
86
+ if (opts.optimize) {
87
+ if (showSpinner) setShowSpinner(true);
88
+ ({ component } = await optimizeComponent(component, opts));
89
+ }
90
+
91
+ if (opts.wasiShim !== false) {
92
+ opts.map = Object.assign({
93
+ 'wasi:cli-base/*': '@bytecodealliance/preview2-shim/cli-base#*',
94
+ 'wasi:filesystem/*': '@bytecodealliance/preview2-shim/filesystem#*',
95
+ 'wasi:io/*': '@bytecodealliance/preview2-shim/io#*',
96
+ 'wasi:random/*': '@bytecodealliance/preview2-shim/random#*',
97
+ }, opts.map || {});
98
+ }
99
+
100
+ let { files, imports, exports } = generate(component, {
101
+ name: opts.name ?? 'component',
102
+ map: Object.entries(opts.map ?? {}),
103
+ instantiation: opts.instantiation || opts.js,
104
+ validLiftingOptimization: opts.validLiftingOptimization ?? false,
105
+ noNodejsCompat: !(opts.nodejsCompat ?? true),
106
+ tlaCompat: opts.tlaCompat ?? false,
107
+ base64Cutoff: opts.js ? 0 : opts.base64Cutoff ?? 5000
108
+ });
109
+
110
+ let outDir = (opts.outDir ?? '').replace(/\\/g, '/');
111
+ if (!outDir.endsWith('/') && outDir !== '')
112
+ outDir += '/';
113
+ files = files.map(([name, source]) => [`${outDir}${name}`, source]);
114
+
115
+ const jsFile = files.find(([name]) => name.endsWith('.js'));
116
+
117
+ if (opts.js) {
118
+ const source = Buffer.from(jsFile[1]).toString('utf8')
119
+ // update imports manging to match emscripten asm
120
+ .replace(/exports(\d+)\['([^']+)']/g, (_, i, s) => `exports${i}['${asmMangle(s)}']`);
121
+
122
+ const wasmFiles = files.filter(([name]) => name.endsWith('.wasm'));
123
+ files = files.filter(([name]) => !name.endsWith('.wasm'));
124
+
125
+ // TODO: imports as specifier list
126
+
127
+ let completed = 0;
128
+ const spinnerText = () => c`{cyan ${completed} / ${wasmFiles.length}} Running Binaryen wasm2js on Wasm core modules (this takes a while)...\n`;
129
+ if (showSpinner) {
130
+ spinner = ora({
131
+ color: 'cyan',
132
+ spinner: 'bouncingBar'
133
+ }).start();
134
+ spinner.text = spinnerText();
135
+ }
136
+
137
+ try {
138
+ const asmFiles = await Promise.all(wasmFiles.map(async ([, source]) => {
139
+ const output = (await wasm2Js(source)).toString('utf8');
140
+ if (spinner) {
141
+ completed++;
142
+ spinner.text = spinnerText();
143
+ }
144
+ return output;
145
+ }));
146
+
147
+ const asms = asmFiles.map((asm, i) =>`function asm${i}(imports) {
148
+ ${
149
+ // strip and replace the asm instantiation wrapper
150
+ asm
151
+ .replace(/import \* as [^ ]+ from '[^']*';/g, '')
152
+ .replace('function asmFunc(imports) {', '')
153
+ .replace(/export var ([^ ]+) = ([^. ]+)\.([^ ]+);/g, '')
154
+ .replace(/var retasmFunc = [\s\S]*$/, '')
155
+ .replace(/var memasmFunc = new ArrayBuffer\(0\);/g, '')
156
+ .replace('memory.grow = __wasm_memory_grow;', '')
157
+ .trim()
158
+ }`).join(',\n');
159
+
160
+ const outSource = `${
161
+ imports.map((impt, i) => `import * as import${i} from '${impt}';`).join('\n')}
162
+ ${source.replace('export async function instantiate', 'async function instantiate')}
163
+
164
+ let ${exports.filter(([, ty]) => ty === 'function').map(([name]) => '_' + name).join(', ')};
165
+
166
+ ${exports.map(([name, ty]) => ty === 'function' ? `\nfunction ${asmMangle(name)} () {
167
+ return _${name}.apply(this, arguments);
168
+ }` : `\nlet ${asmMangle(name)};`).join('\n')}
169
+
170
+ const asmInit = [${asms}];
171
+
172
+ ${opts.tlaCompat ? 'export ' : ''}const $init = (async () => {
173
+ let idx = 0;
174
+ ({ ${exports.map(([name, ty]) => asmMangle(name) === name ? name : `'${name}': ${asmMangle(name)}`).join(',\n')} } = await instantiate(n => idx++, {
175
+ ${imports.map((impt, i) => ` '${impt}': import${i},`).join('\n')}
176
+ }, (i, imports) => ({ exports: asmInit[i](imports) })));
177
+ })();${exports.length > 0 ? `\nexport { ${exports.map(([name]) => name === asmMangle(name) ? `${name}` : `${asmMangle(name)} as "${name}"`).join(', ')} }` : ''}
178
+ ${opts.tlaCompat ? '' : '\nawait $init;\n'}`;
179
+
180
+ jsFile[1] = Buffer.from(outSource);
181
+ }
182
+ finally {
183
+ if (spinner)
184
+ spinner.stop();
185
+ }
186
+ }
187
+
188
+ if (opts.minify) {
189
+ ({ code: jsFile[1] } = await minify(Buffer.from(jsFile[1]).toString('utf8'), {
190
+ module: true,
191
+ compress: {
192
+ ecma: 9,
193
+ unsafe: true
194
+ },
195
+ mangle: {
196
+ keep_classnames: true
197
+ }
198
+ }));
199
+ }
200
+
201
+ return { files: Object.fromEntries(files), imports, exports };
202
+ }
203
+
204
+ // emscripten asm mangles specifiers to be valid identifiers
205
+ // for imports to match up we must do the same
206
+ // See https://github.com/WebAssembly/binaryen/blob/main/src/asmjs/asmangle.cpp
207
+ function asmMangle (name) {
208
+ if (name === '')
209
+ return '$';
210
+
211
+ let mightBeKeyword = true;
212
+ let i = 1;
213
+
214
+ // Names must start with a character, $ or _
215
+ switch (name[0]) {
216
+ case '0':
217
+ case '1':
218
+ case '2':
219
+ case '3':
220
+ case '4':
221
+ case '5':
222
+ case '6':
223
+ case '7':
224
+ case '8':
225
+ case '9': {
226
+ name = '$' + name;
227
+ i = 2;
228
+ // fallthrough
229
+ }
230
+ case '$':
231
+ case '_': {
232
+ mightBeKeyword = false;
233
+ break;
234
+ }
235
+ default: {
236
+ let chNum = name.charCodeAt(0);
237
+ if (!(chNum >= 97 && chNum <= 122) && !(chNum >= 65 && chNum <= 90)) {
238
+ name = '$' + name.substr(1);
239
+ mightBeKeyword = false;
240
+ }
241
+ }
242
+ }
243
+
244
+ // Names must contain only characters, digits, $ or _
245
+ let len = name.length;
246
+ for (; i < len; ++i) {
247
+ switch (name[i]) {
248
+ case '0':
249
+ case '1':
250
+ case '2':
251
+ case '3':
252
+ case '4':
253
+ case '5':
254
+ case '6':
255
+ case '7':
256
+ case '8':
257
+ case '9':
258
+ case '$':
259
+ case '_': {
260
+ mightBeKeyword = false;
261
+ break;
262
+ }
263
+ default: {
264
+ let chNum = name.charCodeAt(i);
265
+ if (!(chNum >= 97 && chNum <= 122) && !(chNum >= 65 && chNum <= 90)) {
266
+ name = name.substr(0, i) + '_' + name.substr(i + 1);
267
+ mightBeKeyword = false;
268
+ }
269
+ }
270
+ }
271
+ }
272
+
273
+ // Names must not collide with keywords
274
+ if (mightBeKeyword && len >= 2 && len <= 10) {
275
+ switch (name[0]) {
276
+ case 'a': {
277
+ if (name == "arguments")
278
+ return name + '_';
279
+ break;
280
+ }
281
+ case 'b': {
282
+ if (name == "break")
283
+ return name + '_';
284
+ break;
285
+ }
286
+ case 'c': {
287
+ if (name == "case" || name == "continue" || name == "catch" ||
288
+ name == "const" || name == "class")
289
+ return name + '_';
290
+ break;
291
+ }
292
+ case 'd': {
293
+ if (name == "do" || name == "default" || name == "debugger")
294
+ return name + '_';
295
+ break;
296
+ }
297
+ case 'e': {
298
+ if (name == "else" || name == "enum" || name == "eval" || // to be sure
299
+ name == "export" || name == "extends")
300
+ return name + '_';
301
+ break;
302
+ }
303
+ case 'f': {
304
+ if (name == "for" || name == "false" || name == "finally" ||
305
+ name == "function")
306
+ return name + '_';
307
+ break;
308
+ }
309
+ case 'i': {
310
+ if (name == "if" || name == "in" || name == "import" ||
311
+ name == "interface" || name == "implements" ||
312
+ name == "instanceof")
313
+ return name + '_';
314
+ break;
315
+ }
316
+ case 'l': {
317
+ if (name == "let")
318
+ return name + '_';
319
+ break;
320
+ }
321
+ case 'n': {
322
+ if (name == "new" || name == "null")
323
+ return name + '_';
324
+ break;
325
+ }
326
+ case 'p': {
327
+ if (name == "public" || name == "package" || name == "private" ||
328
+ name == "protected")
329
+ return name + '_';
330
+ break;
331
+ }
332
+ case 'r': {
333
+ if (name == "return")
334
+ return name + '_';
335
+ break;
336
+ }
337
+ case 's': {
338
+ if (name == "super" || name == "static" || name == "switch")
339
+ return name + '_';
340
+ break;
341
+ }
342
+ case 't': {
343
+ if (name == "try" || name == "this" || name == "true" ||
344
+ name == "throw" || name == "typeof")
345
+ return name + '_';
346
+ break;
347
+ }
348
+ case 'v': {
349
+ if (name == "var" || name == "void")
350
+ return name + '_';
351
+ break;
352
+ }
353
+ case 'w': {
354
+ if (name == "with" || name == "while")
355
+ return name + '_';
356
+ break;
357
+ }
358
+ case 'y': {
359
+ if (name == "yield")
360
+ return name + '_';
361
+ break;
362
+ }
363
+ }
364
+ }
365
+ return name;
366
+ }