airx 0.2.2 → 0.3.0-alpha.1

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.
@@ -4,14 +4,14 @@
4
4
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.airx = {}));
5
5
  })(this, (function (exports) { 'use strict';
6
6
 
7
- const globalContext$1 = {
7
+ const globalContext = {
8
8
  current: null
9
9
  };
10
10
  function useContext() {
11
- if (globalContext$1.current == null) {
11
+ if (globalContext.current == null) {
12
12
  throw new Error('Unable to find a valid component context');
13
13
  }
14
- return globalContext$1.current;
14
+ return globalContext.current;
15
15
  }
16
16
  const onMounted = (listener) => {
17
17
  return useContext().onMounted(listener);
@@ -26,85 +26,65 @@
26
26
  return useContext().provide(key, value);
27
27
  };
28
28
 
29
- const isDev = localStorage.getItem('airx-dev');
29
+ const isPrintLogs = typeof process != 'undefined'
30
+ && process?.env?.NODE_ENV === 'development'
31
+ && process?.env?.AIRX_DEBUG === 'true';
30
32
  function createLogger(name) {
31
33
  function getPrintPrefix() {
32
34
  const date = new Date().toLocaleString();
33
35
  return `[${date}][${name}]`;
34
36
  }
35
37
  function debug(...args) {
36
- if (isDev)
38
+ if (isPrintLogs)
37
39
  console.log(getPrintPrefix(), ...args);
38
40
  }
39
41
  return { debug };
40
42
  }
41
43
 
42
- const airxElementSymbol = Symbol('airx-element');
43
- const airxReactiveDependenciesSymbol = Symbol('airx-dependencies');
44
-
45
- /** FIXME: 污染全局总是不好的 */
46
- const globalContext = {
47
- dependencies: new Set()
48
- };
49
- function createCollector() {
50
- const newDependencies = new Set();
51
- return {
52
- clear: () => newDependencies.clear(),
53
- complete: () => [...newDependencies.values()],
54
- collect: (process) => {
55
- const beforeDeps = globalContext.dependencies;
56
- globalContext.dependencies = newDependencies;
57
- const result = process();
58
- globalContext.dependencies = beforeDeps;
59
- return result;
60
- }
61
- };
62
- }
63
- function triggerRef(ref) {
64
- requestAnimationFrame(() => {
65
- const deps = Reflect.get(ref, airxReactiveDependenciesSymbol);
66
- for (const dep of deps) {
67
- dep();
68
- }
69
- });
44
+ let firstSignal = undefined;
45
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
46
+ const globalNS = (function () {
47
+ // the only reliable means to get the global object is
48
+ // `Function('return this')()`
49
+ // However, this causes CSP violations in Chrome apps.
50
+ if (typeof self !== 'undefined') {
51
+ return self;
52
+ }
53
+ if (typeof window !== 'undefined') {
54
+ return window;
55
+ }
56
+ if (typeof global !== 'undefined') {
57
+ return global;
58
+ }
59
+ throw new Error('unable to locate global object');
60
+ })();
61
+ // 通过函数包装来延迟加载 Signal
62
+ // 这对使用 Polyfill 的应用来说更友好
63
+ function getSignal() {
64
+ const globalSignal = globalNS['Signal'];
65
+ if (globalSignal == null)
66
+ throw new Error('Signal is undefined');
67
+ if (firstSignal == null)
68
+ firstSignal = globalSignal;
69
+ if (firstSignal !== globalSignal)
70
+ throw new Error('Signal have multiple instances');
71
+ return globalSignal;
70
72
  }
71
- function createRefObject(value) {
72
- const object = Object.create({ value });
73
- Reflect.defineProperty(object, airxReactiveDependenciesSymbol, {
74
- configurable: false,
75
- enumerable: false,
76
- writable: true,
77
- value: new Set()
78
- });
79
- return object;
73
+ function createWatch(notify) {
74
+ const signal = getSignal();
75
+ return new signal.subtle.Watcher(notify);
80
76
  }
81
- function watchSignal(ref, listener) {
82
- const deps = Reflect.get(ref, airxReactiveDependenciesSymbol);
83
- deps.add(listener);
84
- return () => { deps.delete(listener); };
77
+ function createComputed(computation, options) {
78
+ const signal = getSignal();
79
+ return new signal.Computed(computation, options);
85
80
  }
86
- function createSignal(obj) {
87
- const ref = createRefObject(obj);
88
- if (!globalContext.dependencies.has(ref)) {
89
- globalContext.dependencies.add(ref);
90
- }
91
- let value = ref.value;
92
- Reflect.defineProperty(ref, 'value', {
93
- get() {
94
- if (!globalContext.dependencies.has(ref)) {
95
- globalContext.dependencies.add(ref);
96
- }
97
- return value;
98
- },
99
- set(newValue) {
100
- value = newValue;
101
- triggerRef(ref);
102
- return value;
103
- }
104
- });
105
- return ref;
81
+ function isState(target) {
82
+ const signal = getSignal();
83
+ return target instanceof signal.State;
106
84
  }
107
85
 
86
+ const airxElementSymbol = Symbol('airx-element');
87
+
108
88
  /**
109
89
  * createElement 是用于创建 AirxElement 的工具函数
110
90
  */
@@ -183,7 +163,15 @@
183
163
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
184
164
  provide(key, value) {
185
165
  this.providedMap.set(key, value);
186
- return v => this.providedMap.set(key, v);
166
+ return v => {
167
+ if (typeof v === 'function') {
168
+ const old = this.providedMap.get(key);
169
+ const func = v;
170
+ this.providedMap.set(key, func(old));
171
+ return;
172
+ }
173
+ this.providedMap.set(key, v);
174
+ };
187
175
  }
188
176
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
189
177
  inject(key) {
@@ -378,9 +366,11 @@
378
366
  // 添加 ref 处理
379
367
  if ('ref' in instance.memoProps) {
380
368
  context.onMounted(() => {
381
- if (instance.domRef) { // 如果组件有自己的 dom
382
- instance.memoProps.ref.value = instance.domRef;
383
- return () => instance.memoProps.ref.value = null;
369
+ const ref = instance.memoProps.ref;
370
+ // 如果组件有自己的 dom 并且 ref state
371
+ if (instance.domRef && isState(ref)) {
372
+ ref.set(instance.domRef);
373
+ return () => ref.set(undefined);
384
374
  }
385
375
  });
386
376
  }
@@ -445,32 +435,44 @@
445
435
  }
446
436
  // airx 组件
447
437
  if (typeof element?.type === 'function') {
448
- const collector = createCollector();
449
- if (instance.render == null) {
438
+ if (instance.signalWatcher == null) {
439
+ // Watch 是惰性的,只有当 Signal 被读取时才会触发 --!
440
+ const signalWatcher = createWatch(async () => {
441
+ instance.needReRender = true;
442
+ onUpdateRequire?.(instance);
443
+ queueMicrotask(() => {
444
+ signalWatcher.watch();
445
+ const paddings = signalWatcher.getPending();
446
+ for (const padding of paddings)
447
+ padding.get();
448
+ });
449
+ });
450
+ instance.signalWatcher = signalWatcher;
451
+ instance.context.addDisposer(() => signalWatcher.unwatch());
452
+ }
453
+ if (instance.childrenRender == null) {
450
454
  const component = element.type;
451
- const beforeContext = globalContext$1.current;
452
- globalContext$1.current = instance.context.getSafeContext();
453
- const componentReturnValue = collector.collect(() => component(instance.memoProps));
455
+ const beforeContext = globalContext.current;
456
+ globalContext.current = instance.context.getSafeContext();
457
+ const componentReturnValue = component(instance.memoProps);
454
458
  if (typeof componentReturnValue !== 'function') {
455
459
  throw new Error('Component must return a render function');
456
460
  }
457
- globalContext$1.current = beforeContext;
458
- instance.render = componentReturnValue;
459
- const children = collector.collect(() => instance.render?.());
461
+ globalContext.current = beforeContext;
462
+ instance.childrenRender = componentReturnValue;
463
+ const childrenComputed = createComputed(() => componentReturnValue());
464
+ instance.signalWatcher.watch(childrenComputed);
465
+ const children = childrenComputed.get();
460
466
  reconcileChildren(pluginContext, instance, childrenAsElements(children));
461
467
  }
462
468
  if (instance.needReRender) {
463
- const children = collector.collect(() => instance.render?.());
469
+ // 这里有个问题,如果是由于父组件导致的子组件渲染
470
+ // 直接使用 childrenComputed.get() 将读取到缓存值
471
+ // const children = instance.childrenRender?.()
472
+ const children = instance.childrenRender();
464
473
  reconcileChildren(pluginContext, instance, childrenAsElements(children));
465
474
  delete instance.needReRender;
466
475
  }
467
- // 处理依赖触发的更新
468
- collector.complete().forEach(ref => {
469
- instance.context.addDisposer(watchSignal(ref, () => {
470
- instance.needReRender = true;
471
- onUpdateRequire?.(instance);
472
- }));
473
- });
474
476
  }
475
477
  // 浏览器组件/标签
476
478
  if (typeof element?.type === 'string') {
@@ -675,7 +677,7 @@
675
677
  Object.keys(prevProps)
676
678
  .filter(isProperty)
677
679
  .filter(isGone(prevProps, nextProps))
678
- .forEach(name => dom.setAttribute(name, ''));
680
+ .forEach(name => dom.removeAttribute(name));
679
681
  // Set new or changed properties
680
682
  Object.keys(nextProps)
681
683
  .filter(isProperty)
@@ -1143,7 +1145,7 @@
1143
1145
  Object.keys(prevProps)
1144
1146
  .filter(isProperty)
1145
1147
  .filter(isGone(prevProps, nextProps))
1146
- .forEach(name => dom.setAttribute(name, ''));
1148
+ .forEach(name => dom.removeAttribute(name));
1147
1149
  // Set new or changed properties
1148
1150
  Object.keys(nextProps)
1149
1151
  .filter(isProperty)
@@ -1196,18 +1198,25 @@
1196
1198
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1197
1199
  function createApp(element) {
1198
1200
  const appContext = new PluginContext();
1201
+ const ensureAsElement = (element) => {
1202
+ if (typeof element === 'function') {
1203
+ return createElement(element, {});
1204
+ }
1205
+ return element;
1206
+ };
1199
1207
  const app = {
1200
1208
  plugin: (...plugins) => {
1201
1209
  appContext.registerPlugin(...plugins);
1202
1210
  return app;
1203
1211
  },
1204
1212
  mount: (container) => {
1205
- render(appContext, element, container);
1213
+ container.innerHTML = ''; // 先清空再说
1214
+ render(appContext, ensureAsElement(element), container);
1206
1215
  return app;
1207
1216
  },
1208
1217
  renderToHTML: () => {
1209
1218
  return new Promise(resolve => {
1210
- render$1(appContext, element, resolve);
1219
+ render$1(appContext, ensureAsElement(element), resolve);
1211
1220
  });
1212
1221
  }
1213
1222
  };
@@ -1218,12 +1227,10 @@
1218
1227
  exports.component = component;
1219
1228
  exports.createApp = createApp;
1220
1229
  exports.createElement = createElement;
1221
- exports.createSignal = createSignal;
1222
1230
  exports.inject = inject;
1223
1231
  exports.onMounted = onMounted;
1224
1232
  exports.onUnmounted = onUnmounted;
1225
1233
  exports.provide = provide;
1226
- exports.watchSignal = watchSignal;
1227
1234
 
1228
1235
  }));
1229
1236
  //# sourceMappingURL=index.js.map