diff --git a/README.md b/README.md index 250c1b6..9e576cc 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,7 @@ Sample code: _Requires Deno `1.35` or later._ ```typescript -import van from "https://deno.land/x/minivan@0.6.0/src/van-plate.js" +import van from "https://deno.land/x/minivan@0.6.1/src/van-plate.js" const {a, body, li, p, ul} = van.tags @@ -195,7 +195,7 @@ _Requires Deno `1.35` or later._ ```typescript import { DOMParser } from "https://deno.land/x/deno_dom@v0.1.38/deno-dom-wasm.ts" -import van from "https://deno.land/x/minivan@0.6.0/src/mini-van.js" +import van from "https://deno.land/x/minivan@0.6.1/src/mini-van.js" const document = new DOMParser().parseFromString("", "text/html")! const {tags, html} = van.vanWithDoc(document) @@ -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.6.0.min.js" +import van from "https://cdn.jsdelivr.net/gh/vanjs-org/mini-van/public/mini-van-0.6.1.min.js" ``` To code without ES6 modules, add the following line to your HTML file instead: ```html - + ``` -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. +Alternative, you can download the files ([`mini-van-0.6.1.min.js`](https://vanjs.org/autodownload?file=mini-van-0.6.1.min.js), [`mini-van-0.6.1.nomodule.min.js`](https://vanjs.org/autodownload?file=mini-van-0.6.1.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). diff --git a/package-lock.json b/package-lock.json index 7f5624a..ddb5fc2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "mini-van-plate", - "version": "0.6.0", + "version": "0.6.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mini-van-plate", - "version": "0.6.0", + "version": "0.6.1", "license": "MIT", "devDependencies": { "esbuild": "^0.17.19", diff --git a/package.json b/package.json index 383bdc8..731c171 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mini-van-plate", - "version": "0.6.0", + "version": "0.6.1", "description": "A minimalist template engine for DOM generation and manipulation, working for both client-side and server-side rendering", "files": [ "src/mini-van.js", diff --git a/public/mini-van-0.6.1.d.ts b/public/mini-van-0.6.1.d.ts new file mode 100644 index 0000000..2796707 --- /dev/null +++ b/public/mini-van-0.6.1.d.ts @@ -0,0 +1,75 @@ +export interface State { + val: T + readonly oldVal: T + readonly rawVal: T +} + +// Defining readonly view of State for covariance. +// Basically we want StateView to implement StateView +export type StateView = Readonly> + +export type Primitive = string | number | boolean | bigint + +export type PropValue = Primitive | ((e: any) => void) | null + +export type Props = Record | (() => PropValue)> + +interface HasFirstChild {firstChild?: unknown} + +type NodeType = + Omit + +export type ValidChildDomValue = + Primitive | ElementType | NodeType | TextNodeType | null | undefined + +export type BindingFunc = + | ((dom?: ElementType | TextNodeType) => ValidChildDomValue) + | ((dom?: ElementType) => ElementType) + +export type ChildDom = + | ValidChildDomValue + | StateView + | BindingFunc + | readonly ChildDom[] + +type AddFunc = + (dom: ElementType, ...children: readonly ChildDom[]) => ElementType + +export type TagFunc = + (first?: Props | ChildDom, + ...rest: readonly ChildDom[]) => ResultType + +type Tags = + Readonly>> + +// Tags type in browser context, which contains the signatures to tag functions that return +// specialized DOM elements. +type BrowserTags = Tags & { + [K in keyof HTMLElementTagNameMap]: TagFunc +} + +declare function state(): State +declare function state(initVal: T): State + +export interface VanObj { + readonly state: typeof state + readonly derive: (f: () => T) => State + readonly add: AddFunc + readonly tags: Tags & ((namespaceURI: string) => Tags) + + // Mini-Van specific API + html: (first?: Props | ChildDom, + ...rest: readonly ChildDom[]) => string +} + +export interface Van extends VanObj { + readonly vanWithDoc: (doc: { + createElement(s: any): ElementType, + createTextNode(s: any): TextNodeType, + }) => VanObj + readonly tags: BrowserTags & ((namespaceURI: string) => Tags) +} + +declare const van: Van + +export default van diff --git a/public/mini-van-0.6.1.js b/public/mini-van-0.6.1.js new file mode 100644 index 0000000..ae2613c --- /dev/null +++ b/public/mini-van-0.6.1.js @@ -0,0 +1,48 @@ +/// + +// 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) => "" + tags.html(...args).outerHTML, + } +} + +export default {"vanWithDoc": vanWithDoc, + ...vanWithDoc(typeof window !== "undefined" ? window.document : null)} diff --git a/public/mini-van-0.6.1.min.d.ts b/public/mini-van-0.6.1.min.d.ts new file mode 100644 index 0000000..2796707 --- /dev/null +++ b/public/mini-van-0.6.1.min.d.ts @@ -0,0 +1,75 @@ +export interface State { + val: T + readonly oldVal: T + readonly rawVal: T +} + +// Defining readonly view of State for covariance. +// Basically we want StateView to implement StateView +export type StateView = Readonly> + +export type Primitive = string | number | boolean | bigint + +export type PropValue = Primitive | ((e: any) => void) | null + +export type Props = Record | (() => PropValue)> + +interface HasFirstChild {firstChild?: unknown} + +type NodeType = + Omit + +export type ValidChildDomValue = + Primitive | ElementType | NodeType | TextNodeType | null | undefined + +export type BindingFunc = + | ((dom?: ElementType | TextNodeType) => ValidChildDomValue) + | ((dom?: ElementType) => ElementType) + +export type ChildDom = + | ValidChildDomValue + | StateView + | BindingFunc + | readonly ChildDom[] + +type AddFunc = + (dom: ElementType, ...children: readonly ChildDom[]) => ElementType + +export type TagFunc = + (first?: Props | ChildDom, + ...rest: readonly ChildDom[]) => ResultType + +type Tags = + Readonly>> + +// Tags type in browser context, which contains the signatures to tag functions that return +// specialized DOM elements. +type BrowserTags = Tags & { + [K in keyof HTMLElementTagNameMap]: TagFunc +} + +declare function state(): State +declare function state(initVal: T): State + +export interface VanObj { + readonly state: typeof state + readonly derive: (f: () => T) => State + readonly add: AddFunc + readonly tags: Tags & ((namespaceURI: string) => Tags) + + // Mini-Van specific API + html: (first?: Props | ChildDom, + ...rest: readonly ChildDom[]) => string +} + +export interface Van extends VanObj { + readonly vanWithDoc: (doc: { + createElement(s: any): ElementType, + createTextNode(s: any): TextNodeType, + }) => VanObj + readonly tags: BrowserTags & ((namespaceURI: string) => Tags) +} + +declare const van: Van + +export default van diff --git a/public/mini-van-0.6.1.min.js b/public/mini-van-0.6.1.min.js new file mode 100644 index 0000000..ed82d89 --- /dev/null +++ b/public/mini-van-0.6.1.min.js @@ -0,0 +1 @@ +let t,e=Object.getPrototypeOf,r=e(e),l={get oldVal(){return this.val},get rawVal(){return this.val}},n=e(l),o=t=>({__proto__:l,val:t}),a=(t,n)=>{let o=e(n??0);return o===l?n.val:o!==r||t?.startsWith("on")?n:n()},d=(e,...r)=>(e.append(...r.flat(1/0).map(a.bind(t,t)).filter(e=>e!=t)),e),u=l=>{let u=(t,o,...u)=>{let[i,...w]=e(u[0]??0)===n?u:[{},...u],f=t?l.createElementNS(t,o):l.createElement(o);for(let[t,l]of Object.entries(i)){let n=a(t,l);e(n)!==r&&f.setAttribute(t,n)}return d(f,...w)},i=e=>({get:(r,l)=>u.bind(t,e,l)}),w=new Proxy(t=>new Proxy(u,i(t)),i());return{add:d,tags:w,state:o,derive:t=>o(t()),html:(...t)=>""+w.html(...t).outerHTML}};export default{vanWithDoc:u,...u("undefined"!=typeof window?window.document:null)}; \ No newline at end of file diff --git a/public/mini-van-0.6.1.nomodule.js b/public/mini-van-0.6.1.nomodule.js new file mode 100644 index 0000000..dfaa60a --- /dev/null +++ b/public/mini-van-0.6.1.nomodule.js @@ -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) => "" + 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; +})(); diff --git a/public/mini-van-0.6.1.nomodule.min.js b/public/mini-van-0.6.1.nomodule.min.js new file mode 100644 index 0000000..c1beea4 --- /dev/null +++ b/public/mini-van-0.6.1.nomodule.min.js @@ -0,0 +1 @@ +{let t,e,r,n,l,o,a,d,w,i;e=Object.getPrototypeOf,r=e(e),l=e(n={get oldVal(){return this.val},get rawVal(){return this.val}}),o=t=>({__proto__:n,val:t}),a=(t,l)=>{let o=e(l??0);return o===n?l.val:o!==r||t?.startsWith("on")?l:l()},d=(e,...r)=>(e.append(...r.flat(1/0).map(a.bind(t,t)).filter(e=>e!=t)),e),i={vanWithDoc:w=n=>{let w=(t,o,...w)=>{let[i,...u]=e(w[0]??0)===l?w:[{},...w],h=t?n.createElementNS(t,o):n.createElement(o);for(let[t,n]of Object.entries(i)){let l=a(t,n);e(l)!==r&&h.setAttribute(t,l)}return d(h,...u)},i=e=>({get:(r,n)=>w.bind(t,e,n)}),u=new Proxy(t=>new Proxy(w,i(t)),i());return{add:d,tags:u,state:o,derive:t=>o(t()),html:(...t)=>""+u.html(...t).outerHTML}},...w("undefined"!=typeof window?window.document:null)},window.van=i;} \ No newline at end of file diff --git a/public/mini-van.version b/public/mini-van.version index 09a3acf..7ceb040 100644 --- a/public/mini-van.version +++ b/public/mini-van.version @@ -1 +1 @@ -0.6.0 \ No newline at end of file +0.6.1 \ No newline at end of file diff --git a/src/van-plate.js b/src/van-plate.js index e0edfa1..6d82266 100644 --- a/src/van-plate.js +++ b/src/van-plate.js @@ -19,6 +19,11 @@ const noChild = { keygen: 1, } +const tagsNoEscape = { + "script": 1, + "style": 1, +} + const escapeMap = { '&': '&', '<': '<', @@ -48,7 +53,7 @@ const elementProto = { for (const c of this.children) { const plainC = plainValue(c) protoOf(plainC) === elementProto ? plainC.renderToBuf(buf) : - buf.push((this.name === "script" ? x => x : escape)(plainC.toString())) + buf.push((tagsNoEscape[this.name] ? x => x : escape)(plainC.toString())) } buf.push(``) }, diff --git a/test/deno/van-plate.test.ts b/test/deno/van-plate.test.ts index c5c5df0..349b013 100644 --- a/test/deno/van-plate.test.ts +++ b/test/deno/van-plate.test.ts @@ -1,7 +1,7 @@ import { assertEquals } from "https://deno.land/std@0.184.0/testing/asserts.ts"; import van from "../../src/van-plate.js" -const {a, body, br, button, div, head, hr, input, li, p, pre, script, span, title, ul} = van.tags +const {a, body, br, button, div, head, hr, input, li, p, pre, script, span, style, title, ul} = van.tags Deno.test("tags", () => { assertEquals(div( @@ -41,6 +41,10 @@ Deno.test("don't escape script tag", () => { assertEquals(script("console.log(a < b && c > d)").render(), "") }) +Deno.test("don't escape style tag", () => { + assertEquals(style("ul > li { list-style-type: square; }").render(), "") +}) + Deno.test("nested children", () => { assertEquals(ul([li("Item 1"), li("Item 2"), li("Item 3")]).render(), "
  • Item 1
  • Item 2
  • Item 3
")