- 模板引擎
【免费下载链接】nunjucks
A powerful templating engine with inheritance, asynchronous control, and more (jinja2 inspired)
本篇技术指南围绕 Nunjucks(Jinja2 风格的高性能模板引擎,支持继承与异步控制)的官方 API 文档展开,系统讲解其渲染、环境配置、模板加载器、浏览器端预编译、异步渲染以及过滤器/标签扩展的全部 API 用法。读完本文,你将掌握nunjucks.configure/render等简化 API、Environment与Template的底层机制、三种内置 Loader 的选型、自定义 Loader 与自定义标签的完整写法,以及生产环境预编译的最佳实践。
安全警告:Nunjucks没有沙箱(sandbox)执行机制。让用户自行定义模板存在潜在安全风险:在服务端,可能成为访问敏感数据的攻击向量;在客户端,可能引入跨站脚本(XSS)漏洞。请勿在不可信输入上直接执行模板。
API 概览
Nunjucks 的 API 覆盖了以下几个核心领域:
- 模板渲染:
render、renderString、compile - 环境配置:
configure、Environment及其全部方法 - 过滤器与扩展:
addFilter、addExtension、addGlobal - 模板加载定制:
FileSystemLoader、WebLoader、NodeResolveLoader及自定义 Loader - 浏览器端优化:预编译(CLI 与 API 两种方式)
- 异步渲染:异步过滤器、异步扩展、异步 Loader
- 语法定制:
tags选项
简化 API(Simplified API)
如果你不需要对系统做深度定制,可以直接使用高层级简化 API 完成模板加载与渲染。它内部维护了一个共享的Environment实例,自动根据运行环境(Node 或浏览器)选择合适的 Loader(详见 入口文件 中的configure实现:Node 下自动创建FileSystemLoader,浏览器下自动创建WebLoader)。
render
nunjucks.render(name, [context], [callback])按名称name渲染模板,context为数据哈希对象。若提供callback,渲染完成后以(err, res)形式回调(err为可能的错误,res为结果);若不提供 callback,render同步返回渲染结果,错误直接抛出。
var res = nunjucks.render('foo.html'); var res = nunjucks.render('foo.html', { username: 'James' }); nunjucks.render('async.html', function(err, res) { });renderString
nunjucks.renderString(str, context, [callback])与render相同,但直接渲染原始字符串而非加载模板:
var res = nunjucks.renderString('Hello {{ username }}', { username: 'James' });compile
nunjucks.compile(str, [env], [path])将给定字符串编译为可复用的 NunjucksTemplate对象。注意nunjucks.compile在 入口文件 中会先确保存在默认环境(不存在时自动configure()),再构造new Template(src, env, path, eagerCompile)。
var template = nunjucks.compile('Hello {{ username }}'); template.render({ username: 'James' });configure
nunjucks.configure([path], [opts]);告诉 Nunjucks 模板所在的目录path,并通过哈希opts开关各项功能。两个参数可同时提供,也可只提供其中一个。path默认为当前工作目录。configure返回一个Environment实例,让你在继续使用简化 API 的同时也能添加过滤器、扩展(见下文 Environment 章节)。
opts 可用选项:
| 选项 | 默认值 | 说明 |
|---|---|---|
autoescape | true | 是否自动转义含危险字符的输出,参见 Autoescaping |
throwOnUndefined | false | 输出 null/undefined 值时是否抛出错误 |
trimBlocks | false | 自动删除 block/tag 结尾的换行符 |
lstripBlocks | false | 自动删除 block/tag 开头的空格 |
watch | false | 模板变更时自动重新加载(服务端)。需安装可选依赖chokidar |
noCache | false | 不使用缓存,每次都重新编译模板(服务端) |
web | — | 浏览器端模板加载配置对象:useCache(默认false,开启后模板永不会看到更新)、async(默认false,异步加载模板,需配合异步 API 使用) |
express | — | 要安装 nunjucks 的 express 应用实例 |
tags | Nunjucks 默认语法 | 自定义 nunjucks 标签语法,参见 语法定制 |
代码示例:
nunjucks.configure('views'); // 浏览器中通常使用绝对 URL nunjucks.configure('/views'); nunjucks.configure({ autoescape: true }); nunjucks.configure('views', { autoescape: true, express: app, watch: true }); var env = nunjucks.configure('views'); // 用 env 做点什么重要告诫:简化 API(如nunjucks.render)始终使用最近一次nunjucks.configure的配置。这种隐式状态可能带来意想不到的副作用,因此在大多数场景(尤其是使用了configure时)不推荐使用简化 API,而应显式创建环境:var env = nunjucks.configure(...)后调用env.render(...)等。从源码看,入口文件 中的模块级变量e正是这个"最后一次配置"的全局单例,render/renderString/compile都依赖它。
installJinjaCompat
nunjucks.installJinjaCompat()安装实验性的 Jinja 兼容支持,向环境添加 Python 风格的 API。虽然 Nunjucks 并不追求与 Jinja/Python 完全兼容,但该功能可帮助需要尽可能贴近 Jinja 的用户。它添加了与 JStrue/false对应的True/False,并为数组、对象补充 Python 风格的方法,同时支持 Python 的 "slice" 切片语法。完整实现见 jinja-compat.js:该文件通过改写runtime.contextOrFrameLookup、runtime.memberLookup以及(存在时)Compiler.prototype.assertType、Parser.prototype.parseAggregate等内部方法来实现兼容,并保留了uninstall()恢复逻辑。
Environment
Environment是管理模板的核心对象:它知道如何加载你的模板,也负责加载这些模板通过继承或包含所依赖的内部模板。上面的简化 API 正是替你维护了一个Environment实例。你也可以手动管理它,从而指定自定义的模板加载器。
构造函数
new Environment([loaders], [opts])构造函数接收loaders列表与配置哈希opts。若loaders为 null,默认从当前目录(或 URL)加载;可以传单个 Loader,也可以传数组——传数组时 Nunjucks 按顺序依次询问,直到某个 Loader 找到模板(见 environment.js 中的lib.asyncIter(this.loaders, ...)遍历逻辑)。
opts可用参数为autoescape、throwOnUndefined、trimBlocks、lstripBlocks,含义与configure相同(express、watch不适用,需分别通过env.express等方法配置)。environment.js 显示,构造函数还会统一处理dev标志(控制错误堆栈详略)、初始化 loaders 缓存、默认过滤器/测试(内置 filters 与 tests 会被批量注册进环境)。
在 Node 中可用FileSystemLoader通过文件系统加载模板;在浏览器中可用WebLoader通过 HTTP 加载(或使用预编译模板)。若使用简化配置 API,Nunjucks 会根据环境自动创建合适的 Loader。此外 Node 下还有NodeResolveLoader,按 Node 模块解析算法(require.resolve)从文件系统加载,但它默认不启用,需要显式传入Environment构造函数。
// FileSystemLoader 在 Node 中可用 var env = new nunjucks.Environment(new nunjucks.FileSystemLoader('views')); var env = new nunjucks.Environment(new nunjucks.FileSystemLoader('views'), { autoescape: false }); var env = new nunjucks.Environment([new nunjucks.FileSystemLoader('views'), new MyCustomLoader()]); // WebLoader 在浏览器中可用 var env = new nunjucks.Environment(new nunjucks.WebLoader('/views'));另一个值得注意的细节:environment.js 会检查全局window.nunjucksPrecompiled,如果存在(即页面已加载预编译模板),会自动把PrecompiledLoader插入到 loaders 队列最前面,实现"预编译模板优先"。
render
env.render(name, [context], [callback])渲染名为name的模板,context为可选数据哈希。提供callback时以(err, res)回调,否则同步返回渲染字符串:
var res = nunjucks.render('foo.html'); var res = nunjucks.render('foo.html', { username: 'James' }); nunjucks.render('async.html', function(err, res) { });renderString
env.renderString(src, [context], [callback])与render相同,但渲染原始字符串。源码实现(environment.js)为new Template(src, this, opts.path)后调用tmpl.render。
var res = nunjucks.renderString('Hello {{ username }}', { username: 'James' });addFilter
env.addFilter(name, func, [async])注册名为name的自定义过滤器,调用时执行func。若过滤器需异步执行,async传true(见 异步支持)。返回env以支持链式调用。实现见 environment.js,异步过滤器名会被记录到this.asyncFilters数组供编译期使用。
getFilter
env.getFilter(name)获取名为name的过滤器函数;不存在时抛出filter not found错误(见 environment.js)。
addExtension
env.addExtension(name, ext)注册名为name的自定义扩展ext。ext是包含若干特定方法的对象,由扩展系统调用。返回env以支持链式调用。见 自定义标签。
removeExtension / getExtension / hasExtension
env.removeExtension(name) env.getExtension(name) env.hasExtension(name)分别用于移除、获取、判断扩展是否存在。移除时源码会同步从extensionsList与extensions哈希中删除(见 environment.js)。
addGlobal / getGlobal
env.addGlobal(name, value) env.getGlobal(name)addGlobal添加对所有模板可见的全局值(同名全局值会被覆盖),返回env支持链式调用;getGlobal获取全局值,不存在时抛出global not found错误(见 environment.js)。模板执行时,Context.lookup会优先检查全局变量(environment.js)。
getTemplate
env.getTemplate(name, [eagerCompile], [callback])获取名为name的模板。eagerCompile为true时立即编译(而非等到渲染时);提供callback时以(err, tmpl)回调,否则同步返回。使用异步 Loader 时必须使用异步 API。内置 Loader 不需要。见 异步支持 与 Loader。
var tmpl = env.getTemplate('page.html'); var tmpl = env.getTemplate('page.html', true); env.getTemplate('from-async-loader.html', function(err, tmpl) { });从实现上看(environment.js),getTemplate先按 loaders 顺序查询缓存,未命中时遍历所有 Loader 调用getSource,并通过src.loader = loader记录来源、按noCache决定是否写入缓存。
express
env.express(app)将 Nunjucks 安装为 express 应用的渲染引擎。安装后即可按 express 常规方式使用res.render。也可以通过简化 API 的configure传入express选项自动完成。返回env支持链式调用。实现见 express-app.js,由env.express(app)转发(environment.js)。
var app = express(); env.express(app); app.get('/', function(req, res) { res.render('index.html'); });opts.autoescape
env.opts.autoescape该布尔属性反映全局是否开启了 autoescaping。编写操纵 HTML 的高级过滤器时可能有用——通常你只需返回安全字符串即可,但少数场景需要查询此开关。
'load' 事件
env.on('load', function(name, source, loader))当某个 Loader 加载模板时,会发出load事件,可用于在运行时探测依赖关系。参数如下:
- name(String):模板名称
- source(Object):
Loader.getSource的返回结果- src(String):模板源码
- path(String):文件路径或 URL
- noCache(Bool):是否不缓存
- loader:发出事件的 Loader 实例
该事件在 environment.js 的_initLoaders中被统一转发(每个 Loader 的load事件被绑定到环境上的load事件),同时 Loader 的update事件也会向上转发。
Template
Template对象负责模板字符串的编译与渲染。通常Environment替你管理它,但也可以直接使用。注意:若模板未与任何环境关联,则无法被其他模板 include 或继承。
构造函数
new Template(src, [env], [path], [eagerCompile])- src:模板字符串
- env:可选
Environment实例,用于加载其他模板 - path:描述模板位置的字符串,用于调试
- eagerCompile:为
true时立即开始编译,否则延迟到首次渲染
从实现看(environment.js),src既可以是普通字符串,也可以是{type: 'code'|'string', obj: ...}形式的对象(后者用于预编译产物);编译在_compile中通过compiler.compile生成 JavaScript 代码并new Function执行(environment.js)。
var tmpl = new nunjucks.Template('Hello {{ username }}'); tmpl.render({ username: "James" }); // -> "Hello James"render
tmpl.render(context, [callback])使用可选数据哈希context渲染模板。提供callback时以(err, res)回调(见 异步支持),否则同步返回渲染字符串。源码中同步调用会走rootRenderFunc并直接返回结果,异步则通过callbackAsap保证即使模板本身是同步的,回调也总是异步触发(environment.js)。
Loader(模板加载器)
Loader 是一个"接收模板名、从某处(如文件系统或网络)加载模板"的对象。内置有以下两个(实为三个)面向不同场景的 Loader。
FileSystemLoader
new FileSystemLoader([searchPaths], [opts])仅 Node 可用。从文件系统加载模板,searchPaths为搜索路径数组(也可传单个路径,默认当前工作目录)。
opts可选属性:
- watch:为
true时模板文件变更自动更新,需安装可选依赖chokidar(源码在 node-loaders.js 中require('chokidar'),未安装会直接抛出'watch requires chokidar to be installed') - noCache:为
true时避免使用缓存,每次都重新编译
// 从 "views" 目录加载模板 var env = new nunjucks.Environment(new nunjucks.FileSystemLoader('views'));实现细节(node-loaders.js):getSource只允许搜索当前目录及其子目录(通过路径前缀校验),返回{src, path, noCache}结构并发出load事件。另外注意,若第二个参数误传布尔值,旧版本兼容逻辑会打印警告。
NodeResolveLoader
new NodeResolveLoader([opts])同样仅 Node 可用。按 Node 模块解析算法(require.resolve)从文件系统加载模板。opts与FileSystemLoader相同。实现中(node-loaders.js)会拒绝以./、../或盘符开头的路径,防止目录穿越。
WebLoader
new WebLoader([baseURL], [opts])仅浏览器可用。baseURL为加载模板的 URL(必须同域),默认当前相对目录。
opts可选属性:
- useCache:为
true时模板总是缓存,看不到更新。默认关闭缓存,因为 HTTP 下无法感知变化、无法做 "dirty cache"。记住:生产环境必须预编译模板 - async:为
true时模板异步加载(默认同步)。使用它时必须使用异步渲染 API(给render传 callback)
该 Loader 还能识别预编译模板并自动使用(而不走 HTTP),生产环境应始终如此,见 预编译。
// 从 /views 加载模板 var env = new nunjucks.Environment(new nunjucks.WebLoader('/views'))实现细节(web-loaders.js):getSource通过XMLHttpRequest加载${baseURL}/${name},并附加时间戳参数做缓存破坏;fetch明确禁止在非浏览器环境(无window)使用。
编写自定义 Loader
对于更复杂的场景(如从数据库加载),可以编写自定义 Loader:只需创建一个带getSource(name)方法的对象即可,name为模板名。
function MyLoader(opts) { // 配置 } MyLoader.prototype.getSource = function(name) { // 加载模板 // 返回一个对象,包含: // - src : String. 模板源码 // - path : String. 模板路径 // - noCache : Bool. 不缓存该模板(可选) }如果你希望跟踪模板更新、清除内部缓存以便看到更新,则需要继承Loader类,从而获得发出事件的能力:
var MyLoader = nunjucks.Loader.extend({ init: function() { // 在此设置监视模板的进程, // 模板变化时调用 `this.emit('update', name)` }, getSource: function(name) { // 加载模板 } });异步 Loader
以上都是同步 Loader:getSource立即返回源码。同步的好处是调用方不必使用异步 API,也无需担心异步模板的副作用。但当你需要从数据库加载时,异步是必要的。只需给 Loader 加上async: true属性:
var MyLoader = nunjucks.Loader.extend({ async: true, getSource: function(name, callback) { // 加载模板 // ... callback(err, res); } });此时你必须使用异步 API(见 异步支持)。
注意:使用异步 Loader 时,不能在for循环内加载模板(for会编译成命令式 JSfor循环)。如需在循环内加载模板,必须显式使用asyncEach标签,它与for完全相同但支持异步(见 注意事项)。
浏览器端使用
浏览器端使用 Nunjucks 需要更多考量,因为涉及模板加载与编译耗时。服务端模板编译一次后缓存在内存中,无需担心;但客户端若在页面加载时编译模板,渲染会非常慢。
解决方案是将模板预编译为 JavaScript,并在页面加载时以单个.js文件引入。开发时你可能想动态加载模板以即时看到修改,Nunjucks 为此设计了对应的开发工作流。
唯一必须遵守的规则:生产环境始终预编译模板。原因不仅是页面加载时编译所有模板很慢,更因为同步 HTTP 加载会阻塞整个页面(Nunjucks 默认不是异步的)。
推荐配置
注意有两个不同的 js 文件:含编译器的nunjucks.js,以及不含编译器的nunjucks-slim.js(后者约 8K,前者约 20K,差异在于是否内置编译器)。两者的差异概述见 法语版入门指南。这两个文件由 scripts/bundle.js 构建,并在 浏览器测试页 与 slim 测试页 中使用。
配置一:仅生产预编译
此配置在开发时动态加载模板(可即时看到变更),生产时使用预编译模板:
- 用
<script>标签或模块加载器引入nunjucks.js - 渲染模板(见上文 简化 API)
- 上线时 预编译 模板为 js 文件并加载到页面
优化:生产环境可改用
nunjucks-slim.js替代nunjucks.js(因为使用的是预编译模板),体积从 20K 降到 8K(不含编译器)。但这会让 dev/prod 使用不同 js 文件,配置更复杂,是否值得自行权衡。
配置二:始终预编译
开发与生产都使用预编译模板,配置简单、dev/prod 代码无差异。代价是开发时需要自动重编译机制:
- 开发时用 grunt 或 gulp 任务监视模板目录,变更时自动 预编译 成 js 文件
- 用
<script>标签或模块加载器引入nunjucks-slim.js与templates.js(或你命名的预编译 js 文件) - 渲染模板
此方法下,只需提交templates.js,即可用同一份代码部署到生产。
预编译(Precompilation)
使用随 Nunjucks 提供的nunjucks-precompile脚本预编译模板。传入目录或文件,它会为模板生成全部 JavaScript:
// 预编译整个目录 $ nunjucks-precompile views > templates.js // 逐个预编译模板 $ nunjucks-precompile views/base.html >> templates.js $ nunjucks-precompile views/index.html >> templates.js $ nunjucks-precompile views/about.html >> templates.js之后只需在页面加载templates.js,系统会自动使用预编译模板,无需任何代码改动。原理见 precompile-global.js:默认的precompileGlobal包装器把每个编译产物写入全局window.nunjucksPrecompiled哈希,而 environment.js 在构造时发现该全局对象会自动插入PrecompiledLoader。
脚本还有多种选项,直接运行nunjucks-precompile可查看帮助。注意:所有异步过滤器的名称必须传给脚本,因为它们在编译期就必须被知晓。用-a传逗号分隔的异步过滤器列表,如-a foo,bar,baz。若只用普通同步过滤器则无需任何操作。
扩展无法通过该脚本指定。如果你使用了扩展,必须使用下面的编程 API。
编程 API
如果你使用扩展或异步过滤器,就需要用编程方式预编译(二者都必须在编译期已知)。可以直接把Environment对象传给预编译器,它会从中读取扩展与过滤器。客户端与服务端应共享同一个Environment对象,保证两者同步。
precompile
nunjucks.precompile(path, [opts])从path预编译一个文件或目录。opts可用选项:
| 选项 | 说明 |
|---|---|
name | 编译字符串时必填的模板名;编译文件时可选(默认path)。编译目录时自动生成名称 |
asFunction | 生成可调用函数 |
force | 出错时继续编译 |
env | 使用的环境(从中获取扩展与异步过滤器) |
include | 要包含的文件/文件夹数组(文件夹自动包含、文件自动排除) |
exclude | 要排除的文件/文件夹数组(文件夹自动包含、文件自动排除) |
wrapper | function(templates, opts):自定义预编译模板的输出格式,必须返回字符串 |
templates:对象数组,含name(模板名)与template(预编译后的 JS 源码字符串) | |
| opts:上述全部选项组成的对象 |
var env = new nunjucks.Environment(); // 扩展必须在编译期已知 env.addExtension('MyExtension', new MyExtension()); // 异步过滤器必须在编译期已知 env.addFilter('asyncFilter', function(val, cb) { // 做点什么 }, true); nunjucks.precompile('/dir/to/views', { env: env });precompileString
nunjucks.precompileString(str, [opts])与precompile完全相同,但编译的是原始字符串(且强制要求name选项,见 precompile.js)。
异步支持(Async Support)
只有在需要异步渲染时才需要读本节。异步渲染没有任何性能优势,它只是让自定义过滤器与扩展能够进行异步调用。若不关心这点,直接使用普通 API(如var res = env.render('foo.html'))即可,无需强制传callback——这就是所有渲染函数中 callback 都可选的原因。
自 1.0 起,Nunjucks 支持异步渲染:自定义过滤器/扩展可以执行数据库查询等异步操作,模板渲染会"挂起"直到 callback 被调用。
模板 Loader 也可以是异步的,从而支持从数据库等来源加载模板(见 编写自定义 Loader)。使用异步 Loader 时必须使用异步 API。内置的"文件系统"与"HTTP" Loader 都是同步的,没有性能问题——因为文件系统有缓存,而生产环境应预编译模板、绝不走 HTTP。
使用异步功能时,必须这样调用异步 API:
nunjucks.render('foo.html', function(err, res) { // 检查 err 并处理结果 });注意事项(Be careful!)
Nunjucks 默认是同步的,因此编写异步模板时必须遵守几条规则:
- 始终使用异步 API:
render必须接收回调函数 - 异步过滤器与扩展必须在编译期已知:预编译时需显式指定(见 预编译)
- 若自定义 Loader 是异步的,不能在
for循环内包含模板:因为for会编译为命令式 JSfor循环。必须显式使用asyncEach标签迭代,它与同步for完全相同
Autoescaping(自动转义)
默认情况下,Nunjucks 会转义所有输出(出于安全考虑推荐保持开启)。若关闭 autoescaping,Nunjucks 将原样输出所有内容。
关闭方法:在Environment上把autoescape设为false。
var env = nunjucks.configure('/path/to/templates', { autoescape: false });语法定制(Customizing the Syntax)
如果希望变量、块、注释使用不同于{{、{%、{#的标记,可以用tags选项指定:
var env = nunjucks.configure('/path/to/templates', { tags: { blockStart: '<%', blockEnd: '%>', variableStart: '<$', variableEnd: '$>', commentStart: '<#', commentEnd: '#>' } });使用该环境后,模板写法如下:
<ul> <% for item in items %> <li><$ item $></li> <% endfor %> </ul>自定义过滤器(Custom Filters)
用Environment的addFilter方法安装自定义过滤器。过滤器就是一个函数:第一个参数是被处理对象,后续参数为模板中按顺序传入的过滤器参数。
var nunjucks = require('nunjucks'); var env = new nunjucks.Environment(); env.addFilter('shorten', function(str, count) { return str.slice(0, count || 5); });这添加了一个返回字符串前count个字符的shorten过滤器(count默认 5)。用法:
{# 显示前 5 个字符 #} Un message pour vous : {{ message|shorten }} {# 显示前 20 个字符 #} Un message pour vous : {{ message|shorten(20) }}关键字参数 / 默认参数
如 模板语法文档 所述,Nunjucks 支持关键字/默认参数,你可以编写利用它们的 JS 过滤器。
所有关键字参数会作为一个哈希,作为最后一个参数传给函数。下面是使用关键字参数的foo过滤器:
env.addFilter('foo', function(num, x, y, kwargs) { return num + (kwargs.bar || 10); })模板中的用法:
{{ 5 | foo(1, 2) }} -> 15 {{ 5 | foo(1, 2, bar=3) }} -> 8必须把所有位置参数放在关键字参数之前(foo(1)合法,但foo(1, bar=10)不合法)。因此不能跳过位置参数只给关键字参数(虽然 Python 允许foo(1, y=1)这种写法,这里不行)。
异步过滤器
异步过滤器接收一个 callback 以推进渲染,创建时给addFilter第三个参数传true:
var env = nunjucks.configure('views'); env.addFilter('lookup', function(name, callback) { db.getItem(name, callback); }, true); env.renderString('{{ item|lookup }}', function(err, res) { // 用 res 做点什么 });务必以两个参数调用 callback:callback(err, res),err显然可为 null。
注意:预编译时,必须把全部异步过滤器名称告知预编译器(见 预编译)。从 environment.js 可见,异步过滤器会被登记到env.asyncFilters,编译模板时(environment.js)作为参数传给compiler.compile。
自定义标签(Custom Tags)
通过创建自定义标签,可以实现更复杂的扩展:这允许你使用解析器(parser)API,对模板做任意处理。
注意:预编译时,必须为编译安装扩展。此时要使用 预编译 API(或 grunt/gulp 任务)而非命令行脚本:需要创建Environment对象、安装扩展并传给预编译器。
一个扩展是至少包含两个字段的 JS 对象:tags与parse。扩展注册新的标签名,并在标签被使用时接管解析器。
tags:扩展负责的标签名数组parse:模板编译时解析这些标签的方法
此外还有一个特殊节点类型CallExtension,它允许你在运行时调用扩展上的任意方法,下文详解。
由于需要直接与 parser API 交互、手动构建 AST,这个过程略显繁琐;但要做真正复杂的事情这是必要的。以下是常用解析器方法:
parseSignature([throwErrors], [noParens]):解析参数列表。默认要求解析器停在左括号左侧、解析到右括号。但自定义标签通常不用括号,因此第二个参数传true表示解析参数列表直到标签块结束,参数间用逗号分隔。示例:{% mytag foo, bar, baz=10 %}parseUntilBlocks(names):解析内容直到遇到names数组中某个名字的块。适合解析标签之间的内容。
parser API 尚需更多文档,可参考上面的说明与下面的完整示例,也可以阅读源码 parser.js。
最常见的用法是在运行时处理某些标签的内容——像过滤器但"加强版",因为不限于单个表达式。通常流程是:轻量解析模板,然后在扩展中拿到带内容的 callback。这通过CallExtension节点完成:它接收扩展实例、要调用的方法、标签中解析出的参数列表、以及内容块列表(用parseUntilBlocks解析)。
例如,下面实现一个从 URL 抓取内容并注入页面的扩展:
function RemoteExtension() { this.tags = ['remote']; this.parse = function(parser, nodes, lexer) { // 获取标签标记 var tok = parser.nextToken(); // 解析参数并移到块结束之后。没有括号时必须 // 给第二个参数传 true var args = parser.parseSignature(null, true); parser.advanceAfterBlockEnd(tok.value); // 解析主体以及可选的 error 块 var body = parser.parseUntilBlocks('error', 'endremote'); var errorBody = null; if(parser.skipSymbol('error')) { parser.skip(lexer.TOKEN_BLOCK_END); errorBody = parser.parseUntilBlocks('endremote'); } parser.advanceAfterBlockEnd(); // 关于 CallExtension 的说明见上文 return new nodes.CallExtension(this, 'run', args, [body, errorBody]); }; this.run = function(context, url, body, errorBody) { var id = 'el' + Math.floor(Math.random() * 10000); var ret = new nunjucks.runtime.SafeString('<div id="' + id + '">' + body() + '</div>'); var ajax = new XMLHttpRequest(); ajax.onreadystatechange = function() { if(ajax.readyState == 4) { if(ajax.status == 200) { document.getElementById(id).innerHTML = ajax.responseText; } else { document.getElementById(id).innerHTML = errorBody(); } } }; ajax.open('GET', url, true); ajax.send(); return ret; }; } env.addExtension('RemoteExtension', new RemoteExtension());模板中的用法:
{% remote "/stuff" %} 这段内容将被 /stuff 的内容替换 {% error %} 抓取 /stuff 时出错 {% endremote %}异步扩展
还有另一个节点CallExtensionAsync,它是CallExtension的异步版本。运行时,它调用扩展时会额外传入一个参数:callback。模板渲染会"挂起"直到 callback 被调用。
上面示例中的run函数将变成:
this.run = function(context, url, body, errorBody, callback) { // 做点异步的事,然后调用 callback(err, res) };若你做出了有意思的扩展,可参考 Nunjucks 官方 Wiki 的 Custom Tags 页面向社区分享。
- 模板引擎
【免费下载链接】nunjucks
A powerful templating engine with inheritance, asynchronous control, and more (jinja2 inspired)
相关推荐
Nunjucks API 完全指南:渲染、扩展与异步模板引擎的实战手册
Nunjucks API 完全指南:渲染、扩展与异步模板引擎的实战手册 Nunjucks 是一个受 Jinja2 启发的 JavaScript 模板引擎(当前仓
模板引擎Vapor Leaf 模板引擎速查清单:从依赖集成、视图渲染到自定义标签的完整实战指南
Vapor Leaf 模板引擎速查清单:从依赖集成、视图渲染到自定义标签的完整实战指南 本指南以仓库中的 Leaf 备忘清单 https://link.gitc
文档教程Egg.js模板引擎集成:Nunjucks渲染实战
Egg.js模板引擎集成:Nunjucks渲染实战 你是否在寻找一种简单高效的方式来构建动态网页?Egg.js作为基于Node.js和Koa的企业级框架,提供了
后端Web框架
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考