@coherent.js/core 1.0.0-beta.2
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.
- package/LICENSE +21 -0
- package/README.md +130 -0
- package/dist/coherent.d.ts +472 -0
- package/dist/coherent.d.ts.map +1 -0
- package/dist/coherent.js +590 -0
- package/dist/coherent.js.map +1 -0
- package/dist/components/component-system.d.ts +1138 -0
- package/dist/components/component-system.d.ts.map +1 -0
- package/dist/components/component-system.js +2220 -0
- package/dist/components/component-system.js.map +1 -0
- package/dist/components/lifecycle.d.ts +212 -0
- package/dist/components/lifecycle.d.ts.map +1 -0
- package/dist/components/lifecycle.js +525 -0
- package/dist/components/lifecycle.js.map +1 -0
- package/dist/core/html-utils.d.ts +14 -0
- package/dist/core/html-utils.d.ts.map +1 -0
- package/dist/core/html-utils.js +166 -0
- package/dist/core/html-utils.js.map +1 -0
- package/dist/core/object-factory.d.ts +38 -0
- package/dist/core/object-factory.d.ts.map +1 -0
- package/dist/core/object-factory.js +63 -0
- package/dist/core/object-factory.js.map +1 -0
- package/dist/core/object-utils.d.ts +77 -0
- package/dist/core/object-utils.d.ts.map +1 -0
- package/dist/core/object-utils.js +502 -0
- package/dist/core/object-utils.js.map +1 -0
- package/dist/dev/dev-tools.d.ts +194 -0
- package/dist/dev/dev-tools.d.ts.map +1 -0
- package/dist/dev/dev-tools.js +846 -0
- package/dist/dev/dev-tools.js.map +1 -0
- package/dist/forms/validation.d.ts +271 -0
- package/dist/forms/validation.d.ts.map +1 -0
- package/dist/forms/validation.js +573 -0
- package/dist/forms/validation.js.map +1 -0
- package/dist/index.cjs +5281 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.js +5204 -0
- package/dist/index.js.map +7 -0
- package/dist/performance/bundle-optimizer.d.ts +95 -0
- package/dist/performance/bundle-optimizer.d.ts.map +1 -0
- package/dist/performance/bundle-optimizer.js +192 -0
- package/dist/performance/bundle-optimizer.js.map +1 -0
- package/dist/performance/cache-manager.d.ts +107 -0
- package/dist/performance/cache-manager.d.ts.map +1 -0
- package/dist/performance/cache-manager.js +314 -0
- package/dist/performance/cache-manager.js.map +1 -0
- package/dist/performance/component-cache.d.ts +120 -0
- package/dist/performance/component-cache.d.ts.map +1 -0
- package/dist/performance/component-cache.js +364 -0
- package/dist/performance/component-cache.js.map +1 -0
- package/dist/performance/monitor.d.ts +189 -0
- package/dist/performance/monitor.d.ts.map +1 -0
- package/dist/performance/monitor.js +347 -0
- package/dist/performance/monitor.js.map +1 -0
- package/dist/rendering/base-renderer.d.ts +140 -0
- package/dist/rendering/base-renderer.d.ts.map +1 -0
- package/dist/rendering/base-renderer.js +409 -0
- package/dist/rendering/base-renderer.js.map +1 -0
- package/dist/rendering/css-manager.d.ts +73 -0
- package/dist/rendering/css-manager.d.ts.map +1 -0
- package/dist/rendering/css-manager.js +176 -0
- package/dist/rendering/css-manager.js.map +1 -0
- package/dist/rendering/dom-renderer.d.ts +62 -0
- package/dist/rendering/dom-renderer.d.ts.map +1 -0
- package/dist/rendering/dom-renderer.js +252 -0
- package/dist/rendering/dom-renderer.js.map +1 -0
- package/dist/rendering/html-renderer.d.ts +67 -0
- package/dist/rendering/html-renderer.d.ts.map +1 -0
- package/dist/rendering/html-renderer.js +444 -0
- package/dist/rendering/html-renderer.js.map +1 -0
- package/dist/rendering/renderer-config.d.ts +282 -0
- package/dist/rendering/renderer-config.d.ts.map +1 -0
- package/dist/rendering/renderer-config.js +144 -0
- package/dist/rendering/renderer-config.js.map +1 -0
- package/dist/rendering/streaming-renderer.d.ts +117 -0
- package/dist/rendering/streaming-renderer.d.ts.map +1 -0
- package/dist/rendering/streaming-renderer.js +326 -0
- package/dist/rendering/streaming-renderer.js.map +1 -0
- package/dist/rendering/vdom-diff.d.ts +47 -0
- package/dist/rendering/vdom-diff.d.ts.map +1 -0
- package/dist/rendering/vdom-diff.js +416 -0
- package/dist/rendering/vdom-diff.js.map +1 -0
- package/dist/routing/router.d.ts +241 -0
- package/dist/routing/router.d.ts.map +1 -0
- package/dist/routing/router.js +648 -0
- package/dist/routing/router.js.map +1 -0
- package/dist/state/reactive-state.d.ts +166 -0
- package/dist/state/reactive-state.d.ts.map +1 -0
- package/dist/state/reactive-state.js +546 -0
- package/dist/state/reactive-state.js.map +1 -0
- package/dist/state/state-manager.d.ts +45 -0
- package/dist/state/state-manager.d.ts.map +1 -0
- package/dist/state/state-manager.js +151 -0
- package/dist/state/state-manager.js.map +1 -0
- package/dist/types/constants.d.ts +8 -0
- package/dist/types/constants.d.ts.map +1 -0
- package/dist/types/constants.js +36 -0
- package/dist/types/constants.js.map +1 -0
- package/dist/utils/dependency-utils.d.ts +43 -0
- package/dist/utils/dependency-utils.d.ts.map +1 -0
- package/dist/utils/dependency-utils.js +105 -0
- package/dist/utils/dependency-utils.js.map +1 -0
- package/dist/utils/error-handler.d.ts +148 -0
- package/dist/utils/error-handler.d.ts.map +1 -0
- package/dist/utils/error-handler.js +468 -0
- package/dist/utils/error-handler.js.map +1 -0
- package/dist/utils/normalization.d.ts +3 -0
- package/dist/utils/normalization.d.ts.map +1 -0
- package/dist/utils/normalization.js +24 -0
- package/dist/utils/normalization.js.map +1 -0
- package/dist/utils/validation.d.ts +10 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +32 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +44 -0
- package/types/index.d.ts +734 -0
|
@@ -0,0 +1,846 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Development Tools for Coherent.js
|
|
3
|
+
* Provides debugging, profiling, and development utilities
|
|
4
|
+
* Only active in development environment for zero production overhead
|
|
5
|
+
*/
|
|
6
|
+
import { performanceMonitor } from '../performance/monitor.js';
|
|
7
|
+
import { validateComponent, isCoherentObject } from '../core/object-utils.js';
|
|
8
|
+
/**
|
|
9
|
+
* Main DevTools class
|
|
10
|
+
*/
|
|
11
|
+
export class DevTools {
|
|
12
|
+
constructor(coherentInstance) {
|
|
13
|
+
this.coherent = coherentInstance;
|
|
14
|
+
this.isEnabled = this.shouldEnable();
|
|
15
|
+
this.renderHistory = [];
|
|
16
|
+
this.componentRegistry = new Map();
|
|
17
|
+
this.warnings = [];
|
|
18
|
+
this.errors = [];
|
|
19
|
+
this.hotReloadEnabled = false;
|
|
20
|
+
if (this.isEnabled) {
|
|
21
|
+
this.initialize();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Check if dev tools should be enabled
|
|
26
|
+
*/
|
|
27
|
+
shouldEnable() {
|
|
28
|
+
// Only enable in development
|
|
29
|
+
if (typeof process !== 'undefined') {
|
|
30
|
+
return process.env.NODE_ENV === 'development';
|
|
31
|
+
}
|
|
32
|
+
// Browser development detection
|
|
33
|
+
if (typeof window !== 'undefined') {
|
|
34
|
+
return window.location.hostname === 'localhost' ||
|
|
35
|
+
window.location.hostname === '127.0.0.1' ||
|
|
36
|
+
window.location.search.includes('dev=true');
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Initialize dev tools
|
|
42
|
+
*/
|
|
43
|
+
initialize() {
|
|
44
|
+
console.log('🛠️ Coherent.js Dev Tools Enabled');
|
|
45
|
+
this.setupGlobalHelpers();
|
|
46
|
+
this.setupRenderInterception();
|
|
47
|
+
this.setupErrorHandling();
|
|
48
|
+
this.setupHotReload();
|
|
49
|
+
this.setupComponentInspector();
|
|
50
|
+
// Browser-specific setup
|
|
51
|
+
if (typeof window !== 'undefined') {
|
|
52
|
+
this.setupBrowserDevTools();
|
|
53
|
+
}
|
|
54
|
+
// Node.js specific setup
|
|
55
|
+
if (typeof process !== 'undefined') {
|
|
56
|
+
this.setupNodeDevTools();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Set up global helper functions
|
|
61
|
+
*/
|
|
62
|
+
setupGlobalHelpers() {
|
|
63
|
+
const helpers = {
|
|
64
|
+
// Inspect any component
|
|
65
|
+
$inspect: (component) => this.inspectComponent(component),
|
|
66
|
+
// Get render history
|
|
67
|
+
$history: () => this.renderHistory,
|
|
68
|
+
// Get performance stats
|
|
69
|
+
$perf: () => this.getPerformanceInsights(),
|
|
70
|
+
// Validate component structure
|
|
71
|
+
$validate: (component) => this.validateComponent(component),
|
|
72
|
+
// Get component registry
|
|
73
|
+
$registry: () => Array.from(this.componentRegistry.entries()),
|
|
74
|
+
// Clear dev data
|
|
75
|
+
$clear: () => this.clearDevData(),
|
|
76
|
+
// Enable/disable features
|
|
77
|
+
$toggle: (feature) => this.toggleFeature(feature),
|
|
78
|
+
// Get warnings and errors
|
|
79
|
+
$issues: () => ({ warnings: this.warnings, errors: this.errors })
|
|
80
|
+
};
|
|
81
|
+
// Expose helpers globally
|
|
82
|
+
if (typeof window !== 'undefined') {
|
|
83
|
+
Object.assign(window, helpers);
|
|
84
|
+
}
|
|
85
|
+
else if (typeof global !== 'undefined') {
|
|
86
|
+
Object.assign(global, helpers);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Intercept render calls for debugging
|
|
91
|
+
*/
|
|
92
|
+
setupRenderInterception() {
|
|
93
|
+
const originalRender = this.coherent.render;
|
|
94
|
+
this.coherent.render = (component, context = {}, options = {}) => {
|
|
95
|
+
const renderStart = performance.now();
|
|
96
|
+
const renderId = this.generateRenderId();
|
|
97
|
+
try {
|
|
98
|
+
// Pre-render validation and logging
|
|
99
|
+
this.preRenderAnalysis(component, context, renderId);
|
|
100
|
+
// Actual render
|
|
101
|
+
const result = originalRender.call(this.coherent, component, context, {
|
|
102
|
+
...options,
|
|
103
|
+
_devRenderId: renderId
|
|
104
|
+
});
|
|
105
|
+
// Post-render analysis
|
|
106
|
+
const renderTime = performance.now() - renderStart;
|
|
107
|
+
this.postRenderAnalysis(component, result, renderTime, renderId);
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
this.handleRenderError(error, component, context, renderId);
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Pre-render analysis and validation
|
|
118
|
+
*/
|
|
119
|
+
preRenderAnalysis(component, context, renderId) {
|
|
120
|
+
// Component structure validation
|
|
121
|
+
const validation = this.deepValidateComponent(component);
|
|
122
|
+
if (!validation.isValid) {
|
|
123
|
+
this.warnings.push({
|
|
124
|
+
type: 'validation',
|
|
125
|
+
message: validation.message,
|
|
126
|
+
component: this.serializeComponent(component),
|
|
127
|
+
renderId,
|
|
128
|
+
timestamp: Date.now()
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
// Performance warnings
|
|
132
|
+
const complexity = this.analyzeComplexity(component);
|
|
133
|
+
if (complexity > 1000) {
|
|
134
|
+
this.warnings.push({
|
|
135
|
+
type: 'performance',
|
|
136
|
+
message: `High complexity component detected (${complexity} nodes)`,
|
|
137
|
+
renderId,
|
|
138
|
+
timestamp: Date.now()
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
// Context analysis
|
|
142
|
+
this.analyzeContext(context, renderId);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Post-render analysis
|
|
146
|
+
*/
|
|
147
|
+
postRenderAnalysis(component, result, renderTime, renderId) {
|
|
148
|
+
// Store render in history
|
|
149
|
+
const renderRecord = {
|
|
150
|
+
id: renderId,
|
|
151
|
+
timestamp: Date.now(),
|
|
152
|
+
component: this.serializeComponent(component),
|
|
153
|
+
renderTime,
|
|
154
|
+
outputSize: result.length,
|
|
155
|
+
complexity: this.analyzeComplexity(component)
|
|
156
|
+
};
|
|
157
|
+
this.renderHistory.push(renderRecord);
|
|
158
|
+
// Keep only last 50 renders
|
|
159
|
+
if (this.renderHistory.length > 50) {
|
|
160
|
+
this.renderHistory.shift();
|
|
161
|
+
}
|
|
162
|
+
// Performance analysis
|
|
163
|
+
if (renderTime > 10) {
|
|
164
|
+
this.warnings.push({
|
|
165
|
+
type: 'performance',
|
|
166
|
+
message: `Slow render detected: ${renderTime.toFixed(2)}ms`,
|
|
167
|
+
renderId,
|
|
168
|
+
timestamp: Date.now()
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
// Log render in development
|
|
172
|
+
if (renderTime > 1) {
|
|
173
|
+
console.log(`🔄 Render ${renderId}: ${renderTime.toFixed(2)}ms`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Deep component validation
|
|
178
|
+
*/
|
|
179
|
+
deepValidateComponent(component, path = 'root', depth = 0) {
|
|
180
|
+
if (depth > 100) {
|
|
181
|
+
return {
|
|
182
|
+
isValid: false,
|
|
183
|
+
message: `Component nesting too deep at ${path}`
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
// Basic validation
|
|
187
|
+
try {
|
|
188
|
+
validateComponent(component);
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
return {
|
|
192
|
+
isValid: false,
|
|
193
|
+
message: `Invalid component at ${path}: ${error.message}`
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
// Recursive validation for objects and arrays
|
|
197
|
+
if (Array.isArray(component)) {
|
|
198
|
+
for (let i = 0; i < component.length; i++) {
|
|
199
|
+
const childValidation = this.deepValidateComponent(component[i], `${path}[${i}]`, depth + 1);
|
|
200
|
+
if (!childValidation.isValid) {
|
|
201
|
+
return childValidation;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
else if (isCoherentObject(component)) {
|
|
206
|
+
for (const [tag, props] of Object.entries(component)) {
|
|
207
|
+
if (props && typeof props === 'object' && props.children) {
|
|
208
|
+
const childValidation = this.deepValidateComponent(props.children, `${path}.${tag}.children`, depth + 1);
|
|
209
|
+
if (!childValidation.isValid) {
|
|
210
|
+
return childValidation;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return { isValid: true };
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Analyze component complexity
|
|
219
|
+
*/
|
|
220
|
+
analyzeComplexity(component, depth = 0) {
|
|
221
|
+
if (depth > 100)
|
|
222
|
+
return 1000; // Prevent infinite recursion
|
|
223
|
+
if (typeof component === 'string' || typeof component === 'number') {
|
|
224
|
+
return 1;
|
|
225
|
+
}
|
|
226
|
+
if (Array.isArray(component)) {
|
|
227
|
+
return component.reduce((sum, child) => sum + this.analyzeComplexity(child, depth + 1), 0);
|
|
228
|
+
}
|
|
229
|
+
if (isCoherentObject(component)) {
|
|
230
|
+
let complexity = 1;
|
|
231
|
+
for (const [, props] of Object.entries(component)) {
|
|
232
|
+
if (props && typeof props === 'object') {
|
|
233
|
+
if (props.children) {
|
|
234
|
+
complexity += this.analyzeComplexity(props.children, depth + 1);
|
|
235
|
+
}
|
|
236
|
+
if (typeof props.text === 'function') {
|
|
237
|
+
complexity += 2; // Functions add complexity
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return complexity;
|
|
242
|
+
}
|
|
243
|
+
return 1;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Context analysis
|
|
247
|
+
*/
|
|
248
|
+
analyzeContext(context, renderId) {
|
|
249
|
+
// Large context warning
|
|
250
|
+
const contextSize = JSON.stringify(_context).length;
|
|
251
|
+
if (contextSize > 10000) {
|
|
252
|
+
this.warnings.push({
|
|
253
|
+
type: 'context',
|
|
254
|
+
message: `Large context object: ${contextSize} characters`,
|
|
255
|
+
renderId,
|
|
256
|
+
timestamp: Date.now()
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
// Circular reference check
|
|
260
|
+
try {
|
|
261
|
+
JSON.stringify(_context);
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
if (error.message.includes('circular')) {
|
|
265
|
+
this.warnings.push({
|
|
266
|
+
type: 'context',
|
|
267
|
+
message: 'Circular reference detected in context',
|
|
268
|
+
renderId,
|
|
269
|
+
timestamp: Date.now()
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Component inspector
|
|
276
|
+
*/
|
|
277
|
+
inspectComponent(component) {
|
|
278
|
+
return {
|
|
279
|
+
type: this.getComponentType(component),
|
|
280
|
+
complexity: this.analyzeComplexity(component),
|
|
281
|
+
validation: this.deepValidateComponent(component),
|
|
282
|
+
structure: this.visualizeStructure(component),
|
|
283
|
+
serialized: this.serializeComponent(component),
|
|
284
|
+
recommendations: this.getOptimizationRecommendations(component)
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Get component type
|
|
289
|
+
*/
|
|
290
|
+
getComponentType(component) {
|
|
291
|
+
if (typeof component === 'string')
|
|
292
|
+
return 'text';
|
|
293
|
+
if (typeof component === 'function')
|
|
294
|
+
return 'function';
|
|
295
|
+
if (Array.isArray(component))
|
|
296
|
+
return 'array';
|
|
297
|
+
if (isCoherentObject(component))
|
|
298
|
+
return 'element';
|
|
299
|
+
return 'unknown';
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Visualize component structure
|
|
303
|
+
*/
|
|
304
|
+
visualizeStructure(component, depth = 0, maxDepth = 5) {
|
|
305
|
+
if (depth > maxDepth)
|
|
306
|
+
return '...';
|
|
307
|
+
const indent = ' '.repeat(depth);
|
|
308
|
+
if (typeof component === 'string') {
|
|
309
|
+
return `${indent}"${component.substring(0, 20)}${component.length > 20 ? '...' : ''}"`;
|
|
310
|
+
}
|
|
311
|
+
if (Array.isArray(component)) {
|
|
312
|
+
const items = component.slice(0, 3).map(child => this.visualizeStructure(child, depth + 1, maxDepth));
|
|
313
|
+
const more = component.length > 3 ? `${indent} ...${component.length - 3} more` : '';
|
|
314
|
+
return `${indent}[\n${items.join('\n')}${more ? `\n${more}` : ''}\n${indent}]`;
|
|
315
|
+
}
|
|
316
|
+
if (isCoherentObject(component)) {
|
|
317
|
+
const entries = Object.entries(component).slice(0, 3);
|
|
318
|
+
const elements = entries.map(([tag, props]) => {
|
|
319
|
+
let result = `${indent}<${tag}`;
|
|
320
|
+
if (props && props.children) {
|
|
321
|
+
result += `>\n${this.visualizeStructure(props.children, depth + 1, maxDepth)}\n${indent}</${tag}>`;
|
|
322
|
+
}
|
|
323
|
+
else if (props && props.text) {
|
|
324
|
+
result += `>${props.text}</${tag}>`;
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
result += ' />';
|
|
328
|
+
}
|
|
329
|
+
return result;
|
|
330
|
+
});
|
|
331
|
+
return elements.join('\n');
|
|
332
|
+
}
|
|
333
|
+
return `${indent}${typeof component}`;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Get optimization recommendations
|
|
337
|
+
*/
|
|
338
|
+
getOptimizationRecommendations(component) {
|
|
339
|
+
const recommendations = [];
|
|
340
|
+
const complexity = this.analyzeComplexity(component);
|
|
341
|
+
if (complexity > 500) {
|
|
342
|
+
recommendations.push({
|
|
343
|
+
type: 'complexity',
|
|
344
|
+
message: 'Consider breaking down this component into smaller parts',
|
|
345
|
+
priority: 'high'
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
// Check for repeated patterns
|
|
349
|
+
const serialized = JSON.stringify(component);
|
|
350
|
+
const patterns = this.findRepeatedPatterns(serialized);
|
|
351
|
+
if (patterns.length > 0) {
|
|
352
|
+
recommendations.push({
|
|
353
|
+
type: 'caching',
|
|
354
|
+
message: 'Consider extracting repeated patterns into cached components',
|
|
355
|
+
priority: 'medium',
|
|
356
|
+
patterns: patterns.slice(0, 3)
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
return recommendations;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Find repeated patterns in component
|
|
363
|
+
*/
|
|
364
|
+
findRepeatedPatterns(serialized) {
|
|
365
|
+
const patterns = [];
|
|
366
|
+
const minPatternLength = 20;
|
|
367
|
+
for (let i = 0; i < serialized.length - minPatternLength; i++) {
|
|
368
|
+
for (let len = minPatternLength; len <= 100 && i + len < serialized.length; len++) {
|
|
369
|
+
const pattern = serialized.substring(i, i + len);
|
|
370
|
+
const occurrences = (serialized.match(new RegExp(this.escapeRegex(pattern), 'g')) || []).length;
|
|
371
|
+
if (occurrences > 2) {
|
|
372
|
+
patterns.push({ pattern: `${pattern.substring(0, 50)}...`, occurrences });
|
|
373
|
+
break; // Found a pattern starting at this position
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return patterns.sort((a, b) => b.occurrences - a.occurrences);
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Escape regex special characters
|
|
381
|
+
*/
|
|
382
|
+
escapeRegex(string) {
|
|
383
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Setup error handling
|
|
387
|
+
*/
|
|
388
|
+
setupErrorHandling() {
|
|
389
|
+
// Global error handler
|
|
390
|
+
const originalConsoleError = console.error;
|
|
391
|
+
console.error = (...args) => {
|
|
392
|
+
// Log to dev tools
|
|
393
|
+
this.errors.push({
|
|
394
|
+
type: 'console',
|
|
395
|
+
message: args.join(' '),
|
|
396
|
+
timestamp: Date.now(),
|
|
397
|
+
stack: new Error().stack
|
|
398
|
+
});
|
|
399
|
+
// Call original
|
|
400
|
+
originalConsoleError.apply(console, args);
|
|
401
|
+
};
|
|
402
|
+
// Unhandled rejection handler (Node.js)
|
|
403
|
+
if (typeof process !== 'undefined') {
|
|
404
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
405
|
+
this.errors.push({
|
|
406
|
+
type: 'unhandled-rejection',
|
|
407
|
+
message: reason.toString(),
|
|
408
|
+
promise: promise.toString(),
|
|
409
|
+
timestamp: Date.now()
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
// Browser error handler
|
|
414
|
+
if (typeof window !== 'undefined') {
|
|
415
|
+
window.addEventListener('error', (event) => {
|
|
416
|
+
this.errors.push({
|
|
417
|
+
type: 'browser-error',
|
|
418
|
+
message: event.message,
|
|
419
|
+
filename: event.filename,
|
|
420
|
+
lineno: event.lineno,
|
|
421
|
+
colno: event.colno,
|
|
422
|
+
timestamp: Date.now()
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Handle render errors specifically
|
|
429
|
+
*/
|
|
430
|
+
handleRenderError(error, component, context, renderId) {
|
|
431
|
+
this.errors.push({
|
|
432
|
+
type: 'render-error',
|
|
433
|
+
message: error.message,
|
|
434
|
+
stack: error.stack,
|
|
435
|
+
component: this.serializeComponent(component),
|
|
436
|
+
context: Object.keys(_context),
|
|
437
|
+
renderId,
|
|
438
|
+
timestamp: Date.now()
|
|
439
|
+
});
|
|
440
|
+
console.error(`🚨 Render Error in ${renderId}:`, error.message);
|
|
441
|
+
console.error('Component:', this.serializeComponent(component));
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Setup hot reload capability
|
|
445
|
+
*/
|
|
446
|
+
setupHotReload() {
|
|
447
|
+
if (typeof window !== 'undefined' && 'WebSocket' in window) {
|
|
448
|
+
// Browser hot reload
|
|
449
|
+
this.setupBrowserHotReload();
|
|
450
|
+
}
|
|
451
|
+
else if (typeof require !== 'undefined') {
|
|
452
|
+
// Node.js file watching
|
|
453
|
+
this.setupNodeHotReload();
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Browser hot reload setup
|
|
458
|
+
*/
|
|
459
|
+
setupBrowserHotReload() {
|
|
460
|
+
// Connect to development server WebSocket
|
|
461
|
+
try {
|
|
462
|
+
const ws = new WebSocket('ws://localhost:3001/coherent-dev');
|
|
463
|
+
ws.onmessage = (event) => {
|
|
464
|
+
const data = JSON.parse(event.data);
|
|
465
|
+
if (data.type === 'component-updated') {
|
|
466
|
+
console.log('🔄 Component updated:', data.componentName);
|
|
467
|
+
this.handleComponentUpdate(data);
|
|
468
|
+
}
|
|
469
|
+
else if (data.type === 'full-reload') {
|
|
470
|
+
window.location.reload();
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
ws.onopen = () => {
|
|
474
|
+
console.log('🔗 Connected to Coherent dev server');
|
|
475
|
+
this.hotReloadEnabled = true;
|
|
476
|
+
};
|
|
477
|
+
ws.onclose = () => {
|
|
478
|
+
console.log('🔌 Disconnected from dev server');
|
|
479
|
+
this.hotReloadEnabled = false;
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
catch {
|
|
483
|
+
// Dev server not available
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Node.js hot reload setup
|
|
488
|
+
*/
|
|
489
|
+
setupNodeHotReload() {
|
|
490
|
+
// File system watching for component changes
|
|
491
|
+
try {
|
|
492
|
+
const fs = require('fs');
|
|
493
|
+
const path = require('path');
|
|
494
|
+
const watchDir = path.join(process.cwd(), 'src');
|
|
495
|
+
fs.watch(watchDir, { recursive: true }, (eventType, filename) => {
|
|
496
|
+
if (filename && filename.endsWith('.js')) {
|
|
497
|
+
console.log(`🔄 File changed: ${filename}`);
|
|
498
|
+
this.handleFileChange(filename, eventType);
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
this.hotReloadEnabled = true;
|
|
502
|
+
}
|
|
503
|
+
catch {
|
|
504
|
+
// File watching not available
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Handle component updates
|
|
509
|
+
*/
|
|
510
|
+
handleComponentUpdate(updateData) {
|
|
511
|
+
// Clear related caches
|
|
512
|
+
if (this.coherent.cache) {
|
|
513
|
+
this.coherent.cache.invalidatePattern(updateData.componentName);
|
|
514
|
+
}
|
|
515
|
+
// Update component registry
|
|
516
|
+
this.componentRegistry.set(updateData.componentName, {
|
|
517
|
+
...updateData,
|
|
518
|
+
lastUpdated: Date.now()
|
|
519
|
+
});
|
|
520
|
+
// Trigger re-render if needed
|
|
521
|
+
if (typeof window !== 'undefined' && window.location.search.includes('auto-reload=true')) {
|
|
522
|
+
window.location.reload();
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Handle file changes
|
|
527
|
+
*/
|
|
528
|
+
handleFileChange(filename, eventType) {
|
|
529
|
+
// Clear require cache for the changed file
|
|
530
|
+
if (typeof require !== 'undefined' && require.cache) {
|
|
531
|
+
const fullPath = require.resolve(path.resolve(filename));
|
|
532
|
+
delete require.cache[fullPath];
|
|
533
|
+
}
|
|
534
|
+
console.log(`📝 ${eventType}: ${filename}`);
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Setup browser-specific dev tools
|
|
538
|
+
*/
|
|
539
|
+
setupBrowserDevTools() {
|
|
540
|
+
// Add dev tools panel to page
|
|
541
|
+
this.createDevPanel();
|
|
542
|
+
// Add keyboard shortcuts
|
|
543
|
+
document.addEventListener('keydown', (e) => {
|
|
544
|
+
// Ctrl+Shift+C = Toggle dev panel
|
|
545
|
+
if (e.ctrlKey && e.shiftKey && e.code === 'KeyC') {
|
|
546
|
+
this.toggleDevPanel();
|
|
547
|
+
e.preventDefault();
|
|
548
|
+
}
|
|
549
|
+
// Ctrl+Shift+P = Performance report
|
|
550
|
+
if (e.ctrlKey && e.shiftKey && e.code === 'KeyP') {
|
|
551
|
+
console.table(this.getPerformanceInsights());
|
|
552
|
+
e.preventDefault();
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Create development panel in browser
|
|
558
|
+
*/
|
|
559
|
+
createDevPanel() {
|
|
560
|
+
// Create floating dev panel
|
|
561
|
+
const panel = document.createElement('div');
|
|
562
|
+
panel.id = 'coherent-dev-panel';
|
|
563
|
+
panel.style.cssText = `
|
|
564
|
+
position: fixed;
|
|
565
|
+
top: 10px;
|
|
566
|
+
right: 10px;
|
|
567
|
+
width: 300px;
|
|
568
|
+
background: #1a1a1a;
|
|
569
|
+
color: #fff;
|
|
570
|
+
font-family: monospace;
|
|
571
|
+
font-size: 12px;
|
|
572
|
+
border-radius: 8px;
|
|
573
|
+
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
|
|
574
|
+
z-index: 999999;
|
|
575
|
+
display: none;
|
|
576
|
+
max-height: 80vh;
|
|
577
|
+
overflow-y: auto;
|
|
578
|
+
`;
|
|
579
|
+
document.body.appendChild(panel);
|
|
580
|
+
this.devPanel = panel;
|
|
581
|
+
this.updateDevPanel();
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Toggle dev panel visibility
|
|
585
|
+
*/
|
|
586
|
+
toggleDevPanel() {
|
|
587
|
+
if (this.devPanel) {
|
|
588
|
+
const isVisible = this.devPanel.style.display === 'block';
|
|
589
|
+
this.devPanel.style.display = isVisible ? 'none' : 'block';
|
|
590
|
+
if (!isVisible) {
|
|
591
|
+
this.updateDevPanel();
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Update dev panel content
|
|
597
|
+
*/
|
|
598
|
+
updateDevPanel() {
|
|
599
|
+
if (!this.devPanel)
|
|
600
|
+
return;
|
|
601
|
+
const stats = this.getPerformanceInsights();
|
|
602
|
+
const recentRenders = this.renderHistory.slice(-5);
|
|
603
|
+
const recentWarnings = this.warnings.slice(-3);
|
|
604
|
+
this.devPanel.innerHTML = `
|
|
605
|
+
<div style="padding: 15px; border-bottom: 1px solid #333;">
|
|
606
|
+
<strong>🛠️ Coherent.js Dev Tools</strong>
|
|
607
|
+
<button onclick="this.parentElement.parentElement.style.display='none'"
|
|
608
|
+
style="float: right; background: none; border: none; color: #fff; cursor: pointer;">×</button>
|
|
609
|
+
</div>
|
|
610
|
+
|
|
611
|
+
<div style="padding: 10px;">
|
|
612
|
+
<h4 style="margin: 0 0 10px 0; color: #4CAF50;">Performance</h4>
|
|
613
|
+
<div style="font-size: 11px;">
|
|
614
|
+
<div>Avg Render: ${stats.averageRenderTime || 0}ms</div>
|
|
615
|
+
<div>Cache Hit Rate: ${((stats.cacheHits || 0) / Math.max(stats.totalRenders || 1, 1) * 100).toFixed(1)}%</div>
|
|
616
|
+
<div>Memory Usage: ${(performance.memory?.usedJSHeapSize / 1024 / 1024 || 0).toFixed(1)}MB</div>
|
|
617
|
+
</div>
|
|
618
|
+
</div>
|
|
619
|
+
|
|
620
|
+
<div style="padding: 10px;">
|
|
621
|
+
<h4 style="margin: 0 0 10px 0; color: #2196F3;">Recent Renders</h4>
|
|
622
|
+
${recentRenders.map(r => `
|
|
623
|
+
<div style="font-size: 10px; margin-bottom: 5px; padding: 3px; background: #333; border-radius: 3px;">
|
|
624
|
+
${r.id}: ${r.renderTime.toFixed(1)}ms (${r.complexity} nodes)
|
|
625
|
+
</div>
|
|
626
|
+
`).join('')}
|
|
627
|
+
</div>
|
|
628
|
+
|
|
629
|
+
${recentWarnings.length > 0 ? `
|
|
630
|
+
<div style="padding: 10px;">
|
|
631
|
+
<h4 style="margin: 0 0 10px 0; color: #FF9800;">Warnings</h4>
|
|
632
|
+
${recentWarnings.map(w => `
|
|
633
|
+
<div style="font-size: 10px; margin-bottom: 5px; padding: 3px; background: #4a2c0a; border-radius: 3px; color: #FFB74D;">
|
|
634
|
+
${w.type}: ${w.message}
|
|
635
|
+
</div>
|
|
636
|
+
`).join('')}
|
|
637
|
+
</div>
|
|
638
|
+
` : ''}
|
|
639
|
+
|
|
640
|
+
<div style="padding: 10px; font-size: 10px; color: #888;">
|
|
641
|
+
Press Ctrl+Shift+P for performance details
|
|
642
|
+
</div>
|
|
643
|
+
`;
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Setup Node.js specific dev tools
|
|
647
|
+
*/
|
|
648
|
+
setupNodeDevTools() {
|
|
649
|
+
// Add process event listeners
|
|
650
|
+
process.on('SIGINT', () => {
|
|
651
|
+
this.printDevSummary();
|
|
652
|
+
process.exit();
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Print development summary
|
|
657
|
+
*/
|
|
658
|
+
printDevSummary() {
|
|
659
|
+
console.log('\n🛠️ Coherent.js Development Summary');
|
|
660
|
+
console.log('=================================');
|
|
661
|
+
console.log(`Total Renders: ${this.renderHistory.length}`);
|
|
662
|
+
console.log(`Total Warnings: ${this.warnings.length}`);
|
|
663
|
+
console.log(`Total Errors: ${this.errors.length}`);
|
|
664
|
+
if (this.renderHistory.length > 0) {
|
|
665
|
+
const avgTime = this.renderHistory.reduce((sum, r) => sum + r.renderTime, 0) / this.renderHistory.length;
|
|
666
|
+
console.log(`Average Render Time: ${avgTime.toFixed(2)}ms`);
|
|
667
|
+
}
|
|
668
|
+
console.log('=================================\n');
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Get performance insights
|
|
672
|
+
*/
|
|
673
|
+
getPerformanceInsights() {
|
|
674
|
+
const insights = {
|
|
675
|
+
totalRenders: this.renderHistory.length,
|
|
676
|
+
averageRenderTime: 0,
|
|
677
|
+
slowestRender: null,
|
|
678
|
+
fastestRender: null,
|
|
679
|
+
cacheHits: 0,
|
|
680
|
+
totalWarnings: this.warnings.length,
|
|
681
|
+
totalErrors: this.errors.length
|
|
682
|
+
};
|
|
683
|
+
if (this.renderHistory.length > 0) {
|
|
684
|
+
const times = this.renderHistory.map(r => r.renderTime);
|
|
685
|
+
insights.averageRenderTime = times.reduce((a, b) => a + b, 0) / times.length;
|
|
686
|
+
insights.slowestRender = Math.max(...times);
|
|
687
|
+
insights.fastestRender = Math.min(...times);
|
|
688
|
+
}
|
|
689
|
+
// Get cache stats if available
|
|
690
|
+
if (this.coherent.cache && this.coherent.cache.getStats) {
|
|
691
|
+
const cacheStats = this.coherent.cache.getStats();
|
|
692
|
+
insights.cacheHits = cacheStats.hits;
|
|
693
|
+
insights.cacheHitRate = cacheStats.hitRate;
|
|
694
|
+
}
|
|
695
|
+
// Add performance monitor data if available
|
|
696
|
+
if (performanceMonitor && performanceMonitor.getStats) {
|
|
697
|
+
const perfStats = performanceMonitor.getStats();
|
|
698
|
+
insights.performanceMonitorStats = perfStats;
|
|
699
|
+
}
|
|
700
|
+
return insights;
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Utility methods
|
|
704
|
+
*/
|
|
705
|
+
generateRenderId() {
|
|
706
|
+
return `render_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`;
|
|
707
|
+
}
|
|
708
|
+
serializeComponent(component, maxDepth = 3, currentDepth = 0) {
|
|
709
|
+
if (currentDepth > maxDepth)
|
|
710
|
+
return '...';
|
|
711
|
+
try {
|
|
712
|
+
if (typeof component === 'function') {
|
|
713
|
+
return `[Function: ${component.name || 'anonymous'}]`;
|
|
714
|
+
}
|
|
715
|
+
if (Array.isArray(component)) {
|
|
716
|
+
return component.slice(0, 3).map(c => this.serializeComponent(c, maxDepth, currentDepth + 1)).concat(component.length > 3 ? [`...(${component.length - 3} more)`] : []);
|
|
717
|
+
}
|
|
718
|
+
if (component && typeof component === 'object') {
|
|
719
|
+
const serialized = {};
|
|
720
|
+
const keys = Object.keys(component).slice(0, 10);
|
|
721
|
+
for (const key of keys) {
|
|
722
|
+
if (key === 'children' && component[key]) {
|
|
723
|
+
serialized[key] = this.serializeComponent(component[key], maxDepth, currentDepth + 1);
|
|
724
|
+
}
|
|
725
|
+
else if (typeof component[key] === 'function') {
|
|
726
|
+
serialized[key] = `[Function: ${component[key].name || 'anonymous'}]`;
|
|
727
|
+
}
|
|
728
|
+
else {
|
|
729
|
+
serialized[key] = component[key];
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
if (Object.keys(component).length > 10) {
|
|
733
|
+
serialized['...'] = `(${Object.keys(component).length - 10} more properties)`;
|
|
734
|
+
}
|
|
735
|
+
return serialized;
|
|
736
|
+
}
|
|
737
|
+
return component;
|
|
738
|
+
}
|
|
739
|
+
catch (error) {
|
|
740
|
+
return `[Serialization Error: ${error.message}]`;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
clearDevData() {
|
|
744
|
+
this.renderHistory = [];
|
|
745
|
+
this.warnings = [];
|
|
746
|
+
this.errors = [];
|
|
747
|
+
this.componentRegistry.clear();
|
|
748
|
+
console.log('🧹 Dev data cleared');
|
|
749
|
+
}
|
|
750
|
+
toggleFeature(feature) {
|
|
751
|
+
switch (feature) {
|
|
752
|
+
case 'cache':
|
|
753
|
+
if (this.coherent.cache) {
|
|
754
|
+
this.coherent.cache.enabled = !this.coherent.cache.enabled;
|
|
755
|
+
console.log(`Cache ${this.coherent.cache.enabled ? 'enabled' : 'disabled'}`);
|
|
756
|
+
}
|
|
757
|
+
break;
|
|
758
|
+
case 'monitoring':
|
|
759
|
+
if (performanceMonitor) {
|
|
760
|
+
performanceMonitor.enabled = !performanceMonitor.enabled;
|
|
761
|
+
console.log(`Monitoring ${performanceMonitor.enabled ? 'enabled' : 'disabled'}`);
|
|
762
|
+
}
|
|
763
|
+
break;
|
|
764
|
+
case 'hot-reload':
|
|
765
|
+
this.hotReloadEnabled = !this.hotReloadEnabled;
|
|
766
|
+
console.log(`Hot reload ${this.hotReloadEnabled ? 'enabled' : 'disabled'}`);
|
|
767
|
+
break;
|
|
768
|
+
default:
|
|
769
|
+
console.log(`Unknown feature: ${feature}`);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
validateComponent(component) {
|
|
773
|
+
return this.deepValidateComponent(component);
|
|
774
|
+
}
|
|
775
|
+
setupComponentInspector() {
|
|
776
|
+
// Register components for inspection
|
|
777
|
+
const originalCreateComponent = this.coherent.createComponent;
|
|
778
|
+
if (originalCreateComponent) {
|
|
779
|
+
this.coherent.createComponent = (config) => {
|
|
780
|
+
const component = originalCreateComponent.call(this.coherent, config);
|
|
781
|
+
// Register component
|
|
782
|
+
this.componentRegistry.set(config.name || 'anonymous', {
|
|
783
|
+
config,
|
|
784
|
+
component,
|
|
785
|
+
registeredAt: Date.now()
|
|
786
|
+
});
|
|
787
|
+
return component;
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Create a lightweight dev tools instance
|
|
794
|
+
*/
|
|
795
|
+
export function createDevTools(coherentInstance) {
|
|
796
|
+
return new DevTools(coherentInstance);
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Global dev tools utilities (available even when DevTools is disabled)
|
|
800
|
+
*/
|
|
801
|
+
export const devUtils = {
|
|
802
|
+
/**
|
|
803
|
+
* Quick component inspection
|
|
804
|
+
*/
|
|
805
|
+
inspect: (component) => {
|
|
806
|
+
console.log('🔍 Component Inspection:');
|
|
807
|
+
console.log('Type:', typeof component);
|
|
808
|
+
console.log('Structure:', component);
|
|
809
|
+
if (isCoherentObject(component)) {
|
|
810
|
+
const tags = Object.keys(component);
|
|
811
|
+
console.log('Tags:', tags);
|
|
812
|
+
for (const tag of tags) {
|
|
813
|
+
const props = component[tag];
|
|
814
|
+
if (props && typeof props === 'object') {
|
|
815
|
+
console.log(`${tag} props:`, Object.keys(_props));
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
},
|
|
820
|
+
/**
|
|
821
|
+
* Simple validation
|
|
822
|
+
*/
|
|
823
|
+
validate: (component) => {
|
|
824
|
+
try {
|
|
825
|
+
validateComponent(component);
|
|
826
|
+
console.log('✅ Component is valid');
|
|
827
|
+
return true;
|
|
828
|
+
}
|
|
829
|
+
catch (error) {
|
|
830
|
+
console.error('❌ Component validation failed:', error.message);
|
|
831
|
+
return false;
|
|
832
|
+
}
|
|
833
|
+
},
|
|
834
|
+
/**
|
|
835
|
+
* Performance timing helper
|
|
836
|
+
*/
|
|
837
|
+
time: (label, fn) => {
|
|
838
|
+
const start = performance.now();
|
|
839
|
+
const result = fn();
|
|
840
|
+
const end = performance.now();
|
|
841
|
+
console.log(`⏱️ ${label}: ${(end - start).toFixed(2)}ms`);
|
|
842
|
+
return result;
|
|
843
|
+
}
|
|
844
|
+
};
|
|
845
|
+
export default DevTools;
|
|
846
|
+
//# sourceMappingURL=dev-tools.js.map
|