@dinoreic/fez 0.2.0 → 0.3.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/README.md +154 -157
- package/dist/fez.js +18 -18
- package/dist/fez.js.map +4 -4
- package/dist/log.js +5 -0
- package/dist/log.js.map +7 -0
- package/package.json +17 -13
- package/src/fez/compile.js +70 -47
- package/src/fez/connect.js +98 -63
- package/src/fez/defaults.js +64 -0
- package/src/fez/instance.js +131 -123
- package/src/fez/lib/template.js +4 -0
- package/src/fez/root.js +33 -134
- package/src/fez/utility.js +184 -0
- package/src/fez.js +5 -37
- package/src/log.js +154 -0
- package/src/rollup.js +73 -22
- package/dist/rollup.js +0 -3
- package/dist/rollup.js.map +0 -7
package/README.md
CHANGED
|
@@ -29,9 +29,7 @@ It replaces modern JS frameworks by using native Autonomous Custom Elements to c
|
|
|
29
29
|
|
|
30
30
|
This article, [Web Components Will Replace Your Frontend Framework](https://www.dannymoerkerke.com/blog/web-components-will-replace-your-frontend-framework/), is from 2019. Join the future, ditch React, Angular and other never defined, always "evolving" monstrosities. Vanilla is the way :)
|
|
31
31
|
|
|
32
|
-
There is no some "internal state" that is by some magic reflected to DOM. No! All methods Fez use to manipulate DOM are just helpers around native DOM interface. Work on DOM raw, use
|
|
33
|
-
|
|
34
|
-
It great in combination with another widely used JS libs, as jQuery, Zepto, underscore of loDash.
|
|
32
|
+
There is no some "internal state" that is by some magic reflected to DOM. No! All methods Fez use to manipulate DOM are just helpers around native DOM interface. Work on DOM raw, use built in [node builder](https://github.com/dux/fez/blob/main/src/lib/n.js) or full template mapping with [morphing](https://github.com/bigskysoftware/idiomorph).
|
|
35
33
|
|
|
36
34
|
## How it works
|
|
37
35
|
|
|
@@ -49,25 +47,17 @@ Here's a simple counter component that demonstrates Fez's core features:
|
|
|
49
47
|
```html
|
|
50
48
|
<!-- Define a counter component in ex-counter.fez.html -->
|
|
51
49
|
<script>
|
|
50
|
+
// called when Fez node is connected to DOM
|
|
52
51
|
init() {
|
|
53
|
-
// called when Fez node is connected to DOM
|
|
54
52
|
this.MAX = 6
|
|
55
53
|
this.state.count = 0
|
|
56
54
|
}
|
|
57
55
|
|
|
58
|
-
onStateChange(key, value, oldValue) {
|
|
59
|
-
// called whenever this.state changes
|
|
60
|
-
console.log(`State ${key} changed from ${oldValue} to ${value}`)
|
|
61
|
-
if (key === 'count' && value >= this.MAX) {
|
|
62
|
-
console.log('Counter reached maximum!')
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
56
|
isMax() {
|
|
67
|
-
// is state is changed, template is re-rendered
|
|
68
57
|
return this.state.count >= this.MAX
|
|
69
58
|
}
|
|
70
59
|
|
|
60
|
+
// is state is changed, template is re-rendered
|
|
71
61
|
more() {
|
|
72
62
|
this.state.count += this.isMax() ? 0 : 1
|
|
73
63
|
}
|
|
@@ -153,12 +143,13 @@ This example showcases:
|
|
|
153
143
|
* **Powerful Template Engine** - Multiple syntaxes (`{{ }}` and `[[ ]]`), control flow (`#if`, `#unless`, `#for`, `#each`), and block templates
|
|
154
144
|
* **Reactive State Management** - Built-in reactive `state` object automatically triggers re-renders on property changes
|
|
155
145
|
* **DOM Morphing** - Uses [Idiomorph](https://github.com/bigskysoftware/idiomorph) for intelligent DOM updates that preserve element state and animations
|
|
146
|
+
* **Preserve DOM Elements** - Use `fez-keep="unique-key"` attribute to preserve DOM elements across re-renders (useful for animations, form inputs, or stateful elements)
|
|
156
147
|
* **Style Macros** - Define custom CSS shortcuts like `Fez.styleMacro('mobile', '@media (max-width: 768px)')` and use as `:mobile { ... }`
|
|
157
148
|
* **Scoped & Global Styles** - Components can define both scoped CSS (`:fez { ... }`) and global styles in the same component
|
|
158
149
|
|
|
159
150
|
### Developer Experience
|
|
160
151
|
|
|
161
|
-
* **Built-in Utilities** - Helpful methods like `formData()`, `setInterval()` (auto-cleanup), `
|
|
152
|
+
* **Built-in Utilities** - Helpful methods like `formData()`, `setInterval()` (auto-cleanup), `onWindowResize()`, and `nextTick()`
|
|
162
153
|
* **Two-Way Data Binding** - Use `fez-bind` directive for automatic form synchronization
|
|
163
154
|
* **Advanced Slot System** - Full `<slot />` support with event listener preservation
|
|
164
155
|
* **Publish/Subscribe** - Built-in pub/sub system for component communication
|
|
@@ -168,8 +159,8 @@ This example showcases:
|
|
|
168
159
|
|
|
169
160
|
### Performance & Integration
|
|
170
161
|
|
|
171
|
-
* **
|
|
172
|
-
* **
|
|
162
|
+
* **Optimized Rendering** - Batched microtask rendering for flicker-free component initialization
|
|
163
|
+
* **Smart DOM Updates** - Efficient DOM manipulation with minimal reflows
|
|
173
164
|
* **Built-in Fetch with Caching** - `Fez.fetch()` includes automatic response caching and JSON/FormData handling
|
|
174
165
|
* **Global Component Access** - Register components globally with `GLOBAL = 'ComponentName'` for easy access
|
|
175
166
|
* **Rich Lifecycle Hooks** - `init`, `onMount`, `beforeRender`, `afterRender`, `onDestroy`, `onPropsChange`, `onStateChange`, `onGlobalStateChange`
|
|
@@ -189,57 +180,9 @@ This example showcases:
|
|
|
189
180
|
### Fez Static Methods
|
|
190
181
|
|
|
191
182
|
```js
|
|
192
|
-
Fez('#foo')
|
|
193
|
-
Fez('ui-tabs', this)
|
|
194
|
-
|
|
195
|
-
// add global scss
|
|
196
|
-
Fez.globalCss(`
|
|
197
|
-
.some-class {
|
|
198
|
-
color: red;
|
|
199
|
-
&.foo { ... }
|
|
200
|
-
.foo { ... }
|
|
201
|
-
}
|
|
202
|
-
...
|
|
203
|
-
`)
|
|
204
|
-
|
|
205
|
-
// internal, get unique ID for a string, poor mans MD5 / SHA1
|
|
206
|
-
Fez.fnv1('some string')
|
|
207
|
-
|
|
208
|
-
// get generated css class name, from scss source string
|
|
209
|
-
Fez.css(text)
|
|
210
|
-
|
|
211
|
-
// get generated css class name without global attachment
|
|
212
|
-
Fez.cssClass(text)
|
|
213
|
-
|
|
214
|
-
// display information about fast/slow bind components in console
|
|
215
|
-
Fez.info()
|
|
216
|
-
|
|
217
|
-
// low-level DOM morphing function
|
|
218
|
-
Fez.morphdom(target, newNode, opts)
|
|
219
|
-
|
|
220
|
-
// HTML escaping utility
|
|
221
|
-
Fez.htmlEscape(text)
|
|
222
|
-
|
|
223
|
-
// create HTML tags with encoded props
|
|
224
|
-
Fez.tag(tag, opts, html)
|
|
225
|
-
|
|
226
|
-
// execute function until it returns true
|
|
227
|
-
Fez.untilTrue(func, pingRate)
|
|
228
|
-
|
|
229
|
-
// add scripts/styles to document head
|
|
230
|
-
// Load JavaScript from URL: Fez.head({ js: 'path/to/script.js' })
|
|
231
|
-
// Load JavaScript with attributes: Fez.head({ js: 'path/to/script.js', type: 'module', async: true })
|
|
232
|
-
// Load JavaScript with callback: Fez.head({ js: 'path/to/script.js' }, () => console.log('loaded'))
|
|
233
|
-
// Load JavaScript module and auto-import to window: Fez.head({ js: 'path/to/module.js', module: 'MyModule' })
|
|
234
|
-
// Load CSS: Fez.head({ css: 'path/to/styles.css' })
|
|
235
|
-
// Load CSS with attributes: Fez.head({ css: 'path/to/styles.css', media: 'print' })
|
|
236
|
-
// Execute inline script: Fez.head({ script: 'console.log("Hello world")' })
|
|
237
|
-
Fez.head(config, callback)
|
|
238
|
-
|
|
239
|
-
// define custom style macro
|
|
240
|
-
// Fez.styleMacro('mobile', '@media (max-width: 768px)')
|
|
241
|
-
// :mobile { ... } -> @media (max-width: 768px) { ... }
|
|
242
|
-
Fez.styleMacro(name, value)
|
|
183
|
+
Fez('#foo') // find fez node with id="foo"
|
|
184
|
+
Fez('ui-tabs', this) // find first parent node ui-tabs
|
|
185
|
+
Fez('ui-tabs', (fez)=> ... ) // loop over all ui-tabs nodes
|
|
243
186
|
|
|
244
187
|
// define custom DOM node name -> <foo-bar>...
|
|
245
188
|
Fez('foo-bar', class {
|
|
@@ -252,14 +195,6 @@ Fez('foo-bar', class {
|
|
|
252
195
|
// set element style, set as property or method
|
|
253
196
|
CSS = `scss string... `
|
|
254
197
|
|
|
255
|
-
// unless node has no innerHTML on initialization, bind will be set to slow (fastBind = false)
|
|
256
|
-
// if you are using components that to not use innerHTML and slots, enable fast bind (fastBind = true)
|
|
257
|
-
// component will be rendered as parsed, and not on next tick (reduces flickering)
|
|
258
|
-
// <fez-icon name="gear" />
|
|
259
|
-
FAST = true
|
|
260
|
-
FAST(node) { ... }
|
|
261
|
-
// alternative: static fastBind() { return true }
|
|
262
|
-
|
|
263
198
|
// define static HTML. calling `this.render()` (no arguments) will refresh current node.
|
|
264
199
|
// if you pair it with `reactiveStore()`, to auto update on props change, you will have Svelte or Vue style reactive behaviour.
|
|
265
200
|
HTML = `...`
|
|
@@ -269,112 +204,175 @@ Fez('foo-bar', class {
|
|
|
269
204
|
GLOBAL = 'Dialog'
|
|
270
205
|
GLOBAL = true // just append node to document, do not create window reference
|
|
271
206
|
|
|
272
|
-
//
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
this.copy('href', 'onclick', 'style')
|
|
207
|
+
// called when fez element is connected to dom, before first render
|
|
208
|
+
// here you still have your slot available in this.root
|
|
209
|
+
init(props) { ... }
|
|
276
210
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
this.setStyle('--color', 'red')
|
|
211
|
+
// execute after init and first render
|
|
212
|
+
onMount() { ... }
|
|
280
213
|
|
|
281
|
-
|
|
282
|
-
|
|
214
|
+
// execute before or after every render
|
|
215
|
+
beforeRender() { ... }
|
|
216
|
+
afterRender() { ... }
|
|
283
217
|
|
|
284
|
-
|
|
285
|
-
|
|
218
|
+
// if you want to monitor new or changed node attributes
|
|
219
|
+
// monitors all original node attributes
|
|
220
|
+
// <ui-icon name="home" color="red" />
|
|
221
|
+
onPropsChange(attrName, attrValue) { ... }
|
|
286
222
|
|
|
287
|
-
|
|
288
|
-
|
|
223
|
+
// called when local component state changes
|
|
224
|
+
onStateChange(key, value, oldValue) { ... }
|
|
289
225
|
|
|
290
|
-
|
|
291
|
-
|
|
226
|
+
// called when global state changes (only if component uses key in question that key)
|
|
227
|
+
onGlobalStateChange(key, value) { ... }
|
|
292
228
|
|
|
293
|
-
|
|
294
|
-
|
|
229
|
+
// called when component is destroyed
|
|
230
|
+
onDestroy() { ... }
|
|
295
231
|
|
|
296
|
-
|
|
297
|
-
this.prop('onclick')
|
|
232
|
+
/* used inside lifecycle methods (init(), onMount(), ... */
|
|
298
233
|
|
|
299
|
-
|
|
300
|
-
|
|
234
|
+
// copy original attributes from attr hash to root node
|
|
235
|
+
this.copy('href', 'onclick', 'style')
|
|
301
236
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
this.val(selector, value)
|
|
237
|
+
// set style property to root node. look at a clock example
|
|
238
|
+
// shortcut to this.root.style.setProperty(key, value)
|
|
239
|
+
this.setStyle('--color', 'red')
|
|
306
240
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
this.subscribe('channel', (foo) => { ... })
|
|
241
|
+
// clasic interval, that runs only while node is attached
|
|
242
|
+
this.setInterval(func, tick) { ... }
|
|
310
243
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
this.childNodes(func) // pass function to loop forEach on selection, removed nodes from DOM
|
|
244
|
+
// get closest form data, as object. Searches for first parent or child FORM element
|
|
245
|
+
this.formData()
|
|
314
246
|
|
|
315
|
-
|
|
316
|
-
|
|
247
|
+
// mounted DOM node root. Only in init() point to original <slot /> data, in onMount() to rendered data.
|
|
248
|
+
this.root
|
|
317
249
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
// this.state has reactiveStore() attached by default. any change will trigger this.render()
|
|
321
|
-
this.state.foo = 123
|
|
250
|
+
// mounted DOM node root wrapped in $, only if jQuery is available
|
|
251
|
+
this.$root
|
|
322
252
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
// delay: throttle delay in ms (default: 200ms)
|
|
326
|
-
// runs immediately on init and then throttled
|
|
327
|
-
this.on(eventName, func, delay)
|
|
253
|
+
// node attributes, converted to properties
|
|
254
|
+
this.props
|
|
328
255
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
// window scroll event with cleanup (shorthand for this.on('scroll', func, delay))
|
|
333
|
-
this.onScroll(func, delay)
|
|
256
|
+
// gets single node attribute or property
|
|
257
|
+
this.prop('onclick')
|
|
334
258
|
|
|
335
|
-
|
|
336
|
-
|
|
259
|
+
// shortcut for this.root.querySelector(selector)
|
|
260
|
+
this.find(selector)
|
|
337
261
|
|
|
338
|
-
|
|
339
|
-
|
|
262
|
+
// gets value for FORM fields or node innerHTML
|
|
263
|
+
this.val(selector)
|
|
264
|
+
// set value to a node, uses value or innerHTML
|
|
265
|
+
this.val(selector, value)
|
|
340
266
|
|
|
341
|
-
|
|
342
|
-
|
|
267
|
+
// you can publish globally, and subscribe locally
|
|
268
|
+
Fez.publish('channel', foo)
|
|
269
|
+
this.subscribe('channel', (foo) => { ... })
|
|
343
270
|
|
|
344
|
-
|
|
345
|
-
|
|
271
|
+
// gets root childNodes
|
|
272
|
+
this.childNodes()
|
|
273
|
+
this.childNodes(func) // pass function to loop forEach on selection, mask nodes out of position
|
|
346
274
|
|
|
347
|
-
|
|
348
|
-
|
|
275
|
+
// check if the this.root node is attached to dom
|
|
276
|
+
this.isConnected
|
|
349
277
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
this.afterRender() { ... }
|
|
278
|
+
// this.state has reactiveStore() attached by default. any change will trigger this.render()
|
|
279
|
+
this.state.foo = 123
|
|
353
280
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
281
|
+
// generic window event handler with automatic cleanup
|
|
282
|
+
// eventName: 'resize', 'scroll', 'mousemove', etc.
|
|
283
|
+
// delay: throttle delay in ms (default: 200ms)
|
|
284
|
+
this.on(eventName, func, delay)
|
|
358
285
|
|
|
359
|
-
|
|
360
|
-
|
|
286
|
+
// window resize event with cleanup (shorthand for this.on('resize', func, delay))
|
|
287
|
+
// runs immediately on init and then throttled
|
|
288
|
+
this.onWindowResize(func, delay)
|
|
361
289
|
|
|
362
|
-
|
|
363
|
-
|
|
290
|
+
// window scroll event with cleanup (shorthand for this.on('scroll', func, delay))
|
|
291
|
+
// runs immediately on init and then throttled
|
|
292
|
+
this.onWindowScroll(func, delay)
|
|
364
293
|
|
|
365
|
-
|
|
366
|
-
|
|
294
|
+
// requestAnimationFrame wrapper with deduplication
|
|
295
|
+
this.nextTick(func, name)
|
|
367
296
|
|
|
368
|
-
|
|
369
|
-
|
|
297
|
+
// get unique ID for root node, set one if needed
|
|
298
|
+
this.rootId()
|
|
370
299
|
|
|
371
|
-
|
|
372
|
-
|
|
300
|
+
// get/set attributes on root node
|
|
301
|
+
this.attr(name, value)
|
|
373
302
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
303
|
+
// hide the custom element wrapper and move children to parent
|
|
304
|
+
this.fezHide()
|
|
305
|
+
|
|
306
|
+
// automatic form submission handling if there is FORM as parent or child node
|
|
307
|
+
this.onSubmit(formData) { ... }
|
|
308
|
+
|
|
309
|
+
// render template and attach result dom to root. uses Idiomorph for DOM morph
|
|
310
|
+
this.render()
|
|
311
|
+
this.render(this.find('.body'), someHtmlTemplate) // you can render to another root too
|
|
377
312
|
})
|
|
313
|
+
|
|
314
|
+
/* Utility methods */
|
|
315
|
+
|
|
316
|
+
// define custom style macro
|
|
317
|
+
// Fez.styleMacro('mobile', '@media (max-width: 768px)')
|
|
318
|
+
// :mobile { ... } -> @media (max-width: 768px) { ... }
|
|
319
|
+
Fez.styleMacro(name, value)
|
|
320
|
+
|
|
321
|
+
// add global scss
|
|
322
|
+
Fez.globalCss(`
|
|
323
|
+
.some-class {
|
|
324
|
+
color: red;
|
|
325
|
+
&.foo { ... }
|
|
326
|
+
.foo { ... }
|
|
327
|
+
}
|
|
328
|
+
...
|
|
329
|
+
`)
|
|
330
|
+
|
|
331
|
+
// internal, get unique ID for a string, poor mans MD5 / SHA1
|
|
332
|
+
Fez.fnv1('some string')
|
|
333
|
+
|
|
334
|
+
// get dom node containing passed html
|
|
335
|
+
Fez.domRoot(htmlData || htmlNode)
|
|
336
|
+
|
|
337
|
+
// activates node by adding class to node, and removing it from siblings
|
|
338
|
+
Fez.activateNode(node, className = 'active')
|
|
339
|
+
|
|
340
|
+
// get generated css class name, from scss source string
|
|
341
|
+
Fez.css(text)
|
|
342
|
+
|
|
343
|
+
// get generated css class name without global attachment
|
|
344
|
+
Fez.cssClass(text)
|
|
345
|
+
|
|
346
|
+
// display information about registered components in console
|
|
347
|
+
Fez.info()
|
|
348
|
+
|
|
349
|
+
// low-level DOM morphing function
|
|
350
|
+
Fez.morphdom(target, newNode, opts)
|
|
351
|
+
|
|
352
|
+
// HTML escaping utility
|
|
353
|
+
Fez.htmlEscape(text)
|
|
354
|
+
|
|
355
|
+
// create HTML tags with encoded props
|
|
356
|
+
Fez.tag(tag, opts, html)
|
|
357
|
+
|
|
358
|
+
// execute function until it returns true
|
|
359
|
+
Fez.untilTrue(func, pingRate)
|
|
360
|
+
|
|
361
|
+
// resolve and execute a function from string or function reference
|
|
362
|
+
// useful for event handlers that can be either functions or strings
|
|
363
|
+
// Fez.resolveFunction('alert("hi")', element) - creates function and calls with element as this
|
|
364
|
+
// Fez.resolveFunction(myFunc, element) - calls myFunc with element as this
|
|
365
|
+
Fez.resolveFunction(pointer, context)
|
|
366
|
+
|
|
367
|
+
// add scripts/styles to document head
|
|
368
|
+
// Load JavaScript from URL: Fez.head({ js: 'path/to/script.js' })
|
|
369
|
+
// Load JavaScript with attributes: Fez.head({ js: 'path/to/script.js', type: 'module', async: true })
|
|
370
|
+
// Load JavaScript with callback: Fez.head({ js: 'path/to/script.js' }, () => console.log('loaded'))
|
|
371
|
+
// Load JavaScript module and auto-import to window: Fez.head({ js: 'path/to/module.js', module: 'MyModule' })
|
|
372
|
+
// Load CSS: Fez.head({ css: 'path/to/styles.css' })
|
|
373
|
+
// Load CSS with attributes: Fez.head({ css: 'path/to/styles.css', media: 'print' })
|
|
374
|
+
// Execute inline script: Fez.head({ script: 'console.log("Hello world")' })
|
|
375
|
+
Fez.head(config, callback)
|
|
378
376
|
```
|
|
379
377
|
|
|
380
378
|
## Fez script loading and definition
|
|
@@ -395,9 +393,6 @@ Fez('foo-bar', class {
|
|
|
395
393
|
<!-- pass JSON template via data-json-template -->
|
|
396
394
|
<script type="text/template">{...}</script>
|
|
397
395
|
<foo-bar data-json-template="true"></foo-bar>
|
|
398
|
-
|
|
399
|
-
<!-- override slow bind behavior -->
|
|
400
|
-
<foo-bar fez-fast="true"></foo-bar>
|
|
401
396
|
```
|
|
402
397
|
|
|
403
398
|
## Component structure
|
|
@@ -459,7 +454,7 @@ All parts are optional
|
|
|
459
454
|
{{json data}} <!-- JSON dump in PRE.json tag -->
|
|
460
455
|
|
|
461
456
|
<!-- fez-this will link DOM node to object property (inspired by Svelte) -->
|
|
462
|
-
<!-- this.listRoot -->
|
|
457
|
+
<!-- linkes to -> this.listRoot -->
|
|
463
458
|
<ul fez-this="listRoot">
|
|
464
459
|
|
|
465
460
|
<!-- when node is added to dom fez-use will call object function by name, and pass current node -->
|
|
@@ -468,7 +463,6 @@ All parts are optional
|
|
|
468
463
|
|
|
469
464
|
<!-- fez-bind for two-way data binding on form elements -->
|
|
470
465
|
<input type="text" fez-bind="state.username" />
|
|
471
|
-
<input onkeyup="fez.list[{{ index }}].name = fez.value" value="{{ name }}" />
|
|
472
466
|
|
|
473
467
|
<!--
|
|
474
468
|
fez-class for adding classes with optional delay.
|
|
@@ -476,6 +470,9 @@ All parts are optional
|
|
|
476
470
|
-->
|
|
477
471
|
<span fez-class="active:100">Delayed class</span>
|
|
478
472
|
|
|
473
|
+
<!-- preserve state by key, not affected by state changes-->>
|
|
474
|
+
<p fez-keep="key">...</p>
|
|
475
|
+
|
|
479
476
|
<!-- :attribute for evaluated attributes (converts to JSON) -->
|
|
480
477
|
<div :data-config="state.config"></div>
|
|
481
478
|
</div>
|