@k3000/ce 0.0.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.
package/README.md ADDED
@@ -0,0 +1,84 @@
1
+ 用最新H5技术实现前端组件
2
+
3
+ ```
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <title>Title</title>
7
+ <script src="ce.mjs?dir=/comp&ext=mjs&dev=true"></script>
8
+ </head>
9
+ <body>
10
+ <div-box>
11
+ <span slot="title">标题</span>
12
+ <form is="new-form" get="info.json">
13
+ <input name="name"/>
14
+ <input name="age"/>
15
+ </form>
16
+ </div-box>
17
+ </body>
18
+ ```
19
+ /comp目录下
20
+ div-box.mjs
21
+ ```
22
+ export default class extends HTMLElement {
23
+
24
+ constructor() {
25
+
26
+ super()
27
+
28
+ this.attachShadow({ mode: 'open' });
29
+ this.shadowRoot.innerHTML = `
30
+ <style>
31
+ :host {
32
+ display: block;
33
+ background-color: #f0f0f0;
34
+ padding: 10px;
35
+ }
36
+ </style>
37
+ <h2><slot name="title"></slot></h2>
38
+ <slot></slot>
39
+ `;
40
+ }
41
+ }
42
+ ```
43
+ new-form.mjs
44
+ ```
45
+ export default class extends HTMLFormElement {
46
+ // connected前执行,此处处理好数据逻辑,入参是父级的数据
47
+ data(data) {
48
+
49
+ return {}
50
+ }
51
+ // node挂载时执行,入参是data()返回的数据
52
+ connected(data) {
53
+
54
+ if (this.attributes.get) {
55
+
56
+ this.getInfo(this.attributes.get.value)
57
+ }
58
+ }
59
+
60
+ find(name) {
61
+
62
+ for (const item of this) {
63
+
64
+ if (item.name === name) return item
65
+ }
66
+ }
67
+
68
+ async getInfo(url) {
69
+
70
+ const info = await fetch(url).then(res => res.json())
71
+
72
+ for (const [key, value] of Object.entries(info || {})) {
73
+
74
+ const item = this.find(key)
75
+
76
+ if (item) {
77
+
78
+ item.value = value
79
+ }
80
+ }
81
+ }
82
+ }
83
+
84
+ ```
@@ -0,0 +1,20 @@
1
+ export default class extends HTMLElement {
2
+
3
+ constructor() {
4
+
5
+ super()
6
+
7
+ this.attachShadow({ mode: 'open' });
8
+ this.shadowRoot.innerHTML = `
9
+ <style>
10
+ :host {
11
+ display: block;
12
+ background-color: #f0f0f0;
13
+ padding: 10px;
14
+ }
15
+ </style>
16
+ <h2><slot name="title"></slot></h2>
17
+ <slot></slot>
18
+ `;
19
+ }
20
+ }
@@ -0,0 +1,39 @@
1
+
2
+ export default class extends HTMLFormElement {
3
+
4
+ data() {
5
+
6
+ return {}
7
+ }
8
+
9
+ connected() {
10
+
11
+ if (this.attributes.get) {
12
+
13
+ this.getInfo(this.attributes.get.value)
14
+ }
15
+ }
16
+
17
+ find(name) {
18
+
19
+ for (const item of this) {
20
+
21
+ if (item.name === name) return item
22
+ }
23
+ }
24
+
25
+ async getInfo(url) {
26
+
27
+ const info = await fetch(url).then(res => res.json())
28
+
29
+ for (const [key, value] of Object.entries(info || {})) {
30
+
31
+ const item = this.find(key)
32
+
33
+ if (item) {
34
+
35
+ item.value = value
36
+ }
37
+ }
38
+ }
39
+ }
package/index.html ADDED
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Title</title>
6
+ <script src="index.mjs?dir=/comp&ext=mjs&dev=true"></script>
7
+ </head>
8
+ <body>
9
+ <div-box>
10
+ <span slot="title">标题</span>
11
+ <form is="new-form" get="info.json">
12
+ <input name="name"/>
13
+ <input name="age"/>
14
+ </form>
15
+ </div-box>
16
+ </body>
17
+ </html>
package/index.mjs ADDED
@@ -0,0 +1,192 @@
1
+ const getCurrentUrl = name => {
2
+
3
+ if (document.currentScript) return new URL(document.currentScript.src)
4
+
5
+ for (const script of document.scripts) {
6
+
7
+ if (!script.src) continue
8
+
9
+ const url = new URL(script.src)
10
+
11
+ if (url.pathname.includes(name)) return url
12
+ }
13
+
14
+ return {
15
+ searchParams: {
16
+ get() {
17
+ return ''
18
+ }
19
+ }
20
+ }
21
+ }
22
+
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')
28
+
29
+ const tools = {
30
+
31
+ createTmpNode() {
32
+
33
+ return window.document.createTextNode('')
34
+ }
35
+ }
36
+
37
+ const style = document.createElement('style')
38
+
39
+ style.innerHTML = `
40
+ *:not(:defined) {
41
+ display: none
42
+ }
43
+ `
44
+
45
+ window.document.head.appendChild(style)
46
+
47
+ const importPool = new Set
48
+
49
+ const importComp = node => {
50
+
51
+ if (importPool.has(node)) return
52
+
53
+ // autonomous custom elements
54
+ const ace = node.nodeName.includes('-')
55
+
56
+ const name = ace ? node.nodeName.toLowerCase() : node.attributes.is.value
57
+
58
+ if (window.customElements.get(name) !== undefined) return
59
+
60
+ importPool.add(node)
61
+
62
+ let tmpNode = null
63
+
64
+ if (node.parentElement) {
65
+
66
+ tmpNode = tools.createTmpNode()
67
+
68
+ node.parentElement.replaceChild(tmpNode, node)
69
+ }
70
+
71
+ const path = `${dir}/${name}.${ext}`
72
+
73
+ import(path).then(result => {
74
+
75
+ if (window.customElements.get(name) === undefined) {
76
+
77
+ result.default.prototype.connectedCallback = async function (qwe) {
78
+
79
+ this.connectedCallback = undefined
80
+
81
+ const {promise, resolve} = Promise.withResolvers()
82
+
83
+ const dataFn = this.data
84
+
85
+ this.data = () => promise
86
+
87
+ let data = await getScopeData(this)
88
+
89
+ if (typeof dataFn === "function") {
90
+
91
+ data = await dataFn.call(this, data)
92
+
93
+ } else if (dataFn !== undefined) {
94
+
95
+ data = dataFn
96
+ }
97
+
98
+ resolve(data)
99
+
100
+ if (typeof this.connected === "function") {
101
+
102
+ this.connected(data)
103
+ }
104
+ }
105
+
106
+ if (ace) {
107
+
108
+ window.customElements.define(name, result.default)
109
+
110
+ } else {
111
+
112
+ window.customElements.define(name, result.default, {
113
+
114
+ extends: node.nodeName.toLowerCase()
115
+ })
116
+ }
117
+ }
118
+
119
+ if (tmpNode === null) return
120
+
121
+ tmpNode.parentElement.replaceChild(node, tmpNode)
122
+
123
+ })
124
+ .catch(error => console.error(error))
125
+ .finally(() => importPool.delete(node))
126
+ }
127
+
128
+ for (const node of window.document.querySelectorAll(':not(:defined)')) {
129
+
130
+ importComp(node)
131
+ }
132
+
133
+ new MutationObserver(mutations => {
134
+
135
+ for (const mutation of mutations) {
136
+
137
+ for (const node of mutation.addedNodes) {
138
+
139
+ if (node.nodeType !== 1) continue
140
+
141
+ if (node.attributes.is || node.nodeName.includes('-')) {
142
+
143
+ importComp(node)
144
+ }
145
+
146
+ for (const n of node.querySelectorAll(':not(:defined)')) {
147
+
148
+ importComp(n)
149
+ }
150
+ }
151
+ }
152
+
153
+ }).observe(document, {
154
+ subtree: true,
155
+ childList: true,
156
+ })
157
+
158
+ const getScopeData = async node => {
159
+
160
+ const f = []
161
+
162
+ const attrs = Object.values(node.attributes).filter(attr => attr.name.startsWith(':'))
163
+
164
+ while (node.parentElement) {
165
+
166
+ node = node.parentElement
167
+
168
+ if (typeof node.data === "function") {
169
+
170
+ const dataFn = node.data
171
+
172
+ f.unshift(data => dataFn(data))
173
+ }
174
+ }
175
+
176
+ return Promise.resolve(f.reduce((d, f) => f(d), {})).then(data => {
177
+
178
+ data = {...data}
179
+
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
+ })
192
+ }
package/info.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "name": "test",
3
+ "age": 21
4
+ }
package/package.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "@k3000/ce",
3
+ "version": "0.0.1",
4
+ "description": "",
5
+ "main": "index.mjs"
6
+ }