Purpose

Produces a self-contained ESM file exposing a fully resolved fw module, with no dependency on the runtime (registry.resolve(...)). Useful for embedding a single feature (e.g. aes_modes, sha256) outside @awacloud/fw.

CLI usage

fw-bundler standalone <moduleName> [--out <path>] [--min] [--classic] [--endpoint <name>] [--no-source-comments]
# or via npm script
bun run build:standalone <moduleName> [--flags]
Argument / flag Description
<moduleName> name of the fw module as discovered by scanAll (aes_modes, sha256, hex, …).
--out <path> Output file. Default: dist/standalone/<moduleName>.js.
--min Minifies output: Bun.build under bun, esbuild.transform under Node (esbuild = on-demand peer, bun run setup:e2e).
--classic Also attaches the resolved module to a global endpoint (default: the module name).
--endpoint <name> Name of the global property for --classic (default: <moduleName>). Implies --classic.
--no-source-comments Strips the header banner.

By default the output is pure ESM (export default <module>). --classic adds globalThis.<endpoint> = <module> (in addition to the export), for consumption from non-module code — symmetric to the classic variant of the preset build.

bun run build:standalone hex --classic --endpoint myHex
# → export default + globalThis.myHex = <hex resolved>

Example

bun run build:standalone aes_modes --min
# → dist/standalone/aes_modes.js (~18 KB minified)

Consumer side:

import aesModes from './aes_modes.js';
const ct = aesModes.cbc.encrypt(key, iv, plaintext);

Generated layout

// Generated by @awacloud/fw standalone-factory generator
// Module: aes_modes v1.0.0
// Dependencies (transitive, 7): bitArray, aes, cbc, ctr, gcm, kw, pad
// Built: 2026-05-25T…

const __bitArray = (function() { … })();
const __aes      = (function() { … })();
const __cbc      = (function() { … })(__bitArray);
// … topological order …
const __aes_modes = (function() { … })(__bitArray, __aes, __cbc, __ctr, __gcm, __kw, __pad);

export default __aes_modes;

How it works

  1. Uses tools/fw-bundler/src/modlib/scan-modules.js (scanAll) to enumerate the descriptors under src/ and build a Map<name, ModuleInfo> — same scanner as the build orchestrator, single source of truth.
  2. Dynamically imports the targeted descriptor to inspect {name, version, type, dependencies, factory}.
  3. Recursive walk on dependencies to produce the topologically sorted list (deps before dependants). Cycle detection with a clear message (a -> b -> a).
  4. For each descriptor, serialises factory.toString() and emits an IIFE that calls the factory positionally with the already-resolved deps — same contract as the runtime (def.factory.apply({}, deps)).
  5. The final module is exposed via export default.
  6. Before writing, each factory is scanned for closure captures — identifiers bound to the module scope (imports, top-level const/let/var/function/class) referenced in the factory body. Factory parameters (e.g. factory({ hex, utf8 })) are extracted and excluded from the set of suspect identifiers — they are bound by the call, not captured. If a real capture is found, generation fails with the list of culprits.
  7. After writing, the output is dynamically imported to validate that it loads.

Programmatic API

import { generateStandalone } from '../../../tools/fw-bundler/src/standalone/index.js';

const res = await generateStandalone('sha256', {
    out: 'out/sha256.js',
    min: false,
    sourceComments: true,
    classic: false,        // true → attaches globalThis.<endpoint>
    endpoint: null,        // default: the module name
});
// → { outPath, bytes, moduleCount, modules, validate, minified, endpoint }

Known limitations

  • Real closure captures (references to the module scope: import, top-level const/let/var, outer function/class) remain unsupported. Refactor the module to inline its data in the factory body.
  • Factory parameters (including destructuring, e.g. factory({ hex, utf8 })) are recognised and excluded — no false positives on injected dependencies.
  • Regex detection is best-effort; common JS built-ins are allow-listed but a contrived name could produce a false positive.
  • No multi-version support: the first occurrence found by scanAll for a name wins.

Tests

bun test tools/fw-bundler/tests/standalone.integration.test.js

Covers hex (0 deps), aes_modes (7 transitive deps), sha256 (known digest vector), --no-source-comments, --min, and the unknown-module error path.

See also