5.7 KiB
Chapter 5 — Modules, import, and packaging (the honest version)
This chapter answers the most common disappointment: “Why can’t I use import in my Bare OS script?”
Short answer
In-image scripts (/boot/init.js, /bin/*, ~/tool.js) are executed as AsyncFunction bodies, not as ES modules. The JavaScript engine never runs the ESM loader for those strings. Therefore:
import x from 'y'is a syntax error in that context (top-levelimportis only valid in modules).requireis likewise unavailable unless the host injected a global (do not rely on it for portable utilities).
How /bin utilities still share code
The bare-os-coreutils build concatenates:
lib/runtime.js(shared helpers:bareStdin, mode formatting, …)- Optional preamble files (
sed-engine.js,awk-engine.js,man-render.js) src/<name>.js(must contain onlyasync function runand helpers in the same string—noimport)
So “modules” become one compiled file on the drive. That is the supported pattern for shared logic in tier-1 utilities.
Patterns that work for user and kernel code
1. Inline helpers
For small scripts, define functions above run:
function double(n) {
return n * 2
}
async function run(ctx, argv) {
ctx.console.log(String(double(21)))
}
2. Copy-paste prelude snippets
You may copy minimal helpers (e.g. stdin reader) from runtime.js into your script. Keep the license header in mind if you redistribute; see LICENSE.
3. Load another file from the drive (advanced)
You can readFile a second script as a string and… you should not eval arbitrary untrusted content. For your own modules stored as ~/lib/helpers.js, a pattern is:
- Store function bodies only or data (JSON), not full
importsyntax. - Or concatenate at build time on the host before uploading to Hyperdrive.
There is no built-in import() dynamic loader wired to Hyperdrive in the stock booter.
4. Host-side bundling
If you generate a single bundle.js on your laptop with esbuild/rollup and upload it to ~/bundle.js, that file can use no external import at runtime because everything is already bundled. This is the closest to “npm on device” without changing the booter.
Host packages (booter / seeder): full ESM
When you edit packages/bare-os-booter/index.js, you are in module land:
import { runBinCommand } from './lib/kernel-runner.js'
Use this for new protocols, drive encryption, alternate kernels, etc. This is not the same as writing /bin/foo.
ctx.bare — Holepunch-style modules without import
When BARE_OS_BARE_MODULES is not disabled, the booter exposes ctx.bare: a frozen object whose keys are defined by bare-module-manifest.json. Each entry names an npm package and a stable ctxKey (for example b4a, protomux, compactEncoding, holesail).
Drive bundles (trusted image): the system image may include /lib/bare/manifest.json and **/lib/bare/bundles/*.js**. Those scripts are IIFE bundles built by bare-os-bare-libs. The booter executes them with Function in the same trust class as seeded /bin utilities and fills ctx.bare for the listed keys. Set **BARE_OS_BARE_DRIVE_BUNDLES=0** to skip this step.
Host resolution: after drive merge, the booter uses dynamic import() for manifest entries that are still missing. When the booter loads from a pear: URL (pear run), rows with bundle: true are not host-**import()**ed (bare-module cannot resolve npm package names from that referrer; ctx.bare.holesail et al. come from /lib/bare/bundles/*). On file: checkouts and Node test harnesses, bundle: true may still be host-imported as a fallback when a drive bundle is absent. Optional packages that fail to load (for example native-only modules on the wrong host) are skipped without aborting boot.
Hardening: set BARE_OS_BARE_MODULES=0 to omit ctx.bare entirely (the property is absent on ctx). Runtime caps bareCtxModules and bareDriveBundles mirror these toggles.
Full ecosystem context: Chapter 12 — Bare modules and Pear ecosystem.
Pear and Bare globals
Under Pear/Bare, some globals (e.g. Bare) may exist for host exit and lifecycle. In-image utilities should still prefer ctx for I/O to stay consistent when the same script pattern is tested under different harnesses. Prefer ctx.bare.* over relying on Bare-specific package side effects when you need bare-url / bare-path on the Pear runtime.
FAQ corner
Can I add dynamic import to the booter for user scripts?
Possible in theory (resolve specifiers from Hyperdrive) but not implemented, and it raises security and package format questions (where do dependencies live?).
Can I put node_modules on my personal drive?
Even if you replicated bytes, the in-image loader would not resolve them as Node does. You would need a host or kernel change to load from that tree.
See also
- Chapter 12 — Bare modules and Pear ecosystem
- Chapter 6 — Extending
/bin - Chapter 9 — Security
- bare-os-coreutils README