news 2026/9/25 7:59:20

Nunjucks API 完全指南:从简化渲染到自定义标签的模板引擎实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Nunjucks API 完全指南:从简化渲染到自定义标签的模板引擎实战
  • 模板引擎

【免费下载链接】nunjucks

A powerful templating engine with inheritance, asynchronous control, and more (jinja2 inspired)

项目地址:https://gitcode.com/gh_mirrors/nu/nunjucks
点击查看免费下载

本篇技术指南围绕 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 可用选项:

选项默认值说明
autoescapetrue是否自动转义含危险字符的输出,参见 Autoescaping
throwOnUndefinedfalse输出 null/undefined 值时是否抛出错误
trimBlocksfalse自动删除 block/tag 结尾的换行符
lstripBlocksfalse自动删除 block/tag 开头的空格
watchfalse模板变更时自动重新加载(服务端)。需安装可选依赖chokidar
noCachefalse不使用缓存,每次都重新编译模板(服务端)
web—浏览器端模板加载配置对象:useCache(默认false,开启后模板永不会看到更新)、async(默认false,异步加载模板,需配合异步 API 使用)
express—要安装 nunjucks 的 express 应用实例
tagsNunjucks 默认语法自定义 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 测试页 中使用。

配置一:仅生产预编译

此配置在开发时动态加载模板(可即时看到变更),生产时使用预编译模板:

  1. 用<script>标签或模块加载器引入nunjucks.js
  2. 渲染模板(见上文 简化 API)
  3. 上线时 预编译 模板为 js 文件并加载到页面

优化:生产环境可改用nunjucks-slim.js替代nunjucks.js(因为使用的是预编译模板),体积从 20K 降到 8K(不含编译器)。但这会让 dev/prod 使用不同 js 文件,配置更复杂,是否值得自行权衡。

配置二:始终预编译

开发与生产都使用预编译模板,配置简单、dev/prod 代码无差异。代价是开发时需要自动重编译机制:

  1. 开发时用 grunt 或 gulp 任务监视模板目录,变更时自动 预编译 成 js 文件
  2. 用<script>标签或模块加载器引入nunjucks-slim.js与templates.js(或你命名的预编译 js 文件)
  3. 渲染模板

此方法下,只需提交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要排除的文件/文件夹数组(文件夹自动包含、文件自动排除)
wrapperfunction(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)

项目地址:https://gitcode.com/gh_mirrors/nu/nunjucks
点击查看免费下载
上一篇:IBM发布2.58亿参数文档智能模型,重新定义多模态处理效率
下一篇:CL4R1T4S年度报告:2025 AI系统透明度研究进展

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/25 7:52:37

豆瓣图书知识图谱实战:Neo4j图数据库推荐系统搭建

简介&#xff1a;本资源是一套面向高校计算机及相关专业&#xff08;人工智能、自动化、物联网等&#xff09;学生的毕业设计级实践项目&#xff0c;聚焦豆瓣图书推荐系统与知识图谱构建&#xff0c;深度融合Neo4j图数据库应用开发。项目完整覆盖数据采集、清洗、图模型设计、实…

作者头像 李华
网站建设 2026/9/25 7:52:18

新手避坑指南:AI博士周有贵教你,选GEO软件拒绝套路只讲干货

很多做网站获客、搜索运营的新手&#xff0c;刚接触 GEO 生成式搜索引擎优化的时候&#xff0c;很容易被各种宣传话术绕晕。市面上相关工具、服务商参差不齐&#xff0c;不少运营新人踩坑&#xff1a;工具功能虚标、关键词挖掘不准、收费暗藏套路&#xff0c;做出来的内容不匹配…

作者头像 李华
网站建设 2026/9/25 7:49:54

安徽部分地区用户力荐的净菜加工配送服务商挑选全攻略

很多安徽连锁餐饮品牌拓展长三角市场&#xff0c;或是跨城布局门店的时候&#xff0c;都在找能做食材溯源的配送公司&#xff0c;也会疑问长三角地区有哪些好的食材配送企业&#xff0c;也会咨询能做食材批量加工配送的公司有哪些靠谱选择。伴随着长三角餐饮连锁化发展不断提速…

作者头像 李华