如何選擇Web前端模板引擎?
Web 模板就在那裡
模板引擎負責組裝資料,以另外一種形式或外觀展現資料。 瀏覽器中的頁面是 Web 模板引擎最終的展現。
無論你是否直接使用模板引擎,Web 模板一直都在,不在前端就在後端,它的出現甚至可以追溯到超文字標記語言 HTML 標準正式確立之前。
伺服器端的模板引擎
我所知道最早的 Web 模板引擎是 PHP,它正式誕生於 1997 年,工作在伺服器端。讓我們看看 PHP 官方的 intro-whatis:
PHP(“PHP: Hypertext Preprocessor”,超文字前處理器的縮寫)是一種被廣泛應用的開放原始碼的多用途指令碼語言,它可嵌入到 HTML中,尤其適合 web 開發。
PHPer 普遍贊同 PHP 本身就是最天然、原生的 PHP 模板引擎,因為她本來就是。在 PHP 的世界裡多次出現過再包裝的模板引擎,著名的有 smarty。
其它伺服器端語言很多都有 HTML 模板引擎,比如 JSP、mustache。
毫無疑問,這些伺服器端模板引擎最終生成的結果是 HTML(XML) 字串,處理流程邏輯使用宿主語言本身的語法實現。
它們的共同特徵:HTML 只是個字串, 最終結果可能還需要類似 Tidy 這樣的清潔或修正驗證工具。
這裡提出一個問題:二次封裝的 smarty 有存在的必要麼?
瀏覽器端的模板引擎
我所知道最早的前端模板引擎是 jCT,它託管於 Google Code,誕生於 2008 年,宿主語言是 JavaScript,工作在瀏覽器中。很榮幸,我就是 jCT 的作者,相關早期部落格可以檢視 achun、github jCT 備份。
直到今天寫這篇文章,我才發現 pure-js 這篇文章裡面也提到不少先行者——jemplate 最早在 2006 年就建立了。
今天在 OSC 搜尋 JavaScript 模板引擎你會得到 100+ 個結果,下邊列舉一些:
- 輕量度:tpl.js、T.js
- 認知度:arttemplate、mustache.js、doT.js、handlebars.js、pug
- DOM-tree-based:domTemplate、transparency、plates
- VDOM-based:htmltemplate-vdom、virtual-stache、html-patcher
- 流行框架:Vue.js、ReactJS、riot
- Real-DOM:PowJS
它們的共同特徵:全都支援插值。
這裡還有 templating-engines 受歡迎度的對比,甚至 best-javascript-templating-engines 投票及正反方的理由。
如何選擇
我認為存在即合理,每個引擎、框架總有可取之處,至少在你的應用裡,在某個時代,所以本文不會評論某個引擎哪一點不好,那樣是不客觀的。現在回答前邊提到的問題:smarty 有存在的必要麼?我的答案是:有。理由很簡單,看給誰用、看大背景。對於前後端沒有分離的應用,或前端人員對後端語言不夠熟悉,或因崗位職責需要,那麼前端人員掌握一種比較通用的模板語法(語言)是現實的,反之讓 PHPer 自己去使用 smarty 那就太浪費技能了。
下面是通常意義上的引擎選擇建議:
- 前提,選擇的引擎能滿足資料渲染需求,且不和現有依賴衝突,如果你已經非常熟悉某個引擎,那你已經有答案了。
- 是一次性的專案需求麼? 是的話直接選擇輕量的,學習複雜度最低的。
- 是要做元件開發麼?
- 引擎支援預編譯結果,不必每次都實時編譯麼?
- 要跨平臺麼? 有官方提供支援的,首選類 React-JSX 的引擎或純粹的 VDOM 引擎。
- 選擇學習或維護複雜度最低的,眾所周知,開發者對除錯的時間超過寫程式碼的時間深惡痛絕。
- 最後才是效能對比,效能對比是一件非常細緻的工作,他人的對比結果不一定符合你的場景。
我認為應該弱化語法風格的對比,偏好是沒有可比性的,一些語法甚至有特殊的背景原因。
為什麼最後才是效能對比?
效能的確很重要,但如果效能還沒有影響到你的應用體驗度,那就忽視它。很難真實地模擬應用場景,通常只有通過真實場景來檢驗,目前的測試工具還達不到這種效果。
前述問題有些有固定答案,下面討論餘下的問題:如何考慮元件開發、支援預編譯、複雜度?
元件開發
進行元件開發已經不再是選擇模板引擎的問題了,這是生態環境選擇的問題。如果你的應用需要更快地完成,那麼時間點是第一位的,就選擇流行框架,有足夠多的元件讓你使用或參考。如果你的應用有獨立的生態環境,需要技術選型以便長期維護,那繼續看下文。
預編譯
預編譯應該具備:
- 編譯結果在目標環境中不再需要編譯過程。
- 編譯結果可除錯性,這意味著結果應該包含原生 ECMAScript 程式碼,而不是純粹的資料描述。
大家都知道 React-JSX 是支援預編譯的,官方的說法是 React Without JSX,即總是 build 過的。
一些基於字串處理的引擎也支援預編譯。如果你需要預編譯,建議拋棄編譯結果依然是基於字串拼接的引擎,那樣還不如不預編譯,那是 HTML5 未被廣泛支援之前的技術手段。
至少也要有類似 React-JSX 這樣的編譯結果才具有可除錯性。備註:Vue.js 支援多種模板引擎,可達到同樣的效果。
原 ReactJS 程式碼,其中用到了 Web Components 技術:
class HelloMessage extends React.Component {
render() {
return <div>Hello <x-search>{this.props.name}</x-search>!</div>;
}
}
編譯後:
class HelloMessage extends React.Component {
render() {
return React.createElement(
"div",
null,
"Hello ",
React.createElement(
"x-search",
null,
this.props.name
),
"!"
);
}
}
不少 VDOM 引擎也可以編譯類似效果,比如 htmltemplate-vdom。
<script> var id = 3;
var env = {
people: [
{
id: `id1`,
name: `John`,
inner: [{ title: `a1` }, { title: `b1` }],
city: `New York`,
active: true
},
{
id: `id2`,
name: `Mary`,
inner: [{ title: `a2` }, { title: `b2` }],
city: `Moscow`
}
],
githubLink: `https://github.com/agentcooper/htmltemplate-vdom`,
itemClick: function(id) {
env.people.forEach(function(person) {
person.active = String(person.id) === String(id);
});
loop.update(env);
}
// Omitted ....
};
</script>
複雜度
很難用唯一的標準去評判兩個引擎哪個複雜度低,這是由使用者的思維模式不同造成的。例如前邊列出的引擎在使用上以及預編譯結果上的區別,不同使用者感觸是不同的,這正是不同引擎存在的合理性、價值性。
- 有的使用者認為這個應用場景有字串模板就滿足了需求,輕量夠用。
- 有的使用者認為字串拼接技術的模板引擎不夠強壯,不夠時代感。
- 有的使用者認為 OOP 夠理性,夠邏輯,夠抽象。
- 有的使用者認為原生 HTML 才叫前端。
- 有的使用者認為 VDOM 適用性更廣。
這些評判都有各自的理由,著眼點不同,標準也就不同了。但是我們還是可以從它們的共性去考慮它們的複雜度。
字串類别範本通常都很輕量,不在本節討論範圍之內。對於非字串模板複雜度評判的共性標準是什麼?我認為,可以考量資料繫結的複雜度。
本文所指的資料繫結不只是插值,還包括上下文以及事件,甚至是整個執行期的宿主環境。
事實上至少需要達到 VDOM 級別的引擎才具有這種能力,因為通過 VDOM 可以對映到真實的 DOM 節點。
大概有幾種模式(組合):
- 入口引數是個 Object,模板中的變數 x 是該物件的 .x 屬性,例:virtual-stache-example
- 特定語法或屬性,比如:Vue.js 的 <a v-on:click=”doSomething”>…</a>,屬性 computed、methods
- 抽象的語義化屬性,比如:Vue.js 的 active 這個詞適用於多種場景,容易理解且不產生歧義
- 不負責繫結,需要使用者非常熟悉原生方法,用原生方法進行繫結,比如:PowJS
這些模式只是理論方面的,通常是模板引擎設計者要解決的問題。對於使用者來說不如直接問:
- 可以在 HTML 模板中直接寫最簡單的 console.log(context) 來除錯麼?
- 可以在多層 DOM 樹繫結或傳遞不同的上下文引數麼?
- 可以在多層 DOM 樹內層向上訪問已經生成的 Node 麼?
模板引擎團隊會給你正確的解決辦法,但通常和問題字面描述的目標有所差異。我覺得這就是你評判選擇的關鍵,你對官方給出的正確方法的認可度。
嵌入到 DOM 中
嵌入到 HTML 中
這是本文開篇 PHP 自述裡面的話,歷史原因使得 PHP 依然是伺服器端的超文字前處理器,HTML 在 PHP 中依然是字串,但是:PHP 視角中的 HTML 就是字串,PHP 真的無縫嵌入到 HTML 這個 “宿主” 中了。
在 WEB 業內標準完善,環境大大改善的今天,前端模板引擎能不能突破僅僅嵌入到 HTML 字串或嵌入到 VDOM,能不能真正地
嵌入到 DOM 中
PowJS 做到了這一點,其實我也是 PowJS 的設計者。PowJS 是這麼實現的:
- 實現模板必須要實現的指令
- 預編譯輸出原生 ECMAScript 程式碼
- 模板語法結構與 ECMAScript 函式寫法一致
最終,寫 PowJS 模板就像在寫 ECMAScript 函式。
GoHub index 中的寫法
<template> <details func="repo" param="data" if="is.object(data.content)&&!sel(`#panel details[sha=`${data.sha}`]`)" open let="ctx=data.content" sha="{{data.sha}}" origin="{{ctx.Repo}}" repo="{{data.owner}}/{{data.repo}}" subdir="{{ctx.Subdir||``}}" filename="{{ctx.Filename}}" render=":ctx" do="this.renew(sel(`#panel details[repo=`${data.owner}/${data.repo}`]`))" break
> <summary>{{ctx.Description}}</summary> <div if="`:`;" each="ctx.Package,val-pkg"> <p title="{{pkg.Progress}}: {{pkg.Synopsis}}">{{pkg.Import}}</p> </div> </details> <dl func="list" param="data" if="!sel(`#panel details[sha=`${data.sha}`]`)&&`:`||``;" each="data.content,data.sha,val-rep" do="this.appendTo(sel(`#panel`))"> <details sha="{{sha}}" repo="{{rep.repo}}"> <summary>{{rep.synopsis}}</summary> </details> </dl> </template>
多數模板引擎都會實現 if 、each 這些指令,上面的 PowJS 模板中還有:
- 全域性物件 is、sel
- 模板(函式)命名 repo 、list
- 模板(函式)入口形參 data
- 自定義區域性變數 ctx
- 下層模板(函式)形參推導 data.sha->sha
- 遍歷值到下層模板形參推導 (ctx.Package,val-pkg)->pkg 、(data.content,val-rep)->rep
- DOM 節點操作 this.renew、 this.appendTo,這直接渲染到頁面 DOM 樹
- 流程控制 break
- 偽節點 if=”`:`;”,渲染時根本不生成 div 節點,它是個偽節點,相當於塊程式碼符號 “{}”
關鍵是整個模板結構,指令語義和 ECMAScript 函式完全一致:
- 沒有資料繫結,你寫的是 ECMAScript 函式,傳引數好了,要什麼繫結
- 沒有事件繫結,每個節點都是真實存在的,直接寫 addEventListener 就好了
- 要除錯,隨便找個 do 或 if 或 let 插入 _=console.log(x), 就好了,逗號表示式幾乎可以無縫插入所有原生語句
- 所有的業務邏輯都是使用者自己寫的,PowJS 只負責把他們粘合成一個函式
- 匯出檢視是 ECMAScript 原始碼,下圖擷取自演示 My Folders
那麼 PowJS 是最終的選擇麼?PowJS 的理念是原生性,原生的 DOM,原生的 ECMAScript。
原生也同樣是 PowJS 的問題所在,不是所有的使用者都喜歡原生,我相信有的使用者更喜歡更抽象風格,他們眼中的原生總是帶了點 “原始”。
原生意味著你可以擴充套件,引入其它 library 進行搭配,但 PowJS 永遠不會出現 define setter/getter實現的 watcher,那超出了模板引擎的範圍,如果有那一定是獨立的專案。
最後,我的觀點依然是:你的需求才是選擇模板的關鍵,適合你的才是好的。
作者介紹
喻恆春,野生程式設計師,常年出沒於 bug 叢林。
原文釋出時間:06/06
原文作者: 編輯部的故事
本文來源開源中國如需轉載請緊急聯絡作者
相關文章
- 如何選擇 Web 前端模板引擎?Web前端
- 如何正確選擇Web前端培訓機構Web前端
- 0基礎轉行,Python和Web前端如何選擇?PythonWeb前端
- 選擇python還是web前端好PythonWeb前端
- 如何選擇mysql的儲存引擎MySql儲存引擎
- 選擇WEB前端開發,如何才能不成為一個碼農Web前端
- CMS企業建站網站模板該如何選擇?網站
- Python和Web前端選擇哪個比較合適?PythonWeb前端
- 前端模板引擎doT.js的使用前端JS
- 前端模板引擎的實現總結前端
- 前端模板引擎Handlebars理解與舉例前端
- 如何選擇Web伺服器硬體配置?Web伺服器
- 學習Web前端,你可選擇的高薪發展方向Web前端高薪
- 好程式設計師web前端CSS選擇符(選擇器):表示要定義樣式的物件程式設計師Web前端CSS物件
- 作為一名前端開發工程師,你必須掌握的WEB模板引擎:Handlebars前端工程師Web
- 前端-選擇開發工具前端
- 【HTML5】Web前端——第四課:CSS3新增選擇器HTMLWeb前端CSSS3
- Django 模板引擎以及模板Django
- 專科生該選擇學習雲端計算還是web前端Web前端
- 選擇轉行學習WEB前端,你必須要了解基本概念Web前端
- Python和Web前端選擇哪個比較合適?哪個前景好?PythonWeb前端
- Signal:更多前端框架的選擇前端框架
- 如何選擇CDN
- Python 模板引擎Python
- FreeMarker模板引擎
- Web前端如何學?Web前端學習方法分享Web前端
- 好程式設計師Web前端教程分享CSS派生選擇器的講解程式設計師Web前端CSS
- web前端培訓教程中程式的順序結構及選擇結構Web前端
- mysql常用儲存引擎(InnoDB、MyISAM、MEMORY、MERGE、ARCHIVE)介紹與如何選擇MySql儲存引擎Hive
- 大前端架構思考與選擇前端架構
- 前端資料渲染及mustache模板引擎的簡單實現前端
- 如何選擇AI晶片?AI晶片
- 如何選擇代理IP?
- 如何選擇jquery版本jQuery
- SDL3 入門(4):選擇圖形引擎
- ejs模板引擎原理JS
- java模板引擎:velocityJava
- Thymeleaf(Java模板引擎)Java