模板引擎的macro不是HTML原生指令,而是服务端或构建时由Jinja2等引擎处理的语法,最终输出纯HTML;macro参数为字符串、无法访问JS运行时状态;Jinja2的extends+block实现轻量指令式布局扩展;Web Components是唯一支持浏览器内真指令式扩展的方案。

模板引擎的 macro 不是 HTML 原生指令
HTML 本身没有 macro、extends 或 include 这类语法——所有这些都属于模板引擎(如 Jinja2、Twig、Nunjucks、Handlebars)在服务端或构建时注入的能力。浏览器加载的最终 HTML 里,根本不存在 {% extends %} 或 {{ input_field() }} 这样的标记,它们早已被渲染成纯 HTML。
常见错误现象:把 {% macro button %} 直接丢进 .html 文件用浏览器打开,结果原样输出、不解析、无效果。
- 必须走模板引擎流程:写好 .jinja / .twig 文件 → 用对应后端(Flask/Django/PHP)或构建工具(webpack + html-webpack-plugin)编译 → 输出静态 HTML
- macro 的参数传递是字符串插值,不是 JS 函数调用:传
required=true是布尔字面量,但引擎实际接收的是字符串"true",需用{% if required == "true" %}判断,而非if required - macro 无法访问运行时 JS 状态:不能在 macro 里读
window.currentUser或响应点击事件,它只处理初始数据快照
Jinja2 的 extends + block 是最轻量的“指令式布局扩展”
真正接近“指令式扩展”的实践,是 Jinja2 的 {% extends %} 配合 {% block %}。它不依赖 JS,不增加运行时开销,且逻辑清晰可维护。
使用场景:多页共用 header/nav/footer,但每页主体内容不同;需要统一 SEO meta 标签、CSS/JS 加载顺序、无障碍属性。
立即学习“前端免费学习笔记(深入)”;
-
base.html中定义{% block content %}{% endblock %}和{% block scripts %}{% endblock %},子模板只覆盖对应块,其余结构自动继承 - 子模板不能漏写
{% extends "base.html" %}—— 缺失时不会报错,而是变成普通 HTML,失去继承关系,样式/脚本错乱 - 路径必须相对模板根目录:如果
base.html在templates/layout/base.html,子模板里要写{% extends "layout/base.html" %},不是"./base.html"或"base.html" - 多个 extends 层级可行,但超过两层(base → section → page)会显著增加调试难度,推荐扁平化:一个 base + 若干 layout 变体(如
admin-base.html)
Web Components 是唯一能在浏览器里“真指令式扩展”的方案
如果你需要在纯静态 HTML 文件中,不经过服务端渲染、不依赖构建工具,就实现类似 <app-header></app-header> 这样的可复用指令式标签,只有 Web Components 符合要求。
关键限制:必须用 customElements.define() 注册,且标签名含连字符(如 my-card),否则浏览器直接忽略。
- Shadow DOM 默认隔离样式,
<style>写在 template 里才生效,全局 CSS 不会穿透进去 - 属性变更不会自动触发重渲染:改
el.title = "New"不会更新 UI,必须监听attributeChangedCallback并手动同步 - 表单集成要额外处理:想让
<my-input>被form.elements收集,必须启用formAssociated: true并调用this.internals.setFormValue(),漏掉就等于“假输入框” - IE 和旧 Edge 完全不支持,必须引入
@webcomponents/custom-elementspolyfill,且必须在任何customElements.define()之前加载
别混淆“模板复用”和“运行时组件”
macro / extends 解决的是开发期结构复用,Web Components 解决的是运行时行为封装——两者目标不同,不能互相替代,也很难混合使用。
最容易被忽略的点:你在 Flask 模板里写了 {% macro card(title) %}...{% endmacro %},又在同一页面里用了 <my-card title="{{ title }}">,这两套系统完全独立。前者生成静态 HTML 字符串,后者靠 JS 在浏览器里实例化并接管生命周期。混用时,容易误以为 macro 参数能驱动自定义元素属性,其实不能。
真正需要指令式扩展时,先问清楚:这个“扩展”是在页面生成前决定结构(选模板引擎),还是在页面加载后响应交互(选 Web Components)。选错方向,后续所有封装都会卡在边界上。



















