Open-source project
asciidoctor/asciidoctor.js avatar
asciidoctor/asciidoctor.js

Asciidoctor.js: AsciiDoc rendering in Node and the browser

:scroll: A JavaScript port of Asciidoctor, a modern implementation of AsciiDoc

805 stars152 forksJavaScriptMIT

At a glance

What is it?
Asciidoctor.js is a native JavaScript implementation of Asciidoctor, the AsciiDoc processor, and it runs on Node.js and in the browser, which means you can convert documentation to HTML5 without a Ruby runtime. The repository is an npm workspaces monorepo with a core package, a published package and a Deno configuration.
Who is it for?
Adopt Asciidoctor.js if you need to render AsciiDoc from a JavaScript or TypeScript codebase, especially in a browser, a documentation site built with a JavaScript toolchain, or a CI job where installing Ruby would be an unwelcome dependency. Do not adopt it if your toolchain is Ruby based, since the original Asciidoctor is the more direct path there.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 4 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

AsciiDoc processing without a Ruby runtime

Asciidoctor.js brings AsciiDoc to the JavaScript world. The project is a native JavaScript implementation of Asciidoctor, which is a modern implementation of AsciiDoc, and the README states that it runs on Node.js 24 or later and in the browser.

That last point is what distinguishes it from the Ruby original. Asciidoctor is a Ruby program, and using it from a JavaScript project has historically meant either a server side call to a Ruby process or a WebAssembly build with its own constraints. This is a reimplementation in JavaScript rather than a bridge, so the parser, the converter and the document model all live in JavaScript.

The consequence for a team is a change in what has to be installed. A JavaScript project that renders AsciiDoc no longer needs a Ruby toolchain in the build or the container. That is the whole argument, and it is why the project exists alongside the Ruby implementation rather than instead of it.

The project is maintained by Dan Allen, Guillaume Grossetie and Anthonny Quérouil, the copyright statement covers 2013 to the present, and the licence is MIT. The documentation lives at asciidoctor.org with the user manual for this specific implementation at the docs site, and there is a project chat on Zulip.

The convert function and the HTML5 it produces

The entry point is a named export called convert, and the quickstart in the README is short enough to read in full.

The install is a single npm command:

bash
npm i asciidoctor --save

The sample script that follows is the canonical usage:

js
import { convert } from 'asciidoctor' // <1>

const content = 'https://asciidoctor.org[*Asciidoctor*] running on Node.js!'
const html = await convert(content) // <2>
console.log(html) // <3>

The callouts in the README explain the three points. You import the named convert function from the asciidoctor package, you convert AsciiDoc content to HTML5 and the function is async, and you print the HTML5 output to the console. That last detail is the one to internalise, since convert returns a promise and code that treats it as a plain string will fail at the first await.

Running the file with node produces HTML5 rather than a fragment of text, and the expected output in the README shows the paragraph wrapper and the resolved link:

html
<div class="paragraph">
<p><a href="https://asciidoctor.org"><strong>Asciidoctor</strong></a> running on Node.js!</p>
</div>

The markup is semantic and class based rather than inline styled, which matters for anyone who intends to restyle the output with their own stylesheet.

Using it directly in a browser page

Because the implementation runs in the browser, the README shows a script tag variant for pages that want to convert AsciiDoc on the client:

html
<script type="module">
import { convert } from 'node_modules/@asciidoctor/core/build/browser/index.js'
</script>

The path in that import is worth noting, because it points at a different package and a different build than the Node example. The Node example imports from asciidoctor, while the browser import reaches into node_modules/@asciidoctor/core/build/browser/index.js. The core package is where the implementation lives, and the browser build is a separate artefact within it.

The module type is significant. The script tag uses type module, so the import is a native module import and the page needs to be served over a context that allows module loading, which also means the file paths are relative to the served directory rather than to the page.

This is the arrangement that makes client side documentation rendering possible without a server, which is useful for a documentation site built with a JavaScript bundler, for a preview inside an editor, and for static site generators that want to render AsciiDoc at build time without shelling out to Ruby.

A monorepo with a core package and a published package

The root package.json describes the shape of the project precisely, and it is worth reading as a piece of documentation.

The root is named @asciidoctor/root and is marked private, so it is a workspace root rather than something anyone installs. It declares type module, which makes the repository an ES module project throughout. The workspaces are packages/core and packages/asciidoctor, which is the split between the implementation and the package with the name people actually install.

The scripts show how the project is checked. The test script runs a root level test and then the tests of each workspace:

json
    "test": "npm run test:root && npm run test --workspaces",
    "test:root": "node --test test/**.test.js",

The root test uses the Node built in test runner, so the project has no test framework dependency of its own. The lint and format scripts are the same shape, running the tool at the root over the test and tasks directories and then delegating to each workspace.

The rest of the tree supports that structure. There are packages/, tasks/, test/, scripts/, docs/ and devdocs/ directories, a deno.json and deno.lock for Deno users, a biome.json for linting and formatting, a renovate.json for dependency updates, and a CHANGELOG.adoc rather than a Markdown one, since the project documents itself in AsciiDoc.

Biome, the Node test runner and Deno

Three details in the manifest describe the toolchain choices, and each one is a small decision with consequences.

First, formatting and linting are handled by Biome rather than by a separate linter and formatter. The lint script runs biome check over the test and tasks directories, and the format script runs biome format with write over the same directories, and both then run again inside each workspace. One tool doing both jobs keeps the configuration in a single biome.json file and removes the need to keep two formatters in agreement.

Second, the root tests use node --test with a glob, so the suite runs on the runtime's own test runner. That keeps the dependency list short, and the three development dependencies give a sense of the scale: downdoc, a documentation tool, semver for version handling, and sinon for test doubles.

Third, there is a deno.json and deno.lock in the tree. The README says the implementation runs on Node.js and in the browser, and the Deno configuration means it also runs on Deno without a separate port, which is a reasonable expectation given that Deno is Node compatible and the code is already written as ES modules.

The Volta pin in the manifest sets node 24.21.0 for contributors, which is the clearest statement of the version the maintainers develop against.

Where the Node version floor is stated two different ways

There is one inconsistency in the documentation worth flagging before you install anything, and it is the kind that produces a confusing support ticket.

The README says the project runs on Node.js 24 and later. The root package.json declares an engines field with node set to >=20, and separately pins node 24.21.0 through Volta for the development environment. So the published constraint and the documented constraint differ by four major versions.

Which one is authoritative depends on where you look. The README describes the runtime the project is tested against, and the Volta pin describes what the maintainers use locally. The engines field is what npm will enforce at install time, and it is the more permissive of the two. In practice this means an install on Node 20 may succeed and then behave differently from the documented environment, which is exactly the kind of gap that makes a bug hard to reproduce.

The answer for an adopter is to develop and deploy on Node 24, since that is what the README states and what the Volta configuration pins, and to treat the engines field as a lower bound rather than a supported version. The recent release history shows the project is moving quickly, with v4.1.1 dated 2026-10-05, v4.1.0 dated 2026-09-22 and v4.0.11 dated 2026-08-18, so a version matrix question is likely to be answered by the maintainers in the project chat rather than by the manifest.

Where Asciidoctor.js is the wrong tool

Three cases point elsewhere.

The first is a Ruby project. If your build already runs Ruby, the original Asciidoctor is the direct dependency and this port buys you nothing. The JavaScript implementation is for projects that cannot or will not take on a Ruby toolchain, and adopting it inside a Ruby codebase adds a second implementation of a format you already have.

The second is a case where you need a feature the port has not caught up with. A port tracks a reference implementation, and the reference implementation moves. AsciiDoc has a wide extension surface, and the README does not enumerate which extensions are supported in the JavaScript version, so the practical check is to try your own content through it. The CHANGELOG.adoc is the place to look for what changed, and the documentation at asciidoctor.org describes the AsciiDoc feature set rather than the state of the port.

The third is a build step you would rather not have at all. Converting AsciiDoc to HTML at build time with a JavaScript toolchain is easy when a bundler is already there, and awkward when it is not. If your documentation build has no JavaScript step at all, adding one to avoid Ruby is not obviously a saving, and the static site generator you already use may be able to call the reference implementation directly.

One operational note for all three cases. The project is a port with a fast release cadence and a small runtime requirement, so the cost of a version upgrade is low, but the cost of a content compatibility surprise is not. Test your actual documents, including the extensions you rely on, rather than a sample.

Editorial conclusion

Adopt Asciidoctor.js if you need to render AsciiDoc from a JavaScript or TypeScript codebase, especially in a browser, a documentation site built with a JavaScript toolchain, or a CI job where installing Ruby would be an unwelcome dependency. Do not adopt it if your toolchain is Ruby based, since the original Asciidoctor is the more direct path there. Verify three things first: which Node version you are on, because the README states the runtime needs Node.js 24 or later while the workspace manifest declares an engine floor of 20 and pins 24.21.0 through Volta; which package you depend on, since the root is a private workspace root while the published package is the one you install; and whether your content uses extensions that the JavaScript implementation does not yet support, since a port can lag behind the reference implementation. The licence is MIT, the last push was on 2026-10-19, and the newest release is v4.1.1 dated 2026-10-05.

Frequently asked questions

What is Asciidoctor.js?

It is a native JavaScript implementation of Asciidoctor, the modern implementation of AsciiDoc, running on Node.js and in the browser. The point is to convert AsciiDoc to HTML5 without installing a Ruby runtime.

How do I convert AsciiDoc to HTML5 in Node.js?

Install the package with npm i asciidoctor --save, import the named convert function from asciidoctor, and await convert(content) because the function is asynchronous. The result is HTML5 markup, as the sample output in the README shows.

Can I use Asciidoctor.js directly in a browser page?

Yes. The README shows a script tag with type module that imports convert from node_modules/@asciidoctor/core/build/browser/index.js, which is the browser build of the core package rather than the published asciidoctor package.

What Node.js version does Asciidoctor.js need?

The README states Node.js 24 and later, and the development environment is pinned to node 24.21.0 through Volta, while the root package.json declares an engines floor of node >=20. Developing on Node 24 matches what the project documents.

How is the project structured and tested?

It is an npm workspaces monorepo with packages/core and packages/asciidoctor, marked as a private ES module root. Tests run with the Node built in test runner at the root and then in each workspace, and linting and formatting are handled by Biome.

Official sources

  1. asciidoctor/asciidoctor.js on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/asciidoctor-asciidoctor-js.svg)](https://hysenlabs.com/projects/asciidoctor-asciidoctor-js)

Was this useful?