@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 +84 -0
- package/comp/div-box.mjs +20 -0
- package/comp/new-form.mjs +39 -0
- package/index.html +17 -0
- package/index.mjs +192 -0
- package/info.json +4 -0
- package/package.json +6 -0
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
|
+
```
|
package/comp/div-box.mjs
ADDED
|
@@ -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