Publish a Library#
Build a reusable library in Jac and ship it to PyPI as a wheel and to npm as a package -- from jac create to twine upload. The publishing reference documents every knob; this tutorial walks the happy path end to end.
What you'll do:
- Scaffold a
py-packageproject and write a small library - Build a PyPI-ready wheel with
jac build --as wheeland test it locally - Upload it with twine
- Do the same for npm with a
js-package
Time: ~15 minutes
1. Scaffold the library#
This creates a minimal library project -- note there is no main.jac; packages don't need an entry point:
greetlib/
├── jac.toml # kind = "py-package", name, version
├── lib.jac # your public API
├── AGENTS.md # guidance for AI coding agents
└── .gitignore
The generated jac.toml already carries the metadata that will become your wheel's METADATA:
[project]
name = "greetlib"
version = "0.1.0"
description = "Distributable Python package (built into a wheel)"
entry-point = "lib.jac"
kind = "py-package"
2. Write the library#
Everything marked :pub is your public API. Replace lib.jac:
"""A tiny greeting library."""
def:pub greet(name: str) -> str {
return f"Hello, {name}!";
}
def:pub greet_many(names: list[str]) -> list[str] {
return [greet(n) for n in names];
}
Add a test in lib.test.jac and run it:
3. Build the wheel#
✔ Built greetlib-0.1.0-py3-none-any.whl (1,151 bytes)
✔ greetlib-0.1.0.tar.gz (815 bytes)
Distributions written to: dist
Upload with: twine upload dist/*
That's a standard PEP 427 wheel plus an sdist. Two things worth knowing:
- Consumers don't need Jac. The wheel ships your
.jacsource (plus.jirbytecode when present);pip install greetlibworks in any Python project, andjaclangis not added as a runtime dependency. Runtime dependencies your library actually uses must be declared under[dependencies]injac.toml-- they becomeRequires-Distentries. - Because the project's
kindispy-package, plainjac runbuilds the wheel too --jac run --showprints the resolved plan.
Test the wheel locally before uploading:
pip install dist/greetlib-0.1.0-py3-none-any.whl
python -c "import greetlib; print(greetlib.greet('PyPI'))"
4. Upload to PyPI#
twine is the only external tool you need, and only for the upload itself:
5. Now for npm#
The npm flow is symmetric -- the js-package kind compiles cl (client) code to ES-module JavaScript with TypeScript declarations:
✔ Built jsgreet-0.1.0.tgz (213 bytes)
Tarball written to: dist/jsgreet-0.1.0.tgz
Publish with: npm publish
The tarball contains compiled .js modules, matching .d.ts TypeScript declarations, and a package.json generated from [project] and [dependencies.npm] in jac.toml. If your components use JSX or the reactive API, Jac auto-wires @jaseci/runtime as a dependency. Publish with:
One project, both targets
There's no single "all" command -- run jac build --as wheel and jac build --as npm separately to produce both the wheel and the npm tarball from a single project, when your library has both server-usable and client-usable modules. Modules that cross the server boundary are rejected with a clear error at build time.
Gotchas#
- Wrap top-level modules in a directory. Single-file top-level modules aren't collected into the wheel -- put your code in a package directory (the scaffold already does this).
- Data files must be declared. Non-code files ship only if listed under
[project.include.data]-- see What gets included. - Version bumps live in
jac.toml. The[project] versionfield is the single source for wheel, sdist, and npm versions.
Where to go next#
- Publishing reference -- metadata fields,
[entrypoints.scripts]console commands, editable installs - Shared library (C ABI) -- the third packaging target:
jac nacompile --sharedbuilds a.so/.dylib/.dllany language can link