前面的內容大都是關於 Express 框架自身的內容,包括:Express 簡介、工作原理、框架特點。在系列的最後,我們將把注意力放到 Express 框架周邊工具鏈上。學習如果使用這些工具來擴充 Express 框架的功能。
首先,本文我們將會討論檢視模版引擎的使用。通過這些模版引擎我們能過動態的生成 HTML 內容。在前面我們已經使用過 EJS 並使用變數語法實現內容的注入。但是這些內容只是整個模版引擎部分的冰山一角。接下來,將會學習到多種內容注入方式、EJS,Pug 等模版引擎的特性。
Express 檢視特性
在開始之前,我們有必要解釋下何為檢視引擎(view engine)?檢視引擎作為程式設計術語它主要意思是指“進行檢視渲染的模組”。而 Express 框架中最常用的兩個檢視引擎是 Pug 和 EJS 。需要注意的是,Pug 早期的名稱是 Jade 由於某些原因不得已改名 。
另外,Express 並沒有指定必須使用何種引擎。只要該檢視引擎的設計符合 Express API 規範,你就可以將其應用到工程中。下面,我們看看它到底是如何工作的。
簡單的檢視渲染示例
下面我們通過一個簡單示例回顧下 EJS 渲染過程:
var express = require("express");
var path = require("path");
var app = express();
app.set("view engine", "ejs");
app.set("views", path.resolve(__dirname, "views"));
app.get("/", function(req, res) {
res.render("index");
});
app.listen(3000);複製程式碼
在執行程式碼之前,你需要通過 npm install 安裝 EJS 和 Express。在安裝完成後訪問應用主頁的話,程式就會尋找 views/index.ejs 檔案並使用 EJS 對其進行渲染。另外,工程中一般都只會使用一個檢視引擎,因為多個引擎會給工程引入不必要的複雜性。
複雜的檢視渲染
下面我們看一個更為複雜的示例,其中會同時用到兩個檢視引擎 Pug 和 EJS:
var express = require("express");
var path = require("path");
var ejs = require("ejs");
var app = express();
app.locals.appName = "Song Lyrics";
app.set("view engine", "jade");
app.set("views", path.resolve(__dirname, "views"));
app.engine("html", ejs.renderFile);
app.use(function(req, res, next) {
res.locals.userAgent = req.headers["user-agent"];
next();
});
app.get("/about", function(req, res) {
res.render("about", {
currentUser: "india-arie123"
});
});
app.get("/contact", function(req, res) {
res.render("contact.ejs");
});
app.use(function(req, res) {
res.status(404);
res.render("404.html", {
urlAttempted: req.url
});
});
app.listen(3000);複製程式碼
雖然程式碼看起來比較複雜,但其實分解後步驟也還簡單。下面,我們就對上面呼叫 render 處的程式碼進行分析:
Express 在你每次呼叫 render 時都會建立上下文物件,並且在進行渲染時會傳入到檢視引擎中。實際上這些上下文物件就是會在檢視中使用到的變數。
Express 首先會將所有請求都公用的 app.local 中已存在的屬性新增檢視中。然後新增 res.locals 中的屬性並對可能與 app.local 衝突的屬性進行覆蓋操作。最後,新增 render 呼叫處的屬性並且也可能進行覆蓋操作。例如,訪問 /about 路徑時,上下文物件就包含三個屬性:appname、userAgent、currentUser;訪問 /contact 路徑時,上下文物件的屬性就只有 appname、userAgent ;而進行 404 處理時上下文物件的屬性就變成了:appname、userAgent、urlAttempted 。
緊接著,我們將會設定是否啟用檢視快取。其實檢視快取並不是快取檢視實際上它快取的檢視路徑。例如,它會將 views/my_views.ejs 路徑快取起來並繫結到 EJS 引擎上。
Express 通過兩種方式來決定是否對檢視檔案進行快取:
文件記錄方式:通過呼叫 app.enabled("view cache") 開啟。在開發模式下預設是被禁用的,但是你可以在正式生產環境中開啟。當然,你可以通過 app.disable("view cache") 手動關閉。
非文件記錄方式:根據第一步上下文中的 cache 物件是否為 true 來決定是否快取該檔案。這樣你就可以對每一個檔案進行自定義設定了。
接下來,Express 會設定檢視檔名及其使用的檢視引擎。如果在第二步中已經進行了檢視快取則可以直接跳到最後一步。否則,則繼續下一步。
- 根據預設檢視引擎將缺少擴充名的檢視檔案補充完整。在本例中,about 會被擴充成 about.jade ,而 contact.ejs 以及 404.html 檔案會儲存不變。如果你既沒有指定預設檢視引擎也沒有明確擴充名,那麼程式會出現崩潰。
- 通過檔案擴充名進行檢視引擎匹配。對於 .html 格式檔案則根據 app.engine("html", xx); 設定進行匹配。
- 在檢視問價夾下,查詢檢視檔名對應的檔案。如果不存在則報錯。
- 判斷查詢到的檢視檔案是否需要進行快取。
- 使用引擎對檢視檔案進行渲染並生成最終的 HTML 檔案。
同時使用多個檢視引擎確實為程式增加了不必要的複雜性,好在絕大多數時候我們並不會這樣做。
Express 給客戶端預設響應的內容是 HTML。雖然大多數時候這沒什麼問題,但是有時可能需要返回的是純文字、XML、JSON 等格式。此時,你可以通過修改引數 res.type 進行自定義設定:
app.get(“/”, function(req, res) { res.type(“text”); res.render(“myview”, { currentUser: “Gilligan” }); }複製程式碼
當然,你可以使用更簡單的 res.json
檢視引擎的 Express 相容設定:Consolidate.js
除了 EJS 和 Pug 之外,其實還有很多中模版引擎。但是這些模版引擎可能並不像 EJS 和 Pug 那樣是為 Express 專門設計的。此時如果需要使用這些未適配的模版引擎,我們就不得不使用 Consolidate.js 進行封裝以期能夠相容 Express API。而且 Consolidate.js 支援的種類也非常多,你可以在專案首頁看到完整的支援列表。
假設,現在你正是使用的引擎是與 Express 並不相容的 Walrus。那麼,下面我們看 Consolidate 是如何進行相容適配工作的。
首先,使用 npm install walrus consolidate 安裝相關類庫和依賴項,然後我們將其引入:
var express = require("express");
var engines = require("consolidate");
var path = require("path");
var app = express();
app.set("view engine", "wal");
app.engine("wal", engines.walrus);
app.set("views", path.resolve(__dirname, "views"));
app.get("/", function(req, res) {
res.render("index");
});
app.listen(3000);複製程式碼
是不是很簡單?只需幾行程式碼我們就完成了整個適配工作。所以當有相容適配需求的時候,我強烈建議你使用 Consolidate 而不是自己悶頭幹。
EJS 中你必須要了解的東西
EJS 是 Express 中最簡單也是最受歡迎的檢視引擎之一。它可以為字串、HTML、純文字建立模版,而且他的整合也非常簡單。它在瀏覽器和 Node 環境中都能正常工作。它與 Ruby 中的 ERB 與法非常的類似。
實際上存在由不同組織維護的兩個不同版本的 EJS。雖然在功能上它們很相似,但是並不是同一個類庫。其中 Express 中使用的 EJS 是由 TJ Holowaychuck 維護的,你可以通過 npm 查詢到該類庫。另一個同名類庫在 09 年就停止了更新且它不能在 Node 環境中執行。
EJS 語法
除了用做 HTML 模版之外,它還能應用於字串和純文字中。請看 EJS 是如何對下面文字模版進行渲染的:
Hi <%= name %>!
You were born in <%= birthyear %>, so that means you're <%= (new Date()).getFullYear() - birthyear %> years old.
<% if (career) { -%>
<%=: career | capitalize %> is a cool career!
<% } else { -%>
Haven't started a career yet? That's cool.
<% } -%>
Oh, let's read your bio: <%- bio %> See you later!複製程式碼
將下面的 JSON 資料傳入上面摸板中:
{
name: "Tony Hawk",
birthyear: 1968,
career: "skateboarding",
bio: "<b>Tony Hawk</b> is the coolest skateboarder around."
}複製程式碼
最終,得到的渲染結果是(假設當前是 2015 年):
Hi Tony Hawk!
You were born in 1968, so that means you’re 47 years old.
Skateboarding is a cool career!
Oh, let’s read your bio: Tony Hawk is the coolest skateboarder around. See
you later!複製程式碼
該示例演示了 EJS 常用的四種語法:列印、列印並轉義、執行 JS 程式碼、過濾。
在 EJS 你可以使用兩種語法列印表示式的值:<%= expression %> 和 <%- expression %>,其中前者會對結果進行 HTML 轉義。例如,當傳入的 expression 值為 Express 時,前者執行的結果是 Express 而後者得到的字串是 Express。我建議你使用前一種方式,因為它更為可靠。
同樣,EJS 還允許你 通過 <% expression %> 語法在其中執行 JS 表示式,並且該表示式並不會被列印出來。該特性在執行迴圈和條件判斷的時候非常有用。另外,你還可以通過 <% expression -%> 避免不必要的換行。
通過 <%=: expression | xxx %> 語法,我們可以對錶達式結果再進行一次過濾處理。例如,上面我們就對錶達式結果應用了首字母大寫過濾器。當然,除了自帶的大量過濾器之外,你還可以進行自定義。
這裡,我做了一個 EJS 的示例程式。雖然介面不是很好看,但是你能從中熟悉 EJS 的各種語法使用。
在已有 EJS 檔案中嵌入其他 EJS 模版
EJS 引擎允許你在當前模版中使用另一個 EJS 模版。這樣我們就能對整個進行元件拆分複用。例如,將 HTML 的頭部和尾部拆分為 header 和 footer 模組,然後在其他模版中進行組合複用。
示例如下:首先我們建立 header.ejs 並拷貝程式碼:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="/the.css">
<title><%= appTitle %>/title>
</head>
<body>
<header>
<h1><%= appTitle %></h1>
</header>複製程式碼
緊接著建立 footer 元件 footer.ejs 並拷貝程式碼:
<footer>
All content copyright <%= new Date().getFullYear() %> <%= appName %>.
</footer>
</body>
</html>複製程式碼
最後,我們通過 include 語法進行元件嵌入操作:
<% include header %>
<h1>Welcome to my page!</h1>
<p>This is a pretty cool page, I must say.</p>
<% include footer %>複製程式碼
假設,你現在需要實現一個展示使用者資訊的組建,那麼你可以建立 userwidget.ejs 檔案並拷貝:
<div class="user-widget">
<img src="<%= user.profilePicture %>">
<div class="user-name"><%= user.name %></div>
<div class="user-bio"><%= user.bio %></div>
</div>複製程式碼
那麼,在渲染當前使用者時可以這樣使用該模版:
<% user = currentUser %>
<% include userwidget %>複製程式碼
或者在渲染使用者列表時:
<% userList.forEach(function(user) { %>
<% include userwidget %>
<% } %>複製程式碼
通過 EJS 中的 include 語法,我們可以在建立模版的同時將其作為元件進行子檢視的渲染操作。
###新增你自己的過濾器
Express 內建的 22 個過濾器,其中包括對陣列和字串的常用操作。通常情況下,它們能過滿足你的需求,但是有時你不得不新增自己的過濾器。
假設,現在你已經引入了 EJS 模組並將其儲存到名為 ejs 變數中。那麼你可以為按照下面的方式為 ejs.filters 擴充一個用於陣列求和的過濾器。
ejs.filters.sum = function(arr) {
var result = 0;
for (var i = 0; i < arr.length; i++) {
result += arr[i];
}
return result;
};複製程式碼
然後,你就可以在程式碼中使用該過濾器了:
<%=: myarray | sum %>複製程式碼
實現和使用都非常簡單,所以我建議你將那些常用操作實現為過濾器。
Pug 中你必須要了解的東西
像 Handlebars ,Mustache ,以及 EJS 這樣的檢視引擎只是在 HTML 擴充了新語法它並沒有對 HTML 語法造成破壞。對於一個瞭解 HTML 語法的設計師來說這最好不過了,畢竟不用學習新語言。同樣它們還適用於非 HTML 模版環境,而這一點則是 Pug 的軟肋。
但是 Pug 也有自己獨特的優勢。它能減少你的程式碼量,而且程式碼風格也非常不錯。尤其在寫 HTML 模版時,標籤會巢狀縮排而且無需閉合。另外,EJS 風格的判斷和迴圈語法也是內建的。雖然需要學的東西比較多,但是它的功能也異常強大。
Pug 語法
像 HTML 這樣的語言是巢狀的,其中有根元素()以及各種子元素(像
和 ),而子元素還可以進一步巢狀其他元素。另外,HTML 的元素必須像 XML 一樣需要閉合。而 Pug 則採用了不同的縮排語法。下面的程式碼就展示了使用 Pug 實現的簡單 web 頁面:
doctype html
html(lang="en")
head
title Hello world!
body
h1 This is a Pug example
#container
p Wow.複製程式碼
上面的程式碼中內容將被轉變為下面的 HTML。
<!DOCTYPE html>
<html lang="en">
<head>
<title>Hello world!</title>
</head
<body>
<h1>This is a Pug example</h1>
<div id="container">
<p>Wow.</p>
</div>
</body>
</html>複製程式碼
你可以去 Pug 專案主頁去檢視它時如何實現這種轉變的。
Pug 的佈局
佈局時所有模版語言的一個重要特性。它可以讓我們實現公共元件然後在其他檔案中實現複用。例如,我們可以將頁面的 header 和 footer 抽離出來。這樣不僅可以保證所有頁面的 header 和 footer 內容的一致,而且修改起來也更加方便。
Pug 佈局的實現步驟大致如下:
第一步,為所有頁面定義一個主佈局檔案,而該檔案幾乎就是一個空模版。它用 block 語法進行佔位操作,然後實際生成的頁面會使用內容替換這些佔位符。示例如下:
doctype html
html
head
meta(charset="utf-8")
title Cute Animals website
link(rel="stylesheet" href="the.css")
block header
body
h1 Cute Animals website
block body複製程式碼
你可以看到上面定義了 header 和 body 兩個佔位符。下面我們將它儲存到 layout.jade 檔案中。緊接著我們實現其中的 body 塊:
extends layout.jade
block body
p Welcome to my cute animals page!複製程式碼
layout.jade 將會被渲染成:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Cute Animals website</title>
<link rel="stylesheet" href="the.css">
</head>
<body>
<h1>Cute Animals website</h1>
<p>Welcome to my cute animals page!</p>
</body>
</html>複製程式碼
注意到當你擴充主佈局時,並不一定需要實現其中的所有佔位塊。例如上面就沒有實現 header 。
在其他頁面可以對 body 塊進行不同的實現:
extends layout.jade
block body
p This is another page using ths layout.
img(src="cute_dog.jpg" alt="A cute dog!")
p Isn't that a cute dog!複製程式碼
Pug 通過佈局進行元件分離讓我們可以避免一些重複的程式碼。
Pug 的 Mixins 功能
Pug 中還有一個被稱為 Mixins 的酷炫特性。通過該特性你可以對檔案中可能需要反覆使用的功能進行一次定義。下面,我們就通過該特性對前面 EJS 部分使用者資訊展示的功能進行重新實現:
mixin user-widget(user)
.user-widget
img(src=user.profilePicture)
.user-name= user.name
.user-bio= user.bio
// 展示當前使用者
+user-widget(currentUser)
// 展示使用者列表
- each user in userList
+user-widget(user)複製程式碼
Pug 的基礎內容到此為止,更多語法細節請檢視官方文件。
總結
這章的內容包括:
- Express 的檢視系統,以及它是如何進行動態渲染的。
- EJS 引擎的語法和基本使用。
- Pug 引擎的語法和基本使用。
原文地址