mini-van-plate
Advanced tools
Comparing version 0.3.9 to 0.4.0-rc.0
{ | ||
"name": "mini-van-plate", | ||
"version": "0.3.9", | ||
"version": "0.4.0-rc.0", | ||
"description": "A minimalist template engine for DOM generation and manipulation, working for both client-side and server-side rendering", | ||
@@ -36,5 +36,6 @@ "files": [ | ||
"devDependencies": { | ||
"esbuild": "^0.17.12", | ||
"terser": "^5.17.1", | ||
"typescript": "^5.0.4" | ||
"esbuild": "^0.17.19", | ||
"node-jq": "^2.3.5", | ||
"terser": "^5.19.2", | ||
"typescript": "^5.1.6" | ||
}, | ||
@@ -41,0 +42,0 @@ "repository": { |
@@ -27,3 +27,3 @@ # **Mini-Van**: A Minimalist Template Engine for Client/Server-side Rendering without JSX | ||
## Server-Side: Npm Integration | ||
## Server-Side: NPM Integration | ||
@@ -54,3 +54,3 @@ **Mini-Van** can be used on the server side as a template engine to render dynamic web content for HTTP servers. An NPM package was published here: [www.npmjs.com/package/mini-van-plate](https://www.npmjs.com/package/mini-van-plate). Thus it can be used in [Node.js](https://nodejs.org/) or [Bun](https://bun.sh/). | ||
console.log("Testing DOM rendering...") | ||
// Expecting `<a href="https://vanjs.org/">🍦VanJS</a>` in the console | ||
// Expecting `<a href="https://vanjs.org/">🍦VanJS</a>` printed in the console | ||
console.log(a({href: "https://vanjs.org/"}, "🍦VanJS").render()) | ||
@@ -77,2 +77,4 @@ | ||
Preview via [CodeSandbox](https://codesandbox.io/p/sandbox/github/vanjs-org/vanjs-org.github.io/tree/master/sitegen/node-examples/van-plate-server?file=/van-plate-server.mjs:1,1). | ||
As illustrated in the example, `render` method can be called on the object returned from the [`tag function`](https://vanjs.org/tutorial#api-tags) to generate a `string` that can be used for serving. | ||
@@ -139,2 +141,4 @@ | ||
Preview via [CodeSandbox](https://codesandbox.io/p/sandbox/github/vanjs-org/vanjs-org.github.io/tree/master/sitegen/node-examples/mini-van-server?file=/mini-van-server.mjs:1,1). | ||
Similar to `van-plate` mode, we have a helper function `html` defined in `mini-van.js` which is equivalent to: | ||
@@ -153,4 +157,6 @@ | ||
Sample code: | ||
_Requires Deno `1.35` or later._ | ||
```typescript | ||
import { serve } from "https://deno.land/std@0.184.0/http/server.ts" | ||
import van from "https://deno.land/x/minivan@0.3.9/src/van-plate.js" | ||
@@ -163,7 +169,7 @@ | ||
console.log("Testing DOM rendering...") | ||
// Expecting `<a href="https://vanjs.org/">🍦VanJS</a>` in the console | ||
// Expecting `<a href="https://vanjs.org/">🍦VanJS</a>` printed in the console | ||
console.log(a({href: "https://vanjs.org/"}, "🍦VanJS").render()) | ||
console.log(`HTTP webserver running. Access it at: http://localhost:${port}/`) | ||
await serve(req => new Response( | ||
Deno.serve({port}, req => new Response( | ||
van.html( | ||
@@ -183,5 +189,7 @@ body( | ||
}, | ||
), {port}) | ||
)) | ||
``` | ||
Preview via [CodeSandbox](https://codesandbox.io/p/sandbox/github/vanjs-org/vanjs-org.github.io/tree/master/sitegen/deno-examples/van-plate-server?file=/van-plate-server.ts:1,1). | ||
### `mini-van` mode | ||
@@ -193,4 +201,5 @@ | ||
_Requires Deno `1.35` or later._ | ||
```typescript | ||
import { serve } from "https://deno.land/std@0.184.0/http/server.ts" | ||
import { DOMParser } from "https://deno.land/x/deno_dom@v0.1.38/deno-dom-wasm.ts" | ||
@@ -212,3 +221,3 @@ import van from "https://deno.land/x/minivan@0.3.9/src/mini-van.js" | ||
console.log(`HTTP webserver running. Access it at: http://localhost:${port}/`) | ||
await serve(req => new Response( | ||
Deno.serve({port}, req => new Response( | ||
html( | ||
@@ -228,5 +237,7 @@ body( | ||
}, | ||
), {port}) | ||
)) | ||
``` | ||
Preview via [CodeSandbox](https://codesandbox.io/p/sandbox/github/vanjs-org/vanjs-org.github.io/tree/master/sitegen/deno-examples/mini-van-server?file=/mini-van-server.ts:1,1). | ||
## Client-Side: Getting Started | ||
@@ -233,0 +244,0 @@ |
@@ -0,22 +1,36 @@ | ||
export interface State<T> { | ||
val: T | ||
readonly oldVal: 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 | Function | null | ||
export type PropValue = Primitive | null | ||
export interface Props { | ||
readonly [key: string]: PropValue | ||
} | ||
export type Props = Record<string, PropValue | StateView<PropValue> | (() => PropValue)> | ||
export type ChildDom<ElementType, TextNodeType> = Primitive | ElementType | TextNodeType | ||
| readonly ChildDom<ElementType, TextNodeType>[] | null | undefined | ||
export type ValidChildDomValue<ElementType, TextNodeType> = | ||
Primitive | ElementType | TextNodeType | null | undefined | ||
export type BindingFunc<ElementType, TextNodeType> = | ||
(dom: ElementType | TextNodeType) => ValidChildDomValue<ElementType, TextNodeType> | ||
export type ChildDom<ElementType, TextNodeType> = | ||
| ValidChildDomValue<ElementType, TextNodeType> | ||
| StateView<Primitive | null | undefined> | ||
| BindingFunc<ElementType, TextNodeType> | ||
| readonly ChildDom<ElementType, TextNodeType>[] | ||
type AddFunc<ElementType, TextNodeType> = | ||
(dom: ElementType, ...children: readonly ChildDom<ElementType, TextNodeType>[]) => ElementType | ||
export type TagFunc<ElementType = Element, TextNodeType = Text, ResultType = ElementType> = | ||
export type TagFunc<ElementType, TextNodeType, ResultType = ElementType> = | ||
(first?: Props | ChildDom<ElementType, TextNodeType>, | ||
...rest: readonly ChildDom<ElementType, TextNodeType>[]) => ResultType | ||
interface Tags<ElementType, TextNodeType> { | ||
readonly [key: string]: TagFunc<ElementType, TextNodeType> | ||
} | ||
type Tags<ElementType, TextNodeType> = Record<string, TagFunc<ElementType, TextNodeType>> | ||
@@ -131,9 +145,13 @@ // Tags type in browser context, which contains the signatures to tag functions that return | ||
type VanWithDoc = <ElementType, TextNodeType>( | ||
doc: { | ||
createElement(s: any): ElementType, | ||
createTextNode(s: any): TextNodeType, | ||
}) => { | ||
add: AddFunc<ElementType, TextNodeType> | ||
tags: Tags<ElementType, TextNodeType> | ||
export interface VanObj<ElementType, TextNodeType> { | ||
readonly state: <T>(initVal: T) => State<T> | ||
readonly val: <T>(s: T | StateView<T>) => T | ||
readonly oldVal: <T>(s: T | StateView<T>) => T | ||
readonly derive: <T>(f: () => T) => State<T> | ||
readonly add: AddFunc<ElementType, TextNodeType> | ||
readonly _: (f: () => PropValue) => () => PropValue | ||
readonly tags: Tags<ElementType, TextNodeType> | ||
readonly tagsNS: (namespaceURI: string) => Tags<ElementType, TextNodeType> | ||
// Mini-Van specific API | ||
html: (first?: Props | ChildDom<ElementType, TextNodeType>, | ||
@@ -143,8 +161,8 @@ ...rest: readonly ChildDom<ElementType, TextNodeType>[]) => string | ||
export interface Van { | ||
readonly vanWithDoc: VanWithDoc | ||
readonly add: AddFunc<Element, Text> | ||
export interface Van extends VanObj<Element, Text> { | ||
readonly vanWithDoc: <ElementType, TextNodeType>(doc: { | ||
createElement(s: any): ElementType, | ||
createTextNode(s: any): TextNodeType, | ||
}) => VanObj<ElementType, TextNodeType> | ||
readonly tags: BrowserTags | ||
html: (first?: Props | ChildDom<Element, Text>, | ||
...rest: readonly ChildDom<Element, Text>[]) => string | ||
} | ||
@@ -151,0 +169,0 @@ |
@@ -6,29 +6,42 @@ /// <reference types="./mini-van.d.ts" /> | ||
// Aliasing some builtin symbols to reduce the bundle size. | ||
let Obj = Object, protoOf = Obj.getPrototypeOf, _undefined | ||
let protoOf = Object.getPrototypeOf, _undefined, funcProto = protoOf(protoOf) | ||
let stateProto = {get oldVal() { return this.val }}, objProto = protoOf(stateProto) | ||
let state = initVal => ({__proto__: stateProto, val: initVal}) | ||
let val = s => protoOf(s ?? 0) === stateProto ? s.val : s | ||
let plainValue = (k, v) => { | ||
let protoOfV = protoOf(v ?? 0) | ||
return protoOfV === stateProto ? v.val : | ||
protoOfV === funcProto && (!k?.startsWith("on") || v._isBindingFunc) ? 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 propSetterCache = {} | ||
let tagsNS = ns => new Proxy((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) | ||
}, {get: (tag, name) => tag.bind(null, name)}) | ||
let _result = { | ||
add: (dom, ...children) => | ||
(dom.append(...children.flat(Infinity).filter(c => c != _undefined)), dom), | ||
let tags = tagsNS() | ||
tags: new Proxy((name, ...args) => { | ||
let [props, ...children] = args[0]?.constructor === Obj ? args : [{}, ...args] | ||
let dom = doc.createElement(name) | ||
for (let [k, v] of Obj.entries(props)) { | ||
let getPropDescriptor = proto => proto ? | ||
Obj.getOwnPropertyDescriptor(proto, k) ?? getPropDescriptor(protoOf(proto)) : | ||
_undefined | ||
let cacheKey = name + "," + k | ||
let propSetter = propSetterCache[cacheKey] ?? | ||
(propSetterCache[cacheKey] = getPropDescriptor(protoOf(dom))?.set ?? 0) | ||
propSetter ? propSetter.call(dom, v) : dom.setAttribute(k, v) | ||
} | ||
return _result.add(dom, ...children) | ||
}, {get: (tag, name) => tag.bind(null, name)}), | ||
"html": (...args) => "<!DOCTYPE html>" + _result.tags.html(...args).outerHTML, | ||
return { | ||
add, _: f => (f._isBindingFunc = 1, f), tags, tagsNS, state, | ||
val, oldVal: val, derive: f => state(f()), | ||
html: (...args) => "<!DOCTYPE html>" + tags.html(...args).outerHTML, | ||
} | ||
return _result | ||
} | ||
@@ -35,0 +48,0 @@ |
@@ -1,23 +0,40 @@ | ||
type Primitive = string | number | boolean | bigint | ||
export interface Props { | ||
readonly [key: string]: Primitive | ||
export interface State<T> { | ||
val: T | ||
readonly oldVal: 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 | null | ||
export type Props = Record<string, PropValue | StateView<PropValue> | (() => PropValue)> | ||
export interface Element { render(): string } | ||
export type ChildDom = Primitive | Element | readonly ChildDom[] | null | undefined | ||
export type ValidChildDomValue = Primitive | Element | null | undefined | ||
export type BindingFunc = (dom: any) => ValidChildDomValue | ||
export type ChildDom = ValidChildDomValue | StateView<Primitive | null | undefined> | BindingFunc | readonly ChildDom[] | ||
export type TagFunc = (first?: Props | ChildDom, ...rest: readonly ChildDom[]) => Element | ||
export type Tags = { | ||
readonly [key: string]: TagFunc | ||
} | ||
declare const van: { | ||
export interface Van { | ||
readonly state: <T>(initVal: T) => State<T> | ||
readonly val: <T>(s: T | StateView<T>) => T | ||
readonly oldVal: <T>(s: T | StateView<T>) => T | ||
readonly derive: <T>(f: () => T) => State<T> | ||
readonly add: (dom: Element, ...children: readonly ChildDom[]) => Element | ||
readonly tags: Tags | ||
readonly _: (f: () => PropValue) => () => PropValue | ||
readonly tags: Record<string, TagFunc> | ||
readonly tagsNS: (namespaceURI: string) => Record<string, TagFunc> | ||
readonly html: (first?: Props | ChildDom, ...rest: readonly ChildDom[]) => string | ||
} | ||
declare const van: Van | ||
export default van |
@@ -22,11 +22,26 @@ /// <reference types="./van-plate.d.ts" /> | ||
const escape = s => { | ||
const map = { | ||
'&': '&', | ||
'<': '<', | ||
'>': '>', | ||
} | ||
return s.replace(/[&<>]/g, tag => map[tag] || tag) | ||
const escapeMap = { | ||
'&': '&', | ||
'<': '<', | ||
'>': '>', | ||
} | ||
const escape = s => s.replace(/[&<>]/g, tag => escapeMap[tag] || tag) | ||
const escapeAttr = v => v.replace(/"/g, """) | ||
const protoOf = Object.getPrototypeOf, funcProto = protoOf(protoOf), objProto = protoOf(noChild) | ||
const stateProto = {get oldVal() { return this.val }} | ||
const state = initVal => ({__proto__: stateProto, val: initVal}) | ||
const val = s => protoOf(s ?? 0) === stateProto ? s.val : s | ||
const plainValue = (v, k) => { | ||
let protoOfV = protoOf(v ?? 0) | ||
return protoOfV === stateProto ? v.val : | ||
protoOfV === funcProto && (!k?.startsWith("on") || v._isBindingFunc) ? v() : v | ||
} | ||
const elementProto = { | ||
@@ -41,10 +56,16 @@ render() { | ||
const toStr = children => children.map( | ||
c => Object.getPrototypeOf(c) === elementProto ? c.render() : escape(c.toString())).join("") | ||
c => { | ||
const plainC = plainValue(c) | ||
return protoOf(plainC) === elementProto ? plainC.render() : escape(plainC.toString()) | ||
}).join("") | ||
const tags = new Proxy((name, ...args) => { | ||
const [props, ...children] = Object.getPrototypeOf(args[0] ?? 0) === Object.prototype ? | ||
args : [{}, ...args] | ||
const propsStr = Object.entries(props).map(([k, v]) => | ||
typeof v === "boolean" ? | ||
(v ? " " + k : "") : ` ${k}=${JSON.stringify(v)}`).join("") | ||
const [props, ...children] = protoOf(args[0] ?? 0) === objProto ? args : [{}, ...args] | ||
const propsStr = Object.entries(props).map(([k, v]) => { | ||
const plainV = plainValue(v, k), lowerK = k.toLowerCase() | ||
return typeof plainV === "boolean" ? (plainV ? " " + lowerK : "") : | ||
// Disable setting attribute for function-valued properties (mostly event handlers), | ||
// as they're usually not useful for SSR (server-side rendering). | ||
protoOf(plainV) !== funcProto ? ` ${lowerK}=${JSON.stringify(escapeAttr(plainV.toString()))}` : "" | ||
}).join("") | ||
const flattenedChildren = children.flat(Infinity).filter(c => c != null) | ||
@@ -61,4 +82,6 @@ return {__proto__: elementProto, name, propsStr, | ||
const html = (...args) => "<!DOCTYPE html>" + tags.html(...args).render() | ||
export default {add, tags, html} | ||
export default { | ||
add, _: f => (f._isBindingFunc = 1, f), tags, tagsNS: () => tags, state, | ||
val, oldVal: val, derive: f => state(f()), | ||
html: (...args) => "<!DOCTYPE html>" + tags.html(...args).render() | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
25552
276
262
4