0
# Plugin System
1
2
Component enhancement system for adding cross-cutting functionality to all components. Plugins are functions that receive component instances and can modify their behavior, add methods, or enhance functionality.
3
4
## Capabilities
5
6
### Install Plugin
7
8
Installs a plugin that will be applied to all component instances.
9
10
```typescript { .api }
11
/**
12
* Define a riot plugin
13
* @param plugin - Function that receives and enhances component instances
14
* @returns Set containing all installed plugins
15
*/
16
function install(plugin: ComponentEnhancer): InstalledPluginsSet;
17
```
18
19
**Usage Example:**
20
21
```javascript
22
import { install } from "riot";
23
24
// Create a plugin that adds logging to components
25
const loggingPlugin = (component) => {
26
const originalMount = component.mount;
27
const originalUnmount = component.unmount;
28
29
component.mount = function(...args) {
30
console.log(`Mounting component: ${this.name || 'unnamed'}`);
31
return originalMount.apply(this, args);
32
};
33
34
component.unmount = function(...args) {
35
console.log(`Unmounting component: ${this.name || 'unnamed'}`);
36
return originalUnmount.apply(this, args);
37
};
38
39
return component;
40
};
41
42
// Install the plugin
43
install(loggingPlugin);
44
```
45
46
### Uninstall Plugin
47
48
Removes a previously installed plugin from the system.
49
50
```typescript { .api }
51
/**
52
* Uninstall a riot plugin
53
* @param plugin - Plugin function to remove
54
* @returns Set containing remaining installed plugins
55
*/
56
function uninstall(plugin: ComponentEnhancer): InstalledPluginsSet;
57
```
58
59
**Usage Example:**
60
61
```javascript
62
import { uninstall } from "riot";
63
64
// Remove the logging plugin
65
uninstall(loggingPlugin);
66
```
67
68
## Plugin Development
69
70
### Component Enhancer Function
71
72
Plugins are functions that receive a component instance and return the enhanced component:
73
74
```typescript { .api }
75
type ComponentEnhancer = <Props extends DefaultProps, State extends DefaultState>(
76
component: RiotComponent<Props, State>
77
) => RiotComponent<Props, State>;
78
```
79
80
### Plugin Patterns
81
82
**Method Enhancement Plugin:**
83
84
```javascript
85
const methodEnhancementPlugin = (component) => {
86
// Add a new method to all components
87
component.addClass = function(className) {
88
this.root.classList.add(className);
89
return this;
90
};
91
92
component.removeClass = function(className) {
93
this.root.classList.remove(className);
94
return this;
95
};
96
97
return component;
98
};
99
100
install(methodEnhancementPlugin);
101
```
102
103
**State Management Plugin:**
104
105
```javascript
106
const stateHistoryPlugin = (component) => {
107
const stateHistory = [];
108
const originalUpdate = component.update;
109
110
component.update = function(newState, ...args) {
111
// Store previous state
112
stateHistory.push({ ...this.state });
113
114
// Add undo functionality
115
this.undo = () => {
116
if (stateHistory.length > 0) {
117
const previousState = stateHistory.pop();
118
return originalUpdate.call(this, previousState, ...args);
119
}
120
};
121
122
return originalUpdate.call(this, newState, ...args);
123
};
124
125
return component;
126
};
127
128
install(stateHistoryPlugin);
129
```
130
131
**Event Plugin:**
132
133
```javascript
134
const eventEmitterPlugin = (component) => {
135
const listeners = new Map();
136
137
component.on = function(event, callback) {
138
if (!listeners.has(event)) {
139
listeners.set(event, []);
140
}
141
listeners.get(event).push(callback);
142
return this;
143
};
144
145
component.emit = function(event, ...args) {
146
if (listeners.has(event)) {
147
listeners.get(event).forEach(callback => callback(...args));
148
}
149
return this;
150
};
151
152
component.off = function(event, callback) {
153
if (listeners.has(event)) {
154
const eventListeners = listeners.get(event);
155
const index = eventListeners.indexOf(callback);
156
if (index > -1) {
157
eventListeners.splice(index, 1);
158
}
159
}
160
return this;
161
};
162
163
return component;
164
};
165
166
install(eventEmitterPlugin);
167
```
168
169
### Plugin Best Practices
170
171
1. **Always return the component** - Plugins must return the component instance
172
2. **Preserve original methods** - Store references to original methods before overriding
173
3. **Use proper `this` binding** - Use arrow functions or `.call()/.apply()` for method context
174
4. **Handle cleanup** - Add cleanup logic in `onUnmounted` if needed
175
5. **Check for existing functionality** - Test if methods/properties already exist before adding
176
177
**Robust Plugin Example:**
178
179
```javascript
180
const robustPlugin = (component) => {
181
// Only add functionality if it doesn't exist
182
if (!component.customMethod) {
183
component.customMethod = function() {
184
console.log("Custom functionality");
185
return this;
186
};
187
}
188
189
// Enhance existing lifecycle method safely
190
const originalOnUnmounted = component.onUnmounted;
191
component.onUnmounted = function(...args) {
192
// Plugin cleanup logic
193
console.log("Plugin cleanup");
194
195
// Call original if it exists
196
if (originalOnUnmounted) {
197
return originalOnUnmounted.apply(this, args);
198
}
199
};
200
201
return component;
202
};
203
```
204
205
## Error Handling
206
207
Plugins are validated when installed:
208
209
```javascript
210
import { install } from "riot";
211
212
// These will throw errors:
213
install("not a function"); // Error: Plugins must be of type function
214
install(loggingPlugin); // First install succeeds
215
install(loggingPlugin); // Error: This plugin was already installed
216
217
// These will throw errors when uninstalling:
218
uninstall(notInstalledPlugin); // Error: This plugin was never installed
219
```
220
221
## Types
222
223
```typescript { .api }
224
type ComponentEnhancer = <
225
Props extends DefaultProps,
226
State extends DefaultState
227
>(
228
component: RiotComponent<Props, State>
229
) => RiotComponent<Props, State>;
230
231
type InstalledPluginsSet = Set<ComponentEnhancer>;
232
233
type DefaultProps = Record<PropertyKey, any>;
234
type DefaultState = Record<PropertyKey, any>;
235
```