Ship source, compile with the app
Publishing libraries
Publish all importable code in the form it was authored. The consuming application compiles it with its own Octane toolchain so generated code and the installed runtime always agree.
The source-package contract
An Octane library is a source package. Its public exports resolve to authored source, not to JavaScript previously emitted by the Octane compiler. The application’s Vite, Rspack, or Rsbuild integration compiles that source alongside the application.
This is required because compiled Octane output is coupled to the compiler/runtime contract and to the build target. Compiling in the application gives the library the same Octane version, client or server runtime selection, production settings, and development behavior as the code importing it.
Package the complete source graph
Publish every file consumers can import through the package exports, plus every module
and runtime asset those entry points transitively import. Preserve each file’s extension
and relative path. That includes all .tsrx, .tsx, .ts, and .js modules in the
importable graph, plus imported CSS, JSON, WebAssembly, or other runtime assets. Do not
replace .tsrx or .tsx modules with compiler output during release packaging.
“Complete source” means the importable graph consumers can execute. Tests, fixtures, benchmarks, examples, and repository-only tooling are outside that graph and should not be published unless the package intentionally exposes them.
A simple package whose src directory contains only importable code can whitelist that
whole directory:
my-octane-library/
├── package.json
├── README.md
└── src/
├── index.ts
├── Button.tsrx
├── Icon.tsx
├── useButton.ts
└── compatibility.js{
"name": "@acme/octane-library",
"version": "1.0.0",
"type": "module",
"files": ["src", "README.md"],
"main": "./src/index.ts",
"module": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
".": "./src/index.ts"
},
"peerDependencies": {
"octane": "^0.1.0"
}
}Choose the peer range you actually test and support. The important parts of the
example are that files includes the complete source graph, exports resolves to raw
source, and octane is a runtime peer rather than only a development dependency.
For multiple public entry points, map every subpath to its source entry:
{
"exports": {
".": "./src/index.ts",
"./button": "./src/Button.tsrx",
"./server": "./src/server.ts"
}
}The consuming application must declare your package in its dependencies. Octane’s
bundler integrations discover installed source packages that depend on or peer-depend
on octane, follow their runtime dependencies recursively, and keep them in the
compiler pipeline. Applications do not need package-specific build aliases or
node_modules include rules.
Authoring and types
Keep the original extensions because they tell the toolchain how to handle each module:
.tsrxand Octane-owned.tsxreceive full compilation.- Plain
.tsand.jsremain source modules; when they define Octane custom hooks, the compiler assigns the hook slots they need. - A
.tsxfile should start with/** @jsxImportSource octane */so TypeScript uses Octane’s JSX types. Installed-package ownership still comes from the package manifest.
Raw .ts entry points can also supply the public types, as in the manifest above. If a
direct .tsrx export needs a generated declaration, ship its corresponding .d.ts and
use a types export condition, but keep the default condition pointed at the raw
.tsrx implementation. Declaration files supplement source; they do not replace it.
Never add an ambient declare module '*.tsrx'. It hides real resolution errors and
turns every matching import into any. Type-check a package containing .tsrx with
tsrx-tsc --noEmit.
Use package metadata only for real exceptions
Most libraries need no top-level octane configuration object; declaring the runtime
dependency is enough. In particular, do not copy this setting from another binding:
{
"octane": {
"hookSlots": {
"manual": ["src"]
}
}
}hookSlots.manual disables automatic hook slotting under those directories. It is only
for a library that deliberately implements and verifies slot forwarding itself. Leaving
it out is the correct default for normal .ts and .js custom hooks.
The Vite-specific octane.vite.optimizeDeps.exclude metadata is similarly narrow: use
it only when an identity-sensitive dependency must stay outside Vite’s dependency
optimizer. See Build tools for that escape hatch.
Verify what users receive
Inspect the release tarball before publishing:
pnpm pack --dry-runConfirm that:
- Every exported path exists in the tarball.
- Every relative import from those entries resolves to another included source file or asset.
- Every
.tsrx,.tsx,.ts, and.jsfile in the importable graph retains its authored contents and extension. octaneis present inpeerDependencies, not onlydevDependencies.- A fixture application can install the packed tarball, type-check with
tsrx-tsc, and build through one of Octane’s supported integrations.
That packed-consumer test is the useful boundary: it catches missing files entries,
incorrect export maps, undeclared runtime dependencies, and release steps that silently
swap source for stale compiled output.