@@ -0,0 +1,1354 @@
+const fs = require('fs');%0Aconst path = require('path');%0Aconst minimatch = require('minimatch');%0Aconst log = require('t-log');%0A%0Aconst typeOf = require('../utils/typeof');%0A%0Afunction render(req, res, next) %7B%0A  const app = req.app;%0A  const router = req.router;%0A%0A  let view = router.view;%0A%0A  if (!view) %7B%0A    view = req.path;%0A  %7D%0A  // view%E9%85%8D%E7%BD%AE%E5%8F%AF%E4%BB%A5%E4%B8%8D%E4%BB%A5/%E5%BC%80%E5%A4%B4%0A  if (!view.startsWith('/')) %7B%0A    view = %60/$%7Bview%7D%60;%0A  %7D%0A%0A  if (typeOf(view).is('function')) %7B%0A    view = view.call(router, req, res, next);%0A  %7D%0A%0A  const defaultEngine = app.get('view engine');%0A  let ext = path.extname(view);%0A%0A  if (!ext && !defaultEngine) %7B%0A    throw new Error('No default engine was specified and no extension was provided.');%0A  %7D%0A%0A  if (!ext) %7B%0A    ext = defaultEngine.startsWith('.') ? defaultEngine : %60.$%7BdefaultEngine%7D%60;%0A    view += ext;%0A  %7D%0A%0A  const filePath = path.join(app.get('views'), view);%0A  const excludes = app.get('viewExclude').filter(exclude =%3E minimatch(filePath, exclude));%0A  // %E5%8C%B9%E9%85%8D%E5%88%B0%E9%9C%80%E6%8E%92%E9%99%A4%E6%B8%B2%E6%9F%93%E7%9A%84%E8%B7%AF%E5%BE%84%0A  if (excludes.length) %7B%0A    log.warn(%60viewPath: $%7Bview%7D is excluded!%60);%0A%0A    next();%0A    return;%0A  %7D%0A%0A  fs.exists(filePath, exists =%3E %7B%0A    if (exists) %7B%0A      const engine = app.engines%5Bext%5D;%0A      const data = Object.assign(res.locals, res.apiData);%0A      // %E6%89%8B%E5%8A%A8%E8%B0%83%E7%94%A8%E6%9C%89%E5%88%A9%E4%BA%8E%E9%94%99%E8%AF%AF%E6%8D%95%E6%8D%89%0A      res.send(engine(filePath, data));%0A    %7D else %7B%0A      next();%0A    %7D%0A  %7D);%0A%7D%0A%0Amodule.exports = render;%0A

add 模板渲染stage