基于egg.js的模版渲染(ssr)

目录约定规范

egg-project
├── package.json
├── app.js (可选)
├── agent.js (可选)
├── app
|   ├── router.js
│   ├── controller
│   |   └── home.js
│   ├── service (可选)
│   |   └── user.js
│   ├── middleware (可选)
│   |   └── response_time.js
│   ├── schedule (可选)
│   |   └── my_task.js
│   ├── public (可选)
│   |   └── reset.css
│   ├── view (可选)
│   |   └── home.tpl
│   └── extend (可选)
│       ├── helper.js (可选)
│       ├── request.js (可选)
│       ├── response.js (可选)
│       ├── context.js (可选)
│       ├── application.js (可选)
│       └── agent.js (可选)
├── config
|   ├── plugin.js
|   ├── config.default.js
│   ├── config.prod.js
|   ├── config.test.js (可选)
|   ├── config.local.js (可选)
|   └── config.unittest.js (可选)
└── test
    ├── middleware
    |   └── response_time.test.js
    └── controller
        └── home.test.js

如上,由框架约定的目录:

  • app/router.js 用于配置 URL 路由规则,具体参见 Router。
  • app/controller/** 用于解析用户的输入,处理后返回相应的结果,具体参见 Controller。
  • app/service/** 用于编写业务逻辑层,可选,建议使用,具体参见 Service。
  • app/middleware/** 用于编写中间件,可选,具体参见 Middleware。
  • app/public/** 用于放置静态资源,可选,具体参见内置插件 egg-static。
  • app/extend/** 用于框架的扩展,可选,具体参见框架扩展。
  • config/config.{env}.js 用于编写配置文件,具体参见配置。
  • config/plugin.js 用于配置需要加载的插件,具体参见插件。
  • test/** 用于单元测试,具体参见单元测试。
  • app.js 和 agent.js 用于自定义启动时的初始化工作,可选,具体参见启动自定义。关于agent.js的作用参见Agent机制。

框架内置 egg-view 作为模板解决方案,并支持多模板渲染,每个模板引擎都以插件的方式引入,但保持渲染的 API 一致。如果想更深入的了解,可以查看模板插件开发。

以下以官方支持的 View 插件 egg-view-nunjucks 为例

1. 引入 view 插件

$ npm i egg-view-nunjucks --save

2. 启用插件

// config/plugin.js
exports.nunjucks = {
  enable: true,
  package: 'egg-view-nunjucks',
};

3. 配置插件

egg-view 提供了 config.view 通用配置

  • root {String}

模板文件的根目录,为绝对路径,默认为 ${baseDir}/app/view。支持配置多个目录,以 , 分割,会从多个目录查找文件。
app/view下存放我们要渲染的html文件

如下示例演示了如何配置多个 view 目录:

// config/config.default.js
const path = require('path');
module.exports = appInfo => {
  const config = {};
  config.view = {
    root: [
      path.join(appInfo.baseDir, 'app/view'),
      path.join(appInfo.baseDir, 'path/to/another'),
    ].join(',')
  };
  return config;
};
  • mapping 和 defaultViewEngine

每个模板在注册时都会指定一个模板名(viewEngineName),在使用时需要根据后缀来匹配模板名,比如指定 .nj 后缀的文件使用 Nunjucks 进行渲染。

module.exports = {
  view: {
    mapping: {
      '.nj': 'nunjucks',
    },
  },
};

调用 render 渲染文件时,会根据上述配置的后缀名去寻找对应的模板引擎。

await ctx.render('home.nj');

必须配置文件后缀和模板引擎的映射,否则无法找到对应的模板引擎,但是可以使用 defaultViewEngine 做全局配置。

// config/config.default.js
module.exports = {
  view: {
    defaultViewEngine: 'nunjucks',
  },
};

如果根据文件后缀没有找到对应的模板引擎,会使用默认的模板引擎进行渲染。对于只使用一种模板引擎的应用,建议配置此选项。

  • defaultExtension

一般在调用 render 时的第一个参数需要包含文件后缀,如果配置了 defaultExtension 可以省略后缀。

// config/config.default.js
module.exports = {
  view: {
    defaultExtension: '.nj',
  },
};

// render app/view/home.nj
await ctx.render('home');
  • 渲染页面

框架在 Context 上提供了 3 个接口,返回值均为 Promise:

  • render(name, locals) 渲染模板文件, 并赋值给 ctx.body
  • renderView(name, locals) 渲染模板文件, 仅返回不赋值
  • renderString(tpl, locals) 渲染模板字符串, 仅返回不赋值
// {app_root}/app/controller/home.js
class HomeController extends Controller {
  async index() {
    const data = { name: 'egg' };

    // render a template, path relate to `app/view`
    await ctx.render('home/index.tpl', data);

    // or manually set render result to ctx.body
    ctx.body = await ctx.renderView('path/to/file.tpl', data);

    // or render string directly
    ctx.body = await ctx.renderString('hi, {{ name }}', data, {
      viewEngine: 'nunjucks',
    });
  }
}
  • Locals

在渲染页面的过程中,我们通常需要一个变量来收集需要传递给模板的变量,在框架里面,我们提供了 app.locals 和 ctx.locals。

  • app.locals 为全局的,一般在 app.js 里面配置全局变量。
  • ctx.locals 为单次请求的,会合并 app.locals。
  • 可以直接赋值对象,框架在对应的 setter 里面会自动 merge。
// `app.locals` 会合并到 `ctx.locals
ctx.app.locals = { a: 1 };
ctx.locals.b = 2;
console.log(ctx.locals); // { a: 1, b: 2 }

// 一次请求过程中,仅会在第一次使用 `ctx.locals` 时把 `app.locals` 合并进去。
ctx.app.locals = { a: 2 };
console.log(ctx.locals); // 上面已经合并过一次,故输出还是 { a: 1, b: 2 }

// 也可以直接赋值整个对象,不用担心会覆盖前面的值,我们通过 setter 做了自动合并。
ctx.locals.c = 3;
ctx.locals = { d: 4 };
console.log(ctx.locals); // { a: 1, b: 2, c: 3, d: 4 }

但在实际业务开发中,controller 中一般不会直接使用这 2 个对象,直接使用 ctx.render(name, data) 即可:

  • 框架会自动把 data 合并到 ctx.locals。
  • 框架会自动注入 ctx, request, helper 方便使用。
ctx.app.locals = { appName: 'showcase' };
const data = { name: 'egg' };

// will auto merge `data` to `ctx.locals`, output: egg - showcase
await ctx.renderString('{{ name }} - {{ appName }}', data);

// helper, ctx, request will auto inject
await ctx.renderString('{{ name }} - {{ helper.lowercaseFirst(ctx.app.config.baseDir) }}', data);

你可能感兴趣的:(javascript,前端开发,ssr,node)