0
# Widget Registry
1
2
The widget registry system provides dynamic loading and registration of widget modules, enabling extensibility and plugin-like architecture for Jupyter widget ecosystems.
3
4
## Capabilities
5
6
### IJupyterWidgetRegistry Interface
7
8
Core interface for widget registries that support external widget module registration.
9
10
```typescript { .api }
11
/**
12
* Interface for Jupyter widget registries
13
*/
14
interface IJupyterWidgetRegistry {
15
/**
16
* Register a widget module with the registry
17
* @param data - Widget module registration data
18
*/
19
registerWidget(data: IWidgetRegistryData): void;
20
}
21
22
/**
23
* Lumino token for dependency injection of widget registries
24
*/
25
const IJupyterWidgetRegistry: Token<IJupyterWidgetRegistry>;
26
```
27
28
**Usage Examples:**
29
30
```typescript
31
// Get registry from dependency injection container
32
const registry = app.serviceManager.get(IJupyterWidgetRegistry);
33
34
// Register a widget module
35
registry.registerWidget({
36
name: '@my-org/custom-widgets',
37
version: '1.2.0',
38
exports: {
39
MySliderModel: MySliderModel,
40
MySliderView: MySliderView,
41
MyButtonModel: MyButtonModel,
42
MyButtonView: MyButtonView
43
}
44
});
45
```
46
47
### Widget Registration Data
48
49
Configuration structure for registering widget modules with their exports.
50
51
```typescript { .api }
52
/**
53
* Data structure for widget module registration
54
*/
55
interface IWidgetRegistryData {
56
/**
57
* Name of the widget module (typically npm package name)
58
*/
59
name: string;
60
61
/**
62
* Version of the widget module
63
*/
64
version: string;
65
66
/**
67
* Widget class exports from the module
68
*/
69
exports: ExportData;
70
}
71
72
/**
73
* Map of export names to widget classes
74
*/
75
type ExportMap = {
76
[key: string]: typeof WidgetModel | typeof WidgetView;
77
};
78
79
/**
80
* Flexible export data supporting synchronous and asynchronous loading
81
*/
82
type ExportData =
83
| ExportMap
84
| Promise<ExportMap>
85
| (() => ExportMap)
86
| (() => Promise<ExportMap>);
87
```
88
89
**Usage Examples:**
90
91
```typescript
92
// Synchronous registration with direct exports
93
const syncRegistration: IWidgetRegistryData = {
94
name: 'my-widgets',
95
version: '1.0.0',
96
exports: {
97
CounterModel: CounterModel,
98
CounterView: CounterView,
99
SliderModel: SliderModel,
100
SliderView: SliderView
101
}
102
};
103
104
// Asynchronous registration with promise
105
const asyncRegistration: IWidgetRegistryData = {
106
name: 'heavy-widgets',
107
version: '2.0.0',
108
exports: import('./heavy-widgets').then(module => ({
109
ChartModel: module.ChartModel,
110
ChartView: module.ChartView,
111
GraphModel: module.GraphModel,
112
GraphView: module.GraphView
113
}))
114
};
115
116
// Lazy loading with function
117
const lazyRegistration: IWidgetRegistryData = {
118
name: 'optional-widgets',
119
version: '1.5.0',
120
exports: () => {
121
// Load only when needed
122
const module = require('./optional-widgets');
123
return {
124
OptionalModel: module.OptionalModel,
125
OptionalView: module.OptionalView
126
};
127
}
128
};
129
130
// Async lazy loading
131
const asyncLazyRegistration: IWidgetRegistryData = {
132
name: 'remote-widgets',
133
version: '3.0.0',
134
exports: async () => {
135
// Dynamic import for code splitting
136
const module = await import('./remote-widgets');
137
return {
138
RemoteModel: module.RemoteModel,
139
RemoteView: module.RemoteView
140
};
141
}
142
};
143
```
144
145
## Registry Implementation Patterns
146
147
### Basic Registry Implementation
148
149
Example implementation of a widget registry:
150
151
```typescript
152
class WidgetRegistry implements IJupyterWidgetRegistry {
153
private _modules = new Map<string, IWidgetRegistryData>();
154
private _resolvedExports = new Map<string, ExportMap>();
155
156
registerWidget(data: IWidgetRegistryData): void {
157
const key = `${data.name}@${data.version}`;
158
this._modules.set(key, data);
159
160
console.log(`Registered widget module: ${key}`);
161
}
162
163
async getExports(name: string, version: string): Promise<ExportMap> {
164
const key = `${name}@${version}`;
165
166
// Check cache first
167
if (this._resolvedExports.has(key)) {
168
return this._resolvedExports.get(key)!;
169
}
170
171
const data = this._modules.get(key);
172
if (!data) {
173
throw new Error(`Widget module not found: ${key}`);
174
}
175
176
// Resolve exports based on type
177
let exports: ExportMap;
178
if (typeof data.exports === 'function') {
179
exports = await data.exports();
180
} else if (data.exports instanceof Promise) {
181
exports = await data.exports;
182
} else {
183
exports = data.exports;
184
}
185
186
// Cache resolved exports
187
this._resolvedExports.set(key, exports);
188
return exports;
189
}
190
191
async getWidgetClass(
192
name: string,
193
version: string,
194
className: string
195
): Promise<typeof WidgetModel | typeof WidgetView> {
196
const exports = await this.getExports(name, version);
197
const WidgetClass = exports[className];
198
199
if (!WidgetClass) {
200
throw new Error(`Widget class not found: ${className} in ${name}@${version}`);
201
}
202
203
return WidgetClass;
204
}
205
206
listRegisteredModules(): string[] {
207
return Array.from(this._modules.keys());
208
}
209
}
210
```
211
212
### Widget Manager Integration
213
214
Integration between registry and widget manager for dynamic loading:
215
216
```typescript
217
class ExtensibleWidgetManager implements IWidgetManager {
218
constructor(private registry: IJupyterWidgetRegistry) {}
219
220
async new_model(options: IModelOptions, state?: JSONObject): Promise<WidgetModel> {
221
try {
222
// Try to get model class from registry
223
const ModelClass = await this.registry.getWidgetClass(
224
options.model_module,
225
options.model_module_version,
226
options.model_name
227
) as typeof WidgetModel;
228
229
// Create model instance
230
const model = new ModelClass(state || {}, {
231
model_id: options.model_id || generateId(),
232
widget_manager: this,
233
comm: options.comm
234
});
235
236
this.register_model(model.model_id, Promise.resolve(model));
237
return model;
238
239
} catch (error) {
240
console.error('Failed to create model:', error);
241
throw error;
242
}
243
}
244
245
async create_view<VT extends WidgetView>(
246
model: WidgetModel,
247
options?: unknown
248
): Promise<VT> {
249
const viewName = model.get('_view_name');
250
const viewModule = model.get('_view_module');
251
const viewModuleVersion = model.get('_view_module_version');
252
253
if (!viewName || !viewModule) {
254
throw new Error('Model missing view information');
255
}
256
257
try {
258
// Get view class from registry
259
const ViewClass = await this.registry.getWidgetClass(
260
viewModule,
261
viewModuleVersion,
262
viewName
263
) as typeof WidgetView;
264
265
// Create view instance
266
const view = new ViewClass({
267
model: model,
268
options: options
269
});
270
271
return view as VT;
272
273
} catch (error) {
274
console.error('Failed to create view:', error);
275
throw error;
276
}
277
}
278
279
// ... other IWidgetManager methods
280
}
281
```
282
283
### Plugin-Style Registration
284
285
Pattern for plugin-style widget registration:
286
287
```typescript
288
// Plugin interface
289
interface IWidgetPlugin {
290
id: string;
291
activate(app: Application, registry: IJupyterWidgetRegistry): void;
292
deactivate?(): void;
293
}
294
295
// Example plugin
296
class ChartWidgetsPlugin implements IWidgetPlugin {
297
id = '@my-org/chart-widgets';
298
299
activate(app: Application, registry: IJupyterWidgetRegistry): void {
300
// Register widget classes
301
registry.registerWidget({
302
name: this.id,
303
version: '1.0.0',
304
exports: {
305
BarChartModel: BarChartModel,
306
BarChartView: BarChartView,
307
LineChartModel: LineChartModel,
308
LineChartView: LineChartView,
309
PieChartModel: PieChartModel,
310
PieChartView: PieChartView
311
}
312
});
313
314
console.log('Chart widgets plugin activated');
315
}
316
317
deactivate(): void {
318
console.log('Chart widgets plugin deactivated');
319
}
320
}
321
322
// Plugin management
323
class PluginManager {
324
private plugins = new Map<string, IWidgetPlugin>();
325
326
registerPlugin(plugin: IWidgetPlugin): void {
327
this.plugins.set(plugin.id, plugin);
328
}
329
330
activatePlugin(
331
pluginId: string,
332
app: Application,
333
registry: IJupyterWidgetRegistry
334
): void {
335
const plugin = this.plugins.get(pluginId);
336
if (plugin) {
337
plugin.activate(app, registry);
338
}
339
}
340
341
deactivatePlugin(pluginId: string): void {
342
const plugin = this.plugins.get(pluginId);
343
if (plugin && plugin.deactivate) {
344
plugin.deactivate();
345
}
346
}
347
}
348
349
// Usage
350
const pluginManager = new PluginManager();
351
const chartPlugin = new ChartWidgetsPlugin();
352
353
pluginManager.registerPlugin(chartPlugin);
354
pluginManager.activatePlugin(chartPlugin.id, app, registry);
355
```
356
357
## Advanced Registry Patterns
358
359
### Version Management
360
361
```typescript
362
class VersionedRegistry implements IJupyterWidgetRegistry {
363
private modules = new Map<string, Map<string, IWidgetRegistryData>>();
364
365
registerWidget(data: IWidgetRegistryData): void {
366
if (!this.modules.has(data.name)) {
367
this.modules.set(data.name, new Map());
368
}
369
370
this.modules.get(data.name)!.set(data.version, data);
371
}
372
373
findCompatibleVersion(name: string, versionRange: string): string | null {
374
const moduleVersions = this.modules.get(name);
375
if (!moduleVersions) return null;
376
377
// Simple version matching (in real implementation, use semver)
378
const availableVersions = Array.from(moduleVersions.keys());
379
380
// Find highest compatible version
381
return availableVersions
382
.filter(version => this.isCompatible(version, versionRange))
383
.sort(this.compareVersions)
384
.pop() || null;
385
}
386
387
private isCompatible(version: string, range: string): boolean {
388
// Simplified version compatibility check
389
// Real implementation would use semver library
390
return version.startsWith(range.replace('~', '').replace('^', ''));
391
}
392
393
private compareVersions(a: string, b: string): number {
394
// Simplified version comparison
395
return a.localeCompare(b, undefined, { numeric: true });
396
}
397
}
398
```
399
400
### Conditional Loading
401
402
```typescript
403
// Registry with conditional widget loading
404
class ConditionalRegistry implements IJupyterWidgetRegistry {
405
registerWidget(data: IWidgetRegistryData): void {
406
// Enhance registration data with conditions
407
const enhancedData = {
408
...data,
409
exports: this.wrapConditionalExports(data.exports)
410
};
411
412
this.doRegister(enhancedData);
413
}
414
415
private wrapConditionalExports(exports: ExportData): ExportData {
416
return async (): Promise<ExportMap> => {
417
// Check environment conditions
418
if (!this.checkEnvironment()) {
419
throw new Error('Widget not supported in current environment');
420
}
421
422
// Check feature detection
423
if (!this.checkFeatures()) {
424
throw new Error('Required features not available');
425
}
426
427
// Resolve actual exports
428
let actualExports: ExportMap;
429
if (typeof exports === 'function') {
430
actualExports = await exports();
431
} else if (exports instanceof Promise) {
432
actualExports = await exports;
433
} else {
434
actualExports = exports;
435
}
436
437
return actualExports;
438
};
439
}
440
441
private checkEnvironment(): boolean {
442
// Check browser capabilities, Jupyter environment, etc.
443
return typeof window !== 'undefined' &&
444
'requestAnimationFrame' in window;
445
}
446
447
private checkFeatures(): boolean {
448
// Check for required features
449
return 'WebGL' in window ||
450
'WebGL2RenderingContext' in window;
451
}
452
}
453
```
454
455
### Registry Events
456
457
```typescript
458
// Registry with event system
459
interface IRegistryEvents {
460
'widget-registered': (data: IWidgetRegistryData) => void;
461
'widget-loaded': (name: string, version: string, exports: ExportMap) => void;
462
'widget-error': (name: string, version: string, error: Error) => void;
463
}
464
465
class EventfulRegistry extends EventTarget implements IJupyterWidgetRegistry {
466
registerWidget(data: IWidgetRegistryData): void {
467
// Perform registration
468
this.doRegister(data);
469
470
// Emit event
471
this.dispatchEvent(new CustomEvent('widget-registered', {
472
detail: data
473
}));
474
}
475
476
async loadWidget(name: string, version: string): Promise<ExportMap> {
477
try {
478
const exports = await this.resolveExports(name, version);
479
480
this.dispatchEvent(new CustomEvent('widget-loaded', {
481
detail: { name, version, exports }
482
}));
483
484
return exports;
485
} catch (error) {
486
this.dispatchEvent(new CustomEvent('widget-error', {
487
detail: { name, version, error }
488
}));
489
throw error;
490
}
491
}
492
493
// Type-safe event listening
494
on<K extends keyof IRegistryEvents>(
495
type: K,
496
listener: IRegistryEvents[K]
497
): void {
498
this.addEventListener(type, listener as EventListener);
499
}
500
}
501
502
// Usage with events
503
const registry = new EventfulRegistry();
504
505
registry.on('widget-registered', (data) => {
506
console.log(`New widget module registered: ${data.name}@${data.version}`);
507
});
508
509
registry.on('widget-loaded', ({ name, version, exports }) => {
510
console.log(`Widget loaded: ${name}@${version}, exports:`, Object.keys(exports));
511
});
512
513
registry.on('widget-error', ({ name, version, error }) => {
514
console.error(`Failed to load widget ${name}@${version}:`, error);
515
});
516
```
517
518
## Registry Integration Examples
519
520
### JupyterLab Extension Pattern
521
522
```typescript
523
// JupyterLab extension using registry
524
const widgetExtension: JupyterFrontEndPlugin<void> = {
525
id: '@my-org/widget-extension',
526
autoStart: true,
527
requires: [IJupyterWidgetRegistry],
528
activate: (app: JupyterFrontEnd, registry: IJupyterWidgetRegistry) => {
529
// Register widgets when extension loads
530
registry.registerWidget({
531
name: '@my-org/widgets',
532
version: '1.0.0',
533
exports: async () => {
534
const module = await import('./widgets');
535
return {
536
MyModel: module.MyModel,
537
MyView: module.MyView
538
};
539
}
540
});
541
}
542
};
543
544
export default widgetExtension;
545
```
546
547
### Runtime Widget Discovery
548
549
```typescript
550
// Discover and register widgets at runtime
551
class RuntimeDiscovery {
552
constructor(private registry: IJupyterWidgetRegistry) {}
553
554
async discoverWidgets(searchPaths: string[]): Promise<void> {
555
for (const path of searchPaths) {
556
try {
557
const packageJson = await this.loadPackageJson(path);
558
559
if (packageJson.keywords?.includes('jupyter-widget')) {
560
await this.registerFromPackage(path, packageJson);
561
}
562
} catch (error) {
563
console.warn(`Failed to discover widgets in ${path}:`, error);
564
}
565
}
566
}
567
568
private async registerFromPackage(path: string, packageJson: any): Promise<void> {
569
this.registry.registerWidget({
570
name: packageJson.name,
571
version: packageJson.version,
572
exports: () => import(path)
573
});
574
}
575
}
576
```