This documentation covers a prerelease version of the software. Follow this link to view the documentation for the stable version (3.0) instead.

Migration guide: @asciidoctor/core v3 → v4

@asciidoctor/core v4 is a complete rewrite of the library. v3 relied on Opal (a Ruby-to-JavaScript transpiler) to produce a JS bundle from the Ruby source. v4 is a native JavaScript implementation — no transpilation, no Ruby, pure ESM.

The library source is written in ESM only. For compatibility, a CommonJS bundle is also provided and the package.json exports field points to the appropriate format automatically — require() continues to work.

Requirements

  • Node.js >= 24 (current LTS)

Breaking changes

Asynchronous API

All parsing and conversion operations now return a Promise and must be awaited.

v3 (Opal) v4 (native) Note

asciidoctor.load(source, opts)Document

load(source, opts)Promise<Document>

Must be awaited

asciidoctor.loadFile(path, opts)Document

loadFile(path, opts)Promise<Document>

Must be awaited

asciidoctor.convert(source, opts)string

convert(source, opts)Promise<string>

Must be awaited

asciidoctor.convertFile(path, opts)string

convertFile(path, opts)Promise<string>

Must be awaited

document.parse(data?)Document

document.parse(data?)Promise<Document>

Must be awaited

document.convert(opts?)string

document.convert(opts?)Promise<string>

Must be awaited

No factory call

v3 exports a factory function that must be called to obtain an Asciidoctor instance. v4 uses named exports — import only what you need, no factory call needed.

v3 v4

require('@asciidoctor/core')() → must call the factory

import { load, convert } from '@asciidoctor/core' → use functions directly

Strict camelCase naming

Some internal properties and options that retained the Ruby snake_case style in v3 are now camelCase.

v3 (snake_case) v4 (camelCase)

node_name

nodeName

source_location

sourceLocation

content_model

contentModel

default_subs

defaultSubs

Renamed methods

v3 v4 Note

isRole(expectedValue?)boolean

hasRoleAttribute(expectedValue?)boolean

Checks whether the role attribute is set

isOption(name)boolean

hasOption(name)boolean

Checks whether the <name>-option attribute is set

isReftext()boolean

hasReftext()boolean

Checks whether the reftext attribute is set. Note: v4 also retains isReftext() with broader semantics (returns true when reftext is set or the node has a title).

getAttr(name, defaultValue?, inherit?)*

getAttribute(name, defaultValue?, inherit?)*

getAttr is removed. Use getAttribute instead — the signature is identical.

getAttribute returns null instead of undefined

In v3, getAttribute(name) returned undefined when the attribute was absent. In v4, it returns null.

v3 v4

node.getAttribute('missing') === undefinedtrue

node.getAttribute('missing') === nulltrue

Update any code that checks the return value against undefined:

// v3
if (node.getAttribute('language') === undefined) { ... }

// v4
if (node.getAttribute('language') === null) { ... }
// or equivalently
if (!node.getAttribute('language')) { ... }

Restructured inner classes

v3 v4 Note

Document.Title (inner class)

DocumentTitle (named export)

Renamed to avoid collision with Document

Document.Author (inner class)

Author (named export)

Top-level export

Document.Footnote (inner class)

Footnote (named export)

Top-level export

Document.ImageReference (inner class)

ImageReference (named export)

Top-level export

Asynchronous methods on AbstractNode

v3 v4

getIconUri(name)string

getIconUri(name)Promise<string>

getImageUri(target, key?)string

getImageUri(target, key?)Promise<string>

readAsset(path, opts)string | undefined

readAsset(path, opts)Promise<string | null>

readContents(target, opts)void

readContents(target, opts)Promise<string | null>

Asynchronous methods on AbstractBlock

v3 v4

convert()string

convert()Promise<string>

getContent()string

getContent()Promise<string>

xreftext(xrefstyle?)string|null

xreftext(xrefstyle?)Promise<string|null>
alias: getXrefText(xrefstyle?)

Extensions transpiled with Opal

Extensions written in Ruby and transpiled to JavaScript via Opal will no longer work. v4 does not include the Opal runtime, so any Opal-generated code will fail at load time.

Extensions must be rewritten in pure JavaScript (ESM). The extension API is largely similar, but some differences may exist — refer to the extension documentation for details.

In addition, extension processors can now be asynchronous: the process method may return a Promise, which will be properly awaited during conversion.

v3 v4

Extensions written in Ruby, transpiled via Opal

Extensions written in pure JavaScript (ESM)

Custom converters

In v4, node.getContent() returns a Promise, so any converter that calls it must await the result. The convert() method itself can remain synchronous if it does not call getContent() or any other async API.

// v4 — synchronous converter (no async content call)
convert(node, transform) {
  return node.getTitle()
}

// v4 — async converter when getContent() is needed
async convert(node, transform) {
  return `<p>${await node.getContent()}</p>`
}

Custom syntax highlighters

The syntax highlighter API distinguishes between two methods with different async requirements:

  • highlight(node, source, lang, opts) — must remain synchronous, returns a string or [string, number]

  • format(node, lang, opts) — can be synchronous or asynchronous; returning a Promise<string> is recommended for I/O-bound operations, but a plain string works too

Not implemented in v4

Feature Note

Asciidoctor.getRuntime()Object

Returns platform / engine / GEM_HOME info; no equivalent concept in a native JS library

New in v4

Named exports

v4 is named-exports only — there is no default export. Every public member is available as a named export.

Functions
import { load, convert, loadFile, convertFile, getVersion, getCoreVersion } from '@asciidoctor/core'
Document and AST nodes
import {
  Document,
  DocumentTitle,
  Author,
  Footnote,
  ImageReference,
  RevisionInfo,
  AbstractNode,
  AbstractBlock,
  Block,
  Section,
  Inline,
  List,
  ListItem,
} from '@asciidoctor/core'
Reader
import { Reader, Cursor } from '@asciidoctor/core'
Converters
import {
  ConverterFactory,
  DefaultConverterFactory,
  Html5Converter,
} from '@asciidoctor/core'
Extensions
import {
  Extensions,
  Registry,
  ProcessorExtension,
  Preprocessor,
  TreeProcessor,
  Postprocessor,
  IncludeProcessor,
  DocinfoProcessor,
  BlockProcessor,
  InlineMacroProcessor,
  BlockMacroProcessor,
} from '@asciidoctor/core'
Syntax highlighter
import {
  SyntaxHighlighter,
  SyntaxHighlighterBase,
  DefaultSyntaxHighlighterFactory,
} from '@asciidoctor/core'
Logging and utilities
import {
  Logger,
  LoggerManager,
  MemoryLogger,
  NullLogger,
  SafeMode,
  ContentModel,
  Timings,
} from '@asciidoctor/core'
loadFile and convertFile are not available as named exports in the browser build.

Document direct properties

The v3 getter methods are preserved unchanged. In addition, v4 exposes these values as direct instance properties for convenience.

Getter method (v3 and v4) Direct property (new in v4)

getSafe()number

document.safe

getCompatMode()boolean

document.compatMode

getSourcemap() / setSourcemap(v)

document.sourcemap

getBaseDir()string

document.baseDir

getOutfilesuffix()string

document.outfilesuffix

getBackend()string

document.backend

getDoctype()string

document.doctype