當前的前端世界中有很多著名的開源javascript模板引擎如Handlebars、Nunjucks、EJS等等,相信很多人對它們都並不陌生。
js模板引擎的現狀
通常來講,這些js模板引擎專案都有一個共同的特性:只專注渲染字串(html)
早在幾年前Backbone等mv*
框架流行的時候,js模板引擎遇到了它們的春天,因為Backbone可以支援選配使用者自己喜好的模板,並提供了接入方案。但是在新一代前端mv*
框架盛行的今天,人們更多的關注點在於React的JSX
支援的邏輯何等地強大、Vue的v-show
等指令使用起來多麼地方便,而js模板引擎呢?它們似乎只能在Node.js伺服器端找到它們的歸宿,而且還被React及Vue的 SSR(服務端渲染) 繼續蠶食著僅有的市場。
為什麼各種各樣的js模板引擎都只專注於渲染html字串?這或許跟歷史原因有關,畢竟5、6年前的時候並沒有虛擬dom
,使用jQuery等框架的$('div').html(str)
方法渲染dom是理所當然的事情。
新型js模板引擎
我們不妨試想一下,其實js模板引擎在當前的時代只要也能做到渲染虛擬dom
物件,或許就可以再次找到它們被重用的機會:
+---------------------+
¦ <Template string /> ¦
+---------------------+
|
|
+---------------------+
| render to |
| |
+-------------+ +-------------------+
¦ html string ¦ ¦ React virtual dom ¦
+-------------+ +-------------------+
複製程式碼
然而目前有一個新的js模板引擎可以做到上述的同時支援渲染html和React元件,它就是NornJ。
github:github.com/joe-sky/nor…
官方文件(github pages):joe-sky.github.io/nornj-guide
官方文件(gitbook):joe-sky.gitbooks.io/nornj-guide
安裝
npm install nornj
npm install nornj-react # React開發請一起安裝此包
npm install nornj-loader # webpack環境請一起安裝此包
複製程式碼
線上演示地址
渲染html字串
渲染React元件
在React開發中的基本使用方法
React在介紹自己時常說JSX是"可選的",但實際上,脫離了JSX的React根本就幾乎無法正常地開發。如果有了另一種DSL(js模板引擎)可適配React開發,那麼JSX才能真正地成為可選的技術。
NornJ的模板語法在參考自Handlebars
、Nunjucks
、Vue
等多個著名專案的基礎上,也有很多自己獨特的語法如tagged template string
、自定義語句與運算子
等等,與html+js非常相似可快速上手。需要提一下另一個React
的模板專案react-templates,它是React
生態中唯一一個比較完善的模板專案,但很可惜的是它現在已經幾乎不維護了,而且功能非常有限。
每個React元件都須要在render返回元件的標籤程式碼,如在HelloWorld
元件中渲染一個下拉框,用JSX
和NornJ
的語法分別實現:
- JSX
export default class HelloWorld extends Component {
render() {
return (
<div className="hello" style={{ width: 300, height: 200 }}>
<input type="text" />
<select>
{[1, 2, 3].map((item, i) => i > 1
? <option>{item + 1}</option>
: <option>{item}</option>
)}
</select>
</div>
);
}
}
複製程式碼
- NornJ
import { template as t } from 'nornj';
import 'nornj-react';
import { Input } from 'antd';
export default class HelloWorld extends Component {
render() {
return t`
<div class="hello" style="width:300px;height:200px;">
<input type="text">
<select>
<#each {1 .. 3}>
<#if {@index > 1}>
<option>{@item + 1}</option>
<#else><option>{@item}</option></#else>
</#if>
</#each>
</select>
</div>
<${Input} placeholder="Basic usage" />
`;
}
}
複製程式碼
如上例,這就是NornJ
最基本的使用方法了,開箱即用。它可以使用ES6+
的tagged template literals
語法在js檔案中描述模板,模板語法在處理邏輯時的結構比JSX
更加易讀,且語法和html更為接近:
- 可以寫class替代className。
- style可以使用html中寫style屬性的方式,當然寫物件也同樣支援。
- 模板語法提供了
#if
、#each
等擴充套件標籤用於處理邏輯,可替代三目運算子
與陣列map方法
。 - input和img等標籤支援只寫開標籤,如
<input type="text">
,JSX中一定要寫為<input type="text" />
。 - 可直接在元件的render中返回同一級別的多個標籤,外面不用套上陣列。
- 雙花括號
{{}}
和單花括{}
號語法在React開發中都支援,除特殊場景外依個人喜好而定。 - 模板和JSX一樣支援嵌入任意js變數,這當然也包含第三方React元件和JSX變數,
NornJ
模板和JSX是可以共存的!
NornJ
的tagged template literals
語法更多細節請檢視官方文件。
上面的例子也可以這樣改寫:
import nj from 'nornj';
import 'nornj-react';
import { Input } from 'antd';
const tmplFn = nj`
<div class="hello" style="width:300px;height:200px;">
...
</div>
<${Input} placeholder="Basic usage" />
`;
export default class HelloWorld extends Component {
render() {
return tmplFn();
}
}
複製程式碼
可以看出,實質上tagged template literals
語法就是建立了一個模板函式,然後再在render中執行了而已。這時不難想到,使用第一種方法將模板函式放到render中執行,這樣會不會每次執行render時都進行模板編譯(內部涉及各種正則析取)會造成效能下降?並不會,因為NornJ
模板在編譯時會進行快取,只有第一次render時會進行模板編譯,之後的每次render就會走快取了。
另外,NornJ
和JSX
還可以巢狀編寫,僅僅在很小的粒度使用NornJ
模板也完全沒有問題,具體請見官方文件。
單檔案模板
NornJ模板除了可以在js檔案中編寫之外,還可以編寫在單獨的模板檔案中,用來做元件(或頁面)展現層與結構層的分離(具體請參考官方文件)。例如編寫一個helloWorld.nj.html
檔案:
<template name="partial">
<ant-Input placeholder="Basic usage" value={value} />
</template>
<template name="helloWorld">
<div class={styles.hello}>
<select>
<#each {1 .. 3}>
<#if {@index > 1}>
<option>{@item + 1}</option>
<#else><option>{@item}</option></#else>
</#if>
</#each>
</select>
</div>
<#include name="partial" />
</template>
複製程式碼
然後可以在js檔案中引入後使用:
import tmpls from './helloWorld.nj.html';
export default class HelloWorld extends Component {
state = {
value: 'test'
};
render() {
return tmpls.helloWorld(this.state, this.props);
}
}
複製程式碼
如上,每個*.nj.html
檔案內都可以定義一個或多個template
標籤。這些template
標籤會在引用它的js檔案中通過nornj-loader進行解析,生成一個以template
標籤的name
屬性為key的模板函式集合物件,在各個元件的render中呼叫它們就會生成相應的React vdom
物件。
針對NornJ
的單檔案模板,我們也提供了一些IDE的語法高亮與提示工具。
擴充套件模板
NornJ與Handlebars
比較類似具有非常強大的可擴充套件性,#if
、#each
等實際上都是擴充套件出來的語法,您完全可以自己擴充套件出#customIf
、#customEach
等新語句。在可擴充套件性這一點上,不難想到JSX也可以通過babel進行擴充套件,也可以搞新的語法出來,例如jsx-control-statements。但是babel擴充套件上手門檻不低,要學各種babel AST的用法,開發一個完美的外掛出來似乎並非易事。
由於NornJ
繼承於Handlebars
的擴充套件方式,它內部的每個擴充套件都可以用一個函式簡單地開發出來,例如為NornJ
擴充套件一個**
運算子,作用是乘方運算:
import nj from 'nornj';
nj.registerFilter('**', (val1, val2) => Math.pow(val1, val2));
複製程式碼
然後就可以直接使用了:
<input value="{ 2 ** 10 / 100 }">
複製程式碼
當然上述只是個最簡單的例子,更多模板擴充套件描述請參考官方文件。
結合Mobx建立雙向資料繫結
利用NornJ的可擴充套件性,模板語法在理論上可以實現無限的可能性。#mobx-model
是NornJ
實現的一個行內擴充套件標籤(類似於vue及ng的指令),具體用法如下:
import { Component } from 'react';
import { observable } from 'mobx';
import nj from 'nornj';
import 'nornj-react';
import 'nornj-react/mobx';
class TestComponent extends Component {
@observable inputValue = '';
render() {
return nj`<input :#mobx-model="inputValue">`(this);
}
}
複製程式碼
#mobx-model
的底層實現方式和Vue的v-model
是比較類似的。React也有其他雙向繫結的實現如Mota,但該專案的實現方式是通過高階元件。利用NornJ
的擴充套件語法,我們還能實現更多類似於#mobx-model
的擴充套件功能。
結合React的各種生態
NornJ可以完美結合各種React生態,包括React-Native
、Redux
、React-Router
、Mobx
、Ant Design
等等,它可以和任何已有的React生態共存。
更多詳細文件請見官方文件。
適配各種React-Like庫
NornJ在理論上可以適配任意React-Like庫,包括Preact、inferno、anu,Nerv等。
具體適配方式請見官方文件。
渲染html字串
NornJ同時還支援渲染html字串,這和傳統的js模板引擎就完全一樣了。使用方法和React中幾乎完全一樣,具體請看這個線上例項:
當然,NornJ
也能夠支援Node.js伺服器Express
及Koa
等。傳統js模板的compile
、render
等方法,NornJ
也支援。
更多細節請見官方文件。
NornJ的未來計劃
NornJ在未來我們還有很多可以增強的方面,例如:
- 開發eslint外掛
NornJ
目前雖然內部有語法錯誤警告機制,但只是將錯誤列印在控制檯。對於靜態語法錯誤檢測,NornJ
將來有必要開發一個eslint外掛。
- 效能持續優化
雖然NornJ
目前的模板渲染效率已然不低(請見模板渲染效率測試),但仍尚有很大的優化空間,會持續進行優化工作。
-
國際化
-
對Vue提供適配
NornJ
適配Vue暫時是個設想,理論上是可以實現的。但有幾個難題:
- Vue使用的
虛擬dom
物件結構並非React那樣簡單,它使用類似snabbdom的結構,其中的事件等方法都綁在特殊的物件上。這對NornJ
來說適配起來和React比有一定難度。 - Vue的模板語法已然很好用,雖然
NornJ
可以提供一些自身獨特的語法,但是提升開發體驗的作用恐怕沒有在React中那樣明顯。
NornJ發展到今天已經擁有了很多強大的功能,日後還會繼續完善,歡迎試用。