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
- Uses
tools/fw-bundler/src/modlib/scan-modules.js(scanAll) to enumerate the descriptors undersrc/and build aMap<name, ModuleInfo>— same scanner as the build orchestrator, single source of truth. - Dynamically imports the targeted descriptor to inspect
{name, version, type, dependencies, factory}. - Recursive walk on
dependenciesto produce the topologically sorted list (deps before dependants). Cycle detection with a clear message (a -> b -> a). - 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)). - The final module is exposed via
export default. - 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. - 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-levelconst/let/var, outerfunction/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
scanAllfor anamewins.
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
precompilation.md— HTML →ParseResultprecompilation.aot.md— AOT HTML → imperative JS factory compilation.bundler.md— main prebuild orchestrator.../README.md— general fw doc index.