Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/xboxyan/web-template

web-template.js 是一款基于 ES6 模板字符串解析的模板引擎。
https://github.com/xboxyan/web-template

dom dom-diff dom-parser es6 html html5 javascript template vue

Last synced: 12 days ago
JSON representation

web-template.js 是一款基于 ES6 模板字符串解析的模板引擎。

Awesome Lists containing this project

README

        

# web-template

web-template.js 是一款基于 [ES6 模板字符串](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/template_strings)解析的模板引擎。

## 特点

1. 纯原生浏览器解析,无任何依赖,无需编译,不拖泥带水
1. 类 vue 模板语法,上手快,几乎可以不用看文档
1. 支持 dom-diff 局部更新,性能高效
1. 代码量极少,~~包含注释不到 100 行~~(由于添加了 dom-diff 特性,已经超过 100 行了~),方便学习和扩展

[演示 demo](https://xboxyan.codelabo.cn/web-template/index.html)

## 更新

* 2020-11-27
* 列表渲染支持 `key` 属性
* 2020-11-24
* 支持 `dom-diff` 局部更新
* 2020-11-20
* 新增 `mount` 方法
* 新增 `block` 标签
* 2020-11-19
* 支持 `{{}}` 插值表达式
* 新增 `fragment` 标签
* 新增 `open` 布尔值属性

## 适用场景

适用于原生开发,又希望有一定模板渲染能力的场景,比如一大堆列表循环渲染

在使用之前

```js
var html = '';
arrData.forEach(function (obj, index) {
html = html + '\
\
\

' + obj.title + '
\
' + obj.time + '\
' + obj.comment + '\
';
});
console.log(html);
```

使用 ES6 模板字符串

```js
let html = `${data.map(function (obj, index) {
return `

${obj.title}

${obj.time}
${obj.comment}
`;
}).join('')}`;
console.log(html);
```

使用 web-template.js 之后

```html



${obj.title}

${obj.time}
${obj.comment}

```

```js
console.log(template.render(data).content); // dom节点
console.log(template.render(data).innerTHML); // dom字符串
```

是不是清爽了许多,可读性也更强了

## 使用方式

直接引用 `web-template.js`

```html

```

在 HTML 页面上添加 `template` 标签,放入模板

```html

  • ${ index + 1} - ${ item }
  • ```

    假设有如下数据

    ```js
    const data = {
    list : [ 'Apple', 'Banana', 'Cat']
    }

    list.appendChild(template.render(data).content)
    ```

    最终,页面上 ul 会被渲染成

    ```html


    • 1 - Apple

    • 2 - Banana

    • 3 - Cat


    ```

    更多用法,可参考下面的详细介绍

    ## 模板语法

    模板必须放在 `` 标签中

    ```html

    ```

    > 以下所有规则都在这个标签中

    ### 1.插值

    数据绑定采用的是 ES6 模板字符串的语法, `${ }`:

    *目前已兼容 `{{ }}` 的语法*

    ```html
    Message: ${ msg }
    ```

    ```js
    const data = {
    msg: 'hello'
    }
    ```

    返回

    ```html
    Message: hello
    ```

    各种属性,或者说只要出现插值语法的地方都会被替换成普通文本

    ```html
    Message: ${ msg }
    ```

    ```js
    const data = {
    msg: 'hello',
    type: 'normal',
    }
    ```

    返回

    ```html
    Message: hello
    ```

    一些为布尔值的属性,比如 `disabled`、`hidden`、`required`、`checked`、`selected`、`readonly`,`open`(欢迎补充~),如果设置值为 `false` ,那么将会移除该属性

    ```html
    button
    button
    ```

    ```js
    const data = {
    disabled: true,
    hidden: false,
    }
    ```

    返回

    ```html
    button
    button
    ```

    ### 2.JavaScript 表达式

    JavaScript 表达式也可以用到模板中的任意地方

    ```html
    ${ number + 1 }
    ${ ok ? 'YES' : 'NO' }
    ```

    ```js
    const data = {
    number: 10,
    ok: true,
    }
    ```

    返回

    ```html
    11
    YES
    ```

    > 注意,这里是单个表达式,推荐使用三元表达式,vue 和 react 也是同样的规定

    JavaScript 函数也可以直接访问,可以是全局函数,也可以是自己定义的函数

    ```html
    ${ new Date() }
    ${ add(1,2) }
    ```

    ```js
    // 定义在全局的函数
    function add(m,n){
    return m + n
    }
    ```

    返回

    ```html
    Tue Nov 17 2020 11:13:44 GMT+0800 (中国标准时间)
    3
    ```

    更极端的是,可以直接嵌套函数,但是必须是立即执行函数

    ```html
    ${
    (()=>{
    return 'hello';
    })()
    }

    ```

    返回

    ```html
    hello
    ```

    > 当然强烈不推荐这么做,把一段 js 放在 html 中实在不怎么优雅

    ### 3.指令

    指令是带有特殊前缀的属性,默认是 `$`,当然你也可以更换成你喜欢的,比如 `v-`,通过修改 `template` 标签的 rule 属性

    ```html

    ```

    > 以下就以 `v-` 为例

    目前有两个指令,分别是**条件渲染**和**列表渲染**

    #### 3.1.条件渲染

    `v-if` 用来条件性地渲染一块内容,当值为 false 时,内容会被完全移除

    ```html

    Template is awesome!


    Oh no 😢


    ```

    ```js
    const data = {
    show: true
    }
    ```

    返回

    ```html

    Template is awesome!

    ```

    > 注意,指令的属性值不需要包裹 `${ }`

    `v-show` 内部通过属性 `hidden` 来实现样式上的隐藏

    ```html

    Template is awesome!


    Oh no 😢


    ```

    ```js
    const data = {
    show: true
    }
    ```

    返回

    ```html

    Template is awesome!


    Oh no 😢


    ```

    > 注意,这里是普通的属性,所以需要包裹 `${ }`

    #### 3.2.列表渲染

    ##### 别名

    `v-for` 用来循环渲染一个列表,格式形如 `item in items`,其中 `items` 是数据源,`item` 是被循环的数组元素的**别名**。

    ```html


  • ${ item.message }
  • ```

    ```js
    const data = {
    items: [
    { message: 'Foo' },
    { message: 'Bar' }
    ]
    }
    ```

    返回

    ```html

  • Foo

  • Bar

  • ```

    ##### 索引

    此外,还支持第二个可选参数,表示**索引**(默认为 `index` ),形如 `(item,index) in items`

    ```html


  • ${ index } - ${ item.message }
  • ```

    返回

    ```html

  • 0 - Foo

  • 1 - Bar

  • ```

    ##### 作用域

    每个循环都有自己的作用域,这在多重循环中特别有用,如下可以轻易的实现一个 9*9 乘法表

    ```html


    ${i*j}

    ```

    返回

    ```html


    1


    2 4


    3 6 9


    4 8 12 16


    5 10 15 20 25


    6 12 18 24 30 36


    7 14 21 28 35 42 49


    8 16 24 32 40 48 56 64


    9 18 27 36 45 54 63 72 81

    ```

    ##### 简写

    每次都要写 `item in items` 有些麻烦,这里可以简写成 `items`,此时默认**别名**和**索引**分别是 `item` 和 `index`

    ```html


  • ${ index } - ${ item.message }
  • ```

    返回

    ```html

  • 0 - Foo

  • 1 - Bar

  • ```

    ##### 重复次数

    `v-for` 也可以接受整数。在这种情况下,它会把模板重复对应次数。

    ```html

    ${ index }

    ```

    返回

    ```html
    0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    ```

    > 虽然有些鸡肋,某些情况下还是有点作用的

    返回如上

    ##### 使用 key 属性

    如果列表有可能会发生顺序改变,可以指定一个不重复的 key ,这样在渲染时会优先根据 key 的顺序重新排序,而不会重新渲染。

    ```html


  • ${ index } - ${ item.message }
  • ```

    > 目前仅适用于循环渲染

    ##### 对象迭代

    你也可以用 `v-for` 来遍历一个对象,这里用 `of` 来区分,形如 `value of object`

    ```html


  • ${ value }
  • ```

    ```js
    const data = {
    object: {
    title: 'How to do lists in Vue',
    author: 'Jane Doe',
    publishedAt: '2016-04-10'
    }
    }
    ```

    返回

    ```html

  • How to do lists in Vue

  • Jane Doe

  • 2016-04-10

  • ```

    同时,支持三个参数,形如 `(value, name, index) of object`,分别为**值**、**键名**、**索引**

    ```html


  • ${ index }. ${ name }. ${ value }
  • ```

    返回

    ```html

  • 0. title. How to do lists in Vue

  • 1. author. Jane Doe

  • 2. publishedAt. 2016-04-10

  • ```

    > 对象迭代不支持简写,尽量多使用数组遍历吧

    #### 3.3 fragment 片段

    有时候我们可能需要遍历这样一种没有父级的元素

    ```html









    ```

    这时可以借助 fragment 标签包裹,渲染后 fragment 标签会被移除

    ```html






    ```

    在 `v-if` 中也适用的

    ```html



    1 dt

    1 dd



    2 dt

    2 dd


    ```

    返回

    ```html


    1 dt

    1 dd


    ```

    其他任意地方也可以添加,只不过不会被渲染

    > 也可使用 block 标签,功能完全一致 (参考微信小程序)

    ### 4.渲染

    在原生 `template` 标签扩展了 `render` 方法,可以传入一个对象,然后返回一个 `template`文档片段([`document-fragment`](https://developer.mozilla.org/zh-CN/docs/Web/API/DocumentFragment))

    ```js
    const tpl = template.render(data) // template document-fragment

    tpl.content; // 返回dom节点
    tpl.innerHTML; // 返回字符串
    ```

    一般通过 `.content` 可以得到模板的 dom 结构,直接以 `appendChild` 的方式渲染到页面,这种方式在追加数据的时候更加有效

    ```js
    container.appendChild(tpl.content);
    ```

    如果内容需要重置,可以简单粗暴的使用 `.innerHTML`

    ```js
    container.innerHTML = tpl.innerHTML;
    ```

    如果需要局部更新,可使用 `.html()` 方法

    ```js
    // 支持dom节点
    container.html(tpl.content);
    // 字符串也支持
    container.html(tpl.innerHTML);
    ```

    ### 5. 挂载

    一般情况通过 `template.render(data)` 来获取到模板的内容,然后再通过容器的 `.innerHTML` 就可以了,但是有些啰嗦,这里提供一个更为简单的方法 `template.mount()`

    需要在容器上指定和模板 id 相同的值,形成映射关系,比如

    ```html

    ${name}

    ```

    然后执行

    ```js
    tpl.mount(data);
    ```

    这样模板内容就自动挂载在页面上了

    > 一般情况下均可满足,不满足的情况可以采用 render 方式,更加灵活

    如果需要局部更新,可以传入第2个参数,表示是否进行 diff 比较

    ```js
    tpl.mount(data, isDiff);
    ```

    > 一般情况下,diff 并不会比直接 innerHTML 要快,但是可以保留元素的状态,建议初次渲染选择 innerHTML,后面更新使用 dom-diff

    ## 兼容性和一些局限

    需要支持 ES6 模板字符串语法的浏览器,还在用 IE 的小伙伴可以放弃了

    dom-diff 基本够用,还有待完善

    由于使用了很多 DOM API,依赖浏览器环境,因此不支持 Node 等其他非浏览器环境,不支持服务端渲染

    ## 参考

    [Vue 模板语法](https://cn.vuejs.org/v2/guide/syntax.html)

    [ES6模板字符串在HTML模板渲染中的应用](https://www.zhangxinxu.com/wordpress/2020/10/es6-html-template-literal/)