0%

nodeJS - Template engine


Template engine是什麼

模板引擎幫助我們在靜態的html檔案中插入一些動態的變數,使頁面的呈現可以實際與資料互動。模板引擎可以讓介面與資料分離,提升開發的效率使分工更明確,還可以減少重複程式碼的產生。
經過模板引擎的編譯,最終會回傳一般的html檔案到用戶端。

使用template engine渲染html

利用express來設定template engine

express提供我們設定專案所使用的模板引擎,省去我們自行設定模板引擎的作業,減少開發者撰寫程式碼的負擔
首先必須安裝模板引擎到專案中,以pug為例 (模板引擎有很多種,需自行分析優缺點選擇喜歡的模板引擎)
npm install --save pug
安裝好後在app.js設定使用的模板引擎,新增app.set('view engine', 'pug');到該檔中
app.use()可以讓我們設定global變數到express的application中 (name: value),其中有些name是用來改變server的行為的,有哪些特殊的name可以參考文件
因此我們設定view engine這個特殊的namepug,告訴express接下來這個專案會使用pug作為模板引擎而不是其他的模板引擎。
設定模板引擎的種類後,還必須告訴express必須渲染的動態檔案 (ex. index.pug)放在哪個資料夾
所以再加入以下這行
app.set('views', 'views')
後面的views為檔案夾名稱 (預設其實已經是views,但可以根據自己檔案夾命名更改),這樣一來express就知道該去哪裡找這些檔案,之後將原本撰寫html的方式改為pug的特殊語法,副檔名也必須是.pug

根據不同的engine可能需要在程式碼中使用require()引入,例如pug不用但handlebars要
handlebars的情況下要加這兩行

1
2
3
4
const expressHbs = require('express-handlebars');
//...some express code
app.engine('hbs', expressHbs()) //hbs為自己命名,之後創建的模組檔案副檔名必須為`.hbs`
app.set('view engine', 'hbs')//這行跟pug一樣

實際渲染.pug檔產生html給用戶端

還有最後一步,原本使用res.sendFile()來回傳html檔案給前端,使用模板引擎後必須改為以下
res.render()
所以在我們處理routes的檔案中會變成
shop.js

1
2
3
4
5
6
7
8
9
10
11
12
const path = require('path');

const express = require('express');

const rootDir = require('../util/path');
const router = express.Router();

router.get('/', (req, res, next) => {
res.render('shop');
});

module.exports = router;

這裡res.render()裡面不需要包含完整的路徑是因為express已經知道該去哪裡找這些檔案 (views),且也已經知道使用的模板引擎種類所以可以省略副檔名.pug

Html語法轉Pug語法

pug的撰寫方式與html非常相像,遵循一定的規則就可以輕鬆改寫。pug語法多了一些邏輯判斷和迴圈幫助減少重複程式碼的撰寫,以及pug可以插入變數,根據變數渲染網頁達到動態的效果。

pug語法重視縮排,如果一個標籤被上層的標籤包住,必須往內縮一排
pug語法不需要結尾標籤

範例如下

1
2
3
4
5
6
7
8
<header class="main-header">
<nav class="main-header__nav">
<ul class="main-header__item-list">
<li class="main-header__item"><a class="active" href="/">Shop</a></li>
<li class="main-header__item"><a href="/admin/add-product">Add Product</a></li>
</ul>
</nav>
</header>

轉換成pug語法後

1
2
3
4
5
6
7
header.main-header
nav.main-header__nav
ul.main-header__item-list
li.main-header__item
a.active(href="/") Shop
li.main-header__item
a(href="/admin/add-product") Add Product

改變一下標籤, class, 屬性的表示方法

如果要在pug使用動態變數,首先要在渲染pug檔指定加上需要傳遞的變數,如下
shop.js

1
2
3
4
router.get('/', (req, res, next) => {
const products = adminData.products;
res.render('shop', {prods: products, docTitle: 'Shop'});
});

res.render()的第二個變數傳遞一個物件結構,使用key value來傳遞
接著在pug檔使用以下方式插入變數

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if prods.length > 0
.grid
each product in prods
article.card.product-item
header.card__header
h1.product__title #{product.title}
div.card__image
img(src="xxx", alt="A Book")
div.card__content
h2.product__price $19.99
p.product__description A very interesting book about so many even more interesting things!
.card__actions
button.btn Add to Cart
else
h1 No Products

共同Layout

在很多時候不同的頁面會有相同的設計,例如每個頁面都有一個導覽列,這樣的話會有同樣的代碼重複在不同的檔案。這時候可以使用pug創造一個共同的layout,每個頁面都可以引入這個layout再根據各自需求去修改它

首先可以在views資料夾再創個layouts資料夾,再新增一個pug檔
main-layout.pug

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
html(lang="en")
head
meta(charset="UTF-8")
meta(name="viewport", content="width=device-width, initial-scale=1.0")
meta(http-equiv="X-UA-Compatible", content="ie=edge")
title #{pageTitle}
link(rel="stylesheet", href="/css/main.css")
block styles
body
header.main-header
nav.main-header__nav
ul.main-header__item-list
li.main-header__item
a(href="/", class=(path === '/' ? 'active' : '')) Shop
li.main-header__item
a(href="/admin/add-product", class=(path === '/admin/add-product' ? 'active' : '')) Add Product
block content

透過在layout中安插佔位空間(placeholder)來提供客製化的空間,例如代碼中
block styles
block content
的部分,這裡被其他pug檔引入後可以做各自的修改

也可以根據條件加入不同的css class
a(href="/admin/add-product", class=(path === '/admin/add-product' ? 'active' : ''))

add-products.pug

1
2
3
4
5
6
7
8
9
10
11
12
13
extends layouts/main-layout.pug

block styles
link(rel="stylesheet", href="/css/forms.css")
link(rel="stylesheet", href="/css/product.css")

block content
main
form.product-form(action="/admin/add-product", method="POST")
.form-control
label(for="title") Title
input(type="text", name="title")#title
button.btn(type="submit") Add Product

3種template engine 官方文件