0
# Internal Plugins
1
2
Internal plugins provide the fundamental functionality required for editor operation. These plugins are automatically loaded during editor creation and form the foundation upon which all other features are built.
3
4
## Capabilities
5
6
### Core Internal Plugins
7
8
The essential plugins that provide basic editor functionality.
9
10
```typescript { .api }
11
/**
12
* Core internal plugins automatically loaded by the editor
13
*/
14
15
/** Creates the ProseMirror schema from registered nodes and marks */
16
const schema: MilkdownPlugin;
17
18
/** Creates the markdown-to-ProseMirror parser */
19
const parser: MilkdownPlugin;
20
21
/** Creates the ProseMirror-to-markdown serializer */
22
const serializer: MilkdownPlugin;
23
24
/** Initializes the command management system */
25
const commands: MilkdownPlugin;
26
27
/** Initializes the keymap management system */
28
const keymap: MilkdownPlugin;
29
30
/** Creates the ProseMirror editor state */
31
const editorState: MilkdownPlugin;
32
33
/** Creates the ProseMirror editor view and mounts it to DOM */
34
const editorView: MilkdownPlugin;
35
```
36
37
**Usage:**
38
39
These plugins are automatically loaded by the editor. You typically don't need to use them directly, but they're available for inspection or custom editor setups:
40
41
```typescript
42
import { Editor, schema, parser, commands } from "@milkdown/core";
43
44
// These are loaded automatically
45
const editor = Editor.make().create();
46
47
// For custom setups, you could theoretically use them individually
48
// (though this is not recommended for normal usage)
49
const customPlugin: MilkdownPlugin = (ctx) => {
50
return async () => {
51
await ctx.wait(SchemaReady);
52
await ctx.wait(ParserReady);
53
await ctx.wait(CommandsReady);
54
55
// Your plugin logic here
56
};
57
};
58
```
59
60
### Configuration and Initialization Plugins
61
62
Plugins that handle editor configuration and initialization.
63
64
```typescript { .api }
65
/**
66
* Configuration and initialization plugin factories
67
*/
68
69
/**
70
* Creates a configuration plugin that executes user configuration
71
* @param configure - Configuration function to execute
72
* @returns MilkdownPlugin for configuration
73
*/
74
function config(configure: Config): MilkdownPlugin;
75
76
/**
77
* Creates the initialization plugin that prepares core slices
78
* @param editor - The editor instance to initialize
79
* @returns MilkdownPlugin for initialization
80
*/
81
function init(editor: Editor): MilkdownPlugin;
82
```
83
84
**Usage Examples:**
85
86
```typescript
87
import { Editor, config } from "@milkdown/core";
88
89
// Create configuration plugin
90
const myConfigPlugin = config(async (ctx) => {
91
// Configure editor context
92
ctx.set(defaultValueCtx, '# Hello World');
93
ctx.set(rootCtx, document.getElementById('editor'));
94
95
// Async configuration
96
const theme = await loadUserTheme();
97
ctx.set(themeCtx, theme);
98
});
99
100
// The editor automatically creates and uses config plugins
101
// from functions passed to .config()
102
const editor = Editor.make()
103
.config((ctx) => {
104
// This creates a config plugin internally
105
ctx.set(defaultValueCtx, 'Initial content');
106
})
107
.create();
108
```
109
110
### Plugin Timing System
111
112
Timers that coordinate plugin loading order and dependencies.
113
114
```typescript { .api }
115
/**
116
* Plugin timing system - timers that resolve when plugins are ready
117
*/
118
119
/** Resolved when configuration plugin completes */
120
const ConfigReady: TimerType;
121
122
/** Resolved when initialization plugin completes */
123
const InitReady: TimerType;
124
125
/** Resolved when schema plugin completes */
126
const SchemaReady: TimerType;
127
128
/** Resolved when parser plugin completes */
129
const ParserReady: TimerType;
130
131
/** Resolved when serializer plugin completes */
132
const SerializerReady: TimerType;
133
134
/** Resolved when commands plugin completes */
135
const CommandsReady: TimerType;
136
137
/** Resolved when keymap plugin completes */
138
const KeymapReady: TimerType;
139
140
/** Resolved when editor state plugin completes */
141
const EditorStateReady: TimerType;
142
143
/** Resolved when editor view plugin completes */
144
const EditorViewReady: TimerType;
145
```
146
147
**Usage Examples:**
148
149
```typescript
150
import {
151
MilkdownPlugin,
152
SchemaReady,
153
ParserReady,
154
CommandsReady
155
} from "@milkdown/core";
156
157
const myPlugin: MilkdownPlugin = (ctx) => {
158
return async () => {
159
// Wait for required plugins to be ready
160
await ctx.wait(SchemaReady);
161
await ctx.wait(ParserReady);
162
await ctx.wait(CommandsReady);
163
164
// Now safe to use schema, parser, and commands
165
const schema = ctx.get(schemaCtx);
166
const parser = ctx.get(parserCtx);
167
const commands = ctx.get(commandsCtx);
168
169
// Plugin initialization logic
170
};
171
};
172
```
173
174
## Plugin Loading Order
175
176
Internal plugins load in a specific order to ensure proper dependencies:
177
178
1. **Config** - Executes user configuration
179
2. **Init** - Prepares core context slices
180
3. **Schema** - Creates ProseMirror schema from nodes/marks
181
4. **Parser** - Creates markdown-to-ProseMirror parser
182
5. **Serializer** - Creates ProseMirror-to-markdown serializer
183
6. **Commands** - Initializes command system
184
7. **Keymap** - Initializes keymap system
185
8. **Editor State** - Creates ProseMirror editor state
186
9. **Editor View** - Creates and mounts ProseMirror editor view
187
188
```typescript
189
import {
190
ConfigReady,
191
InitReady,
192
SchemaReady,
193
ParserReady,
194
SerializerReady,
195
CommandsReady,
196
KeymapReady,
197
EditorStateReady,
198
EditorViewReady
199
} from "@milkdown/core";
200
201
// Example plugin that waits for multiple stages
202
const advancedPlugin: MilkdownPlugin = (ctx) => {
203
return async () => {
204
// Wait for basic setup
205
await ctx.wait(InitReady);
206
console.log('Editor initialized');
207
208
// Wait for content processing
209
await ctx.wait(ParserReady);
210
await ctx.wait(SerializerReady);
211
console.log('Content processing ready');
212
213
// Wait for interaction systems
214
await ctx.wait(CommandsReady);
215
await ctx.wait(KeymapReady);
216
console.log('Interaction systems ready');
217
218
// Wait for final editor
219
await ctx.wait(EditorViewReady);
220
console.log('Editor fully ready');
221
};
222
};
223
```
224
225
## Advanced Usage
226
227
### Custom Plugin Dependencies
228
229
```typescript
230
import { MilkdownPlugin, createTimer } from "@milkdown/ctx";
231
import { SchemaReady, CommandsReady } from "@milkdown/core";
232
233
// Create custom timer
234
const MyPluginReady = createTimer('MyPluginReady');
235
236
const myPlugin: MilkdownPlugin = (ctx) => {
237
ctx.record(MyPluginReady);
238
239
return async () => {
240
// Wait for dependencies
241
await ctx.wait(SchemaReady);
242
await ctx.wait(CommandsReady);
243
244
// Plugin logic
245
setupMyPlugin(ctx);
246
247
// Signal ready
248
ctx.done(MyPluginReady);
249
250
return () => {
251
// Cleanup
252
ctx.clearTimer(MyPluginReady);
253
};
254
};
255
};
256
257
// Another plugin can wait for your plugin
258
const dependentPlugin: MilkdownPlugin = (ctx) => {
259
return async () => {
260
await ctx.wait(MyPluginReady);
261
// This runs after myPlugin is ready
262
};
263
};
264
```
265
266
### Intercepting Internal Plugin Behavior
267
268
```typescript
269
import { Editor, schemaTimerCtx, SchemaReady } from "@milkdown/core";
270
271
// Plugin that modifies internal plugin timing
272
const schemaModifier: MilkdownPlugin = (ctx) => {
273
return async () => {
274
// Add additional dependency for schema plugin
275
ctx.update(schemaTimerCtx, (timers) => [
276
...timers,
277
MyCustomTimer
278
]);
279
280
// Schema plugin will now wait for MyCustomTimer too
281
};
282
};
283
```
284
285
### Accessing Internal Plugin State
286
287
```typescript
288
import { Editor, schemaCtx, parserCtx, serializerCtx } from "@milkdown/core";
289
290
const inspectorPlugin: MilkdownPlugin = (ctx) => {
291
return async () => {
292
await ctx.wait(SchemaReady);
293
await ctx.wait(ParserReady);
294
await ctx.wait(SerializerReady);
295
296
// Access internal plugin state
297
const schema = ctx.get(schemaCtx);
298
const parser = ctx.get(parserCtx);
299
const serializer = ctx.get(serializerCtx);
300
301
console.log('Available nodes:', Object.keys(schema.nodes));
302
console.log('Available marks:', Object.keys(schema.marks));
303
304
// Test parser/serializer
305
const markdown = '# Hello World';
306
const doc = parser(markdown);
307
const backToMarkdown = serializer(doc);
308
console.log('Round trip:', { markdown, backToMarkdown });
309
};
310
};
311
```
312
313
## Configuration Types
314
315
### Configuration Function Type
316
317
```typescript { .api }
318
/**
319
* Type for configuration functions executed during editor initialization
320
* @param ctx - The editor context to configure
321
*/
322
type Config = (ctx: Ctx) => void | Promise<void>;
323
```
324
325
### Additional Types
326
327
```typescript { .api }
328
/** Editor view options type (ProseMirror DirectEditorProps without state) */
329
type EditorOptions = Omit<DirectEditorProps, 'state'>;
330
331
/** Root element types for editor mounting */
332
type RootType = Node | undefined | null | string;
333
334
/** Editor state creation options override function */
335
type StateOptionsOverride = (prev: StateOptions) => StateOptions;
336
```
337
338
**Examples:**
339
340
```typescript
341
import { Config, defaultValueCtx, rootCtx } from "@milkdown/core";
342
343
// Synchronous configuration
344
const syncConfig: Config = (ctx) => {
345
ctx.set(defaultValueCtx, '# Hello');
346
ctx.set(rootCtx, document.body);
347
};
348
349
// Asynchronous configuration
350
const asyncConfig: Config = async (ctx) => {
351
const content = await fetch('/api/content').then(r => r.text());
352
ctx.set(defaultValueCtx, content);
353
354
const userPrefs = await loadUserPreferences();
355
ctx.set(themeCtx, userPrefs.theme);
356
};
357
358
// Error handling in configuration
359
const safeConfig: Config = async (ctx) => {
360
try {
361
const content = await fetch('/api/content').then(r => r.text());
362
ctx.set(defaultValueCtx, content);
363
} catch (error) {
364
console.warn('Failed to load remote content, using default');
365
ctx.set(defaultValueCtx, '# Welcome');
366
}
367
};
368
```
369
370
## Plugin Metadata
371
372
Internal plugins include metadata for debugging and inspection:
373
374
```typescript
375
import { Editor } from "@milkdown/core";
376
377
const editor = Editor.make()
378
.enableInspector(true)
379
.create();
380
381
// Get telemetry for internal plugins
382
const telemetry = editor.inspect();
383
telemetry.forEach(item => {
384
console.log(`Plugin: ${item.displayName}`);
385
console.log(`Package: ${item.package}`);
386
console.log(`Group: ${item.group}`);
387
});
388
389
// Internal plugins have metadata like:
390
// { displayName: 'Schema', package: '@milkdown/core', group: 'System' }
391
// { displayName: 'Parser', package: '@milkdown/core', group: 'System' }
392
// { displayName: 'Commands', package: '@milkdown/core', group: 'System' }
393
```
394
395
## Error Handling
396
397
### Plugin Loading Failures
398
399
```typescript
400
import { Editor } from "@milkdown/core";
401
402
try {
403
const editor = await Editor.make()
404
.config((ctx) => {
405
// Potentially failing configuration
406
if (!document.getElementById('editor')) {
407
throw new Error('Editor mount point not found');
408
}
409
})
410
.create();
411
} catch (error) {
412
console.error('Editor creation failed:', error);
413
// Handle plugin loading failure
414
}
415
```
416
417
### Timer Resolution Issues
418
419
```typescript
420
import { MilkdownPlugin } from "@milkdown/ctx";
421
import { SchemaReady } from "@milkdown/core";
422
423
const timeoutPlugin: MilkdownPlugin = (ctx) => {
424
return async () => {
425
try {
426
// Wait with timeout
427
await Promise.race([
428
ctx.wait(SchemaReady),
429
new Promise((_, reject) =>
430
setTimeout(() => reject(new Error('Schema timeout')), 5000)
431
)
432
]);
433
} catch (error) {
434
console.error('Plugin loading timed out:', error);
435
throw error;
436
}
437
};
438
};
439
```
440
441
## Best Practices
442
443
### Plugin Dependency Management
444
445
```typescript
446
// Good: Explicit dependency waiting
447
const myPlugin: MilkdownPlugin = (ctx) => {
448
return async () => {
449
await ctx.wait(SchemaReady);
450
await ctx.wait(CommandsReady);
451
// Safe to use schema and commands
452
};
453
};
454
455
// Avoid: Assuming plugins are ready
456
const badPlugin: MilkdownPlugin = (ctx) => {
457
return async () => {
458
const schema = ctx.get(schemaCtx); // May not be ready!
459
};
460
};
461
```
462
463
### Custom Timer Usage
464
465
```typescript
466
import { createTimer } from "@milkdown/ctx";
467
468
// Good: Descriptive timer names
469
const DataLoaderReady = createTimer('DataLoaderReady');
470
const ThemeManagerReady = createTimer('ThemeManagerReady');
471
472
// Good: Proper timer lifecycle
473
const myPlugin: MilkdownPlugin = (ctx) => {
474
ctx.record(DataLoaderReady);
475
476
return async () => {
477
// Plugin logic
478
ctx.done(DataLoaderReady);
479
480
return () => {
481
ctx.clearTimer(DataLoaderReady); // Important cleanup
482
};
483
};
484
};
485
```
486
487
### Configuration Organization
488
489
```typescript
490
// Good: Organized configuration
491
const editorConfig: Config = async (ctx) => {
492
// Content configuration
493
ctx.set(defaultValueCtx, await loadContent());
494
495
// UI configuration
496
ctx.set(rootCtx, document.getElementById('editor'));
497
ctx.set(rootAttrsCtx, { class: 'editor-theme' });
498
499
// Feature configuration
500
ctx.update(inputRulesCtx, (rules) => [...rules, ...customRules]);
501
};
502
503
// Avoid: Scattered configuration
504
const messyConfig: Config = (ctx) => {
505
ctx.set(defaultValueCtx, 'content');
506
ctx.update(inputRulesCtx, (rules) => [...rules, rule1]);
507
ctx.set(rootCtx, document.body);
508
ctx.update(inputRulesCtx, (rules) => [...rules, rule2]); // Redundant
509
};
510
```