@quillmark/wasm 0.101.0 → 0.102.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -52,6 +52,10 @@
52
52
  // verbatim" case (`Quill === CoreQuill`) is the executable guard for this
53
53
  // invariant.
54
54
  //
55
+ // It is also why the pre-init guard sits inside the generated builds instead
56
+ // of here: nothing may stand between a consumer and these classes
57
+ // (runtime/uninit.js).
58
+ //
55
59
  // The identity is what makes `instanceof` the whole membership test: a handle
56
60
  // either belongs to this copy's classes or it belongs to another copy, and the
57
61
  // second is always a consumer bug. `Engine` is NOT duck-typed on its inputs; it
@@ -60,8 +64,16 @@
60
64
  // Imported (not bare re-exported) so `Quill` is a local binding this module can
61
65
  // augment: `quill.writer(doc)` is patched onto its prototype below. The
62
66
  // re-export keeps the identity: the exported `Quill` IS the core class.
63
- import { Quill, Document, init } from '../core/wasm.js';
64
- export { Quill, Document, init };
67
+ //
68
+ // The default import is the core build's generated instantiation entry
69
+ // (`--target web`); `init` below is the only thing that calls it.
70
+ import initCore, { Quill, Document } from '../core/wasm.js';
71
+ export { Quill, Document };
72
+ // The wasm byte source, resolved per environment by package.json's `imports`
73
+ // map: a pass-through in a browser (the glue fetches and streams the URL
74
+ // itself), a `node:fs` read under Node, whose `fetch` rejects `file:` URLs.
75
+ // Resolution-time, so `node:fs` never enters a browser graph.
76
+ import { toModuleSource } from '#quillmark-env';
65
77
  // The document-free content codec: re-exported verbatim from the core build so
66
78
  // the runtime subpath exposes `exportMarkdown(body)` (the on-demand markdown
67
79
  // projection), `importMarkdown`, and the position-mapping pair (`rebase`,
@@ -72,6 +84,100 @@ export { importMarkdown, exportMarkdown, rebase, mapPos } from '../core/wasm.js'
72
84
  // segments instead of reverse-engineering the grammar.
73
85
  export { parseDocPath, formatDocPath } from '../core/wasm.js';
74
86
 
87
+ // ── Initialization ──────────────────────────────────────────────────────────
88
+ // The builds are `--target web`: they export their classes synchronously but
89
+ // carry no wasm instance until something instantiates them. This module owns
90
+ // that for core, behind one awaited gate; `Engine` owns it for the backends,
91
+ // inside their lazy load, so a consumer never initializes a backend by hand.
92
+ //
93
+ // The gate is the shape the lazy-backend idiom (§ DEFAULT_BACKENDS) takes when
94
+ // the surface it guards cannot be async: `Quill.fromTree` and `seedDocument`
95
+ // are sync and static, so there is nowhere to hide an await except in front.
96
+ //
97
+ // Reaching core before the gate resolves is not a silent wrong answer: the
98
+ // build is patched to throw `runtime::not_initialized` naming the fix
99
+ // (runtime/uninit.js).
100
+
101
+ /** The in-flight or settled core instantiation. The memo is the PROMISE, not a
102
+ * boolean, so concurrent callers share one instantiation instead of racing. */
103
+ let coreInit;
104
+ /** The source `init` was first called with; the conflict check reads it. */
105
+ let coreInitSource;
106
+
107
+ /**
108
+ * Instantiate the core WASM build. Call once at startup, before any other
109
+ * export is used; extra calls are free.
110
+ *
111
+ * ```js
112
+ * import { init, Quill, Engine } from '@quillmark/wasm';
113
+ * await init();
114
+ * ```
115
+ *
116
+ * Identical in every environment: in a browser the binary is fetched and
117
+ * streamed, under Node it is read off disk, and the call site is the same line.
118
+ *
119
+ * Idempotent and concurrency-safe: every call returns the same promise, so
120
+ * `await init()` at each of several entry points costs one instantiation. A
121
+ * failed init clears the memo, so a retry is possible.
122
+ *
123
+ * @param {import('../core/wasm.js').InitInput} [source] override the binary's
124
+ * source (bytes, a `Response`, a `WebAssembly.Module`, a URL) for hosts that
125
+ * route assets themselves or embed the binary. Pass it on the FIRST call; a
126
+ * later call passing a *different* source throws `runtime::init_conflict`
127
+ * rather than silently ignoring it. Passing the same value again is fine, so
128
+ * several entry points may each `await init(BYTES)` against one constant.
129
+ * @returns {Promise<void>} resolves when the sync surface is usable
130
+ */
131
+ export function init(source) {
132
+ if (coreInit) {
133
+ if (source !== undefined && source !== coreInitSource) {
134
+ throw quillmarkError(
135
+ 'runtime::init_conflict',
136
+ 'init(source): core is already initializing or initialized from a different source.',
137
+ 'Pass a source on the first call only, or pass the same value every time.'
138
+ );
139
+ }
140
+ return coreInit;
141
+ }
142
+ coreInitSource = source;
143
+ // Assign before the first await so a synchronous second call sees the memo.
144
+ coreInit = instantiateCore(source).catch((err) => {
145
+ // Self-heal, as `#resolveBackend` does: one transient failure (a 404, an
146
+ // offline fetch) must not poison every later attempt.
147
+ coreInit = undefined;
148
+ coreInitSource = undefined;
149
+ throw err;
150
+ });
151
+ return coreInit;
152
+ }
153
+
154
+ /**
155
+ * @param {import('../core/wasm.js').InitInput | undefined} source
156
+ * @returns {Promise<void>}
157
+ */
158
+ async function instantiateCore(source) {
159
+ // The literal `new URL(..., import.meta.url)` form: every bundler rewrites it
160
+ // into an emitted asset, and unbundled browsers and Node resolve it against
161
+ // the shipped package layout.
162
+ const resolved = await toModuleSource(
163
+ source ?? new URL('../core/wasm_bg.wasm', import.meta.url)
164
+ );
165
+ try {
166
+ await initCore({ module_or_path: resolved });
167
+ } catch (cause) {
168
+ throw Object.assign(
169
+ quillmarkError(
170
+ 'runtime::init_failed',
171
+ `init(): could not load or instantiate the core WASM binary: ${
172
+ /** @type {any} */ (cause)?.message ?? cause
173
+ }`,
174
+ "The binary ships beside the package files; check the network tab for a 404 or an HTML response. Under Vite's dev server, dependency pre-bundling moves the package away from it: add optimizeDeps: { exclude: ['@quillmark/wasm'] }."
175
+ ),
176
+ { cause }
177
+ );
178
+ }
179
+ }
180
+
75
181
  // ── The main-card address ───────────────────────────────────────────────────
76
182
  /**
77
183
  * The main card's address: the default target of the card-scoped verbs
@@ -143,7 +249,7 @@ function quillmarkError(code, message, hint) {
143
249
  // front-run it with a `QuillmarkError` that names both.
144
250
  //
145
251
  // They also cover the seams with NO `_assertClass` to front-run: `Engine` and
146
- // `LiveSession.apply` cross into backend memory as data (`toTree`/`toJson`), so
252
+ // `LiveSession.update` cross into backend memory as data (`toTree`/`toJson`), so
147
253
  // a foreign handle there would silently work, at the price of the round-trip
148
254
  // above and a quill clone cache split per copy.
149
255
  //
@@ -391,11 +497,51 @@ export function isUnknownIsland(island) {
391
497
  return typeof island?.type === 'string' && !KNOWN_ISLAND_TYPES.has(island.type);
392
498
  }
393
499
 
500
+ /**
501
+ * Build a `load` thunk: dynamic-import a backend build, then instantiate it.
502
+ *
503
+ * Under `--target web` a freshly imported build is inert (the import resolves
504
+ * before there is a wasm instance behind the classes), so instantiation is part
505
+ * of loading and the consumer never sees it. Memoized at MODULE scope, not per
506
+ * `Engine`: two engines issuing their first render concurrently must share one
507
+ * instantiation, and the generated entry's own `wasm !== undefined` guard only
508
+ * catches a call that arrives after one finished, not one already in flight.
509
+ *
510
+ * @param {string} id backend id, for the failure message
511
+ * @param {() => Promise<any>} importThunk the dynamic `import()`
512
+ * @param {() => URL} wasmUrl the build's binary, resolved at call time
513
+ * @returns {() => Promise<any>} resolves to a ready-to-use module
514
+ */
515
+ function backendLoad(id, importThunk, wasmUrl) {
516
+ /** @type {Promise<any> | undefined} */
517
+ let loaded;
518
+ return () =>
519
+ (loaded ??= (async () => {
520
+ const mod = await importThunk();
521
+ await mod.default({ module_or_path: await toModuleSource(wasmUrl()) });
522
+ return mod;
523
+ })().catch((cause) => {
524
+ // Self-heal, as core's `init` does.
525
+ loaded = undefined;
526
+ throw Object.assign(
527
+ quillmarkError(
528
+ 'runtime::backend_load_failed',
529
+ `Engine: could not load the '${id}' backend: ${
530
+ /** @type {any} */ (cause)?.message ?? cause
531
+ }`,
532
+ 'The backend binary ships beside the package files; check the network tab for a 404 or an HTML response.'
533
+ ),
534
+ { cause }
535
+ );
536
+ }));
537
+ }
538
+
394
539
  // Backend builds are NEVER statically imported here: that would pull a
395
540
  // multi-MB binary into the eager graph and defeat lazy loading. Each entry is a
396
- // DESCRIPTOR: `load` is a thunk returning a dynamic `import()` (a backend's
397
- // chunk is fetched only when something actually renders against that backend),
398
- // and `formats`/`canvas` are the REQUIRED static capability manifest so the
541
+ // DESCRIPTOR: `load` is a thunk that dynamically imports a backend's chunk and
542
+ // instantiates it, so the binary is fetched only when something actually renders
543
+ // against that backend and is ready to use when the promise resolves;
544
+ // `formats`/`canvas` are the REQUIRED static capability manifest so the
399
545
  // cheap probes (`supportedFormats`/`supportsCanvas`) ALWAYS answer without
400
546
  // loading the binary or cloning the quill. The manifest values are verified
401
547
  // against each backend's Rust source (`crates/backends/<id>/src/lib.rs`
@@ -405,12 +551,20 @@ export function isUnknownIsland(island) {
405
551
  // format list includes a visual-page format (`svg` or `png`).
406
552
  const DEFAULT_BACKENDS = {
407
553
  typst: {
408
- load: () => import('../backends/typst/wasm.js'),
554
+ load: backendLoad(
555
+ 'typst',
556
+ () => import('../backends/typst/wasm.js'),
557
+ () => new URL('../backends/typst/wasm_bg.wasm', import.meta.url)
558
+ ),
409
559
  formats: ['pdf', 'svg', 'png'], // crates/backends/typst/src/lib.rs SUPPORTED_FORMATS
410
560
  canvas: true // has svg/png → formats_support_canvas == true
411
561
  },
412
562
  pdfform: {
413
- load: () => import('../backends/pdfform/wasm.js'),
563
+ load: backendLoad(
564
+ 'pdfform',
565
+ () => import('../backends/pdfform/wasm.js'),
566
+ () => new URL('../backends/pdfform/wasm_bg.wasm', import.meta.url)
567
+ ),
414
568
  // crates/backends/pdfform/src/lib.rs SUPPORTED_FORMATS == [Pdf, Svg, Png]
415
569
  formats: ['pdf', 'svg', 'png'],
416
570
  canvas: true // has svg/png → formats_support_canvas == true
@@ -475,6 +629,10 @@ export class Engine {
475
629
  * `supportedFormats`/`supportsCanvas` always free (no binary load, no quill
476
630
  * clone). Malformed entries throw here, at construction. The default
477
631
  * registry maps `"typst"` to the bundled Typst build.
632
+ *
633
+ * `load` resolves to a READY module: a registrant shipping its own
634
+ * `--target web` build instantiates inside the thunk. More than one
635
+ * `Engine` may call it, so memoize (the built-ins do, at module scope).
478
636
  */
479
637
  constructor(options) {
480
638
  const merged = { ...DEFAULT_BACKENDS, ...(options?.backends ?? {}) };
@@ -641,8 +799,8 @@ export class Engine {
641
799
  }
642
800
 
643
801
  /**
644
- * Open a live render session (canvas preview / per-page paint / `apply`).
645
- * The session is self-contained (it retains what it needs for `apply`), so
802
+ * Open a live render session (canvas preview / per-page paint / `update`).
803
+ * The session is self-contained (it retains what it needs for `update`), so
646
804
  * the transient quill and document clones are freed before this returns;
647
805
  * the caller owns the returned session and must `.free()` it. The `quill`
648
806
  * and `doc` handles are read synchronously before the first await, so the
@@ -693,15 +851,15 @@ export class Engine {
693
851
 
694
852
  /**
695
853
  * Thin wrapper over a backend's live render session. Reads serve the current
696
- * compile; `apply(doc)` recompiles in place (transactional: on throw, reads
854
+ * compile; `update(doc)` recompiles in place (transactional: on throw, reads
697
855
  * keep serving the last-good compile). The quill/document clones it was
698
- * opened from have already been freed: the session retains what `apply`
856
+ * opened from have already been freed: the session retains what `update`
699
857
  * needs.
700
858
  *
701
859
  * Geometry reads (`regions`, `positionAt`, `locate`) resolve against the
702
860
  * current compile; anchoring a caret or selection across edits is the editor's
703
861
  * job (its own transaction mapping): re-read geometry after each committed
704
- * `apply`.
862
+ * `update`.
705
863
  *
706
864
  * `paint` writes a COMPLETE page raster (all content visible, no caller-side
707
865
  * compositing) for every backend that supports canvas (Typst rasterizes
@@ -709,8 +867,8 @@ export class Engine {
709
867
  */
710
868
  export class LiveSession {
711
869
  /**
712
- * @param {{ pageCount: number, backendId: string, supportsCanvas: boolean, warnings: any[], apply: Function, render: Function, regions: Function, pageSize: Function, paint: Function, free: Function }} inner backend-build LiveSession (typst or pdfform)
713
- * @param {{ Document: { fromJson(json: string): any } }} mod the session's backend build, used to materialize `apply` documents in its linear memory
870
+ * @param {{ pageCount: number, backendId: string, supportsCanvas: boolean, warnings: any[], update: Function, render: Function, regions: Function, pageSize: Function, paint: Function, free: Function }} inner backend-build LiveSession (typst or pdfform)
871
+ * @param {{ Document: { fromJson(json: string): any } }} mod the session's backend build, used to materialize `update` documents in its linear memory
714
872
  */
715
873
  constructor(inner, mod) {
716
874
  this.#inner = inner;
@@ -726,12 +884,12 @@ export class LiveSession {
726
884
  * @param {Document} doc
727
885
  * @returns {import('./runtime.d.ts').ChangeSet}
728
886
  */
729
- apply(doc) {
730
- requireLocalDoc(doc, 'session.apply(doc)');
887
+ update(doc) {
888
+ requireLocalDoc(doc, 'session.update(doc)');
731
889
  let backendDoc = null;
732
890
  try {
733
891
  backendDoc = this.#mod.Document.fromJson(doc.toJson());
734
- return this.#inner.apply(backendDoc);
892
+ return this.#inner.update(backendDoc);
735
893
  } finally {
736
894
  backendDoc?.free();
737
895
  }
@@ -907,14 +1065,15 @@ export class DocumentWriter {
907
1065
  return this.#doc._commitFields(this.#quill, MAIN_CARD_ADDR, fields);
908
1066
  }
909
1067
  /**
910
- * Set the main body from markdown (edit semantics: surviving anchors rebase),
911
- * discarding the text delta, the receipt-free body write. Call
912
- * `doc.revise({}, md)` for the {@link Delta} receipt.
1068
+ * Revise the main body from markdown (edit semantics: surviving anchors
1069
+ * rebase), returning the text {@link Delta}. The content lane's `revise`
1070
+ * reached through the writer: a body carries no field schema, so there is
1071
+ * nothing for a typed verb to type, and the receipt is the content lane's.
913
1072
  * @param {string} markdown
914
- * @returns {void}
1073
+ * @returns {import('../core/wasm.js').Delta}
915
1074
  */
916
- setBody(markdown) {
917
- this.#doc.revise({}, markdown);
1075
+ reviseBody(markdown) {
1076
+ return this.#doc.revise({}, markdown);
918
1077
  }
919
1078
  /**
920
1079
  * Revise the content main-card field `name` from authored text: typed *and*
@@ -1033,13 +1192,13 @@ export class CardWriter {
1033
1192
  return this.#doc._commitFields(this.#quill, { card: this.#index }, fields);
1034
1193
  }
1035
1194
  /**
1036
- * Set this card's body from markdown (edit semantics), discarding the delta:
1037
- * the card twin of {@link DocumentWriter.setBody}.
1195
+ * Revise this card's body from markdown (edit semantics), returning the text
1196
+ * {@link Delta}: the card twin of {@link DocumentWriter.reviseBody}.
1038
1197
  * @param {string} markdown
1039
- * @returns {void}
1198
+ * @returns {import('../core/wasm.js').Delta}
1040
1199
  */
1041
- setBody(markdown) {
1042
- this.#doc.revise({ card: this.#index }, markdown);
1200
+ reviseBody(markdown) {
1201
+ return this.#doc.revise({ card: this.#index }, markdown);
1043
1202
  }
1044
1203
  /**
1045
1204
  * Revise the content field `name` on this card from authored text: typed *and*
@@ -1084,7 +1243,7 @@ Quill.prototype.writer = function writer(doc) {
1084
1243
  // interpret by declared type: a richtext field to markdown, a plaintext field to
1085
1244
  // its literal text, every other type verbatim, and an unknown name throws
1086
1245
  // `UnknownField`. A field's markdown lives here, not on the body-only
1087
- // `getMarkdown`. Like the writer classes these hold the caller's handles by
1246
+ // `bodyMarkdown`. Like the writer classes these hold the caller's handles by
1088
1247
  // reference, own no WASM object, and have nothing to `free()`.
1089
1248
 
1090
1249
  /**
@@ -1115,7 +1274,7 @@ export class DocumentReader {
1115
1274
  * to markdown, every other type verbatim. A bare string is `Addr` shorthand
1116
1275
  * for `{ field }`; an absent `addr.field` reads the body markdown. `undefined`
1117
1276
  * for an absent field; throws `UnknownField` for a name the schema does not
1118
- * declare, `FieldRichtextDecode` for a richtext field holding an undecodable
1277
+ * declare, `FieldDecode` for a richtext field holding an undecodable
1119
1278
  * value, and `IndexOutOfRange` for a bad `addr.card`.
1120
1279
  * @param {import('../core/wasm.js').Addr | string} addr
1121
1280
  * @returns {unknown}
@@ -1124,14 +1283,14 @@ export class DocumentReader {
1124
1283
  return this.#doc._readerGet(this.#quill, addr);
1125
1284
  }
1126
1285
  /**
1127
- * Read the content field at `addr` as its canonical `Content` corpus: the
1128
- * corpus twin of {@link get}, which projects. Decodes through the codec the
1286
+ * Read the content field at `addr` as its canonical `Content`: the
1287
+ * `Content` twin of {@link get}, which projects. Decodes through the codec the
1129
1288
  * declared type names (`richtext` as markdown, `plaintext` as literal text),
1130
- * so a field the writer committed as a corpus and one a markdown parse left
1289
+ * so a field the writer committed as a `Content` and one a markdown parse left
1131
1290
  * as an authored string read back the same; no branching on how the
1132
- * document was built. An absent `addr.field` reads the body corpus.
1291
+ * document was built. An absent `addr.field` reads the body `Content`.
1133
1292
  * `undefined` for an absent field; throws `UnknownField`, `FieldNotContent`
1134
- * for a type that is not a content leaf, `FieldRichtextDecode` for an undecodable
1293
+ * for a type that is not a content leaf, `FieldDecode` for an undecodable
1135
1294
  * value, and `IndexOutOfRange` for a bad `addr.card`.
1136
1295
  * @param {import('../core/wasm.js').Addr | string} addr
1137
1296
  * @returns {import('../core/wasm.js').Content | undefined}
@@ -1144,7 +1303,7 @@ export class DocumentReader {
1144
1303
  * format fact, not a schema fact). Equivalent to `get({})`.
1145
1304
  * @returns {string}
1146
1305
  */
1147
- getBody() {
1306
+ bodyMarkdown() {
1148
1307
  return this.#doc._readerGet(this.#quill, {});
1149
1308
  }
1150
1309
  /**
@@ -1163,7 +1322,7 @@ export class DocumentReader {
1163
1322
 
1164
1323
  /**
1165
1324
  * A single composable card bound to its {@link Quill} for typed reads, from
1166
- * {@link DocumentReader.card}. Same `get` / `getBody` verbs as
1325
+ * {@link DocumentReader.card}. Same `get` / `bodyMarkdown` verbs as
1167
1326
  * {@link DocumentReader}, reading the card at its bound index.
1168
1327
  */
1169
1328
  export class CardReader {
@@ -1206,7 +1365,7 @@ export class CardReader {
1206
1365
  }
1207
1366
  /**
1208
1367
  * Read the content field `name` on this card as its canonical `Content`
1209
- * corpus: the card twin of {@link DocumentReader.getContent}.
1368
+ * `Content`: the card twin of {@link DocumentReader.getContent}.
1210
1369
  * @param {string} name
1211
1370
  * @returns {import('../core/wasm.js').Content | undefined}
1212
1371
  */
@@ -1214,10 +1373,10 @@ export class CardReader {
1214
1373
  return this.#doc._readerGetContent(this.#quill, { card: this.#index, field: name });
1215
1374
  }
1216
1375
  /**
1217
- * This card's body markdown: the card twin of {@link DocumentReader.getBody}.
1376
+ * This card's body markdown: the card twin of {@link DocumentReader.bodyMarkdown}.
1218
1377
  * @returns {string}
1219
1378
  */
1220
- getBody() {
1379
+ bodyMarkdown() {
1221
1380
  return this.#doc._readerGet(this.#quill, { card: this.#index });
1222
1381
  }
1223
1382
  }
@@ -0,0 +1,57 @@
1
+ // The pre-initialization sentinel.
2
+ //
3
+ // wasm-bindgen's `--target web` glue holds its instance exports in one
4
+ // module-level binding (`let wasmModule, wasm;`) that every generated path
5
+ // reads through: constructors, statics, methods, free functions. Until
6
+ // instantiation assigns it, that binding is `undefined`, so a consumer who
7
+ // skipped `await init()` gets `Cannot read properties of undefined (reading
8
+ // 'quill_fromTree')` from inside generated code.
9
+ //
10
+ // `scripts/build-wasm.sh` (`guard_wasm_js`) patches the binding to start as
11
+ // this sentinel, so the same access throws a `QuillmarkError` naming the cause
12
+ // and the fix. The patch is three line edits per variant, shape-asserted before
13
+ // it applies; the logic lives here rather than inside the awk program.
14
+ //
15
+ // It sits in the generated build, not in the runtime layer, because the
16
+ // canonical invariant (`runtime.js` § "CANONICAL INVARIANT") requires the root
17
+ // to re-export `Quill`/`Document` VERBATIM: no wrapper, subclass, or proxy may
18
+ // stand between a consumer and those classes. Prototype patching preserves
19
+ // identity but cannot cover a public constructor (`new Document(...)`) without
20
+ // wrapping the class. The binding they all read through is the one place that
21
+ // covers every path and touches no class.
22
+
23
+ /**
24
+ * A stand-in for a wasm build's exports that throws on use. Every property read
25
+ * throws except the marker the patched init guards test, and `then`,
26
+ * `constructor`, and symbols, which return `undefined`: an incidental `await`
27
+ * or `util.inspect` reports nothing rather than throwing somewhere unrelated to
28
+ * the cause.
29
+ *
30
+ * @param {string} message what a consumer sees when they reach a build early
31
+ * @param {string} hint the fix, as a `Diagnostic.hint`
32
+ * @returns {any} the sentinel
33
+ */
34
+ export function uninitSentinel(message, hint) {
35
+ return new Proxy(
36
+ {},
37
+ {
38
+ get(_target, prop) {
39
+ if (prop === UNINIT) return true;
40
+ if (prop === 'then' || prop === 'constructor' || typeof prop === 'symbol') {
41
+ return undefined;
42
+ }
43
+ const err = /** @type {any} */ (new Error(message));
44
+ err.diagnostics = [
45
+ { severity: 'error', code: 'runtime::not_initialized', message, hint }
46
+ ];
47
+ throw err;
48
+ }
49
+ }
50
+ );
51
+ }
52
+
53
+ /**
54
+ * The marker the patched `if (wasm !== undefined)` guards read to tell a
55
+ * sentinel from real instance exports, which never carry it.
56
+ */
57
+ export const UNINIT = '__quillmarkUninit';