Skip to content

Commit

Permalink
0.6.0: Enables more features and improves the developer experience fo…
Browse files Browse the repository at this point in the history
…r fullstack rendering (SSR, CSR and Hydration)
  • Loading branch information
Tao-VanJS committed Jul 10, 2024
1 parent cf97cdc commit 6d4f92a
Show file tree
Hide file tree
Showing 13 changed files with 359 additions and 10 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ Sample code:
_Requires Deno `1.35` or later._
```typescript
import van from "https://deno.land/x/minivan@0.5.7/src/van-plate.js"
import van from "https://deno.land/x/minivan@0.6.0/src/van-plate.js"

const {a, body, li, p, ul} = van.tags

Expand Down Expand Up @@ -195,7 +195,7 @@ _Requires Deno `1.35` or later._
```typescript
import { DOMParser } from "https://deno.land/x/[email protected]/deno-dom-wasm.ts"
import van from "https://deno.land/x/minivan@0.5.7/src/mini-van.js"
import van from "https://deno.land/x/minivan@0.6.0/src/mini-van.js"

const document = new DOMParser().parseFromString("", "text/html")!
const {tags, html} = van.vanWithDoc(document)
Expand Down Expand Up @@ -235,16 +235,16 @@ Preview via [CodeSandbox](https://codesandbox.io/p/sandbox/github/vanjs-org/vanj
To get started on the client side, add the line below to your script:
```javascript
import van from "https://cdn.jsdelivr.net/gh/vanjs-org/mini-van/public/mini-van-0.5.7.min.js"
import van from "https://cdn.jsdelivr.net/gh/vanjs-org/mini-van/public/mini-van-0.6.0.min.js"
```
To code without ES6 modules, add the following line to your HTML file instead:
```html
<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/vanjs-org/mini-van/public/mini-van-0.5.7.nomodule.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/vanjs-org/mini-van/public/mini-van-0.6.0.nomodule.min.js"></script>
```
Alternative, you can download the files ([`mini-van-0.5.7.min.js`](https://vanjs.org/autodownload?file=mini-van-0.5.7.min.js), [`mini-van-0.5.7.nomodule.min.js`](https://vanjs.org/autodownload?file=mini-van-0.5.7.nomodule.min.js)) and serve them locally.
Alternative, you can download the files ([`mini-van-0.6.0.min.js`](https://vanjs.org/autodownload?file=mini-van-0.6.0.min.js), [`mini-van-0.6.0.nomodule.min.js`](https://vanjs.org/autodownload?file=mini-van-0.6.0.nomodule.min.js)) and serve them locally.
You can find all relevant **Mini-Van** files in this [Download Table](https://vanjs.org/minivan#download-table).
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mini-van-plate",
"version": "0.5.7",
"version": "0.6.0",
"description": "A minimalist template engine for DOM generation and manipulation, working for both client-side and server-side rendering",
"files": [
"src/mini-van.js",
Expand Down
75 changes: 75 additions & 0 deletions public/mini-van-0.6.0.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
export interface State<T> {
val: T
readonly oldVal: T
readonly rawVal: T
}

// Defining readonly view of State<T> for covariance.
// Basically we want StateView<string> to implement StateView<string | number>
export type StateView<T> = Readonly<State<T>>

export type Primitive = string | number | boolean | bigint

export type PropValue = Primitive | ((e: any) => void) | null

export type Props = Record<string, PropValue | StateView<PropValue> | (() => PropValue)>

interface HasFirstChild {firstChild?: unknown}

type NodeType<ElementType extends HasFirstChild> =
Omit<ElementType["firstChild"], "after" | "before" | "remove" | "replaceWith">

export type ValidChildDomValue<ElementType extends HasFirstChild, TextNodeType> =
Primitive | ElementType | NodeType<ElementType> | TextNodeType | null | undefined

export type BindingFunc<ElementType extends HasFirstChild, TextNodeType> =
| ((dom?: ElementType | TextNodeType) => ValidChildDomValue<ElementType, TextNodeType>)
| ((dom?: ElementType) => ElementType)

export type ChildDom<ElementType extends HasFirstChild, TextNodeType> =
| ValidChildDomValue<ElementType, TextNodeType>
| StateView<Primitive | null | undefined>
| BindingFunc<ElementType, TextNodeType>
| readonly ChildDom<ElementType, TextNodeType>[]

type AddFunc<ElementType extends HasFirstChild, TextNodeType> =
(dom: ElementType, ...children: readonly ChildDom<ElementType, TextNodeType>[]) => ElementType

export type TagFunc<ElementType extends HasFirstChild, TextNodeType, ResultType = ElementType> =
(first?: Props | ChildDom<ElementType, TextNodeType>,
...rest: readonly ChildDom<ElementType, TextNodeType>[]) => ResultType

type Tags<ElementType extends HasFirstChild, TextNodeType> =
Readonly<Record<string, TagFunc<ElementType, TextNodeType>>>

// Tags type in browser context, which contains the signatures to tag functions that return
// specialized DOM elements.
type BrowserTags = Tags<Element, Text> & {
[K in keyof HTMLElementTagNameMap]: TagFunc<Element, Text, HTMLElementTagNameMap[K]>
}

declare function state<T>(): State<T>
declare function state<T>(initVal: T): State<T>

export interface VanObj<ElementType extends HasFirstChild, TextNodeType> {
readonly state: typeof state
readonly derive: <T>(f: () => T) => State<T>
readonly add: AddFunc<ElementType, TextNodeType>
readonly tags: Tags<ElementType, TextNodeType> & ((namespaceURI: string) => Tags<ElementType, TextNodeType>)

// Mini-Van specific API
html: (first?: Props | ChildDom<ElementType, TextNodeType>,
...rest: readonly ChildDom<ElementType, TextNodeType>[]) => string
}

export interface Van extends VanObj<Element, Text> {
readonly vanWithDoc: <ElementType extends HasFirstChild, TextNodeType>(doc: {
createElement(s: any): ElementType,
createTextNode(s: any): TextNodeType,
}) => VanObj<ElementType, TextNodeType>
readonly tags: BrowserTags & ((namespaceURI: string) => Tags<Element, Text>)
}

declare const van: Van

export default van
48 changes: 48 additions & 0 deletions public/mini-van-0.6.0.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/// <reference types="./mini-van.d.ts" />

// This file consistently uses `let` keyword instead of `const` for reducing the bundle size.

// Aliasing some builtin symbols to reduce the bundle size.
let protoOf = Object.getPrototypeOf, _undefined, funcProto = protoOf(protoOf)

let stateProto = {get oldVal() { return this.val }, get rawVal() { return this.val }}
let objProto = protoOf(stateProto)

let state = initVal => ({__proto__: stateProto, val: initVal})

let plainValue = (k, v) => {
let protoOfV = protoOf(v ?? 0)
return protoOfV === stateProto ? v.val :
protoOfV !== funcProto || k?.startsWith("on") ? v : v()
}

let add = (dom, ...children) =>
(dom.append(...children.flat(Infinity)
.map(plainValue.bind(_undefined, _undefined))
.filter(c => c != _undefined)),
dom)

let vanWithDoc = doc => {
let tag = (ns, name, ...args) => {
let [props, ...children] = protoOf(args[0] ?? 0) === objProto ? args : [{}, ...args]
let dom = ns ? doc.createElementNS(ns, name) : doc.createElement(name)
for (let [k, v] of Object.entries(props)) {
let plainV = plainValue(k, v)
// Disable setting attribute for function-valued properties (mostly event handlers),
// as they're usually not useful for SSR (server-side rendering).
protoOf(plainV) !== funcProto && dom.setAttribute(k, plainV)
}
return add(dom, ...children)
}

let handler = ns => ({get: (_, name) => tag.bind(_undefined, ns, name)})
let tags = new Proxy(ns => new Proxy(tag, handler(ns)), handler())

return {
add, tags, state, derive: f => state(f()),
html: (...args) => "<!DOCTYPE html>" + tags.html(...args).outerHTML,
}
}

export default {"vanWithDoc": vanWithDoc,
...vanWithDoc(typeof window !== "undefined" ? window.document : null)}
75 changes: 75 additions & 0 deletions public/mini-van-0.6.0.min.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
export interface State<T> {
val: T
readonly oldVal: T
readonly rawVal: T
}

// Defining readonly view of State<T> for covariance.
// Basically we want StateView<string> to implement StateView<string | number>
export type StateView<T> = Readonly<State<T>>

export type Primitive = string | number | boolean | bigint

export type PropValue = Primitive | ((e: any) => void) | null

export type Props = Record<string, PropValue | StateView<PropValue> | (() => PropValue)>

interface HasFirstChild {firstChild?: unknown}

type NodeType<ElementType extends HasFirstChild> =
Omit<ElementType["firstChild"], "after" | "before" | "remove" | "replaceWith">

export type ValidChildDomValue<ElementType extends HasFirstChild, TextNodeType> =
Primitive | ElementType | NodeType<ElementType> | TextNodeType | null | undefined

export type BindingFunc<ElementType extends HasFirstChild, TextNodeType> =
| ((dom?: ElementType | TextNodeType) => ValidChildDomValue<ElementType, TextNodeType>)
| ((dom?: ElementType) => ElementType)

export type ChildDom<ElementType extends HasFirstChild, TextNodeType> =
| ValidChildDomValue<ElementType, TextNodeType>
| StateView<Primitive | null | undefined>
| BindingFunc<ElementType, TextNodeType>
| readonly ChildDom<ElementType, TextNodeType>[]

type AddFunc<ElementType extends HasFirstChild, TextNodeType> =
(dom: ElementType, ...children: readonly ChildDom<ElementType, TextNodeType>[]) => ElementType

export type TagFunc<ElementType extends HasFirstChild, TextNodeType, ResultType = ElementType> =
(first?: Props | ChildDom<ElementType, TextNodeType>,
...rest: readonly ChildDom<ElementType, TextNodeType>[]) => ResultType

type Tags<ElementType extends HasFirstChild, TextNodeType> =
Readonly<Record<string, TagFunc<ElementType, TextNodeType>>>

// Tags type in browser context, which contains the signatures to tag functions that return
// specialized DOM elements.
type BrowserTags = Tags<Element, Text> & {
[K in keyof HTMLElementTagNameMap]: TagFunc<Element, Text, HTMLElementTagNameMap[K]>
}

declare function state<T>(): State<T>
declare function state<T>(initVal: T): State<T>

export interface VanObj<ElementType extends HasFirstChild, TextNodeType> {
readonly state: typeof state
readonly derive: <T>(f: () => T) => State<T>
readonly add: AddFunc<ElementType, TextNodeType>
readonly tags: Tags<ElementType, TextNodeType> & ((namespaceURI: string) => Tags<ElementType, TextNodeType>)

// Mini-Van specific API
html: (first?: Props | ChildDom<ElementType, TextNodeType>,
...rest: readonly ChildDom<ElementType, TextNodeType>[]) => string
}

export interface Van extends VanObj<Element, Text> {
readonly vanWithDoc: <ElementType extends HasFirstChild, TextNodeType>(doc: {
createElement(s: any): ElementType,
createTextNode(s: any): TextNodeType,
}) => VanObj<ElementType, TextNodeType>
readonly tags: BrowserTags & ((namespaceURI: string) => Tags<Element, Text>)
}

declare const van: Van

export default van
1 change: 1 addition & 0 deletions public/mini-van-0.6.0.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 45 additions & 0 deletions public/mini-van-0.6.0.nomodule.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
(() => {
// mini-van.js
var protoOf = Object.getPrototypeOf;
var _undefined;
var funcProto = protoOf(protoOf);
var stateProto = { get oldVal() {
return this.val;
}, get rawVal() {
return this.val;
} };
var objProto = protoOf(stateProto);
var state = (initVal) => ({ __proto__: stateProto, val: initVal });
var plainValue = (k, v) => {
let protoOfV = protoOf(v ?? 0);
return protoOfV === stateProto ? v.val : protoOfV !== funcProto || k?.startsWith("on") ? v : v();
};
var add = (dom, ...children) => (dom.append(...children.flat(Infinity).map(plainValue.bind(_undefined, _undefined)).filter((c) => c != _undefined)), dom);
var vanWithDoc = (doc) => {
let tag = (ns, name, ...args) => {
let [props, ...children] = protoOf(args[0] ?? 0) === objProto ? args : [{}, ...args];
let dom = ns ? doc.createElementNS(ns, name) : doc.createElement(name);
for (let [k, v] of Object.entries(props)) {
let plainV = plainValue(k, v);
protoOf(plainV) !== funcProto && dom.setAttribute(k, plainV);
}
return add(dom, ...children);
};
let handler = (ns) => ({ get: (_, name) => tag.bind(_undefined, ns, name) });
let tags = new Proxy((ns) => new Proxy(tag, handler(ns)), handler());
return {
add,
tags,
state,
derive: (f) => state(f()),
html: (...args) => "<!DOCTYPE html>" + tags.html(...args).outerHTML
};
};
var mini_van_default = {
"vanWithDoc": vanWithDoc,
...vanWithDoc(typeof window !== "undefined" ? window.document : null)
};

// mini-van.forbundle.js
window.van = mini_van_default;
})();
1 change: 1 addition & 0 deletions public/mini-van-0.6.0.nomodule.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion public/mini-van.version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.5.7
0.6.0
23 changes: 23 additions & 0 deletions src/shared.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,27 @@ export interface VanObj {
readonly add: Function;
readonly tags: Record<string, Function> & ((namespaceURI: string) => Record<string, Function>);
}
export type StateOf<T> = {
readonly [K in keyof T]: State<T[K]>;
};
export type ValueType<T> = T extends (infer V)[] ? V : T[keyof T];
export type KeyType<T> = T extends unknown[] ? number : string;
export type ReplacementFunc<T> = T extends (infer V)[] ? (items: V[]) => readonly V[] : (items: [string, T[keyof T]][]) => readonly [string, T[keyof T]][];
export interface VanXObj {
readonly calc: <R>(f: () => R) => R;
readonly reactive: <T extends object>(obj: T) => T;
readonly noreactive: <T extends object>(obj: T) => T;
readonly stateFields: <T extends object>(obj: T) => StateOf<T>;
readonly raw: <T extends object>(obj: T) => T;
readonly list: <T extends object>(container: any, items: T, itemFunc: (v: State<ValueType<T>>, deleter: () => void, k: KeyType<T>) => any) => any;
readonly replace: <T extends object>(obj: T, replacement: ReplacementFunc<T> | T) => T;
readonly compact: <T extends object>(obj: T) => T;
}
export interface EnvObj {
readonly van: VanObj;
readonly vanX: VanXObj;
}
export declare const env: EnvObj;
export declare const registerEnv: (input: Partial<EnvObj>) => void;
export declare const dummyVanX: VanXObj;
export {};
30 changes: 29 additions & 1 deletion src/shared.js
Original file line number Diff line number Diff line change
@@ -1 +1,29 @@
export {};
export const env = {};
export const registerEnv = (input) => {
if (input.van)
env.van = input.van;
if (input.vanX)
env.vanX = input.vanX;
};
export const dummyVanX = {
calc: f => f(),
reactive: obj => obj,
noreactive: obj => obj,
stateFields: (obj) => {
const states = Array.isArray(obj) ? [] : { __proto__: Object.getPrototypeOf(obj) };
for (const [k, v] of Object.entries(obj))
states[k] = env.van.state(v);
return states;
},
raw: obj => obj,
list: (container, items, itemFunc) => {
if (container instanceof Function)
container = container();
const isArray = Array.isArray(items);
for (const [k, v] of Object.entries(items))
env.van.add(container, itemFunc(env.van.state(v), () => { }, (isArray ? Number(k) : k)));
return container;
},
replace: obj => obj,
compact: obj => obj,
};
Loading

0 comments on commit 6d4f92a

Please sign in to comment.