Skip to content

Commit

Permalink
0.6.1: [van-plate] not escaping for <style> tag as well (in addition …
Browse files Browse the repository at this point in the history
…to <script> tag)
  • Loading branch information
Tao-VanJS committed Nov 7, 2024
1 parent 6d4f92a commit 369c88f
Show file tree
Hide file tree
Showing 12 changed files with 265 additions and 11 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/[email protected].0/src/van-plate.js"
import van from "https://deno.land/x/[email protected].1/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/[email protected].0/src/mini-van.js"
import van from "https://deno.land/x/[email protected].1/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.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
<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/vanjs-org/mini-van/public/mini-van-0.6.0.nomodule.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/vanjs-org/mini-van/public/mini-van-0.6.1.nomodule.min.js"></script>
```
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).
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.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",
Expand Down
75 changes: 75 additions & 0 deletions public/mini-van-0.6.1.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.1.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.1.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.1.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.1.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.1.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.6.0
0.6.1
7 changes: 6 additions & 1 deletion src/van-plate.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ const noChild = {
keygen: 1,
}

const tagsNoEscape = {
"script": 1,
"style": 1,
}

const escapeMap = {
'&': '&amp;',
'<': '&lt;',
Expand Down Expand Up @@ -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(`</${this.name}>`)
},
Expand Down
6 changes: 5 additions & 1 deletion test/deno/van-plate.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { assertEquals } from "https://deno.land/[email protected]/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(
Expand Down Expand Up @@ -41,6 +41,10 @@ Deno.test("don't escape script tag", () => {
assertEquals(script("console.log(a < b && c > d)").render(), "<script>console.log(a < b && c > d)</script>")
})

Deno.test("don't escape style tag", () => {
assertEquals(style("ul > li { list-style-type: square; }").render(), "<style>ul > li { list-style-type: square; }</style>")
})

Deno.test("nested children", () => {
assertEquals(ul([li("Item 1"), li("Item 2"), li("Item 3")]).render(),
"<ul><li>Item 1</li><li>Item 2</li><li>Item 3</li></ul>")
Expand Down

0 comments on commit 369c88f

Please sign in to comment.