Using Installed Packages
Installed package graph support is experimental. Sloppy can bundle installed pure-JavaScript package dependencies when the compiler can resolve, transform, package, and execute them inside Sloppy's runtime boundary.
This is not a package manager. Use npm, pnpm, Yarn, or another package manager to install packages first. Sloppy reads the already installed files from node_modules during build and emits a sealed artifact graph.
Prerequisites
- A Sloppy project with
sloppy.json. - An installed package under
node_modules. - Package code that uses JavaScript Sloppy can transform and runtime APIs that Sloppy supports or shims.
Obvious native Node addon package shapes are rejected with clear diagnostics. Sloppy does not support Node native addons or N-API yet. Detection is based on known package and native-entry patterns, not a formal guarantee that every native package shape is recognized.
Unrestricted Node builtins and packages that rely on implicit Node globals may also fail with clear diagnostics.
The current Node compatibility layer covers practical subsets of common pure-JavaScript package assumptions: explicit node:process, node:buffer, node:fs/promises, node:assert, node:stream, node:stream/promises, node:crypto, node:module, node:string_decoder, node:zlib, node:diagnostics_channel, path/events, and related utility shims. node:http, node:https, and node:tty are importable stubs with clear unsupported or non-TTY behavior. See the Node compatibility reference for exact members and known differences from Node.
Install With Your Package Manager
npm install tiny-invariantSloppy does not install dependencies for you and does not solve semver ranges. The package manager owns version selection.
Import The Package
import invariant from "tiny-invariant";
export function main(args) {
invariant(args.length > 0, "expected at least one argument");
console.log(`first arg: ${args[0]}`);
}Build or run the app:
sloppy build
sloppy run -- oneThe compiler resolves the package from the importing module by walking upward to node_modules/<package>. Scoped packages and package subpaths are supported when they resolve through the supported package rules.
Supported Package Rules
Sloppy supports a practical package.json subset for installed packages:
exportsstring entriesexportsobject entries withsloppy,import,require, anddefaultconditionsnode,development, andproductionconditions aftersloppyand the mode-specific condition- package subpath exports such as
"./feature" importsfor package-local#...specifiers where resolvablemaintype: "module"andtype: "commonjs"- extension and directory index resolution for
.ts,.tsx,.js,.mjs,.cjs, and.json
Condition order is:
- ESM/import:
sloppy,import,node,development,production,default - CommonJS/require:
sloppy,require,node,development,production,default
Unsupported package export shapes fail with SLOPPYC_E_PACKAGE_EXPORT_UNSUPPORTED. The diagnostic identifies the package, the offending exports/imports field, the subpath that was requested, and the reason resolution failed.
CommonJS And JSON
CommonJS packages can be bundled when their require(...) calls are statically resolvable. The generated CommonJS wrapper provides exports, module, require, require.resolve, require.cache, __filename, and __dirname. ESM imports of CommonJS modules receive the CommonJS module.exports value as the default-like module value. Named imports from CommonJS are best-effort and should not be treated as full Node semantics.
JSON files can be imported as JSON modules. They are parsed at build time and emitted into the bundle.
Dynamic Imports
String-literal dynamic imports are resolved at build time:
const feature = await import("./features/report.js");
const pkg = await import("tiny-invariant");Computed imports only work against modules already sealed into the artifact graph. Add possible targets to moduleInclude:
{
"kind": "program",
"entry": "src/main.ts",
"moduleInclude": ["src/plugins/**/*.js"]
}At runtime, import("./plugins/" + name + ".js") succeeds only when the resolved module is in that graph. Otherwise the generated bundle throws SLOPPY_E_MODULE_NOT_FOUND with a moduleInclude hint.
Include Assets
Use assetInclude for files that should be packaged but not executed:
{
"assetInclude": ["public/**/*", "src/views/**/*.html"]
}Assets are recorded in the dependency graph and package metadata. They are not JavaScript modules.
Inspect The Graph
sloppy deps .sloppy
sloppy deps .sloppy --explain
sloppy deps .sloppy --format json
sloppy audit .sloppy
sloppy doctor .sloppysloppy deps lists bundled packages, module counts, assets, Node compatibility shims, and compatibility findings. --explain adds a text compatibility summary for package review.
Current Limits
- No registry install or package manager integration.
- No native Node addons or N-API. Obvious native addon shapes are rejected, but detection is not a complete native-package classifier.
- No full Node builtin parity. The stream and crypto shims are partial, and full HTTP, sockets, TLS, DNS, workers, VM, child process, inspector, REPL, and Node internals remain unsupported.
- No process-wide Node runtime identity. Bundled package runs install only
global,process, andBufferwhile the generated Sloppy program entry is executing. - No unrestricted runtime discovery outside the sealed graph.
Real installed packages work when their JavaScript and runtime API usage are compatible with Sloppy's loader and shims.
Compatibility Gauntlets
Sloppy commits two complementary gauntlets that together cover npm package compatibility.
Resolver Matrix
The compiler exercises a committed package compatibility matrix at tests/fixtures/npm-compat/. Each fixture is a tiny hand-authored package shape (CommonJS main, ESM main, type module/commonjs, scoped, exports string/object/subpath/extensionless/pattern/nested-conditions, imports aliases and patterns, self-reference, JSON require, optional dependencies, peer dependency metadata, native addon, unsupported Node builtin, etc.). The resolver test walks matrix.json and asserts that each shape resolves to the documented outcome. The matrix is the regression baseline for currently tested package shapes — shapes outside the matrix are not implicit non-regressions; add a new fixture before claiming support for a new shape.
The resolver matrix proves package shapes resolve.
Runtime Behavior Matrix
The runtime matrix at tests/fixtures/npm-runtime/ is built, packaged, copied outside the source checkout, and re-run under V8. Each fixture pins expected stdout (for supported fixtures) or expected stderr substring (for stubbed and negative-runtime fixtures). Fixtures cover CJS semantics (module.exports = fn, exports.foo mutation, exports = ... rebind), ESM/CJS interop (default and named imports across module systems), createRequire/require.resolve against the sealed graph, __dirname and __filename, package self-reference, imports aliases, import/require exports conditions, and node: builtins (Buffer, crypto, stream, zlib, and the sealed sync fs subset).
The runtime matrix proves selected package behaviors execute after packaging.
The two matrices are complementary; neither implies the other. Add a fixture plus matrix entry alongside any change that broadens either resolver or runtime behavior.
Ad-hoc smoke script
There is also an optional ad-hoc smoke script at tools/scripts/npm-compat-smoke.mjs that installs a curated list of small pure-JavaScript packages from the npm registry and reports whether sloppy build accepts them. The smoke script is not part of normal CI, does not gate release, and does not promote packages to officially supported status.