API

Nunjucks 的 API 包括渲染模板,添加过滤器和扩展,自定义模板加载器等等。

注意: nunjucks并不是在沙盒中运行的,所以使用用户定义的模板可能存在风险。这可能导致的风险有:在服务器上运行时敏感数据被窃取,或是在客户端运行时遭遇跨站脚本攻击(详情请查看这里)。

Simple API

如果你不需要深度定制,可以直接使用初级(higher-level) api 来加载和渲染模板。

render

nunjucks.render(name, [context], [callback])

渲染模式时需要两个参数,模板名 name 和数据 context。如果 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' });

configure

nunjucks.configure([path], [opts]);

将给定的字符串编译成可重复使用的nunjucks模板对象。

var template = nunjucks.compile('Hello {{ username }}');
template.render({ username: 'James' });

configure

nunjucks.configure([path], [opts]);

传入 path 指定存放模板的目录,opts 可让某些功能开启或关闭,这两个变量都是可选的。path 的默认值为当前的工作目录,opts 提供以下功能:

  • autoescape (默认值: true) 控制输出是否被转义,查看 Autoescaping
  • throwOnUndefined (default: false) 当输出为 null 或 undefined 会抛出异常
  • trimBlocks (default: false) 自动去除 block/tag 后面的换行符
  • lstripBlocks (default: false) 自动去除 block/tag 签名的空格
  • watch (默认值: false) 当模板变化时重新加载。使用前请确保已安装可选依赖 chokidar
  • noCache (default: false) 不使用缓存,每次都重新编译
  • web 浏览器模块的配置项
    • useCache (default: false) 是否使用缓存,否则会重新请求下载模板
    • async (default: false) 是否使用 ajax 异步下载模板
  • express 传入 express 实例初始化模板设置
  • tags: (默认值: see nunjucks syntax) 定义模板语法,查看 Customizing Syntax

configure 返回一个 Environment 实例, 他提供了简单的 api 添加过滤器 (filters) 和扩展 (extensions),可在 Environment 查看更多的使用方法。

注意: 简单的API (比如说上面的nunjucks.render) 通常会使用最近一次调用nunjucks.configure时的配置。由于这种做法是隐性的,它可能会渲染出意料之外的结果,所以在大多数情况下我们不推荐使用这类简单的API(特别是用到configure的情况);我们推荐使用var env = nunjucks.configure(...)创建一个独立的环境,并调用env.render(...)进行渲染。

nunjucks.configure('views');

// 在浏览器端最好使用绝对地址
nunjucks.configure('/views');

nunjucks.configure({ autoescape: true });

nunjucks.configure('views', {
    autoescape: true,
    express: app,
    watch: true
});

var env = nunjucks.configure('views');
// do stuff with env

installJinjaCompat

nunjucks.installJinjaCompat()

这个方法为了与 Jinja 更好的兼容,增加了一些适配 Python 的 API。但是 nunjucks 不是为了完全兼容 Jinja/Pyhton,这只为了帮助使用者查看。

增加了 TrueFalse,与 js 的 truefalse 相对应。并增加 Array 和 Object 使之适配 Python 风格的。查看源码能看到所有功能。

就是这么简单,如果希望自己定义模板加载等更多的个性化设置,那么可以继续往下看。

Environment

Environment 类用来管理模板,使用他可以加载模板,模板之间可以继承和包含,上面提到的 api 都是调用了 Environment 的 api。

你可以根据需要来定制,比如定制模板加载。

constructor

new Environment([loaders], [opts])

实例化 Environment 时传入两个参数,一组 loaders 和配置项 opts。如果 loaders 不存在,则默认从当前目录或地址加载。loaders 可为一个或多个,如果传入一个数组,nunjucks 会按顺序查找直到找到模板。更多查看 Loader

opts 的配置有 autoescapethrowOnUndefinedtrimBlockslstripBlocks,在 configure 查看具体配置(express 和 watch 配置在这里不适用,而是在 env.express 进行配置)。

在 node 端使用 FileSystemLoader 加载模板,浏览器端则使用 WebLoader 通过 http 加载(或使用编译后的模板)。如果你使用了 configure 的 api,nunjucks 会根据平台(node 或浏览器)自动选择对应的 loader。查看更多 Loader

// the FileSystemLoader is available if in 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()]);

// the WebLoader is available if in the browser
var env = new nunjucks.Environment(new nunjucks.WebLoader('/views'));

render

env.render(name, [context], [callback])

渲染名为 name 的模板,使用 context 作为数据,如果 callback 存在,在完成时会调用,回调有两个参数:错误和结果( 查看 asynchronous support)。如果 callback 不存在则直接回返结果。

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 相同,只是渲染一个字符串而不是加载的模块。

var res = nunjucks.renderString('Hello {{ username }}', { username: 'James' });

addFilter

env.addFilter(name, func, [async])

添加名为 name 的自定义过滤器,func 为调用的函数,如果过滤器需要异步的,async 应该为 true (查看 asynchronous support)。查看 Custom Filters

getFilter

env.getFilter(name)

获取过滤器,传入名字返回一个函数。

addExtension

env.addExtension(name, ext)

添加一个名为 name 的扩展,ext 为一个对象,并存在几个指定的方法供系统调用,查看 Custom Tags

removeExtension

env.removeExtension(name)

删除之前添加的扩展 name

getExtension

env.getExtension(name)

获取扩展,传入名字返回一个函数。

hasExtension

env.hasExtension(name)

如果 name 扩展已经被添加,那返回 true。

addGlobal

env.addGlobal(name, value)

添加一个全局变量,可以在所有模板使用。注意:这个会覆盖已有的 name 变量。

getGlobal

env.getGlobal(name)

返回一个名为 name 的全局变量。

getTemplate

env.getTemplate(name, [eagerCompile], [callback])

获取一个名为 name 的模板。如果 eagerCompiletrue,模板会立即编译而不是在渲染的时候再编译。如果 callback 存在会被调用,参数为错误和模板,否则会直接返回。如果使用异步加载器,则必须使用异步的 api,内置的加载器不需要。查看 asynchronous supportloaders

var tmpl = env.getTemplate('page.html');

var tmpl = env.getTemplate('page.html', true);

env.getTemplate('from-async-loader.html', function(err, tmpl) {
});

express

env.express(app)

使用 nunjucks 作为 express 的模板引擎,调用后可正常使用 express。你也可以调用 configure,将 app 作为 express 参数传入。

var app = express();
env.express(app);

app.get('/', function(req, res) {
    res.render('index.html');
});

opts.autoescape

env.opts.autoescape

你可以通过这个配置控制是否全局开启模板转义,这对于创建高级过滤(如 html 操作)非常有用。 正常情况你可以返回 SafeString,输出保持和输入一致不做任何处理,但这只在很少场景下有用。

Template

Template 是一个模板编译后的对象,然后进行渲染。通常情况下,Environment 已经帮你处理了,但你也可以自己进行处理。 如果使用 Template 渲染模板时未指定 Environment,那么这个模板不支持包含 (include) 和继承 (inherit) 其他模板。

constructor

new Template(src, [env], [path], [eagerCompile])

实例化 Template 时需要四个参数,src 为模板的字符串,Environment 的实例 env(可选)用来加载其他模板,path 为一个路径,在调试中使用,如果 eagerCompiletrue,模板会立即编译而不是在渲染的时候再编译。

var tmpl = new nunjucks.Template('Hello {{ username }}');

tmpl.render({ username: "James" }); // -> "Hello James"

render

tmpl.render(context, [callback])

渲染模板,context 为数据,如果 callback 存在会在渲染完成后调用,参数为错误和结果 (查看 asynchronous support),否则直接返回。

Loader

加载器是一个对象,从资源(如文件系统或网络)中加载模板,以下为两个内置的加载器。 A loader is an object that takes a template name and loads it from a source, such as the filesystem or network. The following two builtin loaders exist, each for different contexts.

FileSystemLoader

new FileSystemLoader([searchPaths], [opt])

只在 node 端可用,他可从文件系统中加载模板,searchPaths 为查找模板的路径,可以是一个也可以是多个,默认为当前的工作目录。

opt 为一个对象,包含如下属性:

  • watch - 如果为 true,当文件系统上的模板变化了,系统会自动更新他。使用前请确保已安装可选依赖 chokidar
  • noCache - 如果为 true,不使用缓存,模板每次都会重新编译。
// Loads templates from the "views" folder
var env = new nunjucks.Environment(new nunjucks.FileSystemLoader('views'));

WebLoader

new WebLoader([baseURL], [opts])

只在浏览器端可用,通过 baseURL(必须为同域)加载模板,默认为当前相对目录。

opt 为一个对象,包含如下属性:

  • useCache 如果为 true,模板将会永远缓存,你将看不到他们更新。缓存默认关闭,因为这样就无法监听变化并更新缓存。 记住,你应该在生产环境预编译模板。
  • async 如果为 true,模板会异步请求,而非同步,同时你必须使用异步 render API(调用 render 时传入一个 callback)。

他还能加载预编译后的模板,自动使用这些模板而不是通过 http 获取,在生产环境应该使用预编译。查看 Precompiling Templates

// Load templates from /views
var env = new nunjucks.Environment(new nunjucks.WebLoader('/views'))

Writing a Loader

你可以自己写一个更复杂一点的加载器(如从数据库加载),只需建一个对象,添加一个 getSource(name) 的方法,name 为模板名。

function MyLoader(opts) {
    // configuration
}

MyLoader.prototype.getSource = function(name) {
    // load the template
    // return an object with:
    //   - src:     String. The template source.
    //   - path:    String. Path to template.
    //   - noCache: Bool. Don't cache the template (optional).
}

如果你希望跟踪模板的更新,并当有更新时清除缓存,这样就有一点复杂了。但你可以继承 Loader 类,可以通过 emit 方法触发事件。

var MyLoader = nunjucks.Loader.extend({
    init: function() {
        // setup a process which watches templates here
        // and call `this.emit('update', name)` when a template
        // is changed
    }

    getSource: function(name) {
        // load the template
    }
});

Asynchronous

这是最后一部分:异步加载器。到现在为止,所有的加载器都是同步,getSource 立即返回资源。这个的好处是用户不必强制使用异步 api,也不用担心异步模板的边缘问题。但是,你可以希望从数据库加载

只需在 load 中添加 async: true 属性就可支持异步调用

var MyLoader = nunjucks.Loader.extend({
    async: true,

    getSource: function(name, callback) {
        // load the template
        // ...
        callback(err, res);
    }
});

记住现在必须使用异步 api,查看 asynchronous support

注意: 如果使用了异步加载器,你将不能在 for 循环中加载模块,但可以使用 asyncEach 替换之。asyncEachfor 相同,只是在异步的时候使用。更多查看 Be Careful!

Browser Usage

在浏览器端使用 nunjucks 需要考虑更多,因为需要非常关注加载和编译时间。在服务端,模板一次编译后就缓存在内存中,就不用担心了。在浏览器端,你甚至不想编译他,因为会降低渲染的速度。

解决方案是将模板预编译成 javascript,和普通的 js 一样加载。

可能你想在开发时动态的加载模板,这样你可以在文件变化的时候马上看到而不需要预编译。Nunjucks 已经帮你适配了你想要的工作流。

有一点必须遵守:在生产环境一定要预编译模板。为什么?不仅因为在页面加载时编译模板速度很慢,而且是同步加载模板的,会阻塞整个页面。这很慢,因为 nunjucks 模板不是异步的。

在客户端,有两种最常用的方式来初始设置 nunjucks。注意这是两个不同的文件,其中一个包括编译器 nunjucks.js,另一个不包括 nunjucks-slim.js。查看 Getting Started 区分两者。

查看 Precompiling 了解预编译。

Setup #1: only precompile in production

这个方法可以让你在开发环境可以动态加载模板(可以马上看到变化),在生产环境使用预编译的模板。

  1. 使用 script 或模块加载器加载 nunjucks.js
  2. 渲染模板 (example)!
  3. 当发布到生产环境时,When pushing to production, 将模板预编译 成 js 文件。

在生产环境中,你可以使用 nunjucks-slim.js 代替 nunjucks.js 进行优化,因为你使用了预编译的模板。 nunjucks-slim.js 只有 8K 而不是 20K,因为不包括编译器。 但是这使初始设置复杂化了,因为在开发和生产环境需要不同的 js 文件,是否值得完全在你如何使用。

Setup #2: always precompile

这个方法是在开发和生产环境都使用预编译的模板,这样可以简化初始设置。但是在开发时,你需要一些工具来自动预编译,而不是手动编译。

  1. 开发时,使用 gruntgulp 监听文件目录,当文件变化后自动编译成 js 文件。
  2. 使用 script 或模块加载器加载 nunjucks-slim.js 和你编译的 js 文件(如 templates.js)。
  3. 渲染模板 (example)!

使用这个方法,开发和生产环境无区别,只需提交 templates.js 并部署到生产环境。

Precompiling

使用 nunjucks-precompile 脚本来预编译模板,可传入一个目录或一个文件,他将把所有的模板生成 javascript。

// Precompiling a whole directory
$ nunjucks-precompile views > templates.js

// Precompiling individual templates
$ nunjucks-precompile views/base.html >> templates.js
$ nunjucks-precompile views/index.html >> templates.js
$ nunjucks-precompile views/about.html >> templates.js

你只需要在页面上加载 templates.js,系统会自动使用预编译的模板,没有改变的必要。

这个脚本还有很多可选项,直接调用 nunjucks-precompile 可以看到更多信息。注意所有的异步过滤器需要当参数传入,因为编译时需要他们,你可以使用 -a 参数来传入(如 -a foo,bar,baz)。如果只使用同步过滤器则不需要处理。

这个脚本无法指定扩展,所以你需要使用如下的预编译 api。

API

如果你希望通过代码来预编译模板,nunjucks 也提供了 api,特别是在使用扩展和异步过滤器的时候需要使用这些 api。可以将 Environment 的实例传给预编译器,其中将包括扩展和过滤器。你需要在客户端和服务器使用同一个 Environment 对象保证同步。

precompile

nunjucks.precompile(path, [opts])

传入 path 预编译一个文件或目录,opts 为如下的一些配置:

  • name: 模板的名字,当编译一个字符串的时候需要,如果是一个文件则是可选的(默认为 path),如果是目录名字会自动生成。
  • asFunction: 生成一个函数
  • force: 如果出错还继续编译
  • env: Environment 的实例
  • include: 包括额外的文件和文件夹 (folders are auto-included, files are auto-excluded)
  • exclude: 排除额外的文件和文件夹 (folders are auto-included, files are auto-excluded)
  • wrapper: function(templates, opts) 自定义预编译模板的输出格式。这个函数必须返回一个字符串
    • templates: 由对象组成的,带有下列属性的数组:
      • name: 模板名称
      • template: 编译后的模板所生成的,运行在javascript上的字符串源码
    • opts: 上面所有配置选项所组成的对象
var env = new nunjucks.Environment();

// extensions must be known at compile-time
env.addExtension('MyExtension', new MyExtension());

// async filters must be known at compile-time
env.addFilter('asyncFilter', function(val, cb) {
  // do something
}, true);

nunjucks.precompile('/dir/to/views', { env: env });

precompileString

nunjucks.precompileString(str, [opts])

precompile 相同,只是编译字符串。

Asynchronous Support

如果你对异步渲染感兴趣才需要看这节,并没有性能上的优势,只是支持异步的过滤器和扩展,如果你不关注异步,那么应该使用同步 api,如 var res = env.render('foo.html');。你不必每次都写 callback,这就是为什么在所有的渲染函数中是一个可选项。

nunjucks 1.0 会提供一种异步渲染模板的方式,这意味着自定义的过滤器和扩展可以做些类似从数据库获取内容的操作,模板渲染会等待直到调用回调。

模板加载器也可以异步,可使你从数据库或其他地方加载模板。查看 Writing a Loader。如果你在使用一个异步的模板加载,你需要使用异步的 api。内置的加载器是同步的,但并没有性能问题,因为文件系统是可以缓存的,而浏览器端会将模板编译成 js。

如果你使用了异步的,那么你需要使用异步的 api:

nunjucks.render('foo.html', function(err, res) {
   // check err and handle result
});

了解更多异步相关的查看 filters, extensionsloaders.

Be Careful!

Nunjucks 默认是同步的,因此你需要按照如下规则写异步模板:

Autoescaping

在默认情况下,nunjuck 渲染时会按原样输出,如果开启了自动转义 (autoescaping),nunjuck 会转义所有的输出,为了安全建议一直开启。

自动转义在 nunjucks 中非常简单,你只需将 autoescapetrue 传入 Environment 对象。

var env = nunjucks.configure('/path/to/templates', { autoescape: true });

Customizing Syntax

如果你希望使用其他的 token 而不是 {{,其中包括变量、区块和注释,你可以使用 tags 来定义不同的 token:

var env = nunjucks.configure('/path/to/templates', {
  tags: {
    blockStart: '<%',
    blockEnd: '%>',
    variableStart: '<$',
    variableEnd: '$>',
    commentStart: '<#',
    commentEnd: '#>'
  }
});

使用这个 env,模板如下所示:

<ul>
<% for item in items %>
  <li><$ item $></li>
<% endfor %>
</ul>

Custom Filters

使用 EnvironmentaddFilter 方法添加一个自定义的过滤器,过滤器时一个函数,第一个参数为目标元素,剩下的参数为传入过滤器的参数。

var nunjucks = require('nunjucks');
var env = new nunjucks.Environment();

env.addFilter('shorten', function(str, count) {
    return str.slice(0, count || 5);
});

添加了一个 shorten 的过滤器,返回前 count 位数的字符,count 默认为 5,如下为如何使用:

{# Show the first 5 characters #}
A message for you: {{ message|shorten }}

{# Show the first 20 characters #}
A message for you: {{ message|shorten(20) }}

Keyword/Default Arguments

模板中说道,nunjucks 支持关键字参数,你可以在 filter 中使用他。

所有的关键字参数会以最后一个参数传入,以下为使用了关键字参数的 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) 不是),你不能使用将一个位置参数当作关键字参数来用 (如 foo(1, y=1))。

Asynchronous

异步过滤器接受一个回调继续渲染,调用 addFilter 时需传入第三个参数 true

var env = nunjucks.configure('views');

env.addFilter('lookup', function(name, callback) {
    db.getItem(name, callback);
}, true);

env.render('{{ item|lookup }}', function(err, res) {
    // do something with res
});

回调需要两个参数 callback(err, res)err 可以为 null。

注意:当预编译时,你必须指定所有的异步过滤器,查看 Precompiling

Custom Tags

你可以添加更多的自定义扩展,nunjucks 提供了 parser api 可以对模板做更多的事。

注意:当预编译时,你必须在编译时添加这些扩展,你应该使用 precompiling API (或者 grunt gulp任务),而不是预编译脚本。你需要创建一个 Environment 实例,添加扩展,传到预编译器中。

一个扩展至少有两个字段 tagsparse,扩展注册一个标签名,如果运行到这个标签则调用 parse。

tags 为这个扩展支持的一组标签名。parse 为一个函数,当编译时会解析模板。除此之外,还有一个特殊的节点名为 CallExtension,在运行时你可以调用本扩展的其他方法,下面会详细说明。

因为你需要直接使用 parse api,并且需要手动分析初 AST,所以有一点麻烦。如果你希望做一些复杂的扩展这是必须的。所以介绍一下你会用到的方法:

parser API 还需要更多的文档,但现在对照上面的文档和下面的例子,你还可以看下源码

最常用使用的是在运行时解析标签间的内容,就像过滤器一样,但是更灵活,因为不只是局限在一个表达式中。通常情况下你会解析模板,然后将内容传入回调。你可以使用 CallExtension,需要传扩展的实例,方法名,解析的参数和内容(使用 parseUntilBlocks 解析的)。

例如,下面实现了从远程获取内容并插入的扩展:

function RemoteExtension() {
    this.tags = ['remote'];

    this.parse = function(parser, nodes, lexer) {
        // get the tag token
        var tok = parser.nextToken();

        // parse the args and move after the block end. passing true
        // as the second arg is required if there are no parentheses
        var args = parser.parseSignature(null, true);
        parser.advanceAfterBlockEnd(tok.value);

        // parse the body and possibly the error block, which is optional
        var body = parser.parseUntilBlocks('error', 'endtruncate');
        var errorBody = null;

        if(parser.skipSymbol('error')) {
            parser.skip(lexer.TOKEN_BLOCK_END);
            errorBody = parser.parseUntilBlocks('endremote');
        }

        parser.advanceAfterBlockEnd();

        // See above for notes about 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" %}
  This content will be replaced with the content from /stuff
{% error %}
  There was an error fetching /stuff
{% endremote %}

Asynchronous

如果是异步的可以使用 CallExtensionAsync,在运行时扩展有一个回调作为最后一个参数,模板渲染会等待回调返回。

上面例子中的 run 如下使用

this.run = function(context, url, body, errorBody, callback) {
   // do async stuff and then call callback(err, res)
};

如果你做了些有趣的东西的话,请记得把他们 添加到wiki中!