2.1 惯例hello world

108 阅读3分钟

第二阶段说明

第二阶段,我们将使用oak_nest来搭建一个博客。本阶段仍不使用数据库。

目标:

  • 用户注册、登陆、登出接口与页面交互
  • 学会session
  • 了解安全守卫
  • 博客接口开发
  • 留言接口开发
  • 博客、留言页面交互

ejs简介

模板引擎(Template Engine)是一个将页面模板和数据结合起来生成 HTML 的工具。

ejs只是其中一种。语法可以参考其官网ejs.bootcss.com/。我们可以将模板拆分成一些组件,然后使用 ejs 的 include 方法将组件组合起来进行渲染。

现在流行的三大框架(vue、react、angular)无不包含自己的模板引擎。之所以没选择他们是因为它们的优势不在服务端渲染,也不是一个量级的产品。

在Deno可以直接使用esm.sh网站转换的地址(相当于复用了Node.js的包)——esm.sh/ejs@3.1.8?p…

版本号一定要固定,而后面添加pin,表示固定该构建版本,因为esm服务器经常会更新,更新后就会重建所有模块,会影响Deno计算出来的hash值,进而影响deno lock文件的校验(前文说过,Deno lock现在不是很稳定,酌情使用,生产中自行验证)。

以下是根据字符串生成:

import { render } from "https://esm.sh/ejs@3.1.8?pin=v86";
const users = ["geddy", "neil", "alex"];
let res = render('<p>[?= users.join(" | "); ?]</p>', { users: users }, {
  delimiter: "?",
  openDelimiter: "[",
  closeDelimiter: "]",
});
console.log(res); // => '<p>geddy | neil | alex</p>'

或者读取文件渲染:

import { renderFile } from "https://esm.sh/ejs@3.1.8?pin=v86";

const res2 = await renderFile(
  "./template.ejs",
  {
    title: "from test2",
    name: "world",
    age: 18,
  },
  {
    cache: true,
    filename: "template",
  },
);
console.log(res2);

vscode插件

在vscode安装一个插件EJS Beautify: image.png

安装成功后,在.vscode/settings.json增加配置:

{
  "emmet.includeLanguages": {
    "ejs": "html",
  },
  "[html]": {
    "editor.defaultFormatter": "j69.ejs-beautify"
  },
}

这样编辑器就能使用它来进行格式化ejs文件了。不然缩进会有问题。

hello world

我们先不考虑博客的事情,先看我们第一个由ejs模板渲染出来的页面。

import_map.json

先在import_map.json中引入我们上面的ejs库:

"ejs": "esm.sh/ejs@3.1.8?p…",

其实现在也可以直接使用npm仓库,比如:

"ejs": "npm:ejs@3.1.8"

只不过,如果你在国内下载缓慢的话,可以考虑使用环境变量NPM_CONFIG_REGISTRY=https://registry.npmmirror.com/,将它添加到deno.jsonc的deno run命令前。本文就暂时先用之前的方案了。

global.ts

修改src/globals.ts,在Config中加一个meta属性:

export interface Config {
  ...
  meta: {
    title: string;
    description: string;
  };
}

config.yaml

修改config.yaml,新增一条:

meta:
  title: Deno Blog
  description: Deno Blog is a simple blog written in Deno.

ejs.ts

新建src/tools/ejs.ts,写个render函数,里面写死了渲染的ejs文件所在的目录(views),数据合并了配置文件和本地参数,以后者为准。

// deno-lint-ignore-file no-explicit-any
import { renderFile } from "ejs";
import globals from "../globals.ts";

export function render(
  path: string,
  data: Record<string, any>,
): Promise<string> {
  return renderFile("views/" + path + ".ejs", {
    ...globals.meta,
    ...data,
  }, {
    cache: true,
    filename: path,
  });
}

这里第三个参数设置cache,其实是为了缓存ejs编译后的函数,当一个模板被反复利用时,适合开启它。

index.ejs

新建views/index.ejs

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8" version="<%= version %>">
  <title>
    <%= title %>
  </title>
</head>

<body>
  <h1>
    <%= title %>
  </h1>
  <p>
    <%= description %>
  </p>
</body>

</html>

app.controller.ts

修改src/app.controller.ts,把版本号返回给页面:

import { Controller, Get } from "oak_nest";
import { render } from "./tools/ejs.ts";
import { Logger } from "./tools/log.ts";
import globals from "./globals.ts";

@Controller("")
export class AppController {
  constructor(private readonly logger: Logger) {}

  @Get("/")
  async version() {
    const version = globals.version;
    this.logger.info(`version: ${version}`);
    return render("index", {
      version,
    });
  }
}

验证

打开http://localhost:8000/,看下页面:

image.png

作业

思考下一个我们的博客有哪些功能设计。