lilact 0.30.0 → 0.31.0

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.
Files changed (44) hide show
  1. package/dist/lilact.development.js +167 -53
  2. package/dist/lilact.development.js.map +3 -3
  3. package/dist/lilact.development.min.js +25 -25
  4. package/dist/lilact.development.min.js.map +3 -3
  5. package/dist/lilact.production.min.js +25 -25
  6. package/docs/assets/hierarchy.js +1 -1
  7. package/docs/assets/navigation.js +1 -1
  8. package/docs/assets/search.js +1 -1
  9. package/docs/classes/accessories.ErrorBoundary.html +8 -8
  10. package/docs/classes/accessories.Suspense.html +7 -7
  11. package/docs/classes/components.Component.html +11 -11
  12. package/docs/classes/components.HTMLComponent.html +11 -11
  13. package/docs/classes/components.RootComponent.html +11 -11
  14. package/docs/functions/components.cloneComponent.html +1 -1
  15. package/docs/functions/components.createComponent.html +1 -1
  16. package/docs/functions/components.createPortal.html +1 -1
  17. package/docs/functions/components.createRoot.html +1 -1
  18. package/docs/functions/components.hotReloadRoot.html +1 -1
  19. package/docs/functions/components.memo.html +1 -1
  20. package/docs/functions/components.render.html +1 -1
  21. package/docs/functions/components.replaceAllComponentInstances.html +1 -0
  22. package/docs/functions/components.replaceComponentEntity.html +1 -1
  23. package/docs/functions/run.lazy.html +1 -1
  24. package/docs/functions/run.run.html +1 -1
  25. package/docs/functions/run.runScripts.html +1 -1
  26. package/docs/hierarchy.html +1 -1
  27. package/docs/modules/components.html +1 -1
  28. package/docs/static/index.html +1 -1
  29. package/docs/static/lilact.development.js +167 -53
  30. package/docs/static/lilact.development.js.map +3 -3
  31. package/docs/static/lilact.development.min.js +25 -25
  32. package/docs/static/lilact.development.min.js.map +3 -3
  33. package/docs/static/lilact.production.min.js +25 -25
  34. package/docs/variables/components.cloneElement.html +1 -1
  35. package/examples/index.html +1 -1
  36. package/examples/lilact.development.js +167 -53
  37. package/examples/lilact.development.js.map +3 -3
  38. package/examples/lilact.development.min.js +25 -25
  39. package/examples/lilact.development.min.js.map +3 -3
  40. package/examples/lilact.production.min.js +25 -25
  41. package/package.json +1 -1
  42. package/src/components.jsx +209 -80
  43. package/src/lilact.jsx +1 -1
  44. package/src/run.jsx +85 -53
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lilact",
3
- "version": "0.30.0",
3
+ "version": "0.31.0",
4
4
  "description": "A Little React/JSX Runtime Implementation for Browser",
5
5
  "repository": "github:arashkazemi/lilact",
6
6
  "homepage": "https://arashkazemi.github.io/lilact/",
@@ -38,10 +38,85 @@ import { PropTypes } from './proptypes.jsx';
38
38
  const SVG_NS = "http://www.w3.org/2000/svg";
39
39
 
40
40
 
41
+
41
42
  function getComponentEntity(entity) {
42
- return entity?.[MEMOIZED]
43
- ? entity.component
44
- : entity;
43
+ return entity?.[MEMOIZED] ? entity.component : entity;
44
+ }
45
+
46
+ const hotEntityAliases = new Map();
47
+
48
+ function resolveHotEntity(entity) {
49
+ entity = getComponentEntity(entity);
50
+
51
+ const visited = new Set();
52
+
53
+ while (
54
+ typeof entity === "function" &&
55
+ hotEntityAliases.has(entity) &&
56
+ !visited.has(entity)
57
+ ) {
58
+ visited.add(entity);
59
+ entity = hotEntityAliases.get(entity);
60
+ }
61
+
62
+ return entity;
63
+ }
64
+
65
+ const componentInstances = new Map();
66
+
67
+ function registerComponentCore(core) {
68
+ const entity = getComponentEntity(core.entity);
69
+
70
+ if (typeof entity !== "function") return;
71
+
72
+ let instances = componentInstances.get(entity);
73
+
74
+ if (!instances) {
75
+ instances = new Set();
76
+ componentInstances.set(entity, instances);
77
+ }
78
+
79
+ instances.add(core);
80
+ core.registered_entity = entity;
81
+ }
82
+
83
+ function unregisterComponentCore(core) {
84
+ const entity = core.registered_entity;
85
+ if (!entity) return;
86
+
87
+ const instances = componentInstances.get(entity);
88
+
89
+ if (instances) {
90
+ instances.delete(core);
91
+
92
+ if (instances.size === 0) {
93
+ componentInstances.delete(entity);
94
+ }
95
+ }
96
+
97
+ delete core.registered_entity;
98
+ }
99
+
100
+ function moveComponentCore(core, oldEntity, newEntity) {
101
+ const oldInstances = componentInstances.get(oldEntity);
102
+
103
+ if (oldInstances) {
104
+ oldInstances.delete(core);
105
+
106
+ if (oldInstances.size === 0) {
107
+ componentInstances.delete(oldEntity);
108
+ }
109
+ }
110
+
111
+ let newInstances = componentInstances.get(newEntity);
112
+
113
+ if (!newInstances) {
114
+ newInstances = new Set();
115
+ componentInstances.set(newEntity, newInstances);
116
+ }
117
+
118
+ newInstances.add(core);
119
+ core.registered_entity = newEntity;
45
120
  }
46
121
 
47
122
  export function replaceComponentEntity(componentCore, nextEntity) {
@@ -50,7 +125,7 @@ export function replaceComponentEntity(componentCore, nextEntity) {
50
125
  }
51
126
 
52
127
  const oldEntity = getComponentEntity(componentCore.entity);
53
- const newEntity = getComponentEntity(nextEntity);
128
+ const newEntity = resolveHotEntity(nextEntity);
54
129
 
55
130
  if (oldEntity === newEntity) {
56
131
  return true;
@@ -62,27 +137,67 @@ export function replaceComponentEntity(componentCore, nextEntity) {
62
137
  return false;
63
138
  }
64
139
 
65
- const oldIsClass = isClass(oldEntity);
66
- const newIsClass = isClass(newEntity);
67
-
68
- // A function component cannot reuse a class instance, and vice versa.
69
- if (oldIsClass !== newIsClass) {
140
+ if (
141
+ typeof oldEntity !== "function" ||
142
+ typeof newEntity !== "function" ||
143
+ isClass(oldEntity) !== isClass(newEntity)
144
+ ) {
70
145
  return false;
71
146
  }
72
147
 
73
- if (newIsClass) {
74
- // componentCore.state and the component instance are preserved.
75
- // Only the class implementation changes.
148
+ if (isClass(newEntity)) {
76
149
  Object.setPrototypeOf(component, newEntity.prototype);
77
150
  } else {
78
- // The function is invoked with the ComponentCore as `this` by apply().
79
- component.render = newEntity;
151
+ component.render = newEntity.bind(component);
80
152
  }
81
153
 
82
154
  componentCore.entity = newEntity;
155
+ moveComponentCore(componentCore, oldEntity, newEntity);
156
+
83
157
  return true;
84
158
  }
85
159
 
160
+ export function replaceAllComponentInstances(oldEntity, newEntity) {
161
+ oldEntity = getComponentEntity(oldEntity);
162
+ newEntity = getComponentEntity(newEntity);
163
+
164
+ if (
165
+ typeof oldEntity !== "function" ||
166
+ typeof newEntity !== "function"
167
+ ) {
168
+ return 0;
169
+ }
170
+
171
+ newEntity = resolveHotEntity(newEntity);
172
+
173
+ if (oldEntity === newEntity) {
174
+ return 0;
175
+ }
176
+
177
+ // Keep this alias even when there are currently no instances.
178
+ // A future instance may still be created with oldEntity.
179
+ hotEntityAliases.set(oldEntity, newEntity);
180
+
181
+ const instances = componentInstances.get(oldEntity);
182
+
183
+ if (!instances) {
184
+ return 0;
185
+ }
186
+
187
+ let count = 0;
188
+
189
+ for (const core of [...instances]) {
190
+ if (!replaceComponentEntity(core, newEntity)) {
191
+ continue;
192
+ }
193
+
194
+ core.apply(core.props, core.state);
195
+ count++;
196
+ }
197
+
198
+ return count;
199
+ }
200
+
86
201
 
87
202
  /*
88
203
  ComponentCache is for internal use. It is the heart of the JSX runtime,
@@ -110,17 +225,18 @@ class ComponentCache
110
225
  bucket[IDX]++;
111
226
 
112
227
  const oldEntity = getComponentEntity(componentCore.entity);
113
- const newEntity = getComponentEntity(nextEntity);
228
+ const newEntity = resolveHotEntity(nextEntity);
114
229
 
115
230
  const incompatible =
231
+ typeof oldEntity !== typeof newEntity ||
232
+ (
116
233
  typeof oldEntity === "function" &&
117
- typeof newEntity === "function" &&
118
- isClass(oldEntity) !== isClass(newEntity);
234
+ isClass(oldEntity) !== isClass(newEntity)
235
+ );
119
236
 
120
237
  if (incompatible) {
121
238
  componentCore = construct_func();
122
- }
123
- else if (newEntity) {
239
+ } else if (newEntity) {
124
240
  replaceComponentEntity(componentCore, newEntity);
125
241
  }
126
242
  } else {
@@ -394,7 +510,8 @@ class ComponentCore
394
510
 
395
511
  async cleanup()
396
512
  {
397
- try {
513
+ unregisterComponentCore(this);
514
+ try {
398
515
  const promises = [];
399
516
 
400
517
  if(this.props?.ref) {
@@ -723,91 +840,97 @@ const renderErrorHandler = (c, e) =>
723
840
 
724
841
 
725
842
 
726
- function constructFunc(core, parent) // returns {text} or component, and not component core.
727
- {
843
+ function constructFunc(core, parent) {
728
844
  let comp = core;
729
845
 
730
- if( core[TEXT]!==undefined ) {
731
- // do nothing...
732
- }
733
- else {
846
+ if (core[TEXT] === undefined) {
734
847
  let entity = core.entity;
735
848
  let memoized = false;
736
849
 
737
- if(typeof(entity)==='object') {
738
- if( entity[MEMOIZED] ) {
850
+ if (typeof entity === "object") {
851
+ if (entity[MEMOIZED]) {
739
852
  memoized = entity[MEMOIZED];
740
853
  entity = entity.component;
741
- }
742
- else {
854
+ } else {
743
855
  throw new Error("Invalid component.");
744
856
  }
745
857
  }
746
858
 
747
- if(typeof(entity)==='string') {
748
- comp = new HTMLComponent(entity, core.props);
749
- }
750
- else {
751
-
752
- if( isClass(entity) ) {
753
- if(entity?.defaultProps) {
754
- core.props = { ...entity.defaultProps, ...core.props };
755
- }
756
-
757
- comp = new entity(core.props);
859
+ entity = resolveHotEntity(entity);
758
860
 
759
- const desc = Object.getOwnPropertyDescriptor(comp, "state");
760
- if(desc) {
761
- if (typeof desc.get !== "function" && typeof desc.set !== "function") {
762
- comp[CORE].state = comp.state;
861
+ if (typeof entity === "string") {
862
+ comp = new HTMLComponent(entity, core.props);
863
+ } else if (isClass(entity)) {
864
+ if (entity?.defaultProps) {
865
+ core.props = {
866
+ ...entity.defaultProps,
867
+ ...core.props,
868
+ };
869
+ }
763
870
 
764
- Object.defineProperty(comp, "state", {
765
- get() { return this[CORE].state },
766
- set(v) {
767
- // todo: this should be changed, it should be only directly settable in constructor.
768
- if(this[CORE].state===undefined) {
769
- this[CORE].state = v;
770
- }
771
- else {
772
- throw new Error('Assigning component state this way is not allowed.');
773
- }
871
+ comp = new entity(core.props);
872
+
873
+ const desc = Object.getOwnPropertyDescriptor(comp, "state");
874
+
875
+ if (desc) {
876
+ if (
877
+ typeof desc.get !== "function" &&
878
+ typeof desc.set !== "function"
879
+ ) {
880
+ comp[CORE].state = comp.state;
881
+
882
+ Object.defineProperty(comp, "state", {
883
+ get() {
884
+ return this[CORE].state;
885
+ },
886
+ set(value) {
887
+ if (this[CORE].state === undefined) {
888
+ this[CORE].state = value;
889
+ } else {
890
+ throw new Error(
891
+ "Assigning component state this way is not allowed."
892
+ );
774
893
  }
775
- });
776
- }
894
+ },
895
+ });
777
896
  }
778
897
  }
779
- else if(typeof(entity)==='function') {
780
-
781
- if(entity?.defaultProps) {
782
- core.props = { ...entity.defaultProps, ...core.props };
783
- }
898
+ } else if (typeof entity === "function") {
899
+ if (entity?.defaultProps) {
900
+ core.props = {
901
+ ...entity.defaultProps,
902
+ ...core.props,
903
+ };
904
+ }
784
905
 
785
- comp = new Component(core.props);
906
+ comp = new Component(core.props);
907
+ comp.render = entity.bind(comp);
908
+ comp[CORE].hooks = [];
909
+ comp[CORE].hook_index = 0;
910
+ } else {
911
+ throw new Error("Error in constructing component.");
912
+ }
786
913
 
787
- // the binding is not necessary and is not according to the specs,
788
- // probably not even recommended! but helpful.
789
- comp.render = entity.bind(comp);
790
- comp[CORE].hooks = [];
791
- comp[CORE].hook_index = 0;
792
- }
793
- else {
794
- throw new Error("Error in constructing component.");
795
- }
914
+ comp[CORE].entity = entity;
915
+ registerComponentCore(comp[CORE]);
796
916
 
797
- comp[CORE].entity = entity;
917
+ if (core.container) {
918
+ comp[CORE].container = core.container;
919
+ }
798
920
 
799
- if(core.container) {
800
- comp[CORE].container = core.container;
801
- }
921
+ if (memoized) {
922
+ comp[CORE][MEMOIZED] = true;
802
923
  }
924
+ }
803
925
 
804
- if(memoized) comp[CORE][MEMOIZED] = true;
926
+ if (parent instanceof ComponentCore) {
927
+ comp[CORE].parent = parent;
805
928
  }
806
929
 
807
- if(parent instanceof ComponentCore) comp[CORE].parent = parent;
808
930
  return comp;
809
931
  }
810
932
 
933
+
811
934
  function prepareCore(parent, core) {
812
935
  try {
813
936
  parent.cache ??= new ComponentCache(parent);
@@ -1274,6 +1397,7 @@ export function createRoot(element)
1274
1397
  let root;
1275
1398
 
1276
1399
  return {
1400
+
1277
1401
  render(component) {
1278
1402
  if(!root) {
1279
1403
  root = new RootComponent( element, {children:[component]} );
@@ -1291,6 +1415,11 @@ export function createRoot(element)
1291
1415
  root.cleanup();
1292
1416
  element.innerHTML="";
1293
1417
  }
1418
+ },
1419
+
1420
+ getCore() {
1421
+ console.log(root, root[CORE]);
1422
+ return root[CORE];
1294
1423
  }
1295
1424
  }
1296
1425
  }
@@ -1299,7 +1428,7 @@ export function createRoot(element)
1299
1428
 
1300
1429
  export function hotReloadRoot(rootCore, nextEntity, props) {
1301
1430
  if (!rootCore || !rootCore.component) {
1302
- throw new Error("Invalid Lilact root component.");
1431
+ throw new Error("Invalid root component.");
1303
1432
  }
1304
1433
 
1305
1434
  const replaced = replaceComponentEntity(rootCore, nextEntity);
@@ -1310,7 +1439,7 @@ export function hotReloadRoot(rootCore, nextEntity, props) {
1310
1439
  );
1311
1440
  }
1312
1441
 
1313
- rootCore.apply(props === undefined ? rootCore.props : props);
1442
+ rootCore.apply(props === undefined ? rootCore.props : {...rootCore.props, ...props});
1314
1443
 
1315
1444
  return rootCore;
1316
1445
  }
package/src/lilact.jsx CHANGED
@@ -94,7 +94,7 @@ export {transpileJSX, transpilerConfig} from "./jsx";
94
94
  export const Lilact =
95
95
  {
96
96
 
97
- VERSION: "RC.6",
97
+ VERSION: "RC.7",
98
98
 
99
99
  // Configuration
100
100
 
package/src/run.jsx CHANGED
@@ -245,69 +245,96 @@ export function require(path)
245
245
 
246
246
 
247
247
  function makeImportsObject(mod, options = {}) {
248
- mod.importsObject = {};
248
+ mod.importsObject ??= {};
249
249
 
250
- for (const path in mod.meta.imports) {
251
- const imps = mod.meta.imports[path];
250
+ for (const key of Object.keys(mod.importsObject)) {
251
+ delete mod.importsObject[key];
252
+ }
252
253
 
253
- const exps = loadModule(path, {
254
- requirer: mod,
255
- forceReload: options.forceReload,
256
- });
254
+ for (const path in mod.meta.imports) {
255
+ const imps = mod.meta.imports[path];
257
256
 
258
- for (const i in imps.named_imports) {
259
- if (i === '') continue;
257
+ const exps = loadModule(path, {
258
+ requirer: mod,
259
+ forceReload: options.forceReload,
260
+ });
260
261
 
261
- const exportedName = imps.named_imports[i];
262
+ for (const i in imps.named_imports) {
263
+ if (i === "") continue;
262
264
 
263
- if (!Object.prototype.hasOwnProperty.call(exps, exportedName)) {
264
- throw new Error(
265
- `Imported module does not export any "${i}".`
266
- );
267
- }
265
+ const exportedName = imps.named_imports[i];
268
266
 
269
- mod.importsObject[i] = exps[exportedName];
270
- }
267
+ if (!Object.prototype.hasOwnProperty.call(exps, exportedName)) {
268
+ throw new Error(
269
+ `Imported module does not export any "${i}".`
270
+ );
271
+ }
271
272
 
272
- for (const i of imps.import_defaults) {
273
- if (!Object.prototype.hasOwnProperty.call(exps, 'default')) {
274
- throw new Error(
275
- `Imported module does not export a default.`
276
- );
277
- }
273
+ mod.importsObject[i] = exps[exportedName];
274
+ }
278
275
 
279
- mod.importsObject[i] = exps.default;
280
- }
276
+ for (const i of imps.import_defaults) {
277
+ if (!Object.prototype.hasOwnProperty.call(exps, "default")) {
278
+ throw new Error(
279
+ "Imported module does not export a default."
280
+ );
281
+ }
281
282
 
282
- for (const i of imps.import_stars) {
283
- mod.importsObject[i] = { ...exps };
284
- }
285
- }
283
+ mod.importsObject[i] = exps.default;
284
+ }
285
+
286
+ for (const i of imps.import_stars) {
287
+ mod.importsObject[i] = { ...exps };
288
+ }
289
+ }
290
+
291
+ return mod.importsObject;
292
+ }
293
+
294
+
295
+ function exportEntries(value) {
296
+ return value && typeof value === "object"
297
+ ? value
298
+ : { default: value };
299
+ }
300
+
301
+ function replaceReloadedExports(previousExports, nextExports) {
302
+ if (!previousExports) return;
303
+
304
+ const oldExports = exportEntries(previousExports);
305
+ const newExports = exportEntries(nextExports);
306
+
307
+ for (const name of Object.keys(oldExports)) {
308
+ const oldExport = oldExports[name];
309
+ const newExport = newExports[name];
310
+
311
+ if (
312
+ typeof oldExport === "function" &&
313
+ typeof newExport === "function"
314
+ ) {
315
+ Lilact.replaceAllComponentInstances(oldExport, newExport);
316
+ }
317
+ }
286
318
  }
287
319
 
288
320
 
289
321
  export function run(
290
- jsx,
291
- path = `InlineJSX-${++Lilact.eval_num}`,
292
- options = {}
322
+ jsx,
323
+ path = `InlineJSX-${++Lilact.eval_num}`,
324
+ options = {}
293
325
  ) {
294
326
  let module = required_scripts[path];
295
327
 
296
- /*
297
- * A module may already have been created by require() while its source is
298
- * being fetched. Reuse that module so the request and its exports remain
299
- * associated with the same entry.
300
- */
328
+ const previousExports =
329
+ module?.loaded
330
+ ? module.exports
331
+ : undefined;
332
+
301
333
  if (!module) {
302
334
  module = createModule(path, {
303
335
  code: jsx,
304
336
  });
305
337
  } else {
306
- /*
307
- * Preserve module.request. It may represent the request that led to this
308
- * run() call, and clearing it here would make retry/deduplication logic
309
- * fragile.
310
- */
311
338
  module.path = path;
312
339
  module.code = String(jsx);
313
340
  module.mappings = [];
@@ -330,14 +357,16 @@ export function run(
330
357
  produceCJS: true,
331
358
  blocksInfo: Lilact.blocksInfo,
332
359
  });
333
- }
334
- catch (value) {
360
+ } catch (value) {
335
361
  const error = asError(value);
362
+
336
363
  error.fileName ??= path;
337
- error.lilact_source ??= {path};
364
+ error.lilact_source ??= { path };
338
365
  error.sourcePhase = "transpile";
366
+
339
367
  module.error = error;
340
368
  Lilact.error = error;
369
+
341
370
  throw error;
342
371
  }
343
372
 
@@ -345,25 +374,28 @@ export function run(
345
374
  Lilact.scanBlockLabels(processed, path);
346
375
  }
347
376
 
348
- /*
349
- * sourceURL helps when the browser includes eval locations in its stack.
350
- * It is not used as the authoritative source; the catch block below is.
351
- */
352
-
353
377
  try {
354
378
  globalThis.Lilact = Lilact;
355
379
  globalThis.createComponent = Lilact.createComponent;
356
380
  globalThis.Fragment = Lilact.Fragment;
357
381
 
358
382
  const result = eval(processed);
383
+
359
384
  module.loaded = true;
360
385
 
361
- return isEmpty(module.exports) ? result : module.exports;
362
- }
363
- catch (value) {
386
+ const nextExports = isEmpty(module.exports)
387
+ ? result
388
+ : module.exports;
389
+
390
+ replaceReloadedExports(previousExports, nextExports);
391
+
392
+ return nextExports;
393
+ } catch (value) {
364
394
  const error = report(value, path);
395
+
365
396
  error.sourcePhase ??= "runtime";
366
397
  module.error = error;
398
+
367
399
  throw error;
368
400
  }
369
401
  }