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.
Breaking changes
Asynchronous API
All parsing and conversion operations now return a Promise and must be awaited.
| v3 (Opal) | v4 (native) | Note |
|---|---|---|
|
|
Must be awaited |
|
|
Must be awaited |
|
|
Must be awaited |
|
|
Must be awaited |
|
|
Must be awaited |
|
|
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 |
|---|---|
|
|
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) |
|---|---|
|
|
|
|
|
|
|
|
Renamed methods
| v3 | v4 | Note |
|---|---|---|
|
|
Checks whether the |
|
|
Checks whether the |
|
|
Checks whether the |
|
|
|
getAttribute returns null instead of undefined
In v3, getAttribute(name) returned undefined when the attribute was absent.
In v4, it returns null.
| v3 | v4 |
|---|---|
|
|
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 |
|---|---|---|
|
|
Renamed to avoid collision with |
|
|
Top-level export |
|
|
Top-level export |
|
|
Top-level export |
Asynchronous methods on AbstractNode
| v3 | v4 |
|---|---|
|
|
|
|
|
|
|
|
Asynchronous methods on AbstractBlock
| v3 | v4 |
|---|---|
|
|
|
|
|
|
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 astringor[string, number] -
format(node, lang, opts)— can be synchronous or asynchronous; returning aPromise<string>is recommended for I/O-bound operations, but a plainstringworks too
New in v4
Named exports
v4 is named-exports only — there is no default export. Every public member is available as a named export.
import { load, convert, loadFile, convertFile, getVersion, getCoreVersion } from '@asciidoctor/core'
import {
Document,
DocumentTitle,
Author,
Footnote,
ImageReference,
RevisionInfo,
AbstractNode,
AbstractBlock,
Block,
Section,
Inline,
List,
ListItem,
} from '@asciidoctor/core'
import { Reader, Cursor } from '@asciidoctor/core'
import {
ConverterFactory,
DefaultConverterFactory,
Html5Converter,
} from '@asciidoctor/core'
import {
Extensions,
Registry,
ProcessorExtension,
Preprocessor,
TreeProcessor,
Postprocessor,
IncludeProcessor,
DocinfoProcessor,
BlockProcessor,
InlineMacroProcessor,
BlockMacroProcessor,
} from '@asciidoctor/core'
import {
SyntaxHighlighter,
SyntaxHighlighterBase,
DefaultSyntaxHighlighterFactory,
} from '@asciidoctor/core'
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) |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|