feat: generate package docs using @deno/doc#135
Conversation
|
@devdumpling is attempting to deploy a commit to the danielroe Team on Vercel. A member of the Team first needs to authorize it. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
deployed, if you want to test ✅ |
Thanks! Just finished writing up the description in the microservice variant. #134 |
There was a problem hiding this comment.
i hate this but... it does work
There was a problem hiding this comment.
although i realize now this isn't going to work in serverless envs 😭
back to the drawing board with this part
| swr: true, | ||
| getKey: event => { | ||
| const pkg = getRouterParam(event, 'pkg') ?? '' | ||
| return `docs:v5:${pkg}` |
There was a problem hiding this comment.
was rolling multiple versions of this locally during rapid prototyping -- needs to be dropped back before moving from poc
| }) | ||
|
|
||
| // Shiki doesn't encode > in text content (e.g., arrow functions) | ||
| html = escapeRawGt(html) |
There was a problem hiding this comment.
this also fixes a lot of random html validation warnings we're getting
There was a problem hiding this comment.
not sure if this is better defined / housed elsewhere (i didn't see anything for it at first glance flipping through the repo)
87ee5b2 to
2987c0c
Compare
|
Still trying to debug this, but tough as whatever is failing seems to be specific to the vercel env. I'm guessing I need to configure something for nitro / vercel to make it happy w wasm. Test urls are here:
I ran the vercel build locally and peeked through the build artifacts. I can see the wasm in the vercel function bundle. It's 4.7mb at the correct path. The patch is there as well and module importing / doc gen works in it. It's under the 25mb vercel limit too so...? Only things I can think of without having access to the logs are:
Or that there's just something completely different wrong I'm missing here (likely). |
4fcc2ee to
3d0121c
Compare
| * Fenced code blocks in descriptions use a subtle left-border style. | ||
| * | ||
| * Design rationale: We use two visual styles for code examples: | ||
| * 1. Boxed style (bg + border + padding) - for formal @example JSDoc tags | ||
| * and function signatures. These are intentional, structured sections. | ||
| * 2. Left-border style (blockquote-like) - for inline code in descriptions. | ||
| * These are illustrative/casual and shouldn't compete with the signature. | ||
| */ |
There was a problem hiding this comment.
LMK if anyone has questions on this. My rationale was that the examples should feel more prominent. For a comparison look at the svelte docs page vs the react one.
React one uses a lot of @example while svelte has inline @description with casual examples.
| <a | ||
| :href="homepageUrl" | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| class="link-subtle font-mono text-sm inline-flex items-center gap-1.5" | ||
| > | ||
| <span class="i-carbon-link w-4 h-4" aria-hidden="true" /> | ||
| {{ t('package.links.homepage') }} | ||
| </a> | ||
| </li> | ||
|
|
There was a problem hiding this comment.
I dropped the homepage url here in favor of the docs link, but as I'm looking back I wonder if maybe we just add both instead?
| "code": "code" | ||
| "code": "code", | ||
| "docs": "docs", | ||
| "socket": "socket" |
There was a problem hiding this comment.
addresses this on main
| export function formatType(type?: TsType): string { | ||
| if (!type) return '' | ||
|
|
||
| // Strip ANSI codes from repr (deno doc may include terminal colors since it's built for that) |
| "resolutions": { | ||
| "vite": "^8.0.0-beta.10" | ||
| }, | ||
| "pnpm": { | ||
| "overrides": { | ||
| "sharp": "0.34.5" | ||
| }, | ||
| "packageExtensions": { | ||
| "@nuxt/scripts": { | ||
| "dependencies": { | ||
| "estree-walker": "^3.0.3" | ||
| } | ||
| } | ||
| }, |
There was a problem hiding this comment.
Did these get dropped since I started working on this? Taking a look now...
@deno/doc
- Add synchronous highlightCodeSync() function to shiki.ts - Update readme.ts to use shared function instead of duplicating logic - Reduces code duplication between readme and docs rendering
Overview
This PR is one of two variants adding autogenerated documentation pages, accessible via a nice little link and routing to
/docs/{package}/v/{version}.If a package provides a
homepagein its manifest, the link will default to that. Otherwise, we attempt to generate docs viadeno docsusing a WASM binary around@deno/docs. More on this below.Problem
Okay, so
npmx.devcurrently shows all kinds of fun package data (metadata, READMEs, file contents, etc). But it lacks a feature I love from JSR/crates/other modern package browsers... autogenerated docs off type definitions.Generating these kind of docs is challenging for npm packages because:
.d.ts,@types/*, jsdocs, or any combinationSolution
Luckily, there's tons of prior art here. In particular, Deno and the JSR folks basically already solved this problem with deno doc.
Architecture
Uses
@deno/doc(official Deno doc generator package) compiled to WASM, running directly in the Node.js/Vercel serverless env. No subprocess shelling or microservice required.It's a bit more complex than the microservice under the hood though, and has some thorns. It works like this:
esh.shwith ax-typescript-typesheader@deno/docWASM to extractDocNode[]Parts of this are the same as the microservice one, just calling it out here.
Arch Diagram
Implementation
This was quite a ride for me, as much of it is transforming the format from
deno docs --json, but there's some key points.Most of the heavy lifting is done in
/server/utils/docs/and is broken down into a few submodules:generateDocsWithDeno(pkg, version)WASM integration
The WASM integration required a Node.js compatability patch since
@deno/docis designed for Deno. This is a bit concerning since we'd have to maintain this or upstream it to the deno repo (possible, but not sure what they'd think). The issues are:.d.tsexports in JS, which node won't natively resolve (seeexport * from './types.d.ts'lines in it)node:fsandnode:urlforfile://URLsI managed the patch with pnpm and is auto-applied on install.
Frontend implementation
The frontend implementation simply fetches from the api endpoint (
/api/registry/docs/{pkg}/v/{version}) and renders the pre-generated HTML inv-html. I'm still learning vue/nuxt, so let me know if I'm doing anything dumb here.The UI could probably be improved / iterated on. I left that out of scope for this POC / initial implementation.
A note on
esm.shI needed to use
esm.shhere instead of our existing calls tojsDeliver. Why?In my testing, I couldn't get
jsDeliverto resolve@types/*packages for untyped packages properly and kept hitting module resolution issues with varying.js|.tsextension 😢. I'm betting there's a way to make it work, butesm.shworked on the first try, so I went with that. It also serves the typescript types natively with ax-typescript-typesheader which I use here.If we're concerned about having two underlying providers here (jsDeliver and esm.sh) I can take this back to the drawing board / iterate.
Note on bundle size
This does add some WASM weight to our bundle. It should load once and then cache and only impact server-side, but worth calling out as downside.
Alternative approaches
I personally prefer this to the microservice as it works without more infra, but the caveats in the WASM bindings are worth exploring fixes / alternatives to.
As in the microservice approach, here are the alternatives:
deno doc --jsonout of a microserviceOption 2 may be interesting, but also comes with the steep cost of diverging from the incredible work the JSR team already put into this. If we want to build out other rust stuff here though, might make sense. We could also theoretically do more interesting stuff than is possible with just the json data we get from
deno doc --json.closes #134