模板引擎負責組裝數據,並以另壹種形式或外觀呈現數據。瀏覽器中的頁面是Web模板引擎的最終呈現。
不管妳是否直接使用模板引擎,Web模板壹直都在,不是在前端而是在後端,它的出現甚至可以追溯到超文本標記語言HTML標準正式建立之前。
服務器端模板引擎
我知道的最早的Web模板引擎是PHP,正式誕生於1997,工作在服務器端。讓我們來看看PHP的官方簡介——what is:
HPer普遍認同PHP本身是最自然、最原生的PHP模板引擎,因為確實如此。在PHP的世界裏,重新打包的模板引擎出現過很多次,著名的就是smarty。
其他很多服務器端語言都有HTML模板引擎,比如JSP和mustache。
毫無疑問,這些服務器端模板引擎生成的最終結果是HTML(XML)字符串,處理流程邏輯是利用宿主語言本身的語法實現的。
它們的* * *特點是壹樣的:HTML只是壹個字符串,最終結果可能需要Tidy這樣的清理或修正驗證工具。
這裏有個問題:smarty有必要存在於二次包裝中嗎?
瀏覽器端模板引擎
我知道的最早的前端模板引擎是jCT,由Google Code托管,誕生於2008年。宿主語言是JavaScript,在瀏覽器中運行。
今天,在OSC中搜索JavaScript模板引擎,妳會得到100+個結果。以下是壹些例子:
輕量級:tpl.js,T.js
認知:arttemplate,mustache.js,doT.js,handlebars.js,pug。
基於DOM樹的:domTemplate、透明度、圖版
基於VDOM的:htmltemplate-vdom,virtual-stache,html-patcher
流行的框架:Vue.js,ReactJS,riot
現實世界:PowJS
他們的* * *特點是壹樣的:都支持插值。
還有壹個模板引擎流行度的比較,甚至是最佳JavaScript模板引擎投票的原因和利弊。
如何選擇
我覺得存在就是合理的,每個引擎和框架總有它的可取之處,至少在妳的應用中,在某個時代,所以本文不會評論壹個引擎有什麽不好,這是不客觀的。現在回答前面提到的問題:smarty有存在的必要嗎?我的回答是:是的。原因很簡單,看用誰,看背景。對於前端和後端不分離,或者前端人員不熟悉後端語言,或者因為工作職責的原因,前端人員掌握壹種更通用的模板語法(語言)是現實的。相反,讓PHPer自己用smarty,會浪費技能。
以下是發動機選擇的壹般建議:
前提是選擇的引擎能夠滿足數據渲染需求,並且不與現有依賴沖突。如果妳已經非常熟悉壹個引擎,那麽妳就已經有答案了。
是壹次性項目要求嗎?如果有,直接選擇學習復雜度最低的輕量級。要不要做組件開發?
該引擎支持預編譯結果。不是每次都要實時編譯嗎?
跨平臺?如果有官方支持,反應JSX發動機或純VDOM發動機是首選。
選擇學習或維護復雜度最低的壹個。眾所周知,開發人員討厭調試比寫代碼花費更多的時間。
最後是性能對比。性能對比是壹個非常細致的工作,別人的對比結果不壹定和妳的場景吻合。
我認為應該弱化語法風格的對比。偏好是不可比擬的,有些語法甚至有特殊的背景原因。
為什麽是最後的性能對比?
性能確實很重要,但是如果性能還沒有影響到妳的應用體驗,那就忽略它吧。很難真實模擬應用場景,通常只能通過真實場景進行測試,而目前的測試工具還達不到這種效果。
以上問題,有些是有固定答案的。下面討論剩下的問題:如何考慮組件開發、預編譯支持和復雜度?
組件開發
開發組件不再是選擇模板引擎的問題,而是選擇生態環境的問題。如果妳的應用需要更快的完成,那麽時間是第壹位的,所以選擇流行的框架,有足夠多的組件供妳使用或參考。如果妳的應用有獨立的生態環境,需要長期維護的技術選型,那麽繼續讀下面。
預編譯
預編譯應該具有:
編譯後的結果不需要目標環境中的編譯過程。
編譯後的結果可以調試,這意味著結果應該包含原生ECMAScript代碼,而不是純數據描述。
大家都知道React-JSX支持預編譯,官方說法是React不帶JSX,意思是壹直都是build。
壹些基於字符串處理的引擎也支持預編譯。如果需要預編譯,建議放棄編譯結果仍然基於字符串拼接的引擎,最好不要預編譯,這是HTML5未被廣泛支持之前的技術手段。
至少要有React-JSX這樣的編譯結果才可以調試。註意:Vue.js支持多個模板引擎,可以達到同樣的效果。
原始ReactJS代碼,其中使用了Web組件技術:
類HelloMessage擴展React。組件{
render() {
return & ltp & gt妳好& ltx-search & gt;{ this . props . name } & lt;/x-search & gt;!& lt/p & gt;;
}
}編譯後:
類HelloMessage擴展React。組件{
render() {
返回React.createElement(
“p”,
空,
“妳好”,
React.createElement(
“x搜索”,
空,
this .道具.名稱
),
"!"
);
}
很多VDOM引擎也可以編譯類似的效果,比如htmltemplate-vdom。
& lt腳本& gt
var id = 3;
var env = {
人物:[
{
id: 'id1 ',
姓名:“約翰”,
內部:[{ title: 'a1' },{ title: 'b1' }],
城市:“紐約”,
活動:真
},
{
id:“id2”,
姓名:“瑪麗”,
內部:[{ title: 'a2' },{ title: 'b2' }],
城市:“莫斯科”
}
],
githubLink:“/agent Cooper/html template-vdom”,
itemClick:函數(id) {
env . people . foreach(function(person){
person . active = String(person . id)= = = String(id);
});
loop . update(env);
}
//省略....
};
& lt/script & gt;復雜性
很難用唯壹的標準來判斷兩個引擎哪個復雜度低,這是用戶的思維模式不同造成的。比如上面列舉的引擎,不同的用戶對於使用和預編譯結果的差異感受是不同的,這就是不同引擎存在的合理性和價值。
有用戶認為這種帶字符串模板的應用場景滿足需求,足夠輕便。
有用戶認為字符串拼接技術的模板引擎不夠強大,沒有時代感。
壹些用戶認為OOP是理性的、邏輯的和抽象的。
有些用戶認為原生HTML是前端。
壹些用戶認為VDOM更適用。
這些判斷各有各的道理,側重點不同標準也不同。但是我們還是可以從他們的* * *,來考慮他們的復雜程度。
字符串類模板通常是輕量級的,超出了本節的範圍。判斷非字符串模板復雜度的標準是什麽?我覺得可以考慮數據綁定的復雜性。
文中提到的數據綁定不僅僅是插值,還有上下文和事件,甚至是整個運行時的宿主環境。
事實上,至少需要壹個VDOM級別的引擎才能具備這種能力,因為VDOM可以映射到真正的DOM節點。
大概有幾種模式(組合):
1.入口參數是壹個對象,模板中的變量x是。對象的x屬性,例如virtual-cache-example。
2.特定的語法或屬性,例如:Vue.js的...、計算的屬性、方法。
3.抽象的語義屬性,比如Vue.js中的“主動”壹詞,適用於各種場景,容易理解,不會產生歧義。
4.不負責綁定,用戶需要非常熟悉原生方法,用原生方法綁定,比如PowJS。
這些模式只是理論上的,通常是模板引擎設計者要解決的問題。用戶最好直接問:
1.可以直接在HTML模板中寫最簡單的console.log(context)進行調試嗎?
2.可以在多級DOM樹中綁定或傳遞不同的上下文參數嗎?
3.生成的節點可以在多層DOM樹的內層向上訪問嗎?
模板引擎團隊會給妳正確的解決方案,但它通常與字面描述的目標不同。我覺得這是妳判斷和選擇的關鍵,是妳對官方給出的正確方法的認可。
嵌入在DOM中
嵌入到HTML中
PowJS是這樣實現的:
實現模板必須實現的指令。
預編譯並輸出本機ECMAScript代碼
模板的語法結構與ECMAScript函數的編寫壹致。
最後,編寫PowJS模板就像編寫ECMAScript函數壹樣。
正在寫入GoHub索引
& lt模板& gt
& ltdetails func = " repo " param = " data " if = " is . object(data . content)& amp;& amp!sel(` # panel details[sha = ' $ { data . sha } ']`),"
打開
let="ctx=data.content "
sha="{{data.sha}} "
origin="{{ctx。回購}} "
repo = " { { data . owner } }/{ { data . repo } } "
subdir="{{ctx。Subdir||''}} "
filename="{{ctx。文件名}} "
render=":ctx "
do = " this . renew(sel(` # panel details[repo = ' $ { data . owner }/$ { data . repo } ']`) "
破裂
& gt
& lt總結& gt{{ctx。描述} } & lt/summary & gt;
& ltp if = " ':';"each="ctx。包,val-pkg " >
& ltp title="{{pkg。進度}}: {{pkg。概要} } " & gt{{pkg。導入} } & lt/p & gt;
& lt/p & gt;
& lt/details & gt;
& ltdl func="list" param="data "
if= "!sel(` # panel details[sha = ' $ { data . sha } ']`)& amp;& amp':'||'';"
each="data.content,data.sha,val-rep "
do = " this . appendto(sel(' # panel ')" & gt;
& lt詳細信息sha = " { { sha } } " repo = " { { rep . repo } } " & gt;
& lt總結& gt{ { rep.synopsis } } & lt/summary & gt;
& lt/details & gt;
& lt/dl & gt;
& lt/template & gt;大多數模板引擎都會實現這些指令,比如if和each。在上面的PowJS模板中,有:
全局對象是,sel
模板(函數)被命名為repo和list。
模板(功能)錄入參數數據
自定義局部變量ctx
下層模板(函數)數據形式參數的推導。sha->;恒星時角
對下級模板形式參數派生的遍歷值(CTX。包,val-pkg)->;pkg 、( data.content,val-rep)-& gt;代表
DOM節點操作this.renew,this.appendTo,直接渲染到頁面DOM樹。
過程控制中斷
偽節點if = " ':';"渲染時,根本不生成P節點。它是壹個偽節點,相當於塊碼符號“{}”。
關鍵是整個模板結構、指令語義和ECMAScript函數完全壹致:
沒有數據綁定。您編寫了壹個ECMAScript函數。只需傳遞參數。妳想要什麽裝訂?
沒有事件綁定,每個節點都是真實的。直接寫addEventListener就行了。
要調試,只需找到壹個do或if或let並插入_=console.log(x)。逗號表達式可以無縫地插入幾乎所有本機語句。
所有的業務邏輯都是用戶自己寫的,PowJS只負責粘貼到壹個函數裏。
export視圖是ECMAScript源代碼,下圖取自demo My Folders。
所以PowJS是最終選擇?PowJS的思想是原生的,原生的DOM和原生的ECMAScript。
原生也是PowJS的問題。不是所有用戶都喜歡native。我相信有些用戶更喜歡抽象的風格。土著在他們眼裏總有壹點“原始”。
Native意味著您可以擴展和引入其他庫來匹配,但是PowJS永遠不會有由define setter/getter實現的watcher,這超出了模板引擎的範圍。如果有,壹定是獨立項目。
最後,我的觀點依然是:妳的需求是選擇模板的關鍵,適合妳的才是好的。