
Wekan 中 Jade 模板引擎完整语法指南从命令行到 meteor-jade-loader 源码级解析【免费下载链接】wekanThe Open Source kanban, built with Meteor. GitHub issues/PRs are only for FLOSS Developers, not for support, support is at https://wekan.fi/commercial-support/ . PR source translation to imports/i18n/data/en.i18n.json, other translations at https://app.transifex.com/wekan/wekan项目地址: https://gitcode.com/GitHub_Trending/we/wekan导读Jade后更名为 Pug是一种受 Haml 影响、用 JavaScript 实现的、对空白敏感的 HTML 模板语言它以极简的缩进语法取代了冗长的标签书写。本文以仓库内捆绑的 Jade 官方语法文档 jade.md 为骨架结合 Wekan 项目实际使用的meteor-jade-loaderRspack/webpack 加载器源码系统讲解 Jade 的 CLI 用法、全部核心语法标签、属性、插值、代码、条件、循环、混入并揭示.jade文件在 Wekan 中是如何被编译成 Blaze 模板注册代码的。读完本文你将既能独立编写 Jade 模板也能理解 Wekan 前端模板的完整编译链路。一、Jade 是什么Jade 是为 Node.js 设计的高性能模板引擎其语法受 Haml 影响通过缩进indentation表达嵌套结构自动为你补全闭合标签并支持在模板中内嵌任意 JavaScript 表达式。在 npm-packages/meteor-jade-loader 中捆绑的是由 mquandalle 分支维护的 jade 1.3.0 版本见 jade-compiler.js 的注释说明它被进一步改造成可以编译输出 Spacebars/Blaze 模板代码这正是 Wekan 前端client/components下 130 余个.jade文件所使用的引擎。一个最简示例即可感受其风格doctype html html(langen) head title pageTitle body h1 Jade - node template engine #container.col if youAreUsingJade p You are amazing else p Get on it!它等价于一段结构相同的 HTML但书写量大幅减少且天然保证标签配对正确。二、命令行用法CLI原文档给出了完整的一行式命令语法jade [-h|--help] [-v|--version] [-o|--obj STR] [-O|--out DIR] [-p|--path PATH] [-P|--pretty] [-c|--client] [-D|--no-debug]各参数含义如下选项作用-h, --help显示帮助信息-v, --version显示版本号-o, --obj STR传入 JSON 字符串作为模板渲染数据对象-O, --out DIR指定输出目录0.31.0 起推荐用-O-p, --path PATH设置模板文件的基准路径用于extends/include解析-P, --pretty美化输出 HTML 缩进格式-c, --client编译为客户端可用的 JavaScript 函数需要运行时 runtime.js-D, --no-debug编译客户端模板时去掉调试插桩输出更轻量需配合--client官方文档给出了若干可直接运行的示例# 翻译整个 templates 目录下的所有 .jade 文件 $ jade templates # 生成 {foo,bar}.html $ jade {foo,bar}.jade # 通过标准输入输出流stdio转换 $ jade my.jade my.html # 管道方式回显一行 Jade 直接得到 HTML $ echo h1 Jade! | jade # 同时编译 foo、bar 两个目录输出到 /tmp $ jade foo bar --out /tmp # 编译为客户端模板且不做调试插桩产物轻量 # 项目运行时需要引入 runtime.js $ jade --client --no-debug my.jade值得一提的是文档中提示自 0.31.0 起script/style的隐式纯文本行为被废弃需要在标签后显式加.同时输出目录选项从-o调整为-O这两点与本仓库捆绑的 1.3.0 版本行为一致。三、标签与块Tags Blocks标签通过空白缩进进行嵌套闭合标签由引擎代劳这些缩进结构称为块blocksul li a Foo li a Bar同一个块内也可以并列多个兄弟标签ul li a Foo a Bar a Baz这等价于三个li下各有一个链接。四、自闭合标签Self-closing Tagsmeta、link等标签默认即视为自闭合。如需显式自闭合任意标签在标签名或标签名加属性后追加/foo/ foo(barbaz)/编译结果为foo/ foo barbaz/在 vendor/jade/lib/self-closing.js 中维护着默认自闭合标签列表编译阶段会依据该表自动处理。五、属性Attributes属性书写与 HTML 类似但属性值就是普通 JavaScript因此三元运算、逻辑表达式都可以直接使用a(hrefgoogle.com) Google a(classbutton, hrefgoogle.com) Google body(classuser.authenticated ? authenticated : anonymous) a(hrefuser.website || http://google.com)属性支持多行书写带逗号、不带逗号、以及任性的空白排版均可input(typecheckbox, nameagreement, checked) input(typecheckbox nameagreement checked) input( typecheckbox nameagreement checked)布尔属性布尔属性接受true/false省略值时默认为trueinput(typecheckbox, checked) // input typecheckbox checkedchecked / input(typecheckbox, checkeduser.agreed) // 当 user.agreed 为 true 时同样输出 checkedchecked类属性Class attributesclass属性可以接收数组便于由 JS 函数动态生成- classes [foo, bar, baz] a(classclasses) // a classfoo bar baz/a类字面量Class literal用.CLASSNAME语法声明类默认生成div.button // div classbutton/div .large.button // div classlarge button/div h1.title My Title // h1 classtitleMy Title/h1ID 字面量Id literal与类字面量对应用#ID语法声明 id#user-1 // div iduser-1/div ul#menu li: a(href/home) Home li: a(href/store) Store li: a(href/contact) Contact类、id、属性还可以任意组合以下写法完全等价a.button#contact(style: color: red) Contact a.button(style: color: red)#contact Contact a(style: color: red).button#contact Contact块展开Block expansion标签后跟一个尾随冒号:即可内联注入一个块ul li: a Foo li: a Bar li: a Baz这与上面的ul#menu例子结合使用可以写出非常紧凑的导航菜单。六、文本Text普通文本直接跟在标签后面p Welcome to my site // pWelcome to my site/p管道文本Pipe text管道符|充当大段文本的文字边距适合多行文本p | This is a large | body of text for | this tag. | | Nothing too | exciting.输出pThis is a large body of text for this tag. Nothing too exciting. /p管道文本中还可以继续混入普通 Jade 标签p | Click to visit a(hrefhttp://google.com) Google | if you want.纯文本标签Text only tags在标签后加尾随.表示块内全部是纯文本、不含标签p. This is a large body of text for this tag. Nothing too exciting.script、textarea、style默认就是纯文本标签无需加.script if (foo) { bar(); } style body { padding: 50px; font: 14px Helvetica; }模板 script 标签当需要在页面里用script嵌入客户端模板片段时只需给script一个任意type属性如text/x-template内部仍可正常使用 Jadescript(typetext/template) h1 Look! p Jade still works in here!七、插值Interpolation普通文本与管道文本都支持插值分为转义与非转义两种形式p Welcome #{user.name} // HTML 会被转义防止 XSS p Welcome !{user.name} // 不转义 HTML只应使用可信字符串内联 HTML也可以在 Jade 中直接嵌入一小段 HTMLp Welcome em#{user.name}/em八、代码Code缓冲输出与!行首或标签后的会将表达式结果缓冲到输出并转义其中的 HTMLp user.description!为不转义版本需谨慎防范 XSSp! user.description非缓冲代码--用于执行 JavaScript 而不输出结果适合定义变量、写条件等- var user { description: foo bar baz } #user - if (user.description) { h2 Description p.description user.description - }编译后的块被包裹在匿名函数中因此也可以省略大括号- var user { description: foo bar baz } #user - if (user.description) h2 Description p.description user.description甚至可以使用.forEach()等任意 JS 手段- users.forEach(function(user){ .user h2 user.name p User #{user.name} is #{user.age} years old - })赋值AssignmentJade 的一等赋值非常简单使用运算符即会自动var声明- var user { name: tobi } user { name: tobi } // 与上一行等价九、条件Conditionals一等条件语法允许省略括号也可以省略行首的-其余仍是普通 JavaScriptuser { description: foo bar baz } #user if user.description h2 Description p.description user.descriptionJade 还提供否定形式unless以下两种写法等价- if (!(user.isAnonymous)) p Youre logged in as #{user.name} unless user.isAnonymous p Youre logged in as #{user.name}十、迭代IterationJade 提供更声明式的for循环结构别名eachfor user in users .user h2 user.name p user #{user.name} is #{user.age} year old each user in users .user h2 user.name可以同时取得索引for user, i in users .user(classuser-#{i}) h2 user.name本质上仍是 JavaScript直接内嵌数组亦可ul#letters for letter in [a, b, c] li letter十一、混入Mixins混入用于抽象出大段可复用的 Jade 片段调用时以前缀。最简单的无参混入mixin hello p Hello hello带参数的混入会被编译成 JavaScript 函数mixin hello(user) p Hello #{user} hello(Tobi) // pHello Tobi/p混入可以接收块传入块时其内容成为隐式的block参数mixin article(title) .article .article-wrapper h1 title if block block else p No content provided article(Hello world) article(Hello world) p This is my p Amazing article输出两段结构相同的文章卡片第二段带内容块div classarticle div classarticle-wrapper h1Hello world/h1 pNo content provided/p /div /div div classarticle div classarticle-wrapper h1Hello world/h1 pThis is my/p pAmazing article/p /div /div混入还能像标签一样接收属性属性会成为隐式的attributes参数可像普通对象属性一样访问mixin centered .centered(classattributes.class) block centered.bold Hello world centered.red p This is my p Amazing article输出div classcentered boldHello world/div div classcentered red pThis is my/p pAmazing article/p /div若直接把attributes传给标签则传入的所有属性都会被使用mixin link a.menu(attributes) block link.highlight(href#top) Top link#sec1.plain(href#section1) Section 1 link#sec2.plain(href#section2) Section 2输出a href#top classhighlight menuTop/a a idsec1 href#section1 classplain menuSection 1/a a idsec2 href#section2 classplain menuSection 2/a带参数与属性的混入调用参数需紧跟混入名属性放后面的括号中mixin list(arr) if block .title block ul(attributes) each item in arr li item list([foo, bar, baz])(idmyList, classbold)输出ul idmyList classbold lifoo/li libar/li libaz/li /ul十二、从文档语法到 Wekan 源码meteor-jade-loader 的编译链路上面是原文档的全部语法内容。在 Wekan 仓库中这些语法并不是用 jade 原版 CLI 处理的而是经由一个定制加载器完成理解它能让你把怎么写模板与模板如何变成可运行代码打通。加载器入口rspack.config.js 中为所有.jade文件注册了加载器{ test: /\.jade$/, use: [path.resolve(__dirname, npm-packages/meteor-jade-loader)], },加载器本体 index.js 是一个标准的 Rspack/webpack loader接收.jade源码返回一段注册 Blaze 模板的 JavaScript。它按文件名区分两种模式文件名以.tpl.jade结尾 →模板模式整个文件内容即一个模板的 AST其余.jade→文件模式文件可同时包含head、body与多个template根节点。文件模式生成的 JS 形如var Template Package[templating-runtime].Template; var HTML Package.htmljs.HTML; var Blaze Package.blaze.Blaze; var Spacebars Package.spacebars.Spacebars; var Meteor Package.meteor.Meteor; Template.body.addContent(renderFunc); Meteor.startup(Template.body.renderToDocument); Template.__checkName(templateName); Template[templateName] new Template(Template.templateName, renderFunc);其中generateTemplateJSindex.js负责模板注册generateBodyJSindex.js负责body内容的挂载。若编译出错加载器会通过this.emitError把错误透传给构建工具并返回注释占位模块避免整个构建崩溃index.js。编译器内部Lexer / Parser / Transpiler核心编译逻辑在 jade-compiler.js它改自 Meteor 生态的mquandalle:jade-compiler包由三层构成自定义 Lexerjade-compiler.js在 jade 原版词法分析器基础上子类化新增两类 token内建组件if/unless/else if/else/with/each用户组件组件名(参数)把 Jade 混入语法对接到 Blaze 的{{ component}}/{{#component}}。自定义 Parserjade-compiler.js覆写parseMixin特别处理markdown混入开启pipeless模式把后续块按原始文本解析从而实现 Markdown 文本的传递。两个 Transpiler负责把 Jade AST 翻译成 Spacebars ASTFileCompilerjade-compiler.js识别head/body/template(name...)根节点并主动拒绝doctype提示 Meteor sets the doctype for you、重复定义同名模板、head带属性等非法用法TemplateCompilerjade-compiler.js递归访问节点textarea/style视为纯文本节点、script被转为attrs.value、if/else if/else链被重组成嵌套的else块、#{expr}插值被替换为{{expr}}而!{expr}被替换为{{{expr}}}见 parseText。值得注意的是文档中介绍的传统 Jade 特性在 meteor-jade 语境下是被明确禁用的visitFilter与visitWhen分别对过滤器filter和 case 语句抛出 not supported in meteor-jade 错误jade-compiler.js。Meteor 包的沙箱化引导meteor-packages.js 用 Node 的vm模块把htmljs、html-tools、blaze-tools、spacebars-compiler四个 Meteor 包加载进沙箱并预先注入Package.meteor、Package.underscore._、Package.tracker.Tracker等全局其中_以原生 JS 实现each/map/indexOf/extend之后导出HTML、HTMLTools、BlazeTools、SpacebarsCompiler供编译器使用meteor-packages.js。加载结果在进程内缓存保证每个构建只执行一次。真实示例popup.tpl.jadeclient/components/main/popup.tpl.jade 是模板模式的典型代表它以.tpl.jade结尾整个文件编译为一个名为popup的 Blaze 模板。文件中用到了本文介绍的大量语法.pop-over.js-pop-over( class{{#unless title}}miniprofile{{/unless}} classthemeColorClass contenteditable="false">【免费下载链接】wekanThe Open Source kanban, built with Meteor. GitHub issues/PRs are only for FLOSS Developers, not for support, support is at https://wekan.fi/commercial-support/ . PR source translation to imports/i18n/data/en.i18n.json, other translations at https://app.transifex.com/wekan/wekan项目地址: https://gitcode.com/GitHub_Trending/we/wekan创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考