@dinoreic/fez 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/LICENSE +30 -0
- package/README.md +638 -0
- package/dist/fez.js +28 -0
- package/dist/fez.js.map +7 -0
- package/dist/rollup.js +3 -0
- package/dist/rollup.js.map +7 -0
- package/package.json +60 -0
- package/src/fez/compile.js +195 -0
- package/src/fez/connect.js +217 -0
- package/src/fez/instance.js +582 -0
- package/src/fez/lib/global-state.js +157 -0
- package/src/fez/lib/n.js +65 -0
- package/src/fez/lib/template.js +119 -0
- package/src/fez/root.js +465 -0
- package/src/fez/vendor/gobber.js +8 -0
- package/src/fez/vendor/idiomorph.js +860 -0
- package/src/fez.js +73 -0
- package/src/rollup.js +31 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// Global state manager with automatic component subscription
|
|
2
|
+
//
|
|
3
|
+
// Components access state via this.globalState proxy which automatically:
|
|
4
|
+
// - Registers component as listener when reading a value
|
|
5
|
+
// - Notifies component when that value changes
|
|
6
|
+
// - Calls onGlobalStateChange(key, value) if defined, then render()
|
|
7
|
+
//
|
|
8
|
+
// Example usage:
|
|
9
|
+
//
|
|
10
|
+
// class Counter extends FezBase {
|
|
11
|
+
// increment() {
|
|
12
|
+
// this.globalState.count = (this.globalState.count || 0) + 1
|
|
13
|
+
// }
|
|
14
|
+
//
|
|
15
|
+
// onGlobalStateChange(key, value) {
|
|
16
|
+
// console.log(`State ${key} changed to ${value}`)
|
|
17
|
+
// }
|
|
18
|
+
//
|
|
19
|
+
// render() {
|
|
20
|
+
// return `<button onclick="fez.increment()">
|
|
21
|
+
// Count: ${this.globalState.count || 0}
|
|
22
|
+
// </button>`
|
|
23
|
+
// }
|
|
24
|
+
// }
|
|
25
|
+
//
|
|
26
|
+
// External access:
|
|
27
|
+
// Fez.state.set('count', 10)
|
|
28
|
+
// Fez.state.get('count') // 10
|
|
29
|
+
|
|
30
|
+
const GlobalState = {
|
|
31
|
+
data: {},
|
|
32
|
+
listeners: new Map(), // key -> Set of components
|
|
33
|
+
subscribers: new Map(), // key -> Set of functions (for subscribe method)
|
|
34
|
+
globalSubscribers: new Set(), // Set of functions that listen to all changes
|
|
35
|
+
|
|
36
|
+
notify(key, value, oldValue) {
|
|
37
|
+
Fez.log(`Global state change for ${key}: ${value} (from ${oldValue})`)
|
|
38
|
+
|
|
39
|
+
// Notify component listeners
|
|
40
|
+
const listeners = this.listeners.get(key)
|
|
41
|
+
if (listeners) {
|
|
42
|
+
listeners.forEach(comp => {
|
|
43
|
+
if (comp.isConnected) {
|
|
44
|
+
comp.onGlobalStateChange(key, value, oldValue)
|
|
45
|
+
comp.render()
|
|
46
|
+
} else {
|
|
47
|
+
listeners.delete(comp)
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Notify key-specific subscribers
|
|
53
|
+
const subscribers = this.subscribers.get(key)
|
|
54
|
+
if (subscribers) {
|
|
55
|
+
subscribers.forEach(func => {
|
|
56
|
+
try {
|
|
57
|
+
func(value, oldValue, key)
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error(`Error in subscriber for key ${key}:`, error)
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Notify global subscribers
|
|
65
|
+
this.globalSubscribers.forEach(func => {
|
|
66
|
+
try {
|
|
67
|
+
func(key, value, oldValue)
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error(`Error in global subscriber:`, error)
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
createProxy(component) {
|
|
75
|
+
return new Proxy({}, {
|
|
76
|
+
get: (target, key) => {
|
|
77
|
+
// Skip if already listening to this key
|
|
78
|
+
component._globalStateKeys ||= new Set()
|
|
79
|
+
if (!component._globalStateKeys.has(key)) {
|
|
80
|
+
component._globalStateKeys.add(key)
|
|
81
|
+
|
|
82
|
+
if (!this.listeners.has(key)) {
|
|
83
|
+
this.listeners.set(key, new Set())
|
|
84
|
+
}
|
|
85
|
+
this.listeners.get(key).add(component)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return this.data[key]
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
set: (target, key, value) => {
|
|
92
|
+
const oldValue = this.data[key]
|
|
93
|
+
if (oldValue !== value) {
|
|
94
|
+
this.data[key] = value
|
|
95
|
+
this.notify(key, value, oldValue)
|
|
96
|
+
}
|
|
97
|
+
return true
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
// Direct methods for use outside components
|
|
103
|
+
set(key, value) {
|
|
104
|
+
const oldValue = this.data[key]
|
|
105
|
+
if (oldValue !== value) {
|
|
106
|
+
this.data[key] = value
|
|
107
|
+
this.notify(key, value, oldValue)
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
get(key) {
|
|
112
|
+
return this.data[key]
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
// Execute function for each component listening to a key
|
|
116
|
+
forEach(key, func) {
|
|
117
|
+
const listeners = this.listeners.get(key)
|
|
118
|
+
if (listeners) {
|
|
119
|
+
listeners.forEach(comp => {
|
|
120
|
+
if (comp.isConnected) {
|
|
121
|
+
func(comp)
|
|
122
|
+
} else {
|
|
123
|
+
listeners.delete(comp)
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
// Subscribe to state changes
|
|
130
|
+
// Usage: Fez.state.subscribe(func) - listen to all changes
|
|
131
|
+
// Fez.state.subscribe(key, func) - listen to specific key changes
|
|
132
|
+
subscribe(keyOrFunc, func) {
|
|
133
|
+
if (typeof keyOrFunc === 'function') {
|
|
134
|
+
// subscribe(func) - global subscription
|
|
135
|
+
this.globalSubscribers.add(keyOrFunc)
|
|
136
|
+
return () => this.globalSubscribers.delete(keyOrFunc)
|
|
137
|
+
} else {
|
|
138
|
+
// subscribe(key, func) - key-specific subscription
|
|
139
|
+
const key = keyOrFunc
|
|
140
|
+
if (!this.subscribers.has(key)) {
|
|
141
|
+
this.subscribers.set(key, new Set())
|
|
142
|
+
}
|
|
143
|
+
this.subscribers.get(key).add(func)
|
|
144
|
+
return () => {
|
|
145
|
+
const keySubscribers = this.subscribers.get(key)
|
|
146
|
+
if (keySubscribers) {
|
|
147
|
+
keySubscribers.delete(func)
|
|
148
|
+
if (keySubscribers.size === 0) {
|
|
149
|
+
this.subscribers.delete(key)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export default GlobalState
|
package/src/fez/lib/n.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Exposes node building method, that gets node name, attrs and body.
|
|
2
|
+
// n('span', {id: id}), n('.foo', {id: id}, body), n('.foo', {id: id}, [...])
|
|
3
|
+
// * you can switch places for attrs and body, and body can be list of nodes
|
|
4
|
+
// * n('.foo.bar') -> n('div', { class: 'foo bar' })
|
|
5
|
+
//
|
|
6
|
+
// copyright @dux, 2024
|
|
7
|
+
// Licence MIT
|
|
8
|
+
|
|
9
|
+
export default function n(name, attrs = {}, data) {
|
|
10
|
+
if (typeof attrs === 'string') {
|
|
11
|
+
[attrs, data] = [data, attrs]
|
|
12
|
+
attrs ||= {}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (attrs instanceof Node) {
|
|
16
|
+
data = attrs
|
|
17
|
+
attrs = {}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (Array.isArray(name)) {
|
|
21
|
+
data = name
|
|
22
|
+
name = 'div'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (typeof attrs !== 'object' || Array.isArray(attrs)) {
|
|
26
|
+
data = attrs
|
|
27
|
+
attrs = {}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (name.includes('.')) {
|
|
31
|
+
const parts = name.split('.')
|
|
32
|
+
name = parts.shift() || 'div'
|
|
33
|
+
const c = parts.join(' ');
|
|
34
|
+
if (attrs.class) {
|
|
35
|
+
attrs.class += ` ${c}`;
|
|
36
|
+
} else {
|
|
37
|
+
attrs.class = c
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const node = document.createElement(name);
|
|
42
|
+
|
|
43
|
+
for (const [k, v] of Object.entries(attrs)) {
|
|
44
|
+
if (typeof v === 'function') {
|
|
45
|
+
node[k] = v.bind(this)
|
|
46
|
+
} else {
|
|
47
|
+
const value = String(v).replaceAll('fez.', this.fezHtmlRoot);
|
|
48
|
+
node.setAttribute(k, value)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (data) {
|
|
53
|
+
if (Array.isArray(data)) {
|
|
54
|
+
for (const n of data) {
|
|
55
|
+
node.appendChild(n)
|
|
56
|
+
}
|
|
57
|
+
} else if (data instanceof Node) {
|
|
58
|
+
node.appendChild(data)
|
|
59
|
+
} else {
|
|
60
|
+
node.innerHTML = String(data)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return node
|
|
65
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
function parseBlock(data, ifStack) {
|
|
2
|
+
data = data
|
|
3
|
+
.replace(/^#?raw/, '@html')
|
|
4
|
+
.replace(/^#?html/, '@html')
|
|
5
|
+
|
|
6
|
+
// Handle #if directive
|
|
7
|
+
if (data.startsWith('#if') || data.startsWith('if')) {
|
|
8
|
+
ifStack.push(false)
|
|
9
|
+
data = data.replace(/^#?if/, '')
|
|
10
|
+
return `\${ ${data} ? \``
|
|
11
|
+
}
|
|
12
|
+
else if (data.startsWith('#unless') || data.startsWith('unless')) {
|
|
13
|
+
ifStack.push(false)
|
|
14
|
+
data = data.replace(/^#?unless/, '')
|
|
15
|
+
return `\${ !(${data}) ? \``
|
|
16
|
+
}
|
|
17
|
+
else if (data == '/block') {
|
|
18
|
+
return '`) && \'\'}'
|
|
19
|
+
}
|
|
20
|
+
else if (data.startsWith('#for') || data.startsWith('for')) {
|
|
21
|
+
data = data.replace(/^#?for/, '')
|
|
22
|
+
const el = data.split(' in ', 2)
|
|
23
|
+
return '${' + el[1] + '.map((' + el[0] + ')=>`'
|
|
24
|
+
}
|
|
25
|
+
else if (data.startsWith('#each') || data.startsWith('each')) {
|
|
26
|
+
data = data.replace(/^#?each/, '')
|
|
27
|
+
const el = data.split(' as ', 2)
|
|
28
|
+
return '${' + el[0] + '.map((' + el[1] + ')=>`'
|
|
29
|
+
}
|
|
30
|
+
else if (data == ':else' || data == 'else') {
|
|
31
|
+
ifStack[ifStack.length - 1] = true
|
|
32
|
+
return '` : `'
|
|
33
|
+
}
|
|
34
|
+
else if (data == '/if' || data == '/unless') {
|
|
35
|
+
return ifStack.pop() ? '`}' : '` : ``}'
|
|
36
|
+
}
|
|
37
|
+
else if (data == '/for' || data == '/each') {
|
|
38
|
+
return '`).join("")}'
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
const prefix = '@html '
|
|
42
|
+
|
|
43
|
+
if (data.startsWith(prefix)) {
|
|
44
|
+
data = data.replace(prefix, '')
|
|
45
|
+
} else {
|
|
46
|
+
data = `Fez.htmlEscape(${data})`
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return '${' + data + '}'
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// let tpl = createTemplate(string)
|
|
54
|
+
// tpl({ ... this state ...})
|
|
55
|
+
export default function createTemplate(text, opts = {}) {
|
|
56
|
+
const ifStack = []
|
|
57
|
+
|
|
58
|
+
// some templating engines, as GoLangs use {{ for templates. Allow usage of [[ for fez
|
|
59
|
+
text = text
|
|
60
|
+
.replaceAll('[[', '{{')
|
|
61
|
+
.replaceAll(']]', '}}')
|
|
62
|
+
|
|
63
|
+
text = text.replace(/(\w+)=\{\{\s*(.*?)\s*\}\}([\s>])/g, (match, p1, p2, p3) => {
|
|
64
|
+
return `${p1}="{`+`{ ${p2} }`+`}"${p3}`
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
// {{block foo}} ... {{/block}}
|
|
68
|
+
// {{block:foo}}
|
|
69
|
+
const blocks = {}
|
|
70
|
+
text = text.replace(/\{\{block\s+(\w+)\s*\}\}([^§]+)\{\{\/block\}\}/g, (_, name, block) => {
|
|
71
|
+
blocks[name] = block
|
|
72
|
+
return ''
|
|
73
|
+
})
|
|
74
|
+
text = text.replace(/\{\{block:([\w\-]+)\s*\}\}/g, (_, name) => blocks[name] || `block:${name}?`)
|
|
75
|
+
|
|
76
|
+
// {{#for el in list }}}}
|
|
77
|
+
// <ui-comment :comment="el"></ui-comment>
|
|
78
|
+
// -> :comment="{{ JSON.stringify(el) }}"
|
|
79
|
+
// skip attr="foo.bar"
|
|
80
|
+
text = text.replace(/:(\w+)="([\w\.\[\]]+)"/, (_, m1, m2) => {
|
|
81
|
+
return `:${m1}=Fez.store.delete({{ Fez.store.set(${m2}) }})`
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
let result = text.replace(/{{(.*?)}}/g, (_, content) => {
|
|
85
|
+
content = content.replaceAll('`', '`')
|
|
86
|
+
|
|
87
|
+
content = content
|
|
88
|
+
.replaceAll('<', '<')
|
|
89
|
+
.replaceAll('>', '>')
|
|
90
|
+
.replaceAll('&', '&')
|
|
91
|
+
const parsedData = parseBlock(content, ifStack);
|
|
92
|
+
|
|
93
|
+
return parsedData
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
result = '`' + result.trim() + '`'
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const funcBody = `const fez = this;
|
|
100
|
+
with (this) {
|
|
101
|
+
return ${result}
|
|
102
|
+
}
|
|
103
|
+
`
|
|
104
|
+
const tplFunc = new Function(funcBody);
|
|
105
|
+
const outFunc = (o) => {
|
|
106
|
+
try {
|
|
107
|
+
return tplFunc.bind(o)()
|
|
108
|
+
} catch(e) {
|
|
109
|
+
e.message = `FEZ template runtime error: ${e.message}\n\nTemplate source: ${result}`
|
|
110
|
+
console.error(e)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return outFunc
|
|
114
|
+
} catch(e) {
|
|
115
|
+
e.message = `FEZ template compile error: ${e.message}Template source:\n${result}`
|
|
116
|
+
console.error(e)
|
|
117
|
+
return ()=>Fez.error(`Template Compile Error`, true)
|
|
118
|
+
}
|
|
119
|
+
}
|