mirror of
https://github.com/ueberdosis/tiptap.git
synced 2025-06-07 17:43:49 +08:00
Fix happy-dom overwriting NodeJS process variable (#6384)
* added safe window and parser functions to avoid overwriting global process * changeset * add correct typing for doc * create wrapper function to restore node internals * Update packages/html/src/createSafeWindow.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update packages/html/src/createSafeParser.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update packages/html/src/preserveAndRestoreNodeInternals.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * make window and dom parser local variables and avoid variable shadowing --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
a84ec2a08e
commit
4f498944b5
5
.changeset/hip-teachers-dream.md
Normal file
5
.changeset/hip-teachers-dream.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
'@tiptap/html': patch
|
||||
---
|
||||
|
||||
Wrap happy-dom Window and DOMParser creation to avoid setting global process to null. See https://github.com/ueberdosis/tiptap/issues/6368
|
18
packages/html/src/createSafeParser.ts
Normal file
18
packages/html/src/createSafeParser.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import type { DOMParser as HappyDOMParser, Window as HappyDOMWindow } from 'happy-dom-without-node'
|
||||
|
||||
import { preserveAndRestoreNodeInternals } from './preserveAndRestoreNodeInternals.js'
|
||||
|
||||
/**
|
||||
* Creates a safe DOMParser instance by wrapping `happy-dom`'s `DOMParser`.
|
||||
* This function ensures that the original `process` is preserved by using
|
||||
* `preserveAndRestoreNodeInternals`.
|
||||
*
|
||||
* @param {HappyDOMWindow} window - The `happy-dom` window object to use for the parser.
|
||||
* @returns {HappyDOMParser} A new instance of `happy-dom`'s `DOMParser`.
|
||||
*/
|
||||
export function createSafeParser(window: HappyDOMWindow) {
|
||||
return preserveAndRestoreNodeInternals(() => {
|
||||
const { DOMParser } = require('happy-dom-without-node')
|
||||
return new DOMParser(window) as HappyDOMParser
|
||||
})
|
||||
}
|
17
packages/html/src/createSafeWindow.ts
Normal file
17
packages/html/src/createSafeWindow.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import type { Window as HappyDOMWindow } from 'happy-dom-without-node'
|
||||
|
||||
import { preserveAndRestoreNodeInternals } from './preserveAndRestoreNodeInternals.js'
|
||||
|
||||
/**
|
||||
* Creates a new `Window` instance using `happy-dom` and ensures that the original
|
||||
* `process` object is restored after the operation. This function wraps the
|
||||
* creation of the `Window` object to provide a safe environment for DOM manipulation.
|
||||
*
|
||||
* @returns {HappyDOMWindow} A new `Window` instance from `happy-dom`.
|
||||
*/
|
||||
export function createSafeWindow() {
|
||||
return preserveAndRestoreNodeInternals(() => {
|
||||
const { Window } = require('happy-dom-without-node')
|
||||
return new Window() as HappyDOMWindow
|
||||
})
|
||||
}
|
@ -2,7 +2,10 @@ import type { Extensions } from '@tiptap/core'
|
||||
import { getSchema } from '@tiptap/core'
|
||||
import type { ParseOptions } from '@tiptap/pm/model'
|
||||
import { DOMParser } from '@tiptap/pm/model'
|
||||
import { DOMParser as HappyDOMParser, Window as HappyDOMWindow } from 'happy-dom-without-node'
|
||||
import type { Document as HappyDOMDocument } from 'happy-dom-without-node'
|
||||
|
||||
import { createSafeParser } from './createSafeParser.js'
|
||||
import { createSafeWindow } from './createSafeWindow.js'
|
||||
|
||||
/**
|
||||
* Generates a JSON object from the given HTML string and converts it into a Prosemirror node with content.
|
||||
@ -18,11 +21,22 @@ import { DOMParser as HappyDOMParser, Window as HappyDOMWindow } from 'happy-dom
|
||||
*/
|
||||
export function generateJSON(html: string, extensions: Extensions, options?: ParseOptions): Record<string, any> {
|
||||
const schema = getSchema(extensions)
|
||||
let doc: Document | HappyDOMDocument | null = null
|
||||
|
||||
const parseInstance =
|
||||
typeof window !== 'undefined' ? new window.DOMParser() : new HappyDOMParser(new HappyDOMWindow())
|
||||
if (typeof window === 'undefined') {
|
||||
const localWindow = createSafeWindow()
|
||||
const localDOMParser = createSafeParser(localWindow)
|
||||
|
||||
doc = localDOMParser.parseFromString(html, 'text/html')
|
||||
} else {
|
||||
doc = new window.DOMParser().parseFromString(html, 'text/html')
|
||||
}
|
||||
|
||||
if (!doc) {
|
||||
throw new Error('Failed to parse HTML string')
|
||||
}
|
||||
|
||||
return DOMParser.fromSchema(schema)
|
||||
.parse(parseInstance.parseFromString(html, 'text/html').body as Node, options)
|
||||
.parse(doc.body as Node, options)
|
||||
.toJSON()
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import type { Node, Schema } from '@tiptap/pm/model'
|
||||
import { DOMSerializer } from '@tiptap/pm/model'
|
||||
import { Window } from 'happy-dom-without-node'
|
||||
|
||||
import { createSafeWindow } from './createSafeWindow.js'
|
||||
|
||||
/**
|
||||
* Returns the HTML string representation of a given document node.
|
||||
@ -25,13 +26,13 @@ export function getHTMLFromFragment(doc: Node, schema: Schema, options?: { docum
|
||||
}
|
||||
|
||||
// Use happy-dom for serialization.
|
||||
const browserWindow = typeof window === 'undefined' ? new Window() : window
|
||||
const localWindow = typeof window === 'undefined' ? createSafeWindow() : window
|
||||
|
||||
const fragment = DOMSerializer.fromSchema(schema).serializeFragment(doc.content, {
|
||||
document: browserWindow.document as unknown as Document,
|
||||
document: localWindow.document as unknown as Document,
|
||||
})
|
||||
|
||||
const serializer = new browserWindow.XMLSerializer()
|
||||
const serializer = new localWindow.XMLSerializer()
|
||||
|
||||
return serializer.serializeToString(fragment as any)
|
||||
}
|
||||
|
19
packages/html/src/preserveAndRestoreNodeInternals.ts
Normal file
19
packages/html/src/preserveAndRestoreNodeInternals.ts
Normal file
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Preserves and restores node internals like the global process object.
|
||||
* @param operation - The operation to perform while preserving the node internals.
|
||||
* @returns The result of the operation.
|
||||
*/
|
||||
export function preserveAndRestoreNodeInternals<T>(operation: () => T): T {
|
||||
// Store the original process object
|
||||
// see https://github.com/ueberdosis/tiptap/issues/6368
|
||||
// eslint-disable-next-line
|
||||
const originalProcess = globalThis.process
|
||||
|
||||
try {
|
||||
return operation()
|
||||
} finally {
|
||||
// Restore the original process object
|
||||
// eslint-disable-next-line
|
||||
globalThis.process = originalProcess
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user