Nunjucks 自称“一种功能丰富且强大的 JavaScript 模板语言”,这听起来很正确。它不像 Mustache 或稍微健壮一些(但仍然非常轻量级)的 Handlebars 那样刻意追求超轻量级。它是一种完整的语言,包含了您在编写模板时可能需要的所有功能。
您可以在浏览器中运行它,但可能不应该这样做。这旨在在 Node.js 中运行,并用于在服务器端编译模板。
换句话说:它是一个真正花哨的 HTML 预处理器。让我们看看我认为 Nunjucks 特别酷的一些特性。
友情提示:这非常主观,并且仅基于轻微的经验!我在这里只使用了 Nunjucks 能力的 10% 左右。

Nunjucks 是 Node 的东西,因此您可以使用 npm 安装它,并通过命令行、构建工具以及整个世界与之交互。
这是一个截图,显示我运行了一个 Node 脚本,该脚本呈现了一个 Nunjucks 模板

nunjucks.render()
的结果记录到控制台1. 它只是 HTML
请注意,我们传递给 nunjucks.render()
的文件实际上只是 HTML,其中包含 {{ handlebars }}
样式的模板语法。我将其命名为 `index.njk`,但这并不是必需的,我只是喜欢明确意图。
我敢打赌,有很多前端开发人员更喜欢在 HTML 中工作,即使 HTML 最终会被处理。我有时喜欢 Pug,但它是一种独立的空格依赖语言。更喜欢 Nunjucks 有点像更喜欢 ERB 而不是 HAML。
这是一个完全合法的模板
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>{{ page_title }}</title>
</head>
<body>
{% for feature in features %}
<div class="module">
<h3>{{ feature.name }}</h3>
<p>{{ feature.description }}</p>
</div>
{% endfor %}
</body>
</html>
什么是 features
?数据!您将其传递给 render 函数。
nunjucks.render(
'index.njk', {
page_title: "Cool Product",
features: [
{
name: "Speed",
description: "It's fast."
},
{
name: "Reliability",
description: "You can count on it."
},
{
name: "Security",
description: "You don't have to worry about it."
}
]
}
);
您可以想象数据来自数据库或 API,我确定。您也可以根据需要在视图中直接定义数据。
<div>
{% set foo = "bar" %}
{{ foo }}
</div>
2. 包含
有时我使用一种语言仅仅是为了包含。例如,CodeKit 有一种几乎只用于包含的语言,因为他们知道包含多么有用。
以下是 Nunjucks 中包含的简单示例 包含
<body>
{% include "_header.njk" %}
<main>
...
</main>
{% include "_footer.njk" %}
</body>
3. 扩展/块
扩展 将包含提升到一个新的水平。扩展允许您定义一个模板文档,其中包含一些旨在接收内容块的“块”。
这是一个包含一些包含项的模板,但中间还有一个块
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>{{ page_title }}</title>
</head>
<body>
{% include "parts/_header.njk" %}
{% block main %}
This is the default content
{% endblock %}
{% include "parts/_footer.njk" %}
</body>
</html>
现在任何其他文件都可以扩展该模板,而无需担心可能与网站所有页面相关的那些样板 HTML。例如,我们的 `index.njk` 变成了
{% extends "parts/_template.njk" %}
{% block main %}
{% for feature in features %}
<div class="module">
<h3>{{ feature.name }}</h3>
<p>{{ feature.description }}</p>
</div>
{% endfor %}
{% endblock %}
我敢打赌,您可以想象将其完善的样子。也许是针对不同类型页面的多个模板。更多块,为您提供了传入额外样式表或脚本的机会。更多用于网站区域的块,例如侧边栏和页脚中的内容。
4. 宏
说到将模板提升到一个新的水平,宏 再次做到了这一点。
宏就像带有参数的导入。就像函数!您提供一些值,它会为您返回一些内容。
想象一个接收三个值的模块。例如,“色板”(就像我们正在构建模式库),它接收颜色值、名称和注释。
我们可以这样构建它
{% macro cardSwatch(colorName, colorValue, colorNotes) %}
<div class="color-swatch-tile">
<div class="color-swatch"
style="background-color: {{ colorValue }};">
</div>
<div class="color-name">
{{ colorName }}
</div>
<div class="color-notes">
{{ colorNotes }}
</div>
</div>
{% endmacro %}
现在我可以一遍又一遍地使用它
{{ cardSwatch("brandColor", "#f06d06", "Our main color.") }}
{{ cardSwatch("brandHighlight", "#d0b000", "For callouts and highlights.") }}
{{ cardSwatch("grayDark", "#333333", "For things like code block backgrounds.") }}
更好的是,我可以将宏移动到它们自己的文件/文件中,并在需要时导入它们。在这里,我将导入一个 macro
,扩展
一个模板,并循环遍历 colors
数据调用一个 macro
{% from "macros/swatch.njk" import swatch %}
{% extends "parts/_template.njk" %}
{% block main %}
{% for color in colors %}
{{ swatch(color.color_name, color.color_value, color.color_notes) }}
{% endfor %}
{% endblock %}
CodePen 项目支持 Nunjucks
确实如此!这很好,因为它不需要任何设置。(您知道 CodePen 项目 吗?)只需将文件名以 `.njk` 结尾,CodePen 就会知道将其作为 Nunjucks 处理。

使用 Gulp 的 Nunjucks
在本地使用 Nunjucks 时,您几乎肯定需要一个像 Gulp 这样的构建工具来帮助处理文件。幸运的是,有 gulp-nunjucks 使其变得更加容易。
gulp.task('default', () =>
gulp.src('index.html')
.pipe(nunjucks.compile({
my_data: "is here"
}))
.pipe(gulp.dest('dist'))
);
仓库
我创建了一个,因为我在撰写本文时使用了 Nunjucks。
其他信息
- Nunjucks 文档
- Zell Liew:如何使用模板引擎和 Gulp 模块化 HTML
- Andy Neale:Nunjucks:一个 JavaScript 模板引擎
这看起来非常像 twig。不错!
有一个 TwigJS:https://github.com/twigjs/twig.js :)
那是因为 Nunjucks、Swig 和 Twig 的灵感都来自 Python 的 Jinja。
事实上,Nunjucks 试图成为 Jinja2 到 JavaScript 的一个非常接近的移植。好吧,这篇文章值得一提。
如果您正在寻找提供许多类似功能的前端 JS 模板,请查看 JSRender。
http://www.jsviews.com/#jsrplaying
客户端应用程序框架和服务器端模板框架如此相似,难道不美妙吗?我的意思是,看看那个宏示例。他们不妨称它们为组件,因为它们就是! :)
(这种美妙实际上通过同构框架形成了完整的循环。还有其他人想到这些东西时起鸡皮疙瘩吗?)
它们不是组件,不要被迷惑了。宏只不过是参数化的字符串,仅此而已。您放入其中的内容甚至可以是纯文本、JSON、Python 代码或任何您想要的内容。(尽管 Nunjucks 旨在为 HTML 转义插值的值。)
更糟糕的是,您可以轻松地测试组件,而测试宏则完全是另一回事。
仅仅因为它是一个参数化的字符串并不意味着我们不能将其视为组件。
const Addition = (a, b) => `${a} + ${b} = ${a + b}`
这是另一个参数化的字符串。您可能会争辩说它只是一个返回字符串的函数(就像 Nunjuck 的宏一样!),但您知道我看到了什么吗?一个组件! :)
像这样建立平行关系正是让 Web 开发如此美妙的原因。
嗯,更准确地说,宏只是函数,在渲染过程中始终打印输出。仅此而已。你可以用它做任何你想做的事情。甚至编写 CSS。
Nunjucks 宏作为组件,甚至更容易测试,因为它们大多是静态的(除了少数罕见的例外情况,比如你在客户端进行异步操作或使用宏触发副作用)。
因此,测试非常简单。
如果有人想知道如何做到这一点,这里有一个示例。
这里我实际上是在测试 Nunjucks 自定义全局函数,但你可以想象它对宏也会以相同的方式工作。
例如,如果我们有一些“组件”
blogPostPreview
你可能会喜欢https://atmin.github.io/funponent 的方法。
快照测试是一种非常基本的测试方法,它并不总是你想要的。事实上,我想说它很少是你想要的,因为由于宏是“静态的”,这种测试几乎没有价值。这就像有一个像
it('should just work', ...)
这样的测试。但还有更多。如何获得宏测试的代码覆盖率?如何应用代码风格检查或任何质量检查方法?
快照测试是自动化回归测试的一种非常有效的方法。
如果你的组件是纯函数,使用 Redux(不一定是 React)或类似方法解耦状态可以很容易地实现这一点,那么随着组件的演变,你可以收集新的输入和生成的快照,并充满信心地重构,而不会破坏任何东西。
你也可以使用快照测试你的 reducer(它们是纯函数)。
宏可以是功能齐全的组件。只需添加类似 Pjax 的东西,并让服务器完全管理客户端状态。对于高度动态的交互,这种方法效率不高,但大量的应用程序可以利用这种方法,并避免很多实现复杂性。
当宏具有复杂的逻辑和多个状态时,它可能很有用。对于只输出渲染 HTML 的工具,你还想测试什么?
这更多是一个工具的问题。据我所知,目前还没有 Nunjucks 的代码覆盖率工具(尽管可以编写一个),也没有代码风格检查工具。就是这样。
我认为对于模板语言来说,代码覆盖率工具并不是真正需要的,除非你疯了,并且在模板中放入了太多逻辑。
但是,是的,我希望能看到代码风格检查工具,绝对希望。
我们实际上在我们的体育颁奖网站上使用了这个版本的模板。在前端使用起来非常容易。
我实际上不建议使用像 Nunjucks(或 Twig、Swig 或 Jinja2,它们基本上都是相同的东西)这样的高级模板语言,因为它们太强大了。
有人可能会注意到,不存在“太”强大的东西,因为你可以避免使用某些特性,但仅仅因为你可以做这些事情,很可能会导致你实际上去做这些事情。
那么,Nunjucks 的问题是什么?它允许你在模板中编写逻辑。想想看:与其用它需要的所有值填充模板,Nunjucks 允许你操作原始数据并提取这些值。这里的问题是,它更难以调试和测试。
宏绝对是我不推荐使用的。但它复杂的块、扩展、嵌入和导入系统也过于复杂。更不用说用自定义过滤器、函数和检查来扩展所有这些了。
在使用 Symfony 时我意识到了这一点,Symfony 默认使用 Twig。哎呀,在那个时候,你可以直接使用 PHP,PHP 实际上诞生于一个模板引擎!
当我切换到 Handlebars 后,在掌握了它之后,一切都变得更简单了(我仍然可以扩展该语言,尽管我这样做的情况很少)。
来自 GitHub 页面
我将使用 pugjs
酷!期待我们开始像 Symfony 一样使用注释配置我们的 JS 的那一天 :)
太棒了!我已经等了这么多年了!谢谢你,Chris!
感谢你的分享,我一直想开始使用 Nunjucks,但发现文档有点多。你的指南很棒!
我在寻找我的gulp 电子邮件模板工作流的模板语言时发现了 Nunjucks,直到我开始使用宏才意识到它有多强大。太有用了!
我很高兴它最终在 CSS-Tricks 上被推荐了。
Nunjucks 是一款极其强大的模板引擎,基于 Jinja2 经过实战检验的可靠理念。
事实上,在Carl Meyer 的领导下,它在某个时候成为了 Jinja2 的非常接近的移植版本,以至于相同的模板可以在 JS 和 Python 环境中使用。
即使它缺乏活跃的维护者,我们仍然找不到更好的替代方案。
与其他知名的模板语言相比,Nunjucks 乍一看可能显得平平无奇(除了相当优雅的语法和一些不错的特性,比如循环期间的解包),但你了解得越深入,就越难以回到其他模板语言。
它是少数几个异步模板语言之一。这有时可能是决定性的因素,尤其是在客户端使用模板并且需要处理大量异步任务时。
Nunjucks 非常适合在浏览器中运行,最初它被开发为客户端模板语言。
Nunjucks 可以预编译,这样你就可以在生产环境中放弃编译器,并运行几乎纯粹的 JavaScript 作为你的模板,并且占用空间很小。
但是,当然,对于现代的一些复杂任务,React 或 VueJS 会带来更多好处,并且减少很多麻烦。
嗯,事实上它们是函数,只是总是打印输出(如果有要打印的东西)。
这意味着,它们不仅可以用来打印一些 HTML,还可以用来调用一些副作用或更改数据(如果你足够疯狂)。这样做值得吗?这是另一个话题。
但是,显然,使用它们最直接的方式是将它们视为组件的化身。我们在 Kotsu 中积极使用这种方法,如内置的 Kotsu 组件所示。
事实上,我鼓励你探索Kotsu 模板,因为随着时间的推移,它们吸收了大量基于 Nunjucks 开发的经验。例如,你可以看到基于 Nunjucks 全局变量的 Gettext 国际化实现或无缝集成的 Markdown。
此外,为了获得灵感,值得查看我们在 Kotsu 中使用的Nunjucks 扩展。在复杂的项目中,其中一些是不可或缺的。
我希望我能展示我们基于 Nunjucks 的商业项目,但不幸的是,我只能在具有大型产品目录的静态生成的网站中展示它可能是什么样子。
我们的组件列表
这里展示了类别页面布局的简洁外观,它生成一个包含产品的非常复杂的表格,其中包含大量真实的数据驱动信息。
所有类别页面都简单地扩展它,并向上下文提供一些其他信息,以便页面使用我们想要显示的数据进行渲染(我们为此使用 Front Matter,但也可以不使用它)。
在顶部可以看到Nunjucks 全局函数,它以选择器的形式,将数据和复杂的计算(应该保持在模板之外)从 Node 应用程序传递到 Nunjucks 中。
这就是选择器(只是纯 JavaScript(CoffeeScript))的样子。
我希望这能给那些想知道 Nunjucks 到底能做什么以及它在实战项目中是什么样的人一些启发 :)
糟糕,发布表单删除了所有使用 markdown
![]()
指定的图片。我将再次发布,使用纯 URL 链接到截图。
我们的组件列表:https://vgy.me/eZXlWq.png
这里展示了类别页面布局的简洁外观,它生成一个包含产品的非常复杂的表格,其中包含大量真实的数据驱动信息:https://vgy.me/bh6N5r.png
所有类别页面都只是扩展它并提供一些额外的上下文信息,以使页面能够渲染我们想要显示的数据(我们为此使用 Front Matter,但也可以不用它):https://vgy.me/tIBxQZ.png
在顶部可以看到Nunjucks 全局函数,以选择器的形式,从 Node 应用程序内部的 Nunjucks 传递数据和复杂计算(应将其保留在模板之外):https://vgy.me/GKf05L.png
这就是选择器的样子,它们只是纯 JavaScript(CoffeeScript):https://vgy.me/uO8i5Z.png
我希望这能给那些想知道 Nunjucks 到底能做什么以及它在实战项目中是什么样的人一些启发 :)