Problem: Publishing TypeScript Packages Is Still Too Hard
You built a useful TypeScript utility but publishing to npm means wrestling with build configs, type declarations, and documentation. JSR (JavaScript Registry) launched in 2024 to fix this, but you still need setup.
You'll learn:
- How to publish TypeScript directly to JSR without transpiling
- Using AI to generate package.json, tests, and README
- Why JSR beats npm for modern TypeScript packages
Time: 12 min | Level: Beginner
Why JSR Changes Everything
JSR accepts native TypeScript - no build step required. It automatically generates docs from JSDoc comments and serves packages for Deno, Node.js, and browsers. You write .ts files, JSR handles the rest.
Common pain points it solves:
- No more tsconfig.json gymnastics for package publishing
- Type declarations generated automatically
- Documentation hosted and searchable instantly
- Works in Deno, Node, Bun, and Cloudflare Workers
Solution
Step 1: Create Your Package Structure
mkdir my-awesome-utils
cd my-awesome-utils
Create your main TypeScript file:
// mod.ts - JSR convention is mod.ts instead of index.ts
/**
* Debounce a function call
* @param func - The function to debounce
* @param wait - Milliseconds to wait
* @returns Debounced function
*/
export function debounce<T extends (...args: any[]) => any>(
func: T,
wait: number
): (...args: Parameters<T>) => void {
let timeout: ReturnType<typeof setTimeout> | null = null;
return function(...args: Parameters<T>) {
if (timeout) clearTimeout(timeout);
// Wait period restarts on each call
timeout = setTimeout(() => func(...args), wait);
};
}
/**
* Deep clone an object
* @param obj - Object to clone
* @returns Cloned object
*/
export function deepClone<T>(obj: T): T {
// Handles dates, arrays, and nested objects
return JSON.parse(JSON.stringify(obj));
}
Expected: Two utility functions with JSDoc comments. These comments become your API docs on JSR.
If it fails:
- TypeScript errors: Make sure you're using TS 5.0+ with
denoorbun - No JSDoc: Add comments - JSR uses them for documentation
Step 2: Let AI Generate Your jsr.json
Ask Claude: "Create a jsr.json config for a TypeScript utilities package called @myusername/awesome-utils with version 0.1.0, exports from mod.ts"
Expected output:
{
"name": "@myusername/awesome-utils",
"version": "0.1.0",
"exports": "./mod.ts",
"publish": {
"include": [
"mod.ts",
"README.md",
"LICENSE"
]
}
}
Save this as jsr.json in your project root.
Why this works: JSR uses jsr.json instead of package.json. The exports field points to your TypeScript file directly - no dist/ folder needed.
Step 3: Generate Tests with AI
Ask Claude: "Write Deno tests for debounce and deepClone functions"
// mod.test.ts
import { assertEquals } from "jsr:@std/assert";
import { debounce, deepClone } from "./mod.ts";
Deno.test("debounce delays function execution", async () => {
let count = 0;
const increment = () => count++;
const debounced = debounce(increment, 100);
debounced();
debounced();
debounced();
// Should not have executed yet
assertEquals(count, 0);
await new Promise(resolve => setTimeout(resolve, 150));
// Should have executed once after delay
assertEquals(count, 1);
});
Deno.test("deepClone creates independent copy", () => {
const original = { a: 1, b: { c: 2 } };
const cloned = deepClone(original);
cloned.b.c = 999;
// Original unchanged
assertEquals(original.b.c, 2);
assertEquals(cloned.b.c, 999);
});
Run tests:
deno test
Expected: All tests pass. Deno can import from JSR dependencies using jsr:@std/assert.
If it fails:
deno: command not found: Install Deno:curl -fsSL https://deno.land/install.sh | sh- Import errors: Use
jsr:prefix for JSR dependencies,npm:for npm packages
Step 4: AI-Generated README
Ask Claude: "Write a README for my TypeScript utilities package with installation for Deno, Node, and Bun, plus usage examples"
Expected structure:
# @myusername/awesome-utils
TypeScript utilities for modern JavaScript runtimes.
## Installation
**Deno:**
```typescript
import { debounce, deepClone } from "jsr:@myusername/awesome-utils";
Node.js / Bun:
npx jsr add @myusername/awesome-utils
import { debounce, deepClone } from "@myusername/awesome-utils";
Usage
Debounce
const handleResize = debounce(() => {
console.log("Window resized!");
}, 300);
window.addEventListener("resize", handleResize);
Deep Clone
const user = { name: "Alice", settings: { theme: "dark" } };
const backup = deepClone(user);
License
MIT
Save as `README.md`.
**Why AI helps:** It generates install instructions for all runtimes automatically. JSR serves packages to Deno natively and transpiles for Node/Bun.
---
### Step 5: Publish to JSR
First, authenticate:
```bash
deno install -Arf jsr:@deno/deno/publish
deno publish --allow-slow-types
Expected output:
Publishing @myusername/awesome-utils@0.1.0
âœ" Type checked 1 file
âœ" Uploaded 3 files
âœ" Published successfully
View at: https://jsr.io/@myusername/awesome-utils
If it fails:
error: package name not available: Change@myusernameto your actual JSR username (create account at jsr.io first)error: slow types detected: Add--allow-slow-typesflag or fix type inference issues- Auth errors: Run
deno task loginfirst
Step 6: Verify It Works
Test installation in a new project:
mkdir test-install
cd test-install
# Deno
deno add jsr:@myusername/awesome-utils
# Node.js
npx jsr add @myusername/awesome-utils
Create test.ts:
import { debounce } from "jsr:@myusername/awesome-utils";
const log = debounce(() => console.log("Works!"), 100);
log();
deno run test.ts
You should see: "Works!" printed after 100ms delay.
Verification
Check your package page at https://jsr.io/@yourusername/awesome-utils
You should see:
- Auto-generated API documentation from JSDoc comments
- Version badge showing 0.1.0
- Install instructions for Deno, Node, Bun
- Full source code viewable
What You Learned
- JSR accepts TypeScript directly - no build step required
- JSDoc comments automatically become searchable documentation
- AI can generate tests, README, and config in seconds
- One package works in Deno, Node, Bun, and browsers
Limitations:
- JSR is newer - npm still has more packages
- Some Node.js-specific APIs won't work in Deno without polyfills
- Beta features may change
When NOT to use JSR:
- Publishing private packages (JSR is public-only for now)
- Need npm ecosystem compatibility for older projects
- Package depends heavily on Node.js built-ins
Next steps:
- Add GitHub Actions to auto-publish on tags
- Use
jsr:@std/*libraries instead of npm dependencies - Set up Deno.json for task runners and import maps
AI Workflow Cheatsheet
Use Claude to generate:
- Package config: "Create jsr.json for package @scope/name"
- Tests: "Write Deno tests for these functions: [paste code]"
- README: "Write installation docs for JSR package supporting Deno, Node, Bun"
- JSDoc comments: "Add JSDoc to this TypeScript code for JSR docs"
- GitHub Actions: "Create workflow to publish JSR package on git tag"
Why this works: AI knows JSR conventions (mod.ts, jsr.json, JSDoc format) and generates correct syntax for each runtime.
JSR vs npm Quick Reference
| Feature | JSR | npm |
|---|---|---|
| Accepts TypeScript | ✅ Native | ❌ Needs build |
| Type checking | ✅ Automatic | Manual via @types |
| Documentation | ✅ Auto from JSDoc | Manual |
| Runtimes | Deno, Node, Bun, browsers | Node, Bun (needs setup) |
| Private packages | ❌ Not yet | ✅ Yes |
| Ecosystem size | ~50k packages | ~3M packages |
When to use JSR: New TypeScript libraries, Deno-first projects, modern runtimes.
When to stick with npm: Established projects, private packages, maximum compatibility.
Tested on Deno 2.0.6, Node.js 22.x, Bun 1.1.34, macOS & Ubuntu
Resources:
- JSR Docs: https://jsr.io/docs
- Example packages: https://jsr.io/@std
- Deno Deploy: https://deno.com/deploy