Skip to content

Introduce jspecify.dev/api. - #858

Open
cpovirk wants to merge 1 commit into
mainfrom
devapi
Open

Introduce jspecify.dev/api.#858
cpovirk wants to merge 1 commit into
mainfrom
devapi

Conversation

@cpovirk

@cpovirk cpovirk commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

It redirects to jspecify.dev/docs/api, which in turn redirects to jspecify.dev/docs/api/org/jspecify/annotations/package-summary.html.

"jspecify.dev/api" is not dramatically shorter, but I figure that every bit helps. This also follows some precendent from some Google libraries, including guava.dev/api and truth.dev/api. We could consider further following Guava precedent by adding jspecify.dev/Nullable, jspecify.dev/NullMarked, etc.

As for the implementation approach: Gemini says:

Here is why a static HTML redirect in docs/static/ is the most correct and idiomatic approach for this project:

1. Why not @docusaurus/plugin-client-redirects?

While Docusaurus has an official redirect plugin (@docusaurus/plugin-client-redirects), that plugin validates during the static SSR compilation step that every destination (to) path matches an internal route known to Docusaurus. Because your Javadoc at /docs/api/ is copied into the output folder by Gradle (copyDocs) after Docusaurus completes its build, the redirect plugin would reject /docs/api/ as an unrecognized route.

2. How docs/static/api/index.html works on GitHub Pages

  • Docusaurus static asset handling: Any file placed in docs/static/ is copied as-is to the root of the output build/ directory during docusaurus build (or ./gradlew buildDocs).
  • GitHub Pages static hosting: Because GitHub Pages is purely static hosting (and .nojekyll disables Jekyll redirect plugins), client-side HTML redirects using <meta http-equiv="refresh"> combined with JavaScript are the standard mechanism.
  • When a user visits https://jspecify.dev/api (or /api/), GitHub Pages serves build/api/index.html, which immediately redirects the browser to /docs/api/.

And when I asked about avoiding the "Javadoc... is copied into the output folder by Gradle (copyDocs) after Docusaurus completes its build" problem by having Gradle copy its ouputs before Docusaurus runs, Gemini said:

Here is a breakdown of what would happen if Gradle copied them earlier, why it wouldn't help with Docusaurus plugins, and the architectural tradeoffs:


1. It wouldn't change how Docusaurus plugins (like redirect) behave

In Docusaurus, the internal routing table is built strictly from React and Markdown source components (docs/, blog/, src/pages/).

  • Neither existing HTML files in the output directory (build/) nor raw assets in static/ are ever registered as internal Single Page Application (SPA) routes.
  • Therefore, even if Javadoc files were present before Docusaurus started compiling, plugins like @docusaurus/plugin-client-redirects, sitemap, and the SPA broken-link checker would still treat /docs/api/ as an unmanaged external asset path rather than an internal route.

2. Option A: Copying into docs/build/ before Docusaurus runs

If Gradle copied Javadoc into docs/build/docs/api/ before executing npm run build:

  • Result: docusaurus build clears the target output directory (build/) by default at the very start of its compilation step. Anything placed in build/ before Docusaurus runs would be instantly wiped out.

3. Option B: Copying into docs/static/docs/api/ before Docusaurus runs

To make Docusaurus preserve and copy the files itself, Gradle would have to output generated Javadoc into Docusaurus's source asset directory (docs/static/docs/api/). While this would technically work, it introduces significant downsides:

  • Polluting the source tree with build artifacts: You would be writing hundreds or thousands of generated HTML/CSS files directly into your source directory (docs/static/). This clutters IDE indexing, file searches, and working directory status, requiring new .gitignore rules to ignore the generated subfolder.
  • Risk of serving stale Javadoc: If a developer ran npm start or npm run build directly without running Gradle first, they might silently serve stale Javadoc leftover in docs/static/ from a build weeks ago without realizing it.
  • Slower Docusaurus builds: During compilation, Webpack enumerates and copies every single asset in static/ over to build/. Forcing Webpack to process thousands of Javadoc files slows down Docusaurus's build. Letting Gradle handle the Javadoc file copying directly from one build folder to another is much faster and more efficient.
  • The only pro: If Javadoc were in docs/static/docs/api/, it would be accessible during local development when running npm start. However, because you can already preview everything cleanly by running ./gradlew buildDocs && npm run serve (as documented in your README.md [cpovirk note: at least after https://github.com/jspecify/jspecify/pull/847!\]), polluting the source tree is not worth this minor benefit.

Conclusion

Keeping your source tree (docs/) strictly separated from build outputs (docs/build/) and having Gradle's copyDocs task assemble the final site post-build is the cleanest, most performant, and most idiomatic architecture.

It redirects to jspecify.dev/docs/api, which in turn redirects to jspecify.dev/docs/api/org/jspecify/annotations/package-summary.html.

"jspecify.dev/api" is not dramatically shorter, but I figure that every bit helps. This also follows some precendent from some Google libraries, including guava.dev/api and truth.dev/api. We could consider further following Guava precedent by adding jspecify.dev/Nullable, jspecify.dev/NullMarked, etc.

As for the implementation approach: Gemini says:

> Here is why a static HTML redirect in `docs/static/` is the most correct and idiomatic approach for this project:
>
> ### 1. Why not `@docusaurus/plugin-client-redirects`?
> While Docusaurus has an official redirect plugin (`@docusaurus/plugin-client-redirects`), that plugin validates during the static SSR compilation step that every destination (`to`) path matches an internal route known to Docusaurus. Because your Javadoc at `/docs/api/` is copied into the output folder by Gradle (`copyDocs`) **after** Docusaurus completes its build, the redirect plugin would reject `/docs/api/` as an unrecognized route.
>
> ### 2. How `docs/static/api/index.html` works on GitHub Pages
> * **Docusaurus static asset handling**: Any file placed in `docs/static/` is copied as-is to the root of the output `build/` directory during `docusaurus build` (or `./gradlew buildDocs`).
> * **GitHub Pages static hosting**: Because GitHub Pages is purely static hosting (and `.nojekyll` disables Jekyll redirect plugins), client-side HTML redirects using `<meta http-equiv="refresh">` combined with JavaScript are the standard mechanism.
> * When a user visits `https://jspecify.dev/api` (or `/api/`), GitHub Pages serves `build/api/index.html`, which immediately redirects the browser to `/docs/api/`.

And when I asked about avoiding the "Javadoc... is copied into the output folder by Gradle (`copyDocs`) **after** Docusaurus completes its build" problem by having Gradle copy its ouputs *before* Docusaurus runs, Gemini said:

> Here is a breakdown of what would happen if Gradle copied them earlier, why it wouldn't help with Docusaurus plugins, and the architectural tradeoffs:
>
> ---
>
> ### 1. It wouldn't change how Docusaurus plugins (like redirect) behave
> In Docusaurus, the internal routing table is built strictly from **React and Markdown source components** (`docs/`, `blog/`, `src/pages/`).
> * Neither existing HTML files in the output directory (`build/`) nor raw assets in `static/` are ever registered as internal Single Page Application (SPA) routes.
> * Therefore, even if Javadoc files were present before Docusaurus started compiling, plugins like `@docusaurus/plugin-client-redirects`, `sitemap`, and the SPA broken-link checker would still treat `/docs/api/` as an unmanaged external asset path rather than an internal route.
>
> ---
>
> ### 2. Option A: Copying into `docs/build/` before Docusaurus runs
> If Gradle copied Javadoc into `docs/build/docs/api/` before executing `npm run build`:
> * **Result**: `docusaurus build` clears the target output directory (`build/`) by default at the very start of its compilation step. Anything placed in `build/` before Docusaurus runs would be instantly wiped out.
>
> ---
>
> ### 3. Option B: Copying into `docs/static/docs/api/` before Docusaurus runs
> To make Docusaurus preserve and copy the files itself, Gradle would have to output generated Javadoc into Docusaurus's source asset directory (`docs/static/docs/api/`). While this would technically work, it introduces significant downsides:
>
> * **Polluting the source tree with build artifacts**: You would be writing hundreds or thousands of generated HTML/CSS files directly into your source directory (`docs/static/`). This clutters IDE indexing, file searches, and working directory status, requiring new `.gitignore` rules to ignore the generated subfolder.
> * **Risk of serving stale Javadoc**: If a developer ran `npm start` or `npm run build` directly without running Gradle first, they might silently serve stale Javadoc leftover in `docs/static/` from a build weeks ago without realizing it.
> * **Slower Docusaurus builds**: During compilation, Webpack enumerates and copies every single asset in `static/` over to `build/`. Forcing Webpack to process thousands of Javadoc files slows down Docusaurus's build. Letting Gradle handle the Javadoc file copying directly from one build folder to another is much faster and more efficient.
> * **The only pro**: If Javadoc were in `docs/static/docs/api/`, it *would* be accessible during local development when running `npm start`. However, because you can already preview everything cleanly by running `./gradlew buildDocs && npm run serve` (as documented in your README.md \[cpovirk note: at least after https://github.com/jspecify/jspecify/pull/847!\]), polluting the source tree is not worth this minor benefit.
>
> ### Conclusion
> Keeping your source tree (`docs/`) strictly separated from build outputs (`docs/build/`) and having Gradle's `copyDocs` task assemble the final site post-build is the cleanest, most performant, and most idiomatic architecture.
cpovirk added a commit that referenced this pull request Jul 27, 2026
…ges over `npm start`.

The advantages were on display as I worked on #849 and #858.

Here's an example of an error that was caught by `./gradlew buildDocs` / `npm run build` but not `npm start`. It came from some bad link syntax when I tried to follow (roughly) `[@NullMarked]` with a parenthetical "(Why?)":

```
[ERROR] Error: Unable to build website for locale en.
    at tryToBuildLocale (/usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/core/lib/commands/build/build.js:78:15)
    at async /usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/core/lib/commands/build/build.js:34:9
    ... 4 lines matching cause stack trace ...
    at async file:///usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/core/bin/docusaurus.mjs:44:3 {
  [cause]: Error: Docusaurus found broken links!

  Please check the pages of your site in the list below, and make sure you don't reference any path that does not exist.
  Note: it's possible to ignore broken links with the 'onBrokenLinks' Docusaurus configuration, and let the build pass.

  Exhaustive list of all broken links found:
  - Broken link on source page path = /docs/user-guide/:
     -> linking to %5BWhy/?%5D(nullness-design-faq.md#null-marked-non-null-by-default) (resolved as: /docs/user-guide/%5BWhy/?%5D(nullness-design-faq.md#null-marked-non-null-by-default))

      at throwError (/usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/logger/lib/logger.js:80:11)
      at reportBrokenLinks (/usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/core/lib/server/brokenLinks.js:250:47)
      at handleBrokenLinks (/usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/core/lib/server/brokenLinks.js:282:5)
      at executeBrokenLinksCheck (/usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/core/lib/commands/build/buildLocale.js:91:47)
      at /usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/core/lib/commands/build/buildLocale.js:70:67
      at Object.async (/usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/logger/lib/perfLogger.js:42:47)
      at buildLocale (/usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/core/lib/commands/build/buildLocale.js:70:31)
      at async runBuildLocaleTask (/usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/core/lib/commands/build/build.js:93:5)
      at async /usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/core/lib/commands/build/build.js:74:13
      at async tryToBuildLocale (/usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/core/lib/commands/build/build.js:70:9)
      at async /usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/core/lib/commands/build/build.js:34:9
      at async mapAsyncSequential (/usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/utils/lib/jsUtils.js:21:24)
      at async Command.build (/usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/core/lib/commands/build/build.js:33:5)
      at async Promise.all (index 0)
      at async runCLI (/usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/core/lib/commands/cli.js:56:5)
      at async file:///usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/core/bin/docusaurus.mjs:44:3
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant