wasm-bindgen-lite 0.3.1 → 0.3.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/src/cli/build.js CHANGED
@@ -11,16 +11,47 @@ function exec(cmd, options = {}) {
11
11
  }
12
12
  }
13
13
 
14
- function runCargoBuild({ crateDir, release, simd }) {
14
+ function resolveTargetDir(crateDir) {
15
+ try {
16
+ const raw = execSync('cargo metadata --format-version 1 --no-deps', {
17
+ cwd: crateDir,
18
+ stdio: ['ignore', 'pipe', 'inherit'],
19
+ }).toString()
20
+ const meta = JSON.parse(raw)
21
+ if (meta?.target_directory) return meta.target_directory
22
+ } catch {
23
+ console.warn(
24
+ 'Warning: failed to read cargo metadata, defaulting to local target dir'
25
+ )
26
+ }
27
+ return join(crateDir, 'target')
28
+ }
29
+
30
+ function runCargoBuild({ crateDir, release, simd, targetDir, features }) {
15
31
  const args = ['build', '--target', 'wasm32-unknown-unknown']
16
32
  if (release) args.push('--release')
33
+ if (features) {
34
+ args.push('--features', features)
35
+ }
17
36
 
18
37
  const env = { ...process.env }
38
+
39
+ if (release) {
40
+ // Explicitly force opt-level 3 for fastest execution
41
+ env.CARGO_PROFILE_RELEASE_OPT_LEVEL = '3'
42
+ env.CARGO_PROFILE_RELEASE_PANIC = 'abort'
43
+ env.CARGO_PROFILE_RELEASE_CODEGEN_UNITS = '1'
44
+ env.CARGO_PROFILE_RELEASE_LTO = 'fat'
45
+ }
46
+
19
47
  if (simd) {
20
- const base = process.env.RUSTFLAGS || ''
48
+ const base = env.RUSTFLAGS || ''
21
49
  const extra = '-C target-feature=+simd128'
22
50
  env.RUSTFLAGS = [base, extra].filter(Boolean).join(' ').trim()
23
51
  }
52
+ if (targetDir) {
53
+ env.CARGO_TARGET_DIR = targetDir
54
+ }
24
55
 
25
56
  exec(`cargo ${args.join(' ')}`, {
26
57
  cwd: crateDir,
@@ -28,18 +59,17 @@ function runCargoBuild({ crateDir, release, simd }) {
28
59
  })
29
60
  }
30
61
 
31
- function wasmPath({ crateDir, release, wasmFileStem }) {
62
+ function wasmPath({ targetDir, release, wasmFileStem }) {
32
63
  const profile = release ? 'release' : 'debug'
33
64
  return join(
34
- crateDir,
35
- 'target',
65
+ targetDir,
36
66
  'wasm32-unknown-unknown',
37
67
  profile,
38
68
  `${wasmFileStem}.wasm`
39
69
  )
40
70
  }
41
71
 
42
- function maybeRunWasmOpt(wasmFile, wasmOpt) {
72
+ function maybeRunWasmOpt(wasmFile, wasmOpt, release) {
43
73
  if (wasmOpt.mode === 'off') return
44
74
  if (wasmOpt.mode === 'auto') {
45
75
  try {
@@ -49,7 +79,24 @@ function maybeRunWasmOpt(wasmFile, wasmOpt) {
49
79
  }
50
80
  }
51
81
 
52
- const args = ['wasm-opt', ...wasmOpt.args, wasmFile, '-o', wasmFile]
82
+ const args = ['wasm-opt']
83
+
84
+ if (release) {
85
+ // Only strip metadata and debug info for fastest performance
86
+ args.push('--strip-debug')
87
+ args.push('--strip-producers')
88
+ args.push('--strip-target-features')
89
+
90
+ // We EXPLICITLY avoid -O passes here to maintain absolute fastest speed
91
+ // unless the user provided specific args
92
+ if (wasmOpt.args.length > 0) {
93
+ args.push(...wasmOpt.args)
94
+ }
95
+ } else {
96
+ args.push(...wasmOpt.args)
97
+ }
98
+
99
+ args.push(wasmFile, '-o', wasmFile)
53
100
  exec(args.join(' '))
54
101
  }
55
102
 
@@ -62,6 +109,7 @@ export function buildArtifacts({
62
109
  release,
63
110
  wasmOpt,
64
111
  }) {
112
+ const targetDir = resolveTargetDir(crateDir)
65
113
  mkdirSync(outDir, { recursive: true })
66
114
  const wasmOutDir = join(outDir, 'wasm')
67
115
  mkdirSync(wasmOutDir, { recursive: true })
@@ -72,9 +120,13 @@ export function buildArtifacts({
72
120
  const label = isSimd ? 'SIMD' : 'baseline'
73
121
  console.log(`Building ${label} wasm...`)
74
122
 
75
- runCargoBuild({ crateDir, release, simd: isSimd })
123
+ const simdFeatures = isSimd ? targets.simdFeatures : null
124
+ const baselineFeatures = !isSimd ? targets.baselineFeatures : null
125
+ const features = simdFeatures || baselineFeatures
126
+
127
+ runCargoBuild({ crateDir, release, simd: isSimd, targetDir, features })
76
128
 
77
- const built = wasmPath({ crateDir, release, wasmFileStem })
129
+ const built = wasmPath({ targetDir, release, wasmFileStem })
78
130
  const dest = join(wasmOutDir, `${artifactBaseName}.${suffix}.wasm`)
79
131
 
80
132
  copyFileSync(built, dest)
package/src/cli/config.js CHANGED
@@ -21,7 +21,7 @@ const DEFAULT_CONFIG = {
21
21
  inline: true,
22
22
  types: true,
23
23
  },
24
- custom: null, // path to custom JS file to include and re-export
24
+ custom: null, // path to custom JS/TS file to include and re-export
25
25
  },
26
26
  exports: [
27
27
  {
@@ -138,6 +138,8 @@ export function loadConfigFromCli(cliOpts = {}) {
138
138
  typeof cliOpts.simd === 'boolean'
139
139
  ? cliOpts.simd
140
140
  : (fileConfig.targets?.simd ?? DEFAULT_CONFIG.targets.simd),
141
+ simdFeatures: fileConfig.targets?.simdFeatures ?? null,
142
+ baselineFeatures: fileConfig.targets?.baselineFeatures ?? null,
141
143
  },
142
144
 
143
145
  inline:
@@ -182,6 +184,12 @@ export function loadConfigFromCli(cliOpts = {}) {
182
184
  version:
183
185
  fileConfig.wasmDelivery?.version ?? fileConfig.version ?? 'latest',
184
186
  },
187
+
188
+ // SIMD variant configuration
189
+ simd: fileConfig.simd ?? null,
190
+
191
+ // Benchmark configuration
192
+ bench: fileConfig.bench ?? null,
185
193
  }
186
194
 
187
195
  return config
package/src/cli/emit.js CHANGED
@@ -1,8 +1,11 @@
1
1
  import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs'
2
- import { join } from 'node:path'
2
+ import { join, extname } from 'node:path'
3
+ import { createRequire } from 'node:module'
3
4
  import { fileURLToPath } from 'node:url'
4
5
 
5
6
  const UTIL_PATH = fileURLToPath(new URL('../js/util.js', import.meta.url))
7
+ const require = createRequire(import.meta.url)
8
+ const TS_EXTS = new Set(['.ts', '.tsx', '.cts', '.mts'])
6
9
 
7
10
  export function buildWrapperIR(exportsList) {
8
11
  return exportsList.map((entry) => {
@@ -449,7 +452,10 @@ export function code() {
449
452
  }
450
453
 
451
454
  export function createLoaderTypes({ exportFrom }) {
452
- return `export function init(imports?: WebAssembly.Imports): Promise<void>;
455
+ return `export interface InitOptions {
456
+ backend?: 'auto' | 'simd' | 'base';
457
+ }
458
+ export function init(imports?: WebAssembly.Imports, opts?: InitOptions): Promise<void>;
453
459
  export * from "${exportFrom}";
454
460
  `
455
461
  }
@@ -461,14 +467,17 @@ export function createLoader({ exportFrom, autoInit, getBytesSrc }) {
461
467
  : '\nregisterInit(init);'
462
468
 
463
469
  return `import { setInstance, registerInit } from "./core.js";
464
- import { instantiateWithFallback } from "./util.js";
470
+ import { instantiateWithBackend } from "./util.js";
465
471
  ${getBytesSrc}
466
472
 
467
473
  let _ready = null;
468
- export function init(imports = {}) {
469
- return (_ready ??= (async () => {
470
- const { simdBytes, baseBytes } = await getWasmBytes();
471
- const { instance } = await instantiateWithFallback(simdBytes, baseBytes, imports);
474
+ let _backend = null;
475
+ export function init(imports = {}, opts = {}) {
476
+ const backend = opts.backend || 'auto';
477
+ if (_ready && _backend === backend) return _ready;
478
+ _backend = backend;
479
+ return (_ready = (async () => {
480
+ const { instance } = await instantiateWithBackend({ getSimdBytes, getBaseBytes, imports, backend });
472
481
  setInstance(instance);
473
482
  })());
474
483
  }
@@ -495,10 +504,14 @@ function createBrowserLoader({ name, autoInit, customJs, wasmDelivery }) {
495
504
  const simdUrl = ${simdUrl};
496
505
  const baseUrl = ${baseUrl};
497
506
 
498
- async function getWasmBytes() {
499
- const [simdRes, baseRes] = await Promise.all([fetch(simdUrl), fetch(baseUrl)]);
500
- const [simdBytes, baseBytes] = await Promise.all([simdRes.arrayBuffer(), baseRes.arrayBuffer()]);
501
- return { simdBytes, baseBytes };
507
+ async function getSimdBytes() {
508
+ const res = await fetch(simdUrl);
509
+ return res.arrayBuffer();
510
+ }
511
+
512
+ async function getBaseBytes() {
513
+ const res = await fetch(baseUrl);
514
+ return res.arrayBuffer();
502
515
  }
503
516
  `
504
517
  return createLoader({ exportFrom, autoInit, getBytesSrc })
@@ -513,9 +526,12 @@ import { fileURLToPath } from "node:url";
513
526
  const simdPath = fileURLToPath(new URL("./wasm/${name}.simd.wasm", import.meta.url));
514
527
  const basePath = fileURLToPath(new URL("./wasm/${name}.base.wasm", import.meta.url));
515
528
 
516
- async function getWasmBytes() {
517
- const [simdBytes, baseBytes] = await Promise.all([readFile(simdPath), readFile(basePath)]);
518
- return { simdBytes, baseBytes };
529
+ async function getSimdBytes() {
530
+ return readFile(simdPath);
531
+ }
532
+
533
+ async function getBaseBytes() {
534
+ return readFile(basePath);
519
535
  }
520
536
  `
521
537
  return createLoader({ exportFrom, autoInit, getBytesSrc })
@@ -524,11 +540,15 @@ async function getWasmBytes() {
524
540
  function createInlineLoader({ name, autoInit, customJs }) {
525
541
  const exportFrom = customJs ? './custom.js' : './core.js'
526
542
  const getBytesSrc = `
527
- import { wasmBytes as simdBytes } from "./wasm-inline/${name}.simd.wasm.js";
528
- import { wasmBytes as baseBytes } from "./wasm-inline/${name}.base.wasm.js";
543
+ import { wasmBytes as _simdBytes } from "./wasm-inline/${name}.simd.wasm.js";
544
+ import { wasmBytes as _baseBytes } from "./wasm-inline/${name}.base.wasm.js";
529
545
 
530
- async function getWasmBytes() {
531
- return { simdBytes, baseBytes };
546
+ async function getSimdBytes() {
547
+ return _simdBytes;
548
+ }
549
+
550
+ async function getBaseBytes() {
551
+ return _baseBytes;
532
552
  }
533
553
  `
534
554
  return createLoader({ exportFrom, autoInit, getBytesSrc })
@@ -540,6 +560,38 @@ export const wasmBytes = new Uint8Array([${bytes.join(',')}]);
540
560
  `
541
561
  }
542
562
 
563
+ function loadCustomModule(crateDir, customJs) {
564
+ const customPath = join(crateDir, customJs)
565
+ const source = readFileSync(customPath, 'utf8')
566
+ const ext = extname(customJs).toLowerCase()
567
+
568
+ if (!TS_EXTS.has(ext)) return source
569
+
570
+ let ts
571
+ try {
572
+ ts = require('typescript')
573
+ } catch {
574
+ throw new Error(
575
+ "Custom TypeScript runtime requires the 'typescript' package. Install it with `npm install typescript` in your project."
576
+ )
577
+ }
578
+
579
+ const { outputText } = ts.transpileModule(source, {
580
+ fileName: customPath,
581
+ compilerOptions: {
582
+ module: ts.ModuleKind.ESNext,
583
+ target: ts.ScriptTarget.ES2020,
584
+ jsx: ts.JsxEmit.ReactJSX,
585
+ esModuleInterop: true,
586
+ allowSyntheticDefaultImports: true,
587
+ sourceMap: false,
588
+ },
589
+ reportDiagnostics: false,
590
+ })
591
+
592
+ return outputText
593
+ }
594
+
543
595
  function writeInlineModules({
544
596
  outDir,
545
597
  artifactBaseName,
@@ -580,11 +632,11 @@ export function emitRuntime({
580
632
  mkdirSync(outDir, { recursive: true })
581
633
 
582
634
  if (customJs) {
583
- const customJsContent = readFileSync(join(crateDir, customJs), 'utf8')
635
+ const customJsContent = loadCustomModule(crateDir, customJs)
584
636
  writeFileSync(join(outDir, 'custom.js'), customJsContent)
585
637
 
586
638
  if (emitTypes) {
587
- const customTsPath = customJs.replace(/\.js$/, '.d.ts')
639
+ const customTsPath = customJs.replace(/\.[^.]+$/, '.d.ts')
588
640
  if (existsSync(join(crateDir, customTsPath))) {
589
641
  writeFileSync(
590
642
  join(outDir, 'custom.d.ts'),
package/src/cli/index.js CHANGED
@@ -3,6 +3,7 @@ import { loadConfigFromCli, summarizeConfig } from './config.js'
3
3
  import { buildArtifacts } from './build.js'
4
4
  import { emitRuntime } from './emit.js'
5
5
  import { updatePackageJson } from './pkg.js'
6
+ import { runBench } from './bench.js'
6
7
 
7
8
  export async function runBuild(cliOpts) {
8
9
  const cfg = loadConfigFromCli(cliOpts)
@@ -56,12 +57,19 @@ export async function runClean(cliOpts) {
56
57
  }
57
58
  }
58
59
 
60
+ export async function runBenchCmd(cliOpts) {
61
+ const cfg = loadConfigFromCli(cliOpts)
62
+ console.log('Configuration:', summarizeConfig(cfg))
63
+ await runBench(cfg, cliOpts)
64
+ }
65
+
59
66
  export function printHelp() {
60
67
  const help = `
61
68
  wasm-bindgen-lite <command> [options]
62
69
 
63
70
  Commands:
64
71
  build Build wasm artifacts and emit JS loaders (default release)
72
+ bench Build variant matrix and run SIMD analysis
65
73
  clean Remove the configured output directory
66
74
  help Show this message
67
75
 
@@ -74,7 +82,27 @@ Options (for build):
74
82
  --simd | --no-simd Build SIMD variant (default: simd on)
75
83
  --wasm-opt | --no-wasm-opt Force enable/disable wasm-opt (default: auto detect)
76
84
  --wasm-opt-args "<args>" Extra args, default "-Oz"
77
- --no-update-package-json Do not modify package.json exports (default: updates if package.json exists)
85
+ --no-update-package-json Do not modify package.json exports
86
+
87
+ Options (for bench):
88
+ --crate <path> Crate root (default: .)
89
+ --config <path> Path to config JSON
90
+ --clean Clean output directory before building
91
+ --skip-build Skip building, use existing variants
92
+
93
+ SIMD Configuration (in wasm-bindgen-lite.config.json):
94
+ {
95
+ "simd": {
96
+ "features": {
97
+ "explicit-simd-encode": { "name": "encode" },
98
+ "explicit-simd-decode": { "name": "decode" }
99
+ },
100
+ "allFeature": "explicit-simd"
101
+ },
102
+ "bench": {
103
+ "outputDir": "bench_out"
104
+ }
105
+ }
78
106
  `
79
107
  console.log(help)
80
108
  }
@@ -1,17 +1,30 @@
1
- import { wasmBytes as simdBytes } from './wasm-inline/mod.simd.wasm.js'
2
- import { wasmBytes as baseBytes } from './wasm-inline/mod.base.wasm.js'
1
+ import { wasmBytes as _simdBytes } from './wasm-inline/mod.simd.wasm.js'
2
+ import { wasmBytes as _baseBytes } from './wasm-inline/mod.base.wasm.js'
3
3
  import { setInstance } from './core.js'
4
- import { instantiateWithFallback } from './util.js'
4
+ import { instantiateWithBackend } from './util.js'
5
+
6
+ async function getSimdBytes() {
7
+ return _simdBytes
8
+ }
9
+
10
+ async function getBaseBytes() {
11
+ return _baseBytes
12
+ }
5
13
 
6
14
  let _ready = null
15
+ let _backend = null
7
16
 
8
- export function init(imports = {}) {
9
- return (_ready ??= (async () => {
10
- const { instance } = await instantiateWithFallback(
11
- simdBytes,
12
- baseBytes,
13
- imports
14
- )
17
+ export function init(imports = {}, opts = {}) {
18
+ const backend = opts.backend || 'auto'
19
+ if (_ready && _backend === backend) return _ready
20
+ _backend = backend
21
+ return (_ready = (async () => {
22
+ const { instance } = await instantiateWithBackend({
23
+ getSimdBytes,
24
+ getBaseBytes,
25
+ imports,
26
+ backend,
27
+ })
15
28
  setInstance(instance)
16
29
  })())
17
30
  }
package/src/js/browser.js CHANGED
@@ -1,28 +1,33 @@
1
1
  import { setInstance } from './core.js'
2
- import { instantiateWithFallback } from './util.js'
2
+ import { instantiateWithBackend } from './util.js'
3
3
 
4
4
  const simdUrl = new URL('./wasm/mod.simd.wasm', import.meta.url)
5
5
  const baseUrl = new URL('./wasm/mod.base.wasm', import.meta.url)
6
6
 
7
- let _ready = null
7
+ async function getSimdBytes() {
8
+ const res = await fetch(simdUrl)
9
+ return res.arrayBuffer()
10
+ }
8
11
 
9
- export function init(imports = {}) {
10
- return (_ready ??= (async () => {
11
- const [simdRes, baseRes] = await Promise.all([
12
- fetch(simdUrl),
13
- fetch(baseUrl),
14
- ])
12
+ async function getBaseBytes() {
13
+ const res = await fetch(baseUrl)
14
+ return res.arrayBuffer()
15
+ }
15
16
 
16
- const [simdBytes, baseBytes] = await Promise.all([
17
- simdRes.arrayBuffer(),
18
- baseRes.arrayBuffer(),
19
- ])
17
+ let _ready = null
18
+ let _backend = null
20
19
 
21
- const { instance } = await instantiateWithFallback(
22
- simdBytes,
23
- baseBytes,
24
- imports
25
- )
20
+ export function init(imports = {}, opts = {}) {
21
+ const backend = opts.backend || 'auto'
22
+ if (_ready && _backend === backend) return _ready
23
+ _backend = backend
24
+ return (_ready = (async () => {
25
+ const { instance } = await instantiateWithBackend({
26
+ getSimdBytes,
27
+ getBaseBytes,
28
+ imports,
29
+ backend,
30
+ })
26
31
  setInstance(instance)
27
32
  })())
28
33
  }
@@ -1,17 +1,30 @@
1
- import { wasmBytes as simdBytes } from './wasm-inline/mod.simd.wasm.js'
2
- import { wasmBytes as baseBytes } from './wasm-inline/mod.base.wasm.js'
1
+ import { wasmBytes as _simdBytes } from './wasm-inline/mod.simd.wasm.js'
2
+ import { wasmBytes as _baseBytes } from './wasm-inline/mod.base.wasm.js'
3
3
  import { setInstance } from './core.js'
4
- import { instantiateWithFallback } from './util.js'
4
+ import { instantiateWithBackend } from './util.js'
5
+
6
+ async function getSimdBytes() {
7
+ return _simdBytes
8
+ }
9
+
10
+ async function getBaseBytes() {
11
+ return _baseBytes
12
+ }
5
13
 
6
14
  let _ready = null
15
+ let _backend = null
7
16
 
8
- export function init(imports = {}) {
9
- return (_ready ??= (async () => {
10
- const { instance } = await instantiateWithFallback(
11
- simdBytes,
12
- baseBytes,
13
- imports
14
- )
17
+ export function init(imports = {}, opts = {}) {
18
+ const backend = opts.backend || 'auto'
19
+ if (_ready && _backend === backend) return _ready
20
+ _backend = backend
21
+ return (_ready = (async () => {
22
+ const { instance } = await instantiateWithBackend({
23
+ getSimdBytes,
24
+ getBaseBytes,
25
+ imports,
26
+ backend,
27
+ })
15
28
  setInstance(instance)
16
29
  })())
17
30
  }
package/src/js/node.js CHANGED
@@ -1,24 +1,33 @@
1
1
  import { readFile } from 'node:fs/promises'
2
2
  import { fileURLToPath } from 'node:url'
3
3
  import { setInstance } from './core.js'
4
- import { instantiateWithFallback } from './util.js'
4
+ import { instantiateWithBackend } from './util.js'
5
5
 
6
6
  const simdPath = fileURLToPath(new URL('./wasm/mod.simd.wasm', import.meta.url))
7
7
  const basePath = fileURLToPath(new URL('./wasm/mod.base.wasm', import.meta.url))
8
8
 
9
+ async function getSimdBytes() {
10
+ return readFile(simdPath)
11
+ }
12
+
13
+ async function getBaseBytes() {
14
+ return readFile(basePath)
15
+ }
16
+
9
17
  let _ready = null
18
+ let _backend = null
10
19
 
11
- export function init(imports = {}) {
12
- return (_ready ??= (async () => {
13
- const [simdBytes, baseBytes] = await Promise.all([
14
- readFile(simdPath),
15
- readFile(basePath),
16
- ])
17
- const { instance } = await instantiateWithFallback(
18
- simdBytes,
19
- baseBytes,
20
- imports
21
- )
20
+ export function init(imports = {}, opts = {}) {
21
+ const backend = opts.backend || 'auto'
22
+ if (_ready && _backend === backend) return _ready
23
+ _backend = backend
24
+ return (_ready = (async () => {
25
+ const { instance } = await instantiateWithBackend({
26
+ getSimdBytes,
27
+ getBaseBytes,
28
+ imports,
29
+ backend,
30
+ })
22
31
  setInstance(instance)
23
32
  })())
24
33
  }
package/src/js/util.js CHANGED
@@ -12,3 +12,33 @@ export async function instantiateWithFallback(
12
12
  return { instance, backend: 'wasm' }
13
13
  }
14
14
  }
15
+
16
+ export async function instantiateWithBackend({
17
+ getSimdBytes,
18
+ getBaseBytes,
19
+ imports,
20
+ backend = 'auto',
21
+ }) {
22
+ if (backend === 'base') {
23
+ const baseBytes = await getBaseBytes()
24
+ const { instance } = await WebAssembly.instantiate(baseBytes, imports)
25
+ return { instance, backend: 'wasm' }
26
+ }
27
+
28
+ if (backend === 'simd') {
29
+ const simdBytes = await getSimdBytes()
30
+ const { instance } = await WebAssembly.instantiate(simdBytes, imports)
31
+ return { instance, backend: 'wasm-simd' }
32
+ }
33
+
34
+ // auto: try simd first, then fallback to baseline
35
+ try {
36
+ const simdBytes = await getSimdBytes()
37
+ const { instance } = await WebAssembly.instantiate(simdBytes, imports)
38
+ return { instance, backend: 'wasm-simd' }
39
+ } catch {
40
+ const baseBytes = await getBaseBytes()
41
+ const { instance } = await WebAssembly.instantiate(baseBytes, imports)
42
+ return { instance, backend: 'wasm' }
43
+ }
44
+ }