Running on Node.js
ProseKit can run in Node.js environments for background processing, CLI applications, and Server-Side Rendering (SSR). The primary limitation is that DOM-dependent APIs are unavailable unless you provide a DOM implementation.
Create an editor in Node.js
Section titled “Create an editor in Node.js”You can create an editor and execute commands just like in a browser environment:
import { defineBasicExtension function defineBasicExtension(): BasicExtension
Define a basic extension that includes some common functionality. You can
copy this function and customize it to your needs.
It's a combination of the following extension functions:
-
{@link
defineDoc
}
-
{@link
defineText
}
-
{@link
defineParagraph
}
-
{@link
defineHeading
}
-
{@link
defineList
}
-
{@link
defineBlockquote
}
-
{@link
defineImage
}
-
{@link
defineHorizontalRule
}
-
{@link
defineHardBreak
}
-
{@link
defineTable
}
-
{@link
defineCodeBlock
}
-
{@link
defineItalic
}
-
{@link
defineBold
}
-
{@link
defineUnderline
}
-
{@link
defineStrike
}
-
{@link
defineCode
}
-
{@link
defineLink
}
-
{@link
defineBaseKeymap
}
-
{@link
defineBaseCommands
}
-
{@link
defineHistory
}
-
{@link
defineGapCursor
}
-
{@link
defineVirtualSelection
}
-
{@link
defineModClickPrevention
} } from 'prosekit/basic'
import { createEditor function createEditor<E extends Extension>(options: EditorOptions<E>): Editor<E>
} from 'prosekit/core'
const extension const extension: BasicExtension
= defineBasicExtension function defineBasicExtension(): BasicExtension
Define a basic extension that includes some common functionality. You can
copy this function and customize it to your needs.
It's a combination of the following extension functions:
-
{@link
defineDoc
}
-
{@link
defineText
}
-
{@link
defineParagraph
}
-
{@link
defineHeading
}
-
{@link
defineList
}
-
{@link
defineBlockquote
}
-
{@link
defineImage
}
-
{@link
defineHorizontalRule
}
-
{@link
defineHardBreak
}
-
{@link
defineTable
}
-
{@link
defineCodeBlock
}
-
{@link
defineItalic
}
-
{@link
defineBold
}
-
{@link
defineUnderline
}
-
{@link
defineStrike
}
-
{@link
defineCode
}
-
{@link
defineLink
}
-
{@link
defineBaseKeymap
}
-
{@link
defineBaseCommands
}
-
{@link
defineHistory
}
-
{@link
defineGapCursor
}
-
{@link
defineVirtualSelection
}
-
{@link
defineModClickPrevention
} ()
const editor const editor: Editor<BasicExtension>
= createEditor createEditor<BasicExtension>(options: EditorOptions<BasicExtension>): Editor<BasicExtension>
({ extension EditorOptions<BasicExtension>.extension: BasicExtension
The extension to use when creating the editor. })
// Set content with JSON
editor const editor: Editor<BasicExtension>
.setContent Editor<BasicExtension>.setContent: (content: Node | NodeJSON | string | HTMLElement, selection?: SelectionJSON | Selection | "start" | "end") => void
Update the editor's document and selection. ({
type type: string
: 'doc',
content content: {
type: string;
content: {
type: string;
text: string;
}[];
}[]
: [
{ type NodeJSON.type: string
: 'paragraph', content NodeJSON.content?: NodeJSON[] | undefined
: [{ type NodeJSON.type: string
: 'text', text NodeJSON.text?: string | undefined
: 'Hello, Node.js!' }] },
],
})
// Run commands
editor const editor: Editor<BasicExtension>
.commands Editor<BasicExtension>.commands: ToCommandAction<{
setParagraph: [];
setHeading: [attrs?: HeadingAttrs | undefined];
insertHeading: [attrs?: HeadingAttrs | undefined];
toggleHeading: [attrs?: HeadingAttrs | undefined];
dedentList: [options?: DedentListOptions];
indentList: [options?: IndentListOptions];
moveList: [direction: "up" | "down"];
splitList: [];
toggleCollapsed: [options?: ToggleCollapsedOptions];
... 55 more ...;
redo: [];
}>
All
{@link
CommandAction
}
s defined by the editor. .insertImage insertImage: CommandAction
(attrs?: ImageAttrs | undefined) => boolean
Execute the current command. Return `true` if the command was successfully
executed, otherwise `false`. ({
src ImageAttrs.src?: string | null | undefined
: 'https://example.com/logo.png',
width ImageAttrs.width?: number | null | undefined
: 120,
height ImageAttrs.height?: number | null | undefined
: 60,
})
// Get the document as JSON
const json const json: NodeJSON
= editor const editor: Editor<BasicExtension>
.getDocJSON Editor<BasicExtension>.getDocJSON: () => NodeJSON
Return a JSON object representing the editor's current document. ()
console var console: Console
The `console` module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
* A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream.
* A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstdout) and
[`process.stderr`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module.
_**Warning**_: The global console object's methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v20.x/api/process.html#a-note-on-process-io) for
more information.
Example using the global `console`:
```js
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
```
Example using the `Console` class:
```js
const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
``` .log Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to `stdout` with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html)
(the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args)).
```js
const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
```
See [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args) for more information. (json const json: NodeJSON
)
DOM-dependent APIs are unavailable
Section titled “DOM-dependent APIs are unavailable”In a standard Node.js environment there is no DOM, so methods requiring DOM access will throw errors:
// ❌ Mounting fails without DOM
editor const editor: Editor<BasicExtension>
.mount Editor<BasicExtension>.mount: (place: HTMLElement | null | undefined) => void
Mount the editor to the given HTML element.
Pass `null` or `undefined` to unmount the editor. (document var document: Document
**`window.document`** returns a reference to the document contained in the window.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/document) .createElement Document.createElement<"div">(tagName: "div", options?: ElementCreationOptions): HTMLDivElement (+2 overloads)
In an HTML document, the **`document.createElement()`** method creates the HTML element specified by `localName`, or an HTMLUnknownElement if `localName` isn't recognized.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/createElement) ('div'))
// ❌ HTML serialization requires DOM APIs
editor const editor: Editor<BasicExtension>
.getDocHTML Editor<BasicExtension>.getDocHTML: (options?: getDocHTMLOptions) => string
Return a HTML string representing the editor's current document. ()
// ❌ HTML parsing requires DOM APIs
editor const editor: Editor<BasicExtension>
.setContent Editor<BasicExtension>.setContent: (content: Node | NodeJSON | string | HTMLElement, selection?: SelectionJSON | Selection | "start" | "end") => void
Update the editor's document and selection. ('<p>HTML input</p>')
Two strategies can address the absence of DOM APIs in server environments.
SSR strategy 1: Browser pre-rendering
Section titled “SSR strategy 1: Browser pre-rendering”The simplest approach for server-side rendering is client-side pre-rendering: allow the browser to convert documents to HTML and store the result.
// Browser code
const html const html: string
= editor const editor: Editor<BasicExtension>
.getDocHTML Editor<BasicExtension>.getDocHTML: (options?: getDocHTMLOptions) => string
Return a HTML string representing the editor's current document. ()
await fetch function fetch(input: string | URL | Request, init?: RequestInit): Promise<Response> (+1 overload)
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/fetch) ('/api/save', { method RequestInit.method?: string | undefined
A string to set request's method. : 'POST', body RequestInit.body?: BodyInit | null | undefined
A BodyInit object or null to set request's body. : html const html: string
})
The backend can then serve this static HTML without requiring DOM access.
SSR strategy 2: Server-side DOM simulation
Section titled “SSR strategy 2: Server-side DOM simulation”For JSON-to-HTML conversion within Node.js, use a headless DOM library such as jsdom or happy-dom.
Using jsdom
Section titled “Using jsdom”import { JSDOM } from 'jsdom'
// Initialize virtual DOM
const dom = new JSDOM('')
const document = dom.window.document
editor.setContent({
type: 'doc',
content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Foo' }] }],
})
editor.commands.insertText({ text: 'Bar' })
// Provide the document to getDocHTML
const html = editor.getDocHTML({ document })
console.log(html) // => <div><p>BarFoo</p></div>
Using happy-dom
Section titled “Using happy-dom”import { Window } from 'happy-dom'
// Initialize virtual DOM
const window = new Window()
const document = window.document
editor.setContent({
type: 'doc',
content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Foo' }] }],
})
editor.commands.insertText({ text: 'Bar' })
// Provide the document to getDocHTML
const html = editor.getDocHTML({ document })
console.log(html) // => <div><p>BarFoo</p></div>