@igojs/signal 6.0.0-beta.1 → 6.0.0-beta.11
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/index.js +3 -13
- package/package.json +16 -7
- package/src/client/EventBinder.js +0 -4
- package/src/client/SignalComponent.js +12 -25
- package/src/client/dust/Templates.js +1 -5
- package/src/client/dust/Utils.js +4 -4
- package/src/client/dust/i18n.js +0 -1
- package/src/client/index.js +6 -5
- package/src/server/SerializeUtils.js +15 -12
- package/src/server/SignalController.js +16 -21
- package/src/server/index.js +2 -2
- package/test/client/DerivedCacheTest.js +1 -1
- package/test/client/SignalComponentTest.js +1 -1
- package/test/client/StateProxyTest.js +1 -1
- package/test/server/SerializeUtilsTest.js +1 -1
package/index.js
CHANGED
|
@@ -1,24 +1,14 @@
|
|
|
1
1
|
// @igojs/signal - Reactive frontend/SSR framework for Igo.js
|
|
2
2
|
|
|
3
3
|
// Server-side exports
|
|
4
|
-
const server = require('./src/server');
|
|
4
|
+
const server = require('./src/server/index.js');
|
|
5
|
+
const SignalComponent = require('./src/client/SignalComponent.js');
|
|
5
6
|
|
|
6
7
|
// Re-export server utilities
|
|
7
8
|
module.exports = {
|
|
8
|
-
// Server middleware for SSR
|
|
9
9
|
middleware: server.middleware,
|
|
10
|
-
|
|
11
|
-
// Template serving endpoint
|
|
12
10
|
templates: server.templates,
|
|
13
|
-
|
|
14
|
-
// Configure signal (translations, etc.)
|
|
15
11
|
configure: server.configure,
|
|
16
|
-
|
|
17
|
-
// Serialization for client hydration
|
|
18
12
|
serialize: server.serialize,
|
|
19
|
-
|
|
20
|
-
// SignalComponent for SSR rendering
|
|
21
|
-
get SignalComponent() {
|
|
22
|
-
return require('./src/client/SignalComponent');
|
|
23
|
-
},
|
|
13
|
+
SignalComponent,
|
|
24
14
|
};
|
package/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@igojs/signal",
|
|
3
|
-
"version": "6.0.0-beta.
|
|
3
|
+
"version": "6.0.0-beta.11",
|
|
4
4
|
"description": "Signal - Reactive frontend/SSR framework for Igo.js",
|
|
5
5
|
"main": "index.js",
|
|
6
|
-
"
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./index.js",
|
|
8
|
+
"./client": "./src/client/index.js"
|
|
9
|
+
},
|
|
7
10
|
"scripts": {
|
|
8
11
|
"test": "mocha --exit 'test/**/*.js'"
|
|
9
12
|
},
|
|
@@ -16,15 +19,21 @@
|
|
|
16
19
|
],
|
|
17
20
|
"author": "@igocreate",
|
|
18
21
|
"license": "ISC",
|
|
22
|
+
"homepage": "https://github.com/igocreate/igo",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+ssh://git@github.com/igocreate/igo.git"
|
|
26
|
+
},
|
|
19
27
|
"publishConfig": {
|
|
20
28
|
"access": "public"
|
|
21
29
|
},
|
|
22
30
|
"dependencies": {
|
|
23
|
-
"
|
|
24
|
-
"@igojs/dust": "*",
|
|
25
|
-
"devalue": "^5.6.1",
|
|
31
|
+
"devalue": "^5.6.2",
|
|
26
32
|
"diff-dom": "^5.2.1",
|
|
27
|
-
"
|
|
28
|
-
|
|
33
|
+
"lodash": "^4.17.23"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"@igojs/dust": "^6.0.0-beta.3",
|
|
37
|
+
"i18next": "^25.0.0"
|
|
29
38
|
}
|
|
30
39
|
}
|
|
@@ -75,14 +75,12 @@ class EventBinder {
|
|
|
75
75
|
const existingHandler = eventMap.get(eventType);
|
|
76
76
|
|
|
77
77
|
if (existingHandler) {
|
|
78
|
-
// ✅ Element preserved by DiffDOM → reuse existing listener
|
|
79
78
|
newListeners.push({
|
|
80
79
|
element: targetElement,
|
|
81
80
|
eventType,
|
|
82
81
|
handler: existingHandler
|
|
83
82
|
});
|
|
84
83
|
} else {
|
|
85
|
-
// ✅ New element or new eventType → create new listener
|
|
86
84
|
const boundHandler = handler.bind(context);
|
|
87
85
|
targetElement.addEventListener(eventType, boundHandler);
|
|
88
86
|
eventMap.set(eventType, boundHandler);
|
|
@@ -105,7 +103,6 @@ class EventBinder {
|
|
|
105
103
|
this._boundListeners.forEach(({ element, eventType, handler }) => {
|
|
106
104
|
const processedEvents = processedElements.get(element);
|
|
107
105
|
if (!processedEvents || !processedEvents.has(eventType)) {
|
|
108
|
-
// Element was removed or event type changed → cleanup
|
|
109
106
|
element?.removeEventListener(eventType, handler);
|
|
110
107
|
const eventMap = this._elementListeners.get(element);
|
|
111
108
|
if (eventMap) {
|
|
@@ -128,7 +125,6 @@ class EventBinder {
|
|
|
128
125
|
element?.removeEventListener(eventType, handler);
|
|
129
126
|
});
|
|
130
127
|
this._boundListeners = [];
|
|
131
|
-
// Note: WeakMap clears itself automatically when elements are garbage collected
|
|
132
128
|
}
|
|
133
129
|
}
|
|
134
130
|
|
|
@@ -1,26 +1,16 @@
|
|
|
1
1
|
// Isomorphic imports (safe for Node.js)
|
|
2
|
-
const DerivedCache
|
|
3
|
-
const StateProxy
|
|
2
|
+
const DerivedCache = require('./DerivedCache.js');
|
|
3
|
+
const StateProxy = require('./StateProxy.js');
|
|
4
4
|
|
|
5
|
-
// Browser-only imports
|
|
5
|
+
// Browser-only imports (top-level, but only used in browser methods)
|
|
6
|
+
const { DiffDOM } = require('diff-dom');
|
|
7
|
+
const EventBinder = require('./EventBinder.js');
|
|
8
|
+
const Templates = require('./dust/Templates.js');
|
|
9
|
+
const FormHandler = require('./FormHandler.js');
|
|
6
10
|
|
|
7
11
|
// Detect server-side rendering
|
|
8
12
|
const isServer = typeof window === 'undefined';
|
|
9
13
|
|
|
10
|
-
// Browser-only dependencies (lazy loaded once)
|
|
11
|
-
let _browserDeps = null;
|
|
12
|
-
const getBrowserDeps = () => {
|
|
13
|
-
if (!_browserDeps) {
|
|
14
|
-
_browserDeps = {
|
|
15
|
-
DiffDOM: require('diff-dom').DiffDOM,
|
|
16
|
-
EventBinder: require('./EventBinder'),
|
|
17
|
-
Templates: require('./dust/Templates'),
|
|
18
|
-
FormHandler: require('./FormHandler'),
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
return _browserDeps;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
14
|
class Igo2Component {
|
|
25
15
|
// Component registry for auto-discovery
|
|
26
16
|
static _registry = {};
|
|
@@ -77,16 +67,14 @@ class Igo2Component {
|
|
|
77
67
|
|
|
78
68
|
// Browser-only setup
|
|
79
69
|
if (!isServer) {
|
|
80
|
-
const deps = getBrowserDeps();
|
|
81
|
-
|
|
82
70
|
this.element = element;
|
|
83
71
|
this.element.__igoInstance = this;
|
|
84
72
|
this._dustTemplateFn = null;
|
|
85
|
-
this._eventBinder = new
|
|
73
|
+
this._eventBinder = new EventBinder();
|
|
86
74
|
this._derivedCache = new DerivedCache();
|
|
87
75
|
this._isInitialized = false;
|
|
88
76
|
this._renderFrame = null;
|
|
89
|
-
this._diffDom = new
|
|
77
|
+
this._diffDom = new DiffDOM();
|
|
90
78
|
}
|
|
91
79
|
|
|
92
80
|
// Default events array (only if not defined as getter in subclass)
|
|
@@ -230,13 +218,12 @@ class Igo2Component {
|
|
|
230
218
|
// Initialize component (called automatically by constructor)
|
|
231
219
|
// Can be overridden in subclasses for custom initialization
|
|
232
220
|
async init() {
|
|
233
|
-
|
|
234
|
-
this._dustTemplateFn = await deps.Templates.loadTemplate(this.template);
|
|
221
|
+
this._dustTemplateFn = await Templates.loadTemplate(this.template);
|
|
235
222
|
this._isInitialized = true;
|
|
236
223
|
|
|
237
224
|
// Initialize form handler if props.form exists
|
|
238
225
|
if (this.props.form) {
|
|
239
|
-
this._formHandler = new
|
|
226
|
+
this._formHandler = new FormHandler(this, this.props.form);
|
|
240
227
|
}
|
|
241
228
|
|
|
242
229
|
await this.render();
|
|
@@ -406,4 +393,4 @@ class Igo2Component {
|
|
|
406
393
|
|
|
407
394
|
}
|
|
408
395
|
|
|
409
|
-
module.exports = Igo2Component;
|
|
396
|
+
module.exports = Igo2Component;
|
package/src/client/dust/Utils.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
const Templates
|
|
2
|
-
const { uneval }
|
|
3
|
-
const igoDustHelpers
|
|
4
|
-
const { createSerializeHelper, htmlencode } = require('../../shared/serialize');
|
|
1
|
+
const Templates = require('./Templates.js');
|
|
2
|
+
const { uneval } = require('devalue');
|
|
3
|
+
const igoDustHelpers = require('@igojs/dust/src/render/Helpers');
|
|
4
|
+
const { createSerializeHelper, htmlencode } = require('../../shared/serialize.js');
|
|
5
5
|
|
|
6
6
|
// Special characters
|
|
7
7
|
const BS = /\\/g,
|
package/src/client/dust/i18n.js
CHANGED
package/src/client/index.js
CHANGED
|
@@ -2,16 +2,17 @@
|
|
|
2
2
|
* Signal - Zero-boilerplate reactive framework (browser entry point)
|
|
3
3
|
*
|
|
4
4
|
* Usage:
|
|
5
|
-
* require('@igojs/signal/src/client')
|
|
5
|
+
* const { start } = require('@igojs/signal/src/client');
|
|
6
|
+
* start({
|
|
6
7
|
* components: { 'Counter': Counter },
|
|
7
|
-
* helpers:
|
|
8
|
+
* helpers: helpers
|
|
8
9
|
* });
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
|
-
require('./dust/i18n');
|
|
12
|
+
require('./dust/i18n.js');
|
|
12
13
|
|
|
13
|
-
const SignalComponent = require('./SignalComponent');
|
|
14
|
-
const Utils = require('./dust/Utils');
|
|
14
|
+
const SignalComponent = require('./SignalComponent.js');
|
|
15
|
+
const Utils = require('./dust/Utils.js');
|
|
15
16
|
|
|
16
17
|
window.__signal = {
|
|
17
18
|
IgoDustUtils: Utils
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
const _ = require('lodash');
|
|
3
|
+
const { isFunction, isDate, isArray, map, isPlainObject, isObject } = _;
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Serialize data for client-side hydration
|
|
@@ -15,23 +16,23 @@ const _ = require('lodash');
|
|
|
15
16
|
* @param {WeakMap} [seen] - Internal map for tracking already-serialized objects
|
|
16
17
|
* @returns {any} - Serialized data ready for devalue
|
|
17
18
|
*/
|
|
18
|
-
|
|
19
|
+
const serialize = (data, seen = new WeakMap()) => {
|
|
19
20
|
if (data === null || data === undefined) {
|
|
20
21
|
return null;
|
|
21
22
|
}
|
|
22
23
|
// Skip functions
|
|
23
|
-
if (
|
|
24
|
+
if (isFunction(data)) {
|
|
24
25
|
return undefined;
|
|
25
26
|
}
|
|
26
27
|
// Keep Date objects as-is (devalue handles them natively)
|
|
27
|
-
if (
|
|
28
|
+
if (isDate(data)) {
|
|
28
29
|
return data;
|
|
29
30
|
}
|
|
30
|
-
if (
|
|
31
|
-
return
|
|
31
|
+
if (isArray(data)) {
|
|
32
|
+
return map(data, item => serialize(item, seen));
|
|
32
33
|
}
|
|
33
34
|
// Model instances with serialize method
|
|
34
|
-
if (
|
|
35
|
+
if (isFunction(data?.serialize)) {
|
|
35
36
|
// Check if already serialized (deduplication)
|
|
36
37
|
if (seen.has(data)) {
|
|
37
38
|
return seen.get(data);
|
|
@@ -41,15 +42,15 @@ module.exports.serialize = (data, seen = new WeakMap()) => {
|
|
|
41
42
|
seen.set(data, serialized);
|
|
42
43
|
// Serialize and merge into placeholder
|
|
43
44
|
const result = data.serialize();
|
|
44
|
-
Object.assign(serialized,
|
|
45
|
+
Object.assign(serialized, serialize(result, seen));
|
|
45
46
|
return serialized;
|
|
46
47
|
}
|
|
47
48
|
// Form instances with getValues method (Igo Form)
|
|
48
|
-
if (
|
|
49
|
-
return
|
|
49
|
+
if (isFunction(data?.getValues) && data.constructor?.schema?.attributes) {
|
|
50
|
+
return serialize(data.getValues(), seen);
|
|
50
51
|
}
|
|
51
52
|
// Plain objects only - recursively serialize values
|
|
52
|
-
if (
|
|
53
|
+
if (isPlainObject(data)) {
|
|
53
54
|
// Check if already processed (for circular plain objects)
|
|
54
55
|
if (seen.has(data)) {
|
|
55
56
|
return seen.get(data);
|
|
@@ -57,14 +58,16 @@ module.exports.serialize = (data, seen = new WeakMap()) => {
|
|
|
57
58
|
const serialized = {};
|
|
58
59
|
seen.set(data, serialized);
|
|
59
60
|
for (const [key, value] of Object.entries(data)) {
|
|
60
|
-
serialized[key] =
|
|
61
|
+
serialized[key] = serialize(value, seen);
|
|
61
62
|
}
|
|
62
63
|
return serialized;
|
|
63
64
|
}
|
|
64
65
|
// Primitives (string, number, boolean)
|
|
65
|
-
if (!
|
|
66
|
+
if (!isObject(data)) {
|
|
66
67
|
return data;
|
|
67
68
|
}
|
|
68
69
|
// Skip non-POJO class instances that we don't know how to serialize
|
|
69
70
|
return undefined;
|
|
70
71
|
};
|
|
72
|
+
|
|
73
|
+
module.exports = { serialize };
|
|
@@ -1,25 +1,19 @@
|
|
|
1
1
|
|
|
2
|
-
const
|
|
2
|
+
const IgoDust = require('@igojs/dust');
|
|
3
3
|
|
|
4
|
-
const SerializeUtils
|
|
5
|
-
const { createSerializeHelper } = require('../shared/serialize');
|
|
6
|
-
|
|
7
|
-
// Load devalue dynamically (ES module)
|
|
8
|
-
let uneval;
|
|
9
|
-
let helperRegistered = false;
|
|
10
|
-
const devaluePromise = import('devalue').then(mod => {
|
|
11
|
-
uneval = mod.uneval;
|
|
12
|
-
});
|
|
4
|
+
const SerializeUtils = require('./SerializeUtils.js');
|
|
5
|
+
const { createSerializeHelper } = require('../shared/serialize.js');
|
|
13
6
|
|
|
14
7
|
// Translations are loaded from user's project (configured via signal.configure())
|
|
15
8
|
let translations = {};
|
|
16
9
|
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
10
|
+
// devalue is ESM-only, load it dynamically at startup
|
|
11
|
+
let uneval;
|
|
12
|
+
const devalueReady = import('devalue').then(m => {
|
|
13
|
+
uneval = m.uneval;
|
|
14
|
+
// Register @serialize helper once devalue is loaded
|
|
21
15
|
IgoDust.helpers.serialize = createSerializeHelper(uneval);
|
|
22
|
-
};
|
|
16
|
+
});
|
|
23
17
|
|
|
24
18
|
|
|
25
19
|
/**
|
|
@@ -35,11 +29,10 @@ const registerHelper = () => {
|
|
|
35
29
|
* 2. Compute SSR derived values from registered signal_components
|
|
36
30
|
* 3. Merge everything into res.locals for template rendering
|
|
37
31
|
*/
|
|
38
|
-
|
|
32
|
+
const middleware = async (req, res, next) => {
|
|
39
33
|
|
|
40
|
-
//
|
|
41
|
-
await
|
|
42
|
-
registerHelper();
|
|
34
|
+
// Ensure devalue is loaded
|
|
35
|
+
await devalueReady;
|
|
43
36
|
|
|
44
37
|
// Inject translations for frontend i18next
|
|
45
38
|
res.locals.__signal_translations = uneval(translations);
|
|
@@ -80,15 +73,17 @@ module.exports.middleware = async (req, res, next) => {
|
|
|
80
73
|
};
|
|
81
74
|
|
|
82
75
|
//
|
|
83
|
-
|
|
76
|
+
const templates = async (req, res) => {
|
|
84
77
|
const file = req.query.file;
|
|
85
78
|
const source = await IgoDust.getSource(`${file}.dust`);
|
|
86
79
|
res.json({ file, source });
|
|
87
80
|
};
|
|
88
81
|
|
|
89
82
|
// Configure Signal (called from user's app)
|
|
90
|
-
|
|
83
|
+
const configure = (options) => {
|
|
91
84
|
if (options.translations) {
|
|
92
85
|
translations = options.translations;
|
|
93
86
|
}
|
|
94
87
|
};
|
|
88
|
+
|
|
89
|
+
module.exports = { middleware, templates, configure };
|
package/src/server/index.js
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* - SerializeUtils: Model serialization with deduplication
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
const SignalController = require('./SignalController');
|
|
10
|
-
const SerializeUtils = require('./SerializeUtils');
|
|
9
|
+
const SignalController = require('./SignalController.js');
|
|
10
|
+
const SerializeUtils = require('./SerializeUtils.js');
|
|
11
11
|
|
|
12
12
|
module.exports = {
|
|
13
13
|
middleware: SignalController.middleware,
|