
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
In-memory, modifier-first JS bundler that outputs an eval-ready string. Build once, bundle many.
In‑memory, modifier‑first JS bundler (eval‑ready).
als-pack is a tiny, dependency‑light bundler focused on on‑the‑fly packaging of ES modules into a single string you can eval or run in a sandbox.
It parses your ESM graph, rewrites imports to a lazy runtime, and lets you apply modifiers to transform code (JSX/TS/minify/instrumentation) at bundle time.
Use it for editors, REPLs, SDKs, demos, and anywhere a heavyweight toolchain is overkill.
npm i als-pack
# or
pnpm add als-pack
# or
yarn add als-pack
Use in Nodejs:
import { pack } from 'als-pack';
Or use in browser:
<script src="/node_modules/als-pack/pack.js"><script>
or
<script src="/node_modules/als-pack/pack.min.js"><script>
project/
entry.js
util.js
util.js
export const greet = (name) => `Hello, ${name}!`;
entry.js
import { greet } from "./util.js";
export default () => greet("world");
bundle & run (Node):
import { pack } from "als-pack";
const p = await pack(resolve("./mini-project/entry.js"));
p.build();
// Get a *pure expression* that evaluates to the exports object:
const expr = p.bundle();
// Execute in a sandbox (recommended) and grab exports:
const { default: greet } = eval(expr);
By default
bundle(varName)returns a statementconst ${varName} = (bundleExpr)which is handy for REPLs, but most apps preferbundle()to get a pure expression and explicitly capture the value.
Result (bundle expression):
new (class {
constructor() {
void this.entry
}
get util() {
if(this._util) return this._util
const greet = (name) => `Hello, ${name}!`;
this._util = {greet:greet}
return this._util
}
get entry() {
if(this._entry) return this._entry
const { greet } = this.util;;
this._entry = {default:() => greet('world')}
return this._entry
}
})().entry
And pack after build should look like this:
{
commonPath: "/examples/demo",
defaultAlias: "main",
entry: "entry",
modifiers: [],
modules: {util: {…}, entry: {…}},
order: (2) ['/examples/demo/util.js', '/examples/demo/entry.js'],
orderByName: (2) ['util', 'entry'],
sep: "/",
}
pack(entry: string): Promise<Pack>Builds a module graph starting at entry. Detects environment (Node/browser) and uses the proper file/resolve helpers.
Throws on cyclic dependencies during preparation (message includes importer, imported and original import string).
Packmodules: Record<string, Module> — internal module table (after build() it contains modified sources and exportKeys).order: string[] — absolute file paths in topological order (leaf → entry).orderByName: string[] — same as above but normalized to export names.commonPath: string — resolved common root of all module paths.entry: string — normalized name of the entry module.modifiers: Array<Modifier> — transformation pipeline applied per‑module during bundling (see below).build(): voidPrepares modules for bundling (rewrites imports/exports, computes exportKeys, throws on cycles). Must be called before bundle().
bundle(varName): stringReturns bundle code string.
varName passed, returns a statement like:
const ${varName} = (/* bundle expression */)
Useful in REPLs; eval(code) will declare those bindings in the current scope.varName, returns a pure expression that evaluates to an exports object:
const expr = pack.bundle();
const exportsObj = (new Function(`return (${expr})`))();
A Module object (internal, for advanced users and modifiers):
type Module = {
modulePath: string; // absolute path
expName: string; // normalized export name ('dir_file_js' etc.)
imported: Array<{
importString: string;
relativePath: string;
fullPath: string; // '' for external/builtin
command: 'import' | 'export';
isBuiltin: boolean; // node:* modules
isExternal: boolean; // bare specifiers (kept external)
}>;
exported: Array<{ asname: string; name: string; exportString: string; declaration: boolean; }>;
modified: string; // source with imports/exports rewritten
exportKeys: Array<[asname: string, name: string]>; // collected during prepare
};
A modifier transforms each module right before it is placed into the bundle.
Signature:
type Modifier = ([modname, code, toReturn]: [string, string, string]) => [string, string, string];
modname — the normalized module name (same as getter name).code — final, prepared source of the module (imports/exports already rewritten).toReturn — string representation of { ... } returned from the module getter.! The modName has to remain the same! It passed only for understanding which module is it.
pack.modifiers.push(([m, c, t]) => [m, c.replace('ENABLE_X=false', 'ENABLE_X=true'), t]);
pack.modifiers.push(([m, c, t]) => {
const out = Babel.transform(c, { presets: ['react'] });
return [m, out.code, t];
});
Modifiers run per bundle call. You can generate multiple different bundles from the same
Packby changingmodifiersbetween calls.
node:* or bare names like fs, path) are treated as externals and removed from the code. You can provide bindings yourself (e.g. via globalThis or a small prelude).react) are treated as externals by default. To include a package in the graph, pass a full file path to its entry instead of a bare name, or use the browser resolver to turn a specifier into a URL.getNodeModule(spec: string): Promise<string>Resolves spec to /node_modules/<pkg>/<target> by reading /node_modules/<pkg>/package.json over fetch and using exports / module / main (simplified).
import { getNodeModule } from 'als-pack/lib/browser/node-module.js';
const url = await getNodeModule('react/jsx-runtime');
// → '/node_modules/react/dist/jsx-runtime.js'
getNodeModule(spec: string): Promise<string | "node:*">Resolves using import.meta.resolve (Node ≥ 20) or Module.createRequire().resolve and converts file:// URLs to filesystem paths.
import { getNodeModule } from 'als-pack/lib/node-module.js';
console.log(await getNodeModule('node:path')); // 'node:path'
console.log(await getNodeModule('react')); // '/abs/path/to/.../react/...'
import 'x' become void this.x; inside the importer. This means side‑effects of x run when the importer module is first evaluated, not eagerly at graph load like in native ESM.Promise from the entry module and await the bundle’s result at the call site.export const A = 1, B = 2; is not supported. Use either separate declarations or export { A, B };.Browser resolver (getNodeModule for browsers)
/node_modules/<package>/package.json and accessible via fetch; this requires proper static hosting and CORS configuration.exports field is honored (e.g., browser, import, module, default). Conditional exports (e.g., { "production": ..., "development": ... }), pattern exports, and advanced condition matching are not fully implemented.@scope/name) and subpath exports (pkg/subpath) are supported, but resolution semantics are simplified compared to Node’s resolver.module or main; if neither is present, the resolver guesses index.js. Extension and directory resolution (index.*, missing extensions) are not performed beyond what the package declares.node:* URLs; there are no polyfills shipped for the browser."imports" map, self‑references ("name": "pkg" + import "pkg/..."), or Node’s conditional flags (e.g., require, node, deno, workerd).HEAD requests must be handled by using GET; servers that block /package.json cannot be resolved.Node resolver (getNodeModule for Node.js)
import.meta.resolve (Node ≥ 20) when available; otherwise falls back to Module.createRequire(...).resolve. Environments that restrict either API may not be supported.node:* for built‑ins; no polyfills are provided.file:// URL. Only node: and file: schemes are supported.FAQs
In-memory, modifier-first JS bundler that outputs an eval-ready string. Build once, bundle many.
We found that als-pack demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.