前言
之前公司的后台项目有个需求,要在后端生成使用 canvas 元素绘制了一些内容的 html 页面,并统一转换成 pdf 文件再发送给前端供用户下载。这就要用到服务端渲染(SSR,即 server side render),之前我介绍过如何使用 nuxt2 生成基于 vue 的 ssr 项目,若专门为这点小需求去使用 nuxt,无疑是大材小用了。
我决定直接通过 express 搭建个服务器来实现,在过程中遇到个问题------html 中的 canvas 绘制的内容是动态的,如何把在服务器获取到的参数传递到 html 页面中呢?本文就来介绍怎样通过 pug 模板引擎实现在 express 搭建的服务器中向视图模板传递参数。
express 搭建服务器
我们创建项目 express-pug 来进行演示,使用 pnpm 作为项目的包管理器。然后执行 pnpm init
、pnpm add express
安装上 express。
启用 esm
新建 server.js 作为服务器文件,在里面引入 express 并搭建服务器:
javascript
// server.js
import express from 'express'
const app = express()
app.listen(4396, () => {
console.log('服务器开启')
})
请注意,我没有使用 CommonJS 语法 const express = require('express')
来引入 express,而是使用了 EcmaScript modules(esm)的导入语法。这需要在 package.json 中配置 "type"
属性为 "module"
:

告诉 node 在执行时将 js 都解释为 es 模块。如此,在 express 的源码中虽然使用的是 CommonJS 的导出语法 module.exports
:
javascript
/*!
* express
* Copyright(c) 2009-2013 TJ Holowaychuk
* Copyright(c) 2013 Roman Shtylman
* Copyright(c) 2014-2015 Douglas Christopher Wilson
* MIT Licensed
*/
'use strict';
module.exports = require('./lib/express');
但是在我们项目的 server.js 中导入时, module.exports
对象会作为默认导出提供。
配置脚本命令
在项目的 package.json 中,给 "scripts"
添加上 "dev": "node server"
:
json
{
"name": "express-pug",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "node server"
},
"dependencies": {
"express": "^4.21.2"
}
}
就可以在命令行执行 pnpm dev
启动服务器了:
添加中间件
在 server.js 中使用 app.get()
添加中间件:
javascript
// server.js 省略部分代码
const app = express()
app.get('/', (req, res) => {
// 在这里生成视图模板并发送给客户端
})
在浏览器地址栏输入 localhost:4396/ 后,请求就会被该中间件拦截。我们就在这个中间件内,编译生成视图模板并作为响应返回给客户端。这部分的处理,需要用到 pug 模板引擎。
使用 pug
express 良好地集成了 pug,在 express 中使用 pug,只需要简单的 3 步:
安装 pug
shell
pnpm add pug
创建模板文件
在项目中创建 views 目录用来存放模板文件,创建 views\index.pug 作为模板。在 server.js 中通过 app.set('view', './views')
告诉 express 模板文件的存放位置。也可以不明确设置,因为默认的存放目录就是 ./views
。
在之前创建的中间件里,通过 res.render('index')
,将视图模板 index.pug 编译成 html 字符串,返回给浏览器呈现:
javascript
// server.js
app.get('/', (req, res) => {
res.render('index')
})
index.pug
pug 文件拥有自己特殊的语法,如果我们想返回给前端的 html 内容如下:
html
<html lang="en">
<head>
<title>Document</title>
<style>
#my-canvas {
background-color: antiquewhite;
}
</style>
</head>
<body>
<canvas id="my-canvas">当前浏览器不支持canvas</canvas>
<script>
const canvas = document.getElementById('my-canvas')
const ctx = canvas.getContext('2d')
ctx.fillStyle = '#358DFD'
ctx.fillRect(10, 10, 50, 50)
</script>
</body>
</html>
在 index.pug 中要写成下面这样,相比 html,pug 的语法写起来其实更加简单精炼:
plain
//- index.pug
html(lang="en")
head
title Document
style
include index.css
body
canvas(id="my-canvas") 当前浏览器不支持canvas
script
include index.js
- 其使用缩进的方式标注不同 html 标签之间的包含关系,所以不需要尾标签;
- 标签的属性写在紧跟着标签后的括号内,如果有多个属性可用空格隔开;
- 标签后的文本即为标签的内容,标签和内容之间要有空格;
include index.css
就是将 index.css 文件的内容以字符的形式加载进 index.pug。index.css 创建于 views 目录下:
css
/* views\index.css */
#my-canvas {
background-color: antiquewhite;
}
- 同理,
include index.js
是将 views\index.js 文件的内容加载进 index.pug:
javascript
// views\index.js
const canvas = document.getElementById('my-canvas')
const ctx = canvas.getContext('2d')
ctx.fillStyle = '#358DFD'
ctx.fillRect(10, 10, 50, 50)
指定模板引擎
在 server.js 中通过 app.set('view engine', 'pug')
告诉 express 在 res.render()
时,使用 pug 作为视图模板引擎。现在运行 pnpm dev
,于浏览器地址栏输入 localhost:4396/,效果如下:
传递参数
原始数据
在后端服务器中向视图模板传参,只需在服务器的中间件回调函数中调用 res.render()
时,把要传递的参数对象作为第 2 个参数传入即可:
javascript
// server.js
app.get('/', (req, res) => {
res.render('index', { msg: '当前浏览器不支持canvas' })
})
index.pug 里就可以通过 #{msg}
使用参数:
plain
//- index.pug
canvas(id="my-canvas") #{msg}
如果要传递的参数是在模板的 js 中使用的,比如要控制 canvas 绘制的矩形的颜色:
javascript
// server.js
app.get('/', (req, res) => {
res.render('index', { msg: '当前浏览器不支持canvas', color: '#705697' })
})
在 index.pug 中,只需在 script
后添加一个 .
,表示要在 <script>
标签中添加大段文本(本例中就一行代码),然后在其中定义获取到的变量 color
,就能在 index.js 中使用了:
plain
//- index.pug
script.
const color = '#{color}'
script
include index.js
javascript
// views\index.js 省略其它代码
ctx.fillStyle = color
重启服务器后,效果如下:

至此,我们已经成功实现在 express 搭建的服务器中,通过 pug 模板引擎,向视图模板传递原始数据的功能了。
对象
如果要传递的参数为对象,则可以先通过 JSON.stringify()
处理成字符串:
javascript
// server.js
const data = { name: '销量', type: 'bar', data: [5, 20, 36, 10, 10, 20] }
res.render('index', { data: JSON.stringify(data) })
在 index.pug 中还是当作字符串传给 data
:
plain
//- index.pug
script.
const data = '#{data}'
script
include index.js
在 index.js 中就能使用了:
javascript
// index.js
console.log(JSON.parse(data.replace(/"/g, '"')))
data.replace(/"/g, '"')
是为了去除 data
中的 "
(代表双引号),如果不做任何处理,在 index.js 中打印 data
,console.log(data)
得到的结果如下: