@k3000/ce 0.0.1 → 0.1.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.
package/index.mjs CHANGED
@@ -1,192 +1,948 @@
1
- const getCurrentUrl = name => {
1
+ let dir = './comp'
2
+ let ext = 'mjs'
3
+
4
+ /**
5
+ * 读取模板字符串内变量集合
6
+ * @param {String} str
7
+ * @returns {Set<String>}
8
+ * @example
9
+ * getVariables('name: {{name}}, age: {{info.age}}')
10
+ * // {'name', 'info.age'}
11
+ */
12
+ let getVariables = str => {
13
+ // 1. 匹配所有 {{ ... }} 结构
14
+ // result: ["{{age + test()}}"]
15
+ const templates = str.match(/\{\{.+?}}/g);
16
+
17
+ if (!templates) return new Set();
18
+
19
+ const variables = new Set();
20
+ // 简单的 JS 关键字黑名单,避免把 true/null 当作变量
21
+ const keywords = new Set(['true', 'false', 'null', 'undefined', 'this', 'new', 'typeof', 'in', 'instanceof']);
22
+
23
+ templates.forEach(tpl => {
24
+ // 去掉外层的 {{ }},只保留内容: "age + test()"
25
+ const content = tpl.slice(2, -2);
26
+
27
+ // 2. 正则核心逻辑
28
+ // \b -> 单词边界,防止匹配到单词的一部分
29
+ // [a-zA-Z_$] -> 变量必须以字母、下划线或 $ 开头
30
+ // [\w$]* -> 后续可以是字母、数字、下划线或 $
31
+ // (?!\s*\() -> 【关键】负向先行断言:排除后面紧跟(可能是空格+)左括号的情况
32
+ // (?!\s*:) -> (可选) 排除对象键值对中的键 (如 { key: val })
33
+ const regex = /\b[a-zA-Z_$][\w$]*(?:\s*\.\s*[a-zA-Z_$][\w$]*)*(?!\s*\()/g;
34
+
35
+ const matches = content.match(regex);
36
+
37
+ if (matches) {
38
+ matches.forEach(item => {
39
+ // 过滤关键字
40
+ if (!keywords.has(item)) {
41
+ variables.add(item);
42
+ }
43
+ });
44
+ }
45
+ });
2
46
 
3
- if (document.currentScript) return new URL(document.currentScript.src)
47
+ // console.log('variables', variables)
4
48
 
5
- for (const script of document.scripts) {
49
+ return variables;
50
+ }
51
+ /**
52
+ * 创建一个临时的 node
53
+ * @returns {Text}
54
+ */
55
+ let createTmpNode = (data = '') => {
6
56
 
7
- if (!script.src) continue
57
+ return document.createTextNode('')
58
+ }
59
+ /**
60
+ *
61
+ * @param {String} code
62
+ * @returns {`with (scope) { return \`${*}\` \n}`}
63
+ * @example
64
+ * literalsCode('name: {{name}}')
65
+ * // 'with (scope) { return `name: ${name}` \n}'
66
+ */
67
+ let literalsCode = code => {
68
+
69
+ return `with (scope) {\n\treturn \`${code.replace(/{{(.*?)}}/g, (_, key) => `\${${key}}`)}\` \n}`
70
+ }
71
+ /**
72
+ *
73
+ * @param code
74
+ * @returns {`with (scope) { return ${*} \n}`}
75
+ * @example
76
+ * literalsCode('{{count() + 1}}')
77
+ * // 'with (scope) { return count() + 1 \n}'
78
+ */
79
+ let functionCode = code => {
80
+
81
+ return `with (scope) {\n\treturn ${code} \n}`
82
+ }
83
+ /**
84
+ * 处理实现<element each="{{[1,2,3]}}"></element>的循环
85
+ * 期望得到<element>1</element><element>2</element><element>3</element>
86
+ * @param {{
87
+ * ownerElement: Node
88
+ * nodeMap: Array
89
+ * mark: Node
90
+ * }} data
91
+ * @param {Array} array
92
+ * @returns {{
93
+ * ownerElement: Node
94
+ * nodeMap: Array
95
+ * mark: Node
96
+ * }}
97
+ */
98
+ let handleArray = (data, array) => {
99
+
100
+ const target = data.ownerElement
101
+
102
+ /**
103
+ * 第一次初始化好相关数据,后面用mark.parentNode去做替换,data.ownerElement变成克隆下来的快照,已经不是属性的归属element了。
104
+ */
105
+ if (!data.nodeMap) {
106
+
107
+ /**
108
+ * @type {[[*, Node]]}
109
+ */
110
+ data.nodeMap = []
111
+
112
+ data.mark = createTmpNode('数组结束标记')
113
+
114
+ target.parentNode.replaceChild(data.mark, target)
115
+
116
+ data.ownerElement = target.cloneNode(true)
117
+ }
8
118
 
9
- const url = new URL(script.src)
119
+ if (!Array.isArray(array)) return data
10
120
 
11
- if (url.pathname.includes(name)) return url
12
- }
121
+ const parent = data.mark.parentNode
13
122
 
14
- return {
15
- searchParams: {
16
- get() {
17
- return ''
18
- }
123
+ let i = 0
124
+
125
+ for (; i < array.length; i++) {
126
+
127
+ if (data.nodeMap[i] === undefined) {
128
+
129
+ const clone = target.cloneNode(true)
130
+
131
+ try {
132
+
133
+ array[i]._ = data.scope
134
+
135
+ } catch (e) { }
136
+
137
+ bindNode(array[i], clone, true)
138
+
139
+ parent.insertBefore(clone, data.mark)
140
+
141
+ data.nodeMap[i] = [array[i], clone]
142
+
143
+ continue
144
+ }
145
+
146
+ if (data.nodeMap[i][0] !== array[i]) {
147
+
148
+ const clone = target.cloneNode(true)
149
+
150
+ try {
151
+
152
+ array[i]._ = data.scope
153
+
154
+ } catch (e) { }
155
+
156
+ bindNode(array[i], clone, true)
157
+
158
+ parent.replaceChild(clone, data.nodeMap[i][1])
159
+
160
+ data.nodeMap[i] = [array[i], clone]
161
+
162
+ // continue
19
163
  }
20
164
  }
165
+
166
+ const index = i
167
+
168
+ for (; i < data.nodeMap.length; i++) {
169
+
170
+ parent.removeChild(data.nodeMap[i][1])
171
+ }
172
+
173
+ data.nodeMap.splice(index, data.nodeMap.length - index)
174
+
175
+ return data
176
+ }
177
+ /**
178
+ * 调用该方法完成可配置参数合方法的初始化
179
+ * @param options
180
+ * @example
181
+ * import { config } from '../index.mjs'
182
+ * config({
183
+ * dir: './app/comp',
184
+ * getVariables: () => {...}
185
+ * ...
186
+ * })
187
+ */
188
+ export const config = (options) => {
189
+
190
+ if (options.dir) {
191
+
192
+ dir = options.dir
193
+ }
194
+
195
+ if (options.ext) {
196
+
197
+ ext = options.ext
198
+ }
199
+
200
+ if (typeof options.getVariables === 'function') {
201
+
202
+ getVariables = options.getVariables
203
+ }
204
+
205
+ if (typeof options.createTmpNode === 'function') {
206
+
207
+ createTmpNode = options.createTmpNode
208
+ }
209
+
210
+ if (typeof options.literalsCode === 'function') {
211
+
212
+ literalsCode = options.literalsCode
213
+ }
214
+
215
+ if (typeof options.functionCode === 'function') {
216
+
217
+ functionCode = options.functionCode
218
+ }
219
+
220
+ if (typeof options.handleArray === 'function') {
221
+
222
+ handleArray = options.handleArray
223
+ }
21
224
  }
225
+ /**
226
+ * 获取自定义元素的名称,如果不只自定义元素返回undefined
227
+ * @param {Node} node
228
+ * @returns {string | undefined}
229
+ */
230
+ const getCEName = node => {
22
231
 
23
- const url = getCurrentUrl('custom-element.mjs')
24
- const params = url.searchParams
25
- const dir = params.get('dir') || '/comp'
26
- const ext = params.get('ext') || 'mjs'
27
- const prod = !params.get('dev')
232
+ if (node.tagName.includes('-')) {
28
233
 
29
- const tools = {
234
+ return node.tagName.toLowerCase()
235
+ }
30
236
 
31
- createTmpNode() {
237
+ if (node.hasAttribute('is')) {
32
238
 
33
- return window.document.createTextNode('')
239
+ return node.getAttribute('is')
34
240
  }
35
241
  }
242
+ /**
243
+ * 函数节流
244
+ * @param {Function} fn
245
+ * @param {Number} delay
246
+ * @returns {(function(...[*]): void)|*}
247
+ */
248
+ export const throttle = (fn, delay = 41) => {
36
249
 
37
- const style = document.createElement('style')
250
+ let timer
38
251
 
39
- style.innerHTML = `
40
- *:not(:defined) {
41
- display: none
252
+ return (...args) => {
253
+
254
+ clearTimeout(timer)
255
+
256
+ timer = setTimeout(() => fn(...args), delay)
257
+ }
42
258
  }
43
- `
44
259
 
45
- window.document.head.appendChild(style)
260
+ /**
261
+ * 实现对象属性变化更新相应节点的函数池
262
+ * @type {WeakMap<WeakKey, Map<String, Set<Function>>>}
263
+ */
264
+ const objectPool = new WeakMap
265
+ /**
266
+ * 避免重复处理自定义元素引入处理的节点池
267
+ * @type {WeakSet<Node>}
268
+ */
269
+ const importPool = new WeakSet
270
+ /**
271
+ * 记录监听属性变化更新相应函数的方法
272
+ * @param {Object} scope
273
+ * @param {String} code
274
+ * @param {Function} update
275
+ * @example
276
+ * addListener({name: 'test'}, 'name', () => {})
277
+ */
278
+ const addListener = (scope, code, update) => {
279
+
280
+ getVariables(code).forEach(variables => {
281
+
282
+ // 处理这种属性 {{user.info.name}} 达到监听info的name变化的效果
283
+ let key = '', obj = scope, index = 0, list = variables.split('.')
284
+
285
+ for ([index, key] of list.entries()) {
286
+
287
+ if (index === list.length - 1) {
288
+
289
+ break
290
+ }
291
+
292
+ if (obj === null || typeof obj !== 'object' || !Reflect.has(obj, key)) return
293
+
294
+ if (Array.isArray(obj[key]) && index === list.length - 2) break
295
+
296
+ obj = obj[key]
297
+ }
298
+
299
+ if (obj === null || typeof obj !== 'object') return
300
+
301
+ if (Object.getOwnPropertyDescriptor(obj, key) && !Object.getOwnPropertyDescriptor(obj, key).configurable) return
302
+
303
+ /**
304
+ * 如果没有记录该对象就新增对象、属性、更新函数的对应关系
305
+ * 如果记录了对象,没有记录属性,就添加属性、更新函数的对应关系
306
+ * 如果属性也存在了,那么就添加更新函数,因为是 Set 所以不用担心函数重复添加
307
+ */
308
+ if (objectPool.has(obj)) {
46
309
 
47
- const importPool = new Set
310
+ const map = objectPool.get(obj)
48
311
 
49
- const importComp = node => {
312
+ if (map.has(key)) {
313
+
314
+ map.get(key).add(update)
315
+
316
+ } else {
317
+
318
+ const set = new Set([update])
319
+
320
+ map.set(key, set)
321
+
322
+ let value = obj[key]
323
+
324
+ Object.defineProperty(obj, key, {
325
+
326
+ get: () => value,
327
+
328
+ set: (newValue) => {
329
+
330
+ if (value === newValue) return
331
+
332
+ value = newValue
333
+ // 数值变化就执行相应的更新函数
334
+ set.forEach(fn => fn())
335
+ }
336
+ })
337
+ }
338
+
339
+ } else {
340
+
341
+ const set = new Set([update])
342
+
343
+ objectPool.set(obj, new Map([[key, set]]))
344
+
345
+ let value = obj[key]
346
+
347
+ Object.defineProperty(obj, key, {
348
+
349
+ get: () => value,
350
+
351
+ set: (newValue) => {
352
+
353
+ if (value === newValue) return
354
+
355
+ value = newValue
356
+ // 数值变化就执行相应的更新函数
357
+ set.forEach(fn => fn())
358
+ }
359
+ })
360
+ }
361
+ })
362
+ }
363
+ /**
364
+ * 用于记录自定义元素引入后的相关数据
365
+ */
366
+ const ceMap = Object.create(null)
367
+ /**
368
+ *
369
+ * @param {Node} node
370
+ * @param {String} name
371
+ */
372
+ const disposeCE = (node, name) => {
373
+
374
+ node.querySelectorAll('*:not(:defined)').forEach(node => importCE(node))
375
+ // 提前创建的 shadowRoot nodes,clone绑定对象
376
+ if (ceMap[name].shadowRoot) {
377
+
378
+ node.attachShadow({mode: 'open'})
379
+
380
+ node.shadowRoot.appendChild(ceMap[name].shadowRoot.create(node))
381
+ }
382
+ // 提前创建的 innerHTML nodes,clone 绑定对象
383
+ if (ceMap[name].innerHTML || ceMap[name].outerHTML) {
384
+ const clone = (ceMap[name].innerHTML || ceMap[name].outerHTML).create(node)
385
+ // 如果没有 shadowRoot 然后子节点里面有 slot 就把标签内的内容插入到 slot 的位置,物理插入不同于 shadowRoot 的逻辑插入
386
+ if (!ceMap[name].shadowRoot) {
387
+ const slots = Array.from(clone.querySelectorAll('slot'))
388
+ const slotMap = slots.reduce((prev, curr) => {
389
+ prev[curr.getAttribute('name') || ''] = curr
390
+ return prev
391
+ }, {})
392
+ const children = Array.from(node.childNodes)
393
+ for (const child of children) {
394
+ const slot = typeof child.getAttribute === "function" ? child.getAttribute('slot') || '' : ''
395
+ if (slotMap[slot]) {
396
+ slotMap[slot].parentNode.insertBefore(child, slotMap[slot])
397
+ }
398
+ }
399
+ for (const slot of slots) {
400
+ slot.parentNode.removeChild(slot)
401
+ }
402
+ }
403
+
404
+ if (ceMap[name].outerHTML) {
405
+
406
+ while (clone.firstChild) {
407
+
408
+ node.parentElement.insertBefore(clone.firstChild, node)
409
+ }
410
+
411
+ node.parentElement.removeChild(node)
412
+
413
+ } else {
414
+
415
+ node.appendChild(clone)
416
+ }
417
+ }
418
+ // 执行ready通知已经处理好 nodes
419
+ if (typeof node.ready === "function") {
420
+
421
+ node.ready()
422
+ }
423
+ }
424
+ let tmp = null
425
+ /**
426
+ * 导入自定义元素
427
+ * @param {Node} node
428
+ * @param {String} name
429
+ */
430
+ const importCE = (node, name = undefined) => {
431
+
432
+ name = name ?? getCEName(node)
50
433
 
51
434
  if (importPool.has(node)) return
52
435
 
436
+ importPool.add(node)
437
+ // 处理导入完成的自定义元素
438
+ if (customElements.get(name) !== undefined) {
439
+
440
+ disposeCE(node, name)
441
+
442
+ return
443
+ }
444
+
53
445
  // autonomous custom elements
54
446
  const ace = node.nodeName.includes('-')
55
447
 
56
- const name = ace ? node.nodeName.toLowerCase() : node.attributes.is.value
448
+ let _dir = dir
449
+ // 处理组件目录
450
+ if (node.attributes.dir && node.attributes.dir.nodeValue) {
57
451
 
58
- if (window.customElements.get(name) !== undefined) return
452
+ const value = node.attributes.dir.nodeValue
59
453
 
60
- importPool.add(node)
454
+ node.removeAttribute('dir')
455
+
456
+ if (value.startsWith('/') || value.startsWith('./')) {
457
+ _dir = value
458
+ } else {
459
+ _dir = `${_dir}/${value}`
460
+ }
461
+ }
462
+
463
+ const path = `${_dir}/${name}.${ext}`
464
+
465
+ // console.log(path)
466
+
467
+ import(path).then(result => {
468
+ // 处理第一次导入的情况
469
+ if (customElements.get(name) === undefined) {
470
+
471
+ ceMap[name] = Object.create(null)
472
+ // 保存好 innerHTML、shadowRoot node
473
+ if (typeof result.innerHTML === 'string') {
474
+
475
+ ceMap[name].innerHTML = bind(result.innerHTML)
476
+ }
477
+
478
+ if (typeof result.outerHTML === 'string') {
479
+
480
+ ceMap[name].outerHTML = bind(result.outerHTML)
481
+ }
482
+
483
+ if (typeof result.shadowRoot === 'string') {
484
+
485
+ ceMap[name].shadowRoot = bind(result.shadowRoot)
486
+ }
487
+ // 注册自定义元素
488
+ if (ace) {
489
+
490
+ customElements.define(name, result.default)
491
+
492
+ } else {
61
493
 
62
- let tmpNode = null
494
+ customElements.define(name, result.default, {
63
495
 
64
- if (node.parentElement) {
496
+ extends: node.nodeName.toLowerCase()
497
+ })
498
+ }
499
+ }
500
+
501
+ disposeCE(node, name)
502
+ })
503
+ .catch(error => {
504
+ console.error(error)
505
+ })
506
+ }
507
+ /**
508
+ * 处理节点内的模板字符串 <div>1 + 1 = {{1 + 1}}</div>
509
+ * @param {Object} scope
510
+ * @param {Node} node
511
+ */
512
+ const disposeNode = (scope, node) => {
513
+
514
+ const value = node.nodeValue
515
+
516
+ if (value.match(/{{(.*?)}}/) === null) return
65
517
 
66
- tmpNode = tools.createTmpNode()
518
+ const f = new Function('scope', literalsCode(value))
67
519
 
68
- node.parentElement.replaceChild(tmpNode, node)
520
+ addListener(scope, value, throttle(() => node.nodeValue = f.call(node, scope)))
521
+
522
+ node.nodeValue = f.call(node, scope)
523
+ }
524
+ /**
525
+ * 处理属性的模板字符串,多种情况
526
+ * @param {Object} scope
527
+ * @param {Node} element
528
+ * @param {Attr} attr
529
+ * @param {String} code
530
+ */
531
+ const disposeAttrValue = (scope, element, attr, code) => {
532
+ // 普通情况:class="theme-{{mode}}"
533
+ if (!code.startsWith('{{') || !code.endsWith('}}') || code.slice(2).includes('{{')) {
534
+
535
+ const f = new Function('scope', literalsCode(code))
536
+
537
+ return () => attr.nodeValue = f.call(element, scope)
69
538
  }
70
539
 
71
- const path = `${dir}/${name}.${ext}`
540
+ const f = new Function('scope', functionCode(code.slice(2, -2)))
72
541
 
73
- import(path).then(result => {
542
+ return () => {
543
+
544
+ const result = f.call(element, scope)
545
+ // 三种情况,如:<div disable="{{disable}}"></div>
546
+ // true - <div disable></div>
547
+ // false - <div></div>
548
+ // "disable" - <div disable="disable"></div>
549
+ switch (result) {
550
+
551
+ case true:
552
+
553
+ attr.nodeValue = ''
554
+
555
+ element.attributes.setNamedItem(attr)
556
+
557
+ break
558
+
559
+ case false:
74
560
 
75
- if (window.customElements.get(name) === undefined) {
561
+ element.removeAttribute(attr.nodeName)
76
562
 
77
- result.default.prototype.connectedCallback = async function (qwe) {
563
+ break
78
564
 
79
- this.connectedCallback = undefined
565
+ default:
80
566
 
81
- const {promise, resolve} = Promise.withResolvers()
567
+ attr.nodeValue = result
82
568
 
83
- const dataFn = this.data
569
+ element.attributes.setNamedItem(attr)
570
+ }
571
+ }
572
+ }
573
+ /**
574
+ * 特殊情况处理属性的模板字符串,多种情况
575
+ * @param {Object} scope
576
+ * @param {Attr} attr
577
+ * @param {String} key
578
+ * @param {String} ceName
579
+ */
580
+ const disposeSpecialAttr = (scope, attr, key, ceName) => {
84
581
 
85
- this.data = () => promise
582
+ const ownerElement = attr.ownerElement
583
+ let name = attr.nodeName
584
+ // 处理属性的事件,是直接覆盖事件,不是 addEventListener
585
+ if (name.startsWith('on')) {
86
586
 
87
- let data = await getScopeData(this)
587
+ ownerElement.removeAttribute(name)
588
+ // 如果绑定的是函数,直接把函数绑定在事件上
589
+ if (!key.includes('(')) {
88
590
 
89
- if (typeof dataFn === "function") {
591
+ const list = key.split('.')
90
592
 
91
- data = await dataFn.call(this, data)
593
+ let k = '', obj = scope, index = 0
92
594
 
93
- } else if (dataFn !== undefined) {
595
+ for ([index, k] of list.entries()) {
94
596
 
95
- data = dataFn
597
+ if (index === list.length - 1) {
598
+
599
+ break
96
600
  }
97
601
 
98
- resolve(data)
602
+ if (obj !== null && typeof obj === 'object' && Reflect.has(obj, k)) {
603
+
604
+ obj = obj[k]
99
605
 
100
- if (typeof this.connected === "function") {
606
+ } else {
101
607
 
102
- this.connected(data)
608
+ obj = {}
609
+
610
+ break
103
611
  }
104
612
  }
105
613
 
106
- if (ace) {
614
+ if (typeof obj[k] === "function") {
107
615
 
108
- window.customElements.define(name, result.default)
616
+ ownerElement[name] = event => obj[k](event)
109
617
 
110
- } else {
618
+ return true
619
+ }
620
+ }
621
+ // 不是函数触发事件执行相应代码块
111
622
 
112
- window.customElements.define(name, result.default, {
623
+ const f = new Function('scope', 'event', functionCode(key))
113
624
 
114
- extends: node.nodeName.toLowerCase()
115
- })
625
+ ownerElement[name] = event => f.call(ownerElement, scope, event)
626
+
627
+ return true
628
+ }
629
+ // 动态把节点加入或者移除 DOM 树
630
+ if (name === 'connect') {
631
+
632
+ ownerElement.removeAttribute(name)
633
+
634
+ const f = new Function('scope', functionCode(key))
635
+
636
+ const tmpNode = createTmpNode('节点断开重连的标记')
637
+
638
+ const update = () => {
639
+
640
+ if (f(scope)) {
641
+
642
+ tmpNode.parentNode?.replaceChild(ownerElement, tmpNode)
643
+
644
+ } else {
645
+
646
+ ownerElement.parentNode?.replaceChild(tmpNode, ownerElement)
116
647
  }
117
648
  }
118
649
 
119
- if (tmpNode === null) return
650
+ // console.log(scope, attr.nodeValue, update)
120
651
 
121
- tmpNode.parentElement.replaceChild(node, tmpNode)
652
+ addListener(scope, attr.nodeValue, throttle(() => update()))
122
653
 
123
- })
124
- .catch(error => console.error(error))
125
- .finally(() => importPool.delete(node))
654
+ update()
655
+
656
+ return true
657
+ }
658
+ // 在节点位置按照数组循环渲染节点
659
+ if (name === 'each') {
660
+
661
+ ownerElement.removeAttribute(name)
662
+
663
+ let data = {ownerElement, scope}
664
+ // 如果绑定的直接就是对象的值,则对象的值改变会重新根据变化的数组渲染
665
+ if (Reflect.has(scope, key)) {
666
+
667
+ const update = () => data = handleArray(data, scope[key])
668
+
669
+ addListener(scope, attr.nodeValue, throttle(() => update()))
670
+
671
+ update()
672
+
673
+ return true
674
+ }
675
+
676
+ const f = new Function('scope', functionCode(key))
677
+
678
+ handleArray(data, f(scope))
679
+
680
+ return true
681
+ }
682
+ // 自定义元素的属性会以键值对的形式更新到自定义属性的对象里面
683
+ if (ceName) {
684
+
685
+ ownerElement.removeAttribute(name)
686
+
687
+ const f = new Function('scope', functionCode(key))
688
+
689
+ if (name.includes('-')) {
690
+
691
+ name = name.split('-').map((key, i) => key && i ? key[0].toUpperCase() + key.slice(1) : key).join('')
692
+ }
693
+
694
+ const update = () => {
695
+
696
+ const result = f(scope)
697
+
698
+ ownerElement[name] = typeof result === 'function' ? result.bind(scope) : result
699
+ }
700
+
701
+ // const update = () => ownerElement[name] = f(scope)
702
+
703
+ addListener(scope, attr.nodeValue, throttle(() => update()))
704
+
705
+ update()
706
+
707
+ return true
708
+ }
709
+
710
+ return false
126
711
  }
712
+ /**
713
+ * 处理节点属性
714
+ * @param {Object} scope
715
+ * @param {Attr} attr
716
+ * @param {String} name
717
+ */
718
+ const disposeAttr = (scope, attr, name) => {
719
+
720
+ const value = attr.nodeValue
721
+
722
+ if (value.match(/{{(.*?)}}/) === null) return
127
723
 
128
- for (const node of window.document.querySelectorAll(':not(:defined)')) {
724
+ // console.log('disposeAttr', attr, value)
725
+ // 如果属性绑定的是特殊值就按照特殊情况处理
726
+ if (value.startsWith('{{') && value.endsWith('}}') && disposeSpecialAttr(scope, attr, value.slice(2, -2), name)) return
129
727
 
130
- importComp(node)
728
+ const fn = disposeAttrValue(scope, attr.ownerElement, attr, value)
729
+
730
+ addListener(scope, value, throttle(() => fn()))
731
+
732
+ fn()
131
733
  }
734
+ /**
735
+ * 处理自身或者子 node 的属性或者节点内容
736
+ * @param scope
737
+ * @param node
738
+ * @param self
739
+ * @returns {*}
740
+ */
741
+ export const bindNode = (scope, node, self) => {
132
742
 
133
- new MutationObserver(mutations => {
743
+ const list = self ? [node] : Array.from(node.childNodes)
134
744
 
135
- for (const mutation of mutations) {
745
+ for (const n of list) {
136
746
 
137
- for (const node of mutation.addedNodes) {
747
+ if (n.attributes) {
138
748
 
139
- if (node.nodeType !== 1) continue
749
+ for (const attr of Array.from(n.attributes)) {
140
750
 
141
- if (node.attributes.is || node.nodeName.includes('-')) {
751
+ disposeAttr(scope, attr, getCEName(n))
142
752
 
143
- importComp(node)
753
+ if (attr.nodeName === 'each'
754
+ && attr.nodeValue.startsWith('{{')
755
+ && attr.nodeValue.endsWith('}}')) return node
144
756
  }
757
+ }
758
+
759
+ if (n.childNodes) {
760
+
761
+ bindNode(scope, n)
762
+ }
763
+
764
+ if (n.nodeType === 3 && n.nodeValue !== null) {
765
+
766
+ disposeNode(scope, n)
767
+ }
145
768
 
146
- for (const n of node.querySelectorAll(':not(:defined)')) {
769
+ if (n.nodeType === 1) {
147
770
 
148
- importComp(n)
771
+ const name = getCEName(n)
772
+
773
+ if (name) {
774
+
775
+ importCE(n, name)
149
776
  }
150
777
  }
151
778
  }
152
779
 
153
- }).observe(document, {
154
- subtree: true,
155
- childList: true,
156
- })
780
+ return node
781
+ }
782
+ /**
783
+ * 把节点字符串转化为节点对象
784
+ * @param html
785
+ * @returns {{create: function(*): *}}
786
+ */
787
+ export const bind = html => {
788
+
789
+ const content = document.createElement('template')
790
+
791
+ content.innerHTML = html
792
+
793
+ return {create: scope => bindNode(scope, document.importNode(content.content, true))}
794
+ }
795
+
796
+ const style = document.createElement('style')
157
797
 
158
- const getScopeData = async node => {
798
+ style.innerHTML = `
799
+ *:not(:defined) {
800
+ display: none
801
+ }
802
+ `
803
+ // 通过样式隐藏没有加载的自定义标签
804
+ document.head.appendChild(style)
159
805
 
160
- const f = []
806
+ // DOM 加载完成后集中处理没有加载的自定义元素
807
+ addEventListener('DOMContentLoaded', () => document.querySelectorAll('*:not(:defined)').forEach(node => importCE(node)))
161
808
 
162
- const attrs = Object.values(node.attributes).filter(attr => attr.name.startsWith(':'))
809
+ class EventBus {
163
810
 
164
- while (node.parentElement) {
811
+ value = undefined
812
+ /**
813
+ * @type {Map<Function, {once: Boolean, preValue: Boolean}>}
814
+ */
815
+ targets = new Map()
165
816
 
166
- node = node.parentElement
817
+ add(callback, options = Object.create(null)) {
167
818
 
168
- if (typeof node.data === "function") {
819
+ this.targets.set(callback, options)
169
820
 
170
- const dataFn = node.data
821
+ if (options.preValue && this.value !== undefined) {
171
822
 
172
- f.unshift(data => dataFn(data))
823
+ callback(...this.value)
173
824
  }
174
825
  }
175
826
 
176
- return Promise.resolve(f.reduce((d, f) => f(d), {})).then(data => {
827
+ remove(callback) {
177
828
 
178
- data = {...data}
829
+ this.targets.delete(callback)
830
+ }
179
831
 
180
- return Promise.all(
181
- attrs.map(({name, value}) => Promise.resolve(new Function(
182
- 'data', `with (data) {${value.match(/^return[ `~!@#$(-+<"{\/;'\[]/) ? value : `return ${value}`}}`
183
- )(data)).then(value => ({name, value})))
184
- )
185
- .then(attrs => {
186
- for (const {name, value} of attrs) {
187
- data[name.substring(1)] = value
188
- }
189
- return data
190
- })
191
- })
832
+ dispatch(...args) {
833
+
834
+ this.value = args
835
+
836
+ return Promise.allSettled(this.targets.entries().map(([callback, {once}]) => {
837
+
838
+ if (once) {
839
+
840
+ this.targets.delete(callback)
841
+ }
842
+
843
+ return callback(...args)
844
+ }))
845
+ }
192
846
  }
847
+
848
+ const eventBusObj = Object.create(null)
849
+ // eventBus 实现订阅和分发,订阅是能选择是否获取上一个分发内容,在事件产生后订阅的也能拿到分发的内容
850
+ const eventBus = Object.defineProperties(Object.create(null), {
851
+ add: {
852
+ value: (name, callback, options) => {
853
+
854
+ if (!Reflect.has(eventBusObj, name)) {
855
+
856
+ eventBusObj[name] = new EventBus()
857
+ }
858
+
859
+ eventBusObj[name].add(callback, options)
860
+ }
861
+ },
862
+ remove: {
863
+ value: (name, callback) => {
864
+
865
+ if (!Reflect.has(eventBusObj, name)) return
866
+
867
+ eventBusObj[name].remove(callback)
868
+ }
869
+ },
870
+ dispatch: {
871
+ value: (name, ...args) => {
872
+
873
+ if (!Reflect.has(eventBusObj, name)) {
874
+
875
+ eventBusObj[name] = new EventBus()
876
+ }
877
+
878
+ return eventBusObj[name].dispatch(...args)
879
+ }
880
+ }
881
+ })
882
+
883
+ /**
884
+ * @returns {{
885
+ * add: (name: String, callback: Function, options: Object) => void,
886
+ * remove: (name: String, callback: Function) => void,
887
+ * dispatch: (name: String, ...args) => void
888
+ * }}
889
+ */
890
+ export const useEvent = () => eventBus
891
+ /**
892
+ * 通过 ref 或者子节点
893
+ * @param {Node} node
894
+ * @returns {any}
895
+ */
896
+ export const useRef = node => {
897
+
898
+ const result = Object.create(null)
899
+
900
+ for (const n of node.querySelectorAll('[ref]')) {
901
+
902
+ result[n.attributes.ref.nodeValue] = n
903
+ }
904
+
905
+ return result
906
+ }
907
+ /**
908
+ * 把属性转化为简单的键值对对象
909
+ * @param node
910
+ * @returns {any}
911
+ */
912
+ export const useAttr = node => {
913
+
914
+ const result = Object.create(null)
915
+
916
+ for (const n of node.attributes) {
917
+
918
+ result[n.nodeName] = n.nodeValue
919
+ }
920
+
921
+ return result
922
+ }
923
+ /**
924
+ * @param {Object} target
925
+ * @returns {(function(*, boolean=): void)|*}
926
+ */
927
+ export const useWatch = target => (props, call = false) => {
928
+
929
+ for (const [key, f] of Object.entries(props)) {
930
+
931
+ if (typeof f !== 'function') continue
932
+
933
+ let value = target[key]
934
+
935
+ Object.defineProperty(target, key, {
936
+
937
+ get: () => value,
938
+ set: val => {
939
+
940
+ const result = f(val, value)
941
+
942
+ if (result !== false) return value = val
943
+ }
944
+ })
945
+
946
+ call && f(value, value)
947
+ }
948
+ }