Distribute a package via `gjsify dlx`
This guide covers the publisher side: how to author an npm package so consumers can run it on GJS with a single gjsify dlx <name> invocation. End-user mechanics live in the CLI Reference → gjsify dlx.
Mental model
Section titled “Mental model”gjsify dlx is a GJS-bundle runner. It downloads the package tarball into a per-spec cache, then invokes gjs -m <bundle> after consulting the package’s gjsify field to find the right entry. The consumer never runs npm install; they don’t even need Node.js installed at runtime.
Three things make a package dlx-friendly:
- A pre-built GJS bundle in the published tarball
- A top-level
gjsifyfield pointing to it - The bundle has every dependency inlined —
gjs -m bundle.jsmust succeed without anynode_modules/next to it
Minimal package
Section titled “Minimal package”my-pkg/├── src/index.ts # the source you author├── dist/gjs.js # the GJS bundle, committed to the npm tarball└── package.json{ "name": "@me/my-pkg", "version": "0.1.0", "type": "module", "files": ["dist"], "scripts": { "build": "gjsify build src/index.ts --outfile dist/gjs.js" }, "gjsify": { "main": "dist/gjs.js" }, "devDependencies": { "@gjsify/cli": "^0.3.9" }}gjsify build produces a single self-contained file: every transitive dep — including the @gjsify/* polyfills picked up by --globals auto — is inlined. Use gjsify block fields rather than top-level main so Node consumers (if any) aren’t accidentally served the GJS bundle.
yarn build && yarn packgjsify dlx ./my-pkg-0.1.0.tgz # offline smoke-test against the tarballOnce on the registry:
gjsify dlx @me/my-pkgMultiple bins (subcommands)
Section titled “Multiple bins (subcommands)”When one package ships several CLIs, list them under gjsify.bin:
{ "gjsify": { "bin": { "fireworks": "dist/fireworks.js", "demo": "dist/demo.js" } }}Resolution order at gjsify dlx <pkg> [binOrArg]:
- user passed a bin name and
gjsify.bin[name]exists → that path gjsify.binhas exactly one entry → that path (auto-pick)gjsify.main→ that path- fallback: top-level
package.json#main→ that path with an advisory warning - otherwise: hard-fail telling the consumer which bins exist
gjsify dlx @me/my-pkg fireworks # explicit bingjsify dlx @me/my-pkg # fails — multiple bins, ask whichgjsify dlx @me/my-pkg fireworks -- --x # forward `--x` to the bundleNative prebuilds
Section titled “Native prebuilds”If your package contains a Vala/GIR-based native extension (.so + .typelib shipped per-arch), declare the prebuilds directory so the dlx runtime sets LD_LIBRARY_PATH and GI_TYPELIB_PATH for you:
{ "gjsify": { "main": "dist/gjs.js", "prebuilds": "prebuilds" }, "files": ["dist", "prebuilds"]}Layout:
prebuilds/ linux-x86_64/ libfoo.so Foo-1.0.typelib linux-aarch64/ libfoo.so Foo-1.0.typelibThe CLI auto-detects the host architecture and exports the matching directory.
Build-time gotchas
Section titled “Build-time gotchas”--outfile must NOT point at a TypeScript source
Section titled “--outfile must NOT point at a TypeScript source”gjsify build refuses to default the output to package.json#main/module when those resolve to a .ts/.tsx path or anything under src/. Always set gjsify.bundler.output.file (or pass --outfile explicitly) when main is your source file:
{ "main": "src/index.ts", // dev path, no build step "gjsify": { "bundler": { "output": { "file": "dist/gjs.js" } }, "bin": { "my-pkg": "dist/gjs.js" } }}Without this, you’d get an error like gjsify build: refusing to default --outfile to src/index.ts (would overwrite a TypeScript source file).
package.json#gjsify and .gjsifyrc.* are merged
Section titled “package.json#gjsify and .gjsifyrc.* are merged”You can split the config across both — package.json#gjsify stays minimal (bin, main, prebuilds) and .gjsifyrc.js carries bundler options. Both files are read; on key collisions the explicit file wins. There’s no first-match-wins anymore.
{ "gjsify": { "bin": { "my-pkg": "dist/gjs.js" } }}export default { bundler: { output: { file: 'dist/gjs.js' }, transform: { target: 'firefox140' }, },};Verify the bundle runs offline
Section titled “Verify the bundle runs offline”Before publishing, simulate the dlx environment — extract the packed tarball into a sibling directory with NO node_modules, then run with raw gjs:
yarn packmkdir /tmp/dlx-check && tar xf my-pkg-0.1.0.tgz -C /tmp/dlx-check --strip-components=1gjs -m /tmp/dlx-check/dist/gjs.jsIf this fails (missing imports, Cannot find module), the build is leaking unbundled deps. Common causes: external config that excluded a package the bundle actually needs, or a runtime import.meta.url-relative read targeting a path not present in the tarball.
When to use gjsify.main vs gjsify.bin
Section titled “When to use gjsify.main vs gjsify.bin”gjsify.main— single-bundle library or app. Preferred default.gjsify.bin— package ships multiple CLIs. Each entry can have its own outfile and shebang.
Both are optional: a package can also rely on the top-level package.json#main fallback, but you’ll get a one-time advisory warning per dlx invocation.
What’s next
Section titled “What’s next”- Publishing a single self-contained executable that doesn’t even need
gjsify dlxto run — see Self-executing GJS packages. - The end-to-end CLI mechanics for consumers — see the CLI Reference.