前言
事情是这样的…
俺从某大神那抄过一个模板引擎,仿 ASP.NET MVC 的 Razor 模板引擎,俺也一直维护着这个razor-tmpl
虽然没人用,但偶自己用啊,老早就想看看模板引擎咋回事来着,亲自造一款发现没那么简单,但也不是太困难,繁琐的很…不过也成了我折腾的玩具,下文就是如此…
模板引擎 for nodejs,一箩筐,jade/ejs 不可避免的要接触到,是可以在模板里执行一部分代码,做做循环什么的,大家都一样,哈哈…偶之前也是玩过 WordPress 的,不过那个站写了几篇之后弃了,现在觉得应该是写作体验不好…PHP 的特性,没有模板,php 既输出 html 标记,也在执行 server 端代码,我想 node 可以这样吗???
折腾
这就是本篇想说的,让模板有能力执行服务端代码 :
- 像往其他地方写入个文件啊…
- 连下数据库什么的啊…
- 如果数据是 json,或者 js exports 出类 json 结构的,模板直接 require 这个数据就好了,不需要 js 来个 require 数据,再调用模板引擎的 render(data),这个只需要告诉模板,你来执行下涩~
说白了,就是要 require…
require 是神马
使用node-inspector + --debug-brk
的同学肯定见过 node 代码,被 wrapper 在一个 function 里面
exports, require, module, __filename, __dirname
require 根 module 一样是传进来的…
而且
- exports = module.exports
- require 实际上是调用 module.require,直接用 module.require 也是一样的,参数也一样
在 Module._compile()
1 | Module.prototype._compile = function(content, filename) { |
实际上是调用一个 Module 实例的 require 方法,顺藤摸瓜,找到 Module._load(request,parent)
就是它了,require 模块就是它
1 | Module._load(request,parent) => |
接着调用_resolveFilename => _resolveLookupPaths
Native 模块
假设是 fs
- _load 里 :
filename = Module._resolveFilename(request, parent);
- _resolveFilename 里 :
1 | if (NativeModule.exists(request)) { |
var module = new Module(filename, parent);
找到 filename 之后 => new Module => 存放到 Module._cache => module.load 会去执行代码,后来会 return module.exports
第三方模块
如 async 模块
在_resolveLookuppath 里:
1 | var start = request.substring(0, 2); |
就是用 modulePaths + 它的 parent 的 paths 属性,作为 paths 返回,modulePaths 是全局模块地址,win 上是C:\User\XXXX\.node_modules 或者 .node library目录
,不是npm i xxx -g
那个地方
这个 paths 作为 async 库的可能的位置,因此与父 Module 的 paths 属性有关,这个属性是在 Module._nodeModulePaths(module)这个函数完成的
绝对路径模块
如 require(‘d:\js\abcd’)
也满足上面第三方模块的条件,开头不是./
,也不是..
所以依赖跟上面一样
相对路径
_resolveLookuppath 代码
1 | var isIndex = /^index\.\w+?$/.test(path.basename(parent.filename)); |
大概就是看这个 parent 是不是 index.xxx,是或否,id 不一样,lookuppath 就是 parent 的所在目录,path.dirname(parent.filename)
require 是根哪个模块相关的,也就是我不能把我的库(razor-tmpl)里面可以 access 到的 require 传给模板,那样它只能 require 到 native 模块…
分析好了这个,可以制作 require 了
自定义基路径的 require 函数
根据给定的文件夹,或文件,假设有个 js 在这个文件夹,这个 require 就是要达到相当于这个 js 的 require 效果…
getRequire.js
1 | var vm = require("vm"); |
如你所见:
- Module 包装模块的类,不能直接 require 得到,不过我们的 module.constructor 可是直接指向它的
- 我会用模板的路径构造一个 require 函数
1 | 假设index.razor模板 |
- 我还将 windows 上的 npm install -g 那个目录给添加到 paths 了,就是可以 require 到那里的包…
razor.directRenderSync
通常是不要 ViewBag 的,实现代码
1 | razor.directRenderSync = function(viewPath, ViewBag) { |
因此 razor 模板可以这样写
1 | @{ |
$result
是 razor-tmpl 中存放结果的变量,在最后使用的话,根 render 的 return 结果一样- 只有 Sync 形式的,这种边进行模板,边执行异步代码,想不出来怎么用…所以只有 Sync 的
直接razor.directRenderSync('index.razor');
补图