@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 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.1",
3
+ "version": "6.0.0-beta.11",
4
4
  "description": "Signal - Reactive frontend/SSR framework for Igo.js",
5
5
  "main": "index.js",
6
- "browser": "src/client/index.js",
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
- "@igojs/server": "*",
24
- "@igojs/dust": "*",
25
- "devalue": "^5.6.1",
31
+ "devalue": "^5.6.2",
26
32
  "diff-dom": "^5.2.1",
27
- "i18next": "^25.7.3",
28
- "lodash": "^4.17.21"
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 = require('./DerivedCache');
3
- const StateProxy = require('./StateProxy');
2
+ const DerivedCache = require('./DerivedCache.js');
3
+ const StateProxy = require('./StateProxy.js');
4
4
 
5
- // Browser-only imports are loaded lazily in constructor
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 deps.EventBinder();
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 deps.DiffDOM();
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
- const deps = getBrowserDeps();
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 deps.FormHandler(this, this.props.form);
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;
@@ -19,8 +19,4 @@ const loadTemplate = async (file) => {
19
19
  return _CACHE[file];
20
20
  };
21
21
 
22
- module.exports = {
23
- getTemplate,
24
- loadTemplate,
25
- _CACHE
26
- };
22
+ module.exports = { getTemplate, loadTemplate, _CACHE };
@@ -1,7 +1,7 @@
1
- const Templates = require('./Templates');
2
- const { uneval } = require('devalue'); // Bundler handles ES modules correctly
3
- const igoDustHelpers = require('@igojs/dust/src/render/Helpers');
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,
@@ -19,4 +19,3 @@ i18next.init({
19
19
 
20
20
  // Expose globally for use in components
21
21
  window.i18next = i18next;
22
-
@@ -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').start({
5
+ * const { start } = require('@igojs/signal/src/client');
6
+ * start({
6
7
  * components: { 'Counter': Counter },
7
- * helpers: require('./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
- module.exports.serialize = (data, seen = new WeakMap()) => {
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 (_.isFunction(data)) {
24
+ if (isFunction(data)) {
24
25
  return undefined;
25
26
  }
26
27
  // Keep Date objects as-is (devalue handles them natively)
27
- if (_.isDate(data)) {
28
+ if (isDate(data)) {
28
29
  return data;
29
30
  }
30
- if (_.isArray(data)) {
31
- return _.map(data, item => module.exports.serialize(item, seen));
31
+ if (isArray(data)) {
32
+ return map(data, item => serialize(item, seen));
32
33
  }
33
34
  // Model instances with serialize method
34
- if (_.isFunction(data?.serialize)) {
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, module.exports.serialize(result, seen));
45
+ Object.assign(serialized, serialize(result, seen));
45
46
  return serialized;
46
47
  }
47
48
  // Form instances with getValues method (Igo Form)
48
- if (_.isFunction(data?.getValues) && data.constructor?.schema?.attributes) {
49
- return module.exports.serialize(data.getValues(), seen);
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 (_.isPlainObject(data)) {
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] = module.exports.serialize(value, seen);
61
+ serialized[key] = serialize(value, seen);
61
62
  }
62
63
  return serialized;
63
64
  }
64
65
  // Primitives (string, number, boolean)
65
- if (!_.isObject(data)) {
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 { dust: IgoDust } = require('@igojs/server');
2
+ const IgoDust = require('@igojs/dust');
3
3
 
4
- const SerializeUtils = require('./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
- // Register @serialize helper (called once after devalue is loaded)
18
- const registerHelper = () => {
19
- if (helperRegistered) return;
20
- helperRegistered = true;
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
- module.exports.middleware = async (req, res, next) => {
32
+ const middleware = async (req, res, next) => {
39
33
 
40
- // Wait for devalue to be loaded, then register helper
41
- await devaluePromise;
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
- module.exports.templates = async (req, res) => {
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
- module.exports.configure = (options) => {
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 };
@@ -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,
@@ -1,5 +1,5 @@
1
1
  const assert = require('assert');
2
- const DerivedCache = require('../../src/client/DerivedCache');
2
+ const DerivedCache = require('../../src/client/DerivedCache.js');
3
3
 
4
4
  describe('DerivedCache', () => {
5
5
 
@@ -1,5 +1,5 @@
1
1
  const assert = require('assert');
2
- const SignalComponent = require('../../src/client/SignalComponent');
2
+ const SignalComponent = require('../../src/client/SignalComponent.js');
3
3
 
4
4
  describe('SignalComponent', () => {
5
5
 
@@ -1,5 +1,5 @@
1
1
  const assert = require('assert');
2
- const StateProxy = require('../../src/client/StateProxy');
2
+ const StateProxy = require('../../src/client/StateProxy.js');
3
3
 
4
4
  describe('StateProxy', () => {
5
5
 
@@ -1,5 +1,5 @@
1
1
  const assert = require('assert');
2
- const SerializeUtils = require('../../src/server/SerializeUtils');
2
+ const SerializeUtils = require('../../src/server/SerializeUtils.js');
3
3
 
4
4
  describe('SerializeUtils', () => {
5
5