Skip to content

Latest commit

 

History

202 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

z-data

An extremely lightweight, zero-config, zero-dependency, VDOM-less embedded front-end framework. No build step required. Write plain H5 DOM, drive the view with template directives, and do all styling inline with an atomic CSS engine.

English | 简体中文

License: MIT GitHub Repo


Table of Contents


About

z-data is a minimal reactive front-end framework built around a simple idea:

  • No virtual DOM — operates directly on the H5 DOM for high performance.
  • Zero config, zero dependency, no build step — a single JS file, plug and play.
  • Embeddable — coexists seamlessly with other frameworks (e.g. Vue, Alpine).
  • Template directives — supports for / if / else / use.
  • Atomic CSS — a Tailwind-like rules engine that generates styles inline, with no build tools.
  • Rich syntax sugar — especially friendly for class and style.

z-data drives view and data through HTML attributes (directives), uses <template> tags for loops and conditionals, and provides a complete set of concise markers such as !, #, ., .., :, ::, @ for binding, styling, and events, so you can stay focused on the H5 DOM itself.

Project homepage: https://funlang.org/z-data/


Features

  • No virtual DOM, direct DOM manipulation, great performance
  • Zero config, zero dependency, no build step
  • Extremely small: ~8K minified, < 5K gzipped
  • H5 template technology with for / if / else / use
  • Templates support multiple root nodes
  • Two-way data binding and events
  • Embeddable alongside Vue, Alpine and other frameworks
  • Rich syntax sugar, friendly to class and style
  • Built-in Atomic CSS engine — inline styling without Tailwind / build tools
  • H5 DOM-centered, build production apps directly in HTML

Quick Start

<script src="//cdn.jsdelivr.net/gh/Funlang/z-data@main/dist/z-data.min.js"></script>

<div z-data="{name:'hello-world.html',items:{i:1,j:2,k:3}}"
     #background=`silver`
>
    <template for='k:v,i in items' key=k>
        <template if=!i>
            <div :text=name
                 :style={fontSize:`200%`}
                 #color=`#fa0a`
            ></div>
            <div class=t0
                 :class={t1:true}
                 .t2
                 :k=k
            >[if !i] i=${i}</div>
        </template>
        <template else if='k=="j"'>
            <div :css.font-size=`120%`>[if k=="j"] k=${k}</div>
        </template>
        <template else>
            <div #font-weight=`bold`>[else] k=${k}</div>
        </template>
    </template>
</div>

Note: #xxx=..., !xxx=..., ..xxx=... etc. are built-in style / class shorthands, documented below.


Installation

CDN

<script src="//cdn.jsdelivr.net/gh/Funlang/z-data@main/dist/z-data.min.js"></script>

Build artifacts live in dist/:

File Description
z-data.min.js Core framework for modern browsers
z-data.js Core framework (unminified)
z-data.min.es2015.js ES2015-compatible build
z-data.min.all.js Core + z-json utilities + full Atomic CSS support

Demos

z-cloak example

Prevents flash of unstyled content before initialization, combined with CSS [z-cloak] { display: none }:

z-data
<div z-cloak :z-cloak.attr=false>z-cloak example</div>

<style>
  [z-cloak] {
    display: none;
  }
</style>

Scope

  • The z-data attribute launches a ZData scope:

    <tag z-data=...></tag>
  • z-data supports an init function that runs on initialization:

    <tag z-data=... init=...></tag>
  • The z-data expression can be a function expression:

    <tag z-data="(args=>{
        // some codes ...
        return {
          // some props ...
        }
      })()"></tag>
    
    <tag z-data="args=>{
        // some codes ...
        return {
          // some props ...
        }
      }"></tag>
  • z-data HTML module file: begins with z-data; the data is whatever a <script> block return {...}s:

    z-data
    <tag init=...></tag>
    
    <script>
      // some codes ...
      return {
        // some props ...
      }
    </script>
  • z-none stops the ZData scope; all inner nodes are ignored by ZData:

    <tag z-none>
      ...
    </tag>
  • Coexistence with other frameworks: add x-ignore (or the other framework's own opt-out attribute) to the ZData root tag; add z-none to other roots to skip ZData processing:

    <tag z-data=... x-ignore></tag>

Template Directives

for

<template for='k:v,i in ...' key=...></template>

ZData relies on key for diffing. If no key is given, k is used first, otherwise v. k : v , i are each optional:

<template for='k:v,i in items'></template>
<template for='  v   in items'></template>
<template for='k:    in items'></template>
<template for='   ,i in items'></template>
<template for='k:v   in items'></template>
<template for='  v,i in items'></template>
<template for=items></template>

if / else

<template if=...></template>
<template else if=...></template>
<template else></template>

use

Reuse a template fragment:

<z z-none>
    <template id=t1>
        <div>template id = t1</div>
    </template>
</z>

<template use=#t1></template>

Data Binding

: does one-way binding, :: does two-way binding (similar to v-model in other frameworks):

<tag :attr-name1=... ::attr-name2=...></tag>

:text :html

Map to textContent and innerHTML respectively.

:class

Supports [], {} and string forms, merged into the classList in order:

<div :class="['a', {b: true}, 'c d']"></div>

:class shorthand (.name)

<tag :class.name1.name2=...></tag>

Can be shortened to .name1.name2=.... If a classname ends with - and the return value is not boolean, the value is appended to the classname; e.g. .p-=1 adds p-1 to the classList.

:style

Supports the :css alias, plus {} and string forms; strings overwrite in order:

<tag :style="{fontSize: '20px'}"></tag>
<tag :css="'color:red'"></tag>

:style shorthand (..name #name !name)

<tag :style.name.value=...></tag>
  • :style.name=value / :css.name=value

  • :style.name.value=condition / :css.name.value=...

  • Shortened to ..name, #name, and !name=string-value:

    ..width=`100px`
    #border-width=`4px`
    !border-width=4px
    #--a-css-var=`'${theCssVarValue}'`
    

The value of .. and # is a JS expression; the value of ! is treated as a string.

ZData.ss(s)

Style shorthand map; ZData.ss returns the full property name for a given abbreviation:

ZData.ss = (s) => AShorthandMap[s] || s;

Important notes

  • The value after : :: @ . .. # is always a JS expression (a variable or a string quoted with `' " ``).
  • A ${...} inside a non-bound attribute value is parsed into a string, equivalent to `...${...}...`.
  • ! behaves like attributes containing ${} and compiles to :css.style-name=\string-value``.

attr modifier

:xxx.attr sets a visible attribute; by default it sets a (hidden) property:

<tag :title.attr='...'></tag>

:xxx.attr === false produces a boolean attribute. attr shorthand:

:!tag-attr  -> :tag-attr.attr
::!tag-attr -> ::tag-attr.attr

camel modifier

:xxx.camel supports camelCase attributes / properties:

::value=propName, ::style.value=propName, ::css.value=propName
  • propName is camelCase only; DOM attributes (including style) support the .camel modifier.
  • Two-way binding fires on the change event by default; only input type=text also fires on input.
  • Use .input / .change to force the corresponding event.
  • Supports .trim / .number modifiers.
  • Some properties (e.g. style.value) do not react automatically when changed; call el.fireChange() in the current execution context.

input type='radio'

::checked=opt==this.value

opt is a property name in data.


class / style Shorthands

Marker Meaning
:xxx property binding
::xxx two-way binding
.name :class.name shorthand
..name :style.name shorthand
#name :style.name shorthand (JS expression)
!name :style.name shorthand (string value)
@event event binding
:*={...} dynamic props
@*={...} dynamic events
z-d-... custom directive

Atomic CSS

A signature z-data capability: a Tailwind-like Atomic CSS rules engine embedded in the framework, zero dependency and zero build. The rule definitions and compiler live in the repo css/ directory, and the generated code is merged into z-data.min.all.js.

Why

Writing lots of CSS classes is tedious, and introducing Tailwind normally requires a build step. z-data provides a set of abbreviation rules that compile atomic markers such as p1, m2, h3, d-flex, c#f00, s:p0, hover=b#ff0 into CSS selectors and auto-inject them into <style id="z-data-ss"> — out of the box, with no build step.

Inline usage (recommended)

Use the shorthand attributes that map to ZData.ss(name, value) directly on elements — no JS required:

<div p1 hover=p2 a-hover=p3 a^hover=p4 a+hover=p5px a~not-hover=m6pt after=p7 m8 mt9%></div>
  • An empty property (e.g. p1) means "style = value".
  • Post-modifiers such as hover=..., after=... map to :hover, :after pseudo-classes.
  • Pre-modifiers such as a-hover=..., a^hover=..., a+hover=..., a~not-hover=... map to parent / previous / sibling pseudo-classes.
  • s:p0 means a @media (min-width: 640px) media query.

Rules cheatsheet

Group Rules Examples
padding / margin pm p0, m.1, pl2, mt3px, px4%, vars m--varname, py--sm, px--lg
max/min, sizing, line-height h/w/xh/nw/font/lh/gap/brad h1, w2, xh100%, nw4%, font12px, lh2, gap2, brad2
left/top/right/bottom l/t/r/b l0, t1, r2pt, b3px
opacity/order/z-index op/ord/z op.1, ord2, z3
position/display/... pos/d/cs/v/ws/us/pe pos-absolute, d-block, cs-pointer, ws-nowrap
overflow o/ox/oy o-scroll, ox-visible, oy-hidden
content/color/background content/c/b content-x, c#000, b#fff
media queries sm/md/lg/xl/2xl/print s:p0
state pseudo-classes `hover/focus/active/after/before/(first last)`
pre-modifier pseudo-classes a- / a^ / a+ / a~ / not- a-hover=m1, parent^active=b#000, elder~hover=b#fff, prev+hover=c#fff
groups bd/bl/bt/br/bb/bg/a/al/font/flex/grid/mask bd="style:bold; width:1px; color:red"

Programmatic API

// Inject a block of CSS directly
ZDataStyle.add({ '.foo': { color: '#f00' } });

// Shorthand mapping
ZData.ss('pos');   // "position"
ZData.ss('brad');  // "border-radius"

The full rule definitions live in css/atomic-css-rules.txt, the compiler in css/rule-compiler.fun, and the runtime in css/z-css.js.


Events

@ binds events with modifier support:

<tag @click="doSomething()"></tag>

Global modifiers

camel     camelCase event name: a-camel-name -> aCamelName
prevent   preventDefault
stop      stopPropagation
debounce  debounce mode, optional time: debounce.750ms, debounce.2s, default 250ms
capture   capture mode
once      run once only
passive   passive mode

Scope modifiers

self      tag only
out       tag not
window
document

Keyboard / mouse modifiers

shift
ctrl
alt
meta      or cmd

Keyboard modifiers

<key>     enter, escape, space, f1 etc., details:
          https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
alias:  space: " ", slash: /, gt: >, eq: =

Mouse modifiers

<button>  left, mid, right

Dynamic Props / Events & Custom Directives

Dynamic props

:*={...}
:*.attr={...}

Example:

<tag :*="{
  min: '1',
  max: '100'
}"></tag>

Dynamic events

@*={...}

Example:

<tag @*="{
  input: 'console.log(event)',
  keydown: 'console.log(event)'
}"></tag>

Custom directives

z-d-...=...

For example z-d-rules-of-validate compiles into a call to rulesOfValidate({e: el, v: val, m: modifiers}):

<tag z-d-rules-of-validate="[
  val => !!val || 'Name is required!',
  val => val.length > 5 || 'The field need 5 or more characters',
]"></tag>
rulesOfValidate({
  e: el,
  v: val, // attr value of z-d-rules-of-validate
  m: modifiers
})

Variables

$el

The component root element.

$el.$data

The wrapped data object of the component.

ZData.nobserve (default false)

Disables DOM observation (for dynamically created z-data nodes you use ZData.loadHTML(), so observing the DOM is usually not needed):

ZData.nobserve = true;

Functions & API

ZData.proxy()

Wrap the data returned by z-data with ZData.proxy() to make it reactive:

return ZData.proxy({
  // some reactive data
});

ZData.loadHTML(html, p, before, args)

Dynamically load html; p is the parent element (default body), before the insert position:

  • html containing <script> must be loaded this way; otherwise :html=... works.
ZData.loadHTML(html, parentElement, beforeElement, args);

el.fireChange()

When you modify a node's style outside z-data and it is two-way bound, call .fireChange() to trigger updates.

$emit(el, name, detail)

Dispatch an event named name to DOM element el, carrying detail.

$emit(el, 'my-event', { any: 'detail' });

ZData.deb(fn, ms)

Debounce helper.

ZData.deb(fn, 250);

Others

  • ZData.start(): manual startup.
  • ZData.on(name, fn) / ZData.call(name, args): pluggable event hooks.

Components (z-comp)

Definition

<tag z-comp=...></tag>

Component URL

z-comp can be a ./ relative path or an http(s): resource:

<tag z-comp=./z-comp-2.html></tag>
<tag z-comp=https://funlang.org/zdata/test/z-comp-2.html></tag>

It can also be a Promise function that returns component code:

<tag z-comp="load_z_comp('z-comp-2')"></tag>

Loader ZData.get (z:// protocol)

When ZData.get exists, the z:// protocol plugin works. ZData.get is a Promise function that returns the part after something like fetch(url).then(res => res.text()):

document.addEventListener("DOMContentLoaded", () => setTimeout(() => ZData.get = ...));
<z- z-comp=z://table-v1.5.2></z->

Placeholder

z-comp can keep or remove its placeholder tag. A z-xxx tag, or one carrying a del attribute, is removed:

<z-comp z-comp=https://funlang.org/zdata/test/z-comp-2.html></z-comp>
<div del z-comp=https://funlang.org/zdata/test/z-comp-2.html></div>

Keep the placeholder:

<div z-comp=https://funlang.org/zdata/test/z-comp-2.html></div>

Arguments

<tag z-comp=... args=...></tag>

Inside the component, use args.xxx to access passed arguments:

<div z-data @mouseover.document=$el.textContent=event.target&&(event.target.title||(event.target.closest('[title]')||{}).title)||''
#color=args.color #background=args.bgcolor !height=100% !padding=8px
></div>

Demos


z-json Utilities

z-data.min.all.js additionally bundles a JSON / CSS conversion toolkit (from css/z-css.js):

  • qs2json(str, dot, defValue, regex): query-string / key-value string to JSON.
  • json2qs(obj, and, eq, dot, cb): JSON to query-string.
  • css2json(str): CSS string to JSON.
  • json2css(obj, level): JSON to CSS, supports nesting (including at-rules such as @media).
  • ZJson.onStringify: serialization hook.

These are great for configuration-driven UI, visual designers, and styling-data serialization.


z-data studio (z-pen)

Online IDE for creating / debugging / previewing z-data code, apps and components:


Browser Compatibility

  • z-data.js, z-data.min.js

    2017: Chrome 61, Firefox 55, Opera 48, Safari 11, iOS 11, Android 8
    
  • z-data.min.es2015.js, z-data.min.all.js

    2016: Chrome 49, Firefox 44, Opera 36, Safari 10, iOS 10, Android 7
    

Project Structure

z-data/
├── README.md            # English docs (default)
├── README-cn.md         # Chinese docs
├── hello-world.html     # Minimal example
├── src/
│   └── z-data.js        # Core framework source
├── css/                 # Atomic CSS subsystem
│   ├── README.md        # Atomic CSS rules doc
│   ├── atomic-css-rules.txt  # Rule definitions (BNF / regex)
│   ├── rule-compiler.fun     # Rule compiler (written in funlang)
│   └── z-css.js         # Runtime (ZData.ss / ZDataStyle)
├── www/                 # z-data studio site assets
├── z-pen/               # Studio build & tooling scripts
├── dist/                # Build artifacts
├── CHANGE.txt           # Changelog
├── package.json
└── LICENSE

Performance

z-data has no virtual DOM; it operates directly on the H5 DOM and reuses keys in template for, so it performs very well on large list insert / update scenarios (the project has participated in js-framework-benchmark comparisons since its early days).


Join Us

Welcome to join the z-data project. Enjoy!


License

MIT

About

Z-data is an extremely lightweight zero configuration embedded mini front-end js framework.

Topics

Resources

Stars

20 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages