Skip to content

Commit 2ef1925

Browse files
authored
Dynamic import of unified dependencies (#1026)
* make unified imports dynamic to support loading from non esm projects * fix remaining imports * initializeESMDependencies
1 parent 14d7793 commit 2ef1925

File tree

14 files changed

+183
-73
lines changed

14 files changed

+183
-73
lines changed

packages/core/src/api/exporters/copyExtension.ts

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,26 @@ import { NodeSelection, Plugin } from "prosemirror-state";
55
import { EditorView } from "prosemirror-view";
66
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor";
77
import { BlockSchema, InlineContentSchema, StyleSchema } from "../../schema";
8+
import { initializeESMDependencies } from "../../util/esmDependencies";
89
import { createExternalHTMLExporter } from "./html/externalHTMLExporter";
910
import { createInternalHTMLSerializer } from "./html/internalHTMLSerializer";
1011
import { cleanHTMLToMarkdown } from "./markdown/markdownExporter";
1112

12-
function selectedFragmentToHTML<
13+
async function selectedFragmentToHTML<
1314
BSchema extends BlockSchema,
1415
I extends InlineContentSchema,
1516
S extends StyleSchema
1617
>(
1718
view: EditorView,
1819
editor: BlockNoteEditor<BSchema, I, S>
19-
): {
20+
): Promise<{
2021
internalHTML: string;
2122
externalHTML: string;
2223
plainText: string;
23-
} {
24+
}> {
2425
const selectedFragment = view.state.selection.content().content;
2526

26-
const internalHTMLSerializer = createInternalHTMLSerializer(
27+
const internalHTMLSerializer = await createInternalHTMLSerializer(
2728
view.state.schema,
2829
editor
2930
);
@@ -32,6 +33,7 @@ function selectedFragmentToHTML<
3233
{}
3334
);
3435

36+
await initializeESMDependencies();
3537
const externalHTMLExporter = createExternalHTMLExporter(
3638
view.state.schema,
3739
editor
@@ -41,7 +43,7 @@ function selectedFragmentToHTML<
4143
{}
4244
);
4345

44-
const plainText = cleanHTMLToMarkdown(externalHTML);
46+
const plainText = await cleanHTMLToMarkdown(externalHTML);
4547

4648
return { internalHTML, externalHTML, plainText };
4749
}
@@ -83,15 +85,16 @@ export const createCopyToClipboardExtension = <
8385
);
8486
}
8587

86-
const { internalHTML, externalHTML, plainText } =
87-
selectedFragmentToHTML(view, editor);
88-
89-
// TODO: Writing to other MIME types not working in Safari for
90-
// some reason.
91-
event.clipboardData!.setData("blocknote/html", internalHTML);
92-
event.clipboardData!.setData("text/html", externalHTML);
93-
event.clipboardData!.setData("text/plain", plainText);
88+
(async () => {
89+
const { internalHTML, externalHTML, plainText } =
90+
await selectedFragmentToHTML(view, editor);
9491

92+
// TODO: Writing to other MIME types not working in Safari for
93+
// some reason.
94+
event.clipboardData!.setData("blocknote/html", internalHTML);
95+
event.clipboardData!.setData("text/html", externalHTML);
96+
event.clipboardData!.setData("text/plain", plainText);
97+
})();
9598
// Prevent default PM handler to be called
9699
return true;
97100
},
@@ -125,15 +128,16 @@ export const createCopyToClipboardExtension = <
125128
event.preventDefault();
126129
event.dataTransfer!.clearData();
127130

128-
const { internalHTML, externalHTML, plainText } =
129-
selectedFragmentToHTML(view, editor);
130-
131-
// TODO: Writing to other MIME types not working in Safari for
132-
// some reason.
133-
event.dataTransfer!.setData("blocknote/html", internalHTML);
134-
event.dataTransfer!.setData("text/html", externalHTML);
135-
event.dataTransfer!.setData("text/plain", plainText);
131+
(async () => {
132+
const { internalHTML, externalHTML, plainText } =
133+
await selectedFragmentToHTML(view, editor);
136134

135+
// TODO: Writing to other MIME types not working in Safari for
136+
// some reason.
137+
event.dataTransfer!.setData("blocknote/html", internalHTML);
138+
event.dataTransfer!.setData("text/html", externalHTML);
139+
event.dataTransfer!.setData("text/plain", plainText);
140+
})();
137141
// Prevent default PM handler to be called
138142
return true;
139143
},

packages/core/src/api/exporters/html/externalHTMLExporter.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
import { DOMSerializer, Fragment, Node, Schema } from "prosemirror-model";
2-
import rehypeParse from "rehype-parse";
3-
import rehypeStringify from "rehype-stringify";
4-
import { unified } from "unified";
52

63
import { PartialBlock } from "../../../blocks/defaultBlocks";
74
import type { BlockNoteEditor } from "../../../editor/BlockNoteEditor";
85
import { BlockSchema, InlineContentSchema, StyleSchema } from "../../../schema";
6+
import { esmDependencies } from "../../../util/esmDependencies";
97
import { blockToNode } from "../../nodeConversions/nodeConversions";
108
import {
119
serializeNodeInner,
@@ -47,6 +45,8 @@ export interface ExternalHTMLExporter<
4745
) => string;
4846
}
4947

48+
// Needs to be sync because it's used in drag handler event (SideMenuPlugin)
49+
// Ideally, call `await initializeESMDependencies()` before calling this function
5050
export const createExternalHTMLExporter = <
5151
BSchema extends BlockSchema,
5252
I extends InlineContentSchema,
@@ -55,6 +55,14 @@ export const createExternalHTMLExporter = <
5555
schema: Schema,
5656
editor: BlockNoteEditor<BSchema, I, S>
5757
): ExternalHTMLExporter<BSchema, I, S> => {
58+
const deps = esmDependencies;
59+
60+
if (!deps) {
61+
throw new Error(
62+
"External HTML exporter requires ESM dependencies to be initialized"
63+
);
64+
}
65+
5866
const serializer = DOMSerializer.fromSchema(schema) as DOMSerializer & {
5967
serializeNodeInner: (
6068
node: Node,
@@ -79,16 +87,17 @@ export const createExternalHTMLExporter = <
7987
// but additionally runs it through the `simplifyBlocks` rehype plugin to
8088
// convert the internal HTML to external.
8189
serializer.exportProseMirrorFragment = (fragment, options) => {
82-
const externalHTML = unified()
83-
.use(rehypeParse, { fragment: true })
90+
const externalHTML = deps.unified
91+
.unified()
92+
.use(deps.rehypeParse.default, { fragment: true })
8493
.use(simplifyBlocks, {
8594
orderedListItemBlockTypes: new Set<string>(["numberedListItem"]),
8695
unorderedListItemBlockTypes: new Set<string>([
8796
"bulletListItem",
8897
"checkListItem",
8998
]),
9099
})
91-
.use(rehypeStringify)
100+
.use(deps.rehypeStringify.default)
92101
.processSync(serializeProseMirrorFragment(fragment, serializer, options));
93102

94103
return externalHTML.value as string;

packages/core/src/api/exporters/html/htmlConversion.test.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { PartialBlock } from "../../../blocks/defaultBlocks";
77
import { BlockSchema } from "../../../schema/blocks/types";
88
import { InlineContentSchema } from "../../../schema/inlineContent/types";
99
import { StyleSchema } from "../../../schema/styles/types";
10+
import { initializeESMDependencies } from "../../../util/esmDependencies";
1011
import { customBlocksTestCases } from "../../testUtil/cases/customBlocks";
1112
import { customInlineContentTestCases } from "../../testUtil/cases/customInlineContent";
1213
import { customStylesTestCases } from "../../testUtil/cases/customStyles";
@@ -44,6 +45,7 @@ async function convertToHTMLAndCompareSnapshots<
4445

4546
expect(parsed).toStrictEqual(fullBlocks);
4647

48+
await initializeESMDependencies();
4749
// Create the "external" HTML, which is a cleaned up HTML representation, but lossy
4850
const exporter = createExternalHTMLExporter(editor.pmSchema, editor);
4951
const externalHTML = exporter.exportBlocks(blocks, {});
@@ -175,7 +177,7 @@ describe("Test ProseMirror fragment edge case conversion", () => {
175177
editor.replaceBlocks(editor.document, blocks);
176178
});
177179

178-
it("Selection within a block's children", () => {
180+
it("Selection within a block's children", async () => {
179181
// Selection starts and ends within the first block's children.
180182
editor.dispatch(
181183
editor._tiptapEditor.state.tr.setSelection(
@@ -186,6 +188,7 @@ describe("Test ProseMirror fragment edge case conversion", () => {
186188
const copiedFragment =
187189
editor._tiptapEditor.state.selection.content().content;
188190

191+
await initializeESMDependencies();
189192
const exporter = createExternalHTMLExporter(editor.pmSchema, editor);
190193
const externalHTML = exporter.exportProseMirrorFragment(
191194
copiedFragment,
@@ -197,7 +200,7 @@ describe("Test ProseMirror fragment edge case conversion", () => {
197200
);
198201
});
199202

200-
it("Selection leaves a block's children", () => {
203+
it("Selection leaves a block's children", async () => {
201204
// Selection starts and ends within the first block's children and ends
202205
// outside, at a shallower nesting level in the second block.
203206
editor.dispatch(
@@ -209,6 +212,7 @@ describe("Test ProseMirror fragment edge case conversion", () => {
209212
const copiedFragment =
210213
editor._tiptapEditor.state.selection.content().content;
211214

215+
await initializeESMDependencies();
212216
const exporter = createExternalHTMLExporter(editor.pmSchema, editor);
213217
const externalHTML = exporter.exportProseMirrorFragment(
214218
copiedFragment,
@@ -220,7 +224,7 @@ describe("Test ProseMirror fragment edge case conversion", () => {
220224
);
221225
});
222226

223-
it("Selection spans multiple blocks' children", () => {
227+
it("Selection spans multiple blocks' children", async () => {
224228
// Selection starts and ends within the first block's children and ends
225229
// within the second block's children.
226230
editor.dispatch(
@@ -231,6 +235,7 @@ describe("Test ProseMirror fragment edge case conversion", () => {
231235

232236
const copiedFragment =
233237
editor._tiptapEditor.state.selection.content().content;
238+
await initializeESMDependencies();
234239
const exporter = createExternalHTMLExporter(editor.pmSchema, editor);
235240
const externalHTML = exporter.exportProseMirrorFragment(
236241
copiedFragment,

packages/core/src/api/exporters/html/util/simplifyBlocksRehypePlugin.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Element as HASTElement, Parent as HASTParent } from "hast";
2-
import { fromDom } from "hast-util-from-dom";
2+
import { esmDependencies } from "../../../../util/esmDependencies";
33

44
type SimplifyBlocksOptions = {
55
orderedListItemBlockTypes: Set<string>;
@@ -16,6 +16,14 @@ type SimplifyBlocksOptions = {
1616
* @param options Options for specifying which block types represent ordered and unordered list items.
1717
*/
1818
export function simplifyBlocks(options: SimplifyBlocksOptions) {
19+
const deps = esmDependencies;
20+
21+
if (!deps) {
22+
throw new Error(
23+
"simplifyBlocks requires ESM dependencies to be initialized"
24+
);
25+
}
26+
1927
const listItemBlockTypes = new Set<string>([
2028
...options.orderedListItemBlockTypes,
2129
...options.unorderedListItemBlockTypes,
@@ -110,13 +118,13 @@ export function simplifyBlocks(options: SimplifyBlocksOptions) {
110118
// type as this was already done earlier.
111119
if (!activeList) {
112120
// Creates a new list element to represent an active list.
113-
activeList = fromDom(
121+
activeList = deps.hastUtilFromDom.fromDom(
114122
document.createElement(listItemBlockType!)
115123
) as HASTElement;
116124
}
117125

118126
// Creates a new list item element to represent the block.
119-
const listItemElement = fromDom(
127+
const listItemElement = deps.hastUtilFromDom.fromDom(
120128
document.createElement("li")
121129
) as HASTElement;
122130

packages/core/src/api/exporters/markdown/markdownExporter.ts

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,42 @@
11
import { Schema } from "prosemirror-model";
2-
import rehypeParse from "rehype-parse";
3-
import rehypeRemark from "rehype-remark";
4-
import remarkGfm from "remark-gfm";
5-
import remarkStringify from "remark-stringify";
6-
import { unified } from "unified";
72
import { PartialBlock } from "../../../blocks/defaultBlocks";
83
import type { BlockNoteEditor } from "../../../editor/BlockNoteEditor";
94
import { BlockSchema, InlineContentSchema, StyleSchema } from "../../../schema";
5+
import {
6+
esmDependencies,
7+
initializeESMDependencies,
8+
} from "../../../util/esmDependencies";
109
import { createExternalHTMLExporter } from "../html/externalHTMLExporter";
1110
import { removeUnderlines } from "./removeUnderlinesRehypePlugin";
1211
import { addSpacesToCheckboxes } from "./util/addSpacesToCheckboxesRehypePlugin";
1312

13+
// Needs to be sync because it's used in drag handler event (SideMenuPlugin)
14+
// Ideally, call `await initializeESMDependencies()` before calling this function
1415
export function cleanHTMLToMarkdown(cleanHTMLString: string) {
15-
const markdownString = unified()
16-
.use(rehypeParse, { fragment: true })
16+
const deps = esmDependencies;
17+
18+
if (!deps) {
19+
throw new Error(
20+
"cleanHTMLToMarkdown requires ESM dependencies to be initialized"
21+
);
22+
}
23+
24+
const markdownString = deps.unified
25+
.unified()
26+
.use(deps.rehypeParse.default, { fragment: true })
1727
.use(removeUnderlines)
1828
.use(addSpacesToCheckboxes)
19-
.use(rehypeRemark)
20-
.use(remarkGfm)
21-
.use(remarkStringify, { handlers: { text: (node) => node.value } })
29+
.use(deps.rehypeRemark.default)
30+
.use(deps.remarkGfm.default)
31+
.use(deps.remarkStringify.default, {
32+
handlers: { text: (node) => node.value },
33+
})
2234
.processSync(cleanHTMLString);
2335

2436
return markdownString.value as string;
2537
}
2638

27-
export function blocksToMarkdown<
39+
export async function blocksToMarkdown<
2840
BSchema extends BlockSchema,
2941
I extends InlineContentSchema,
3042
S extends StyleSchema
@@ -33,7 +45,8 @@ export function blocksToMarkdown<
3345
schema: Schema,
3446
editor: BlockNoteEditor<BSchema, I, S>,
3547
options: { document?: Document }
36-
): string {
48+
): Promise<string> {
49+
await initializeESMDependencies();
3750
const exporter = createExternalHTMLExporter(schema, editor);
3851
const externalHTML = exporter.exportBlocks(blocks, options);
3952

packages/core/src/api/exporters/markdown/util/addSpacesToCheckboxesRehypePlugin.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
import { Element as HASTElement, Parent as HASTParent } from "hast";
2-
import { fromDom } from "hast-util-from-dom";
2+
import { esmDependencies } from "../../../../util/esmDependencies";
33

44
/**
55
* Rehype plugin which adds a space after each checkbox input element. This is
66
* because remark doesn't add any spaces between the checkbox input and the text
77
* itself, but these are needed for correct Markdown syntax.
88
*/
99
export function addSpacesToCheckboxes() {
10+
const deps = esmDependencies;
11+
12+
if (!deps) {
13+
throw new Error(
14+
"simplifyBlocks requires ESM dependencies to be initialized"
15+
);
16+
}
17+
1018
const helper = (tree: HASTParent) => {
1119
if (tree.children && "length" in tree.children && tree.children.length) {
1220
for (let i = tree.children.length - 1; i >= 0; i--) {
@@ -29,7 +37,9 @@ export function addSpacesToCheckboxes() {
2937
nextChild.children.splice(
3038
0,
3139
0,
32-
fromDom(document.createTextNode(" ")) as HASTElement
40+
deps.hastUtilFromDom.fromDom(
41+
document.createTextNode(" ")
42+
) as HASTElement
3343
);
3444
} else {
3545
helper(child as HASTParent);

packages/core/src/api/parsers/html/util/nestedLists.test.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
import rehypeFormat from "rehype-format";
2-
import rehypeParse from "rehype-parse";
3-
import rehypeStringify from "rehype-stringify";
4-
import { unified } from "unified";
51
import { describe, expect, it } from "vitest";
2+
import { initializeESMDependencies } from "../../../../util/esmDependencies";
63
import { nestedListsToBlockNoteStructure } from "./nestedLists";
74

85
async function testHTML(html: string) {
6+
const deps = await initializeESMDependencies();
7+
98
const htmlNode = nestedListsToBlockNoteStructure(html);
109

11-
const pretty = await unified()
12-
.use(rehypeParse, { fragment: true })
13-
.use(rehypeFormat)
14-
.use(rehypeStringify)
10+
const pretty = await deps.unified
11+
.unified()
12+
.use(deps.rehypeParse.default, { fragment: true })
13+
.use(deps.rehypeFormat.default)
14+
.use(deps.rehypeStringify.default)
1515
.process(htmlNode.innerHTML);
1616

1717
expect(pretty.value).toMatchSnapshot();

0 commit comments

Comments
 (0)