Nue HTML 语法深度指南:用表达式、控制流与组件扩展标准 HTML

发布时间:2026/9/16 17:57:18
Nue HTML 语法深度指南:用表达式、控制流与组件扩展标准 HTML Nue HTML 语法深度指南用表达式、控制流与组件扩展标准 HTML【免费下载链接】nueFastest way to build modern websites项目地址: https://gitcode.com/GitHub_Trending/nu/nueNue 在标准 HTML 之上增加了一套声明式模板语法{ }表达式、:each循环、:if条件、组件、事件处理与生命周期钩子让静态标记与动态逻辑在同一份文档中自然共存。本文以 html-syntax.md 为骨架结合 nuedom 包的编译器与渲染器源码tokenizer.js、attributes.js、node.js以及测试用例render.test.js逐条讲解语法规则、底层实现与可运行的完整示例。读完本文你将能够独立编写服务端渲染的静态页面、客户端交互组件以及可复用的 UI 组件库。标准 HTML 就是 NueNue 的核心设计原则是“不发明新语言只扩展标准”。任何一份合法的 HTML 文档同时也就是一份合法的 Nue 文档!doctype html html head.../head body article button onclickhistory.go(-1)Back/button button popovertargetconfirm-deleteDelete/button /article dialog idconfirm-delete h2Delete user?/h2 /dialog /body /html这份代码不需要任何改动即可被 Nue 原样处理。这与 html-file-types.md 中描述的文档类型体系一致以!doctype html开头的文件按服务端静态页面处理构建时生成完整的 HTML 文档不带动态特性的内容无需任何特殊声明。从源码看Nue 的模板解析从 tokenizer.js 开始它按script、普通标签、{表达式、纯文本四种情况切分模板随后由 document.js 组装成文档对象再经 ast.js 生成 AST最终由 node.js 渲染为 DOM。整个过程对标准 HTML 零侵入。表达式{ }插值与转义控制用花括号在文本内容中插入动态值!-- text content -- span{ username }/span !-- JavaScript expressions -- p{ username.toUpperCase() }/p !-- unescaped HTML -- div{{ markdown(description) }}/div div{{ renderContent(article) }}/div !-- triple brackets also supported -- div{{{ userSubmittedContent }}}/div要点说明单花括号{ }求值后作为纯文本插入值会被转义text node 方式写入undefined、null会被清空NaN显示为N/A表达式抛错时渲染为[Error]——这些行为都有 render.test.js 中的测试用例支撑。双花括号{{ }}按 HTML 解析插入用于渲染 Markdown、富文本等已信任的 HTML 内容值为false时渲染为空见 render.test.js。三花括号{{{ }}}双花括号的等价写法。tokenizer 在解析时会先把{{{ foo }}}规范化为{{ foo }}见 tokenizer.js三种写法最终走同一条渲染路径。表达式的“上下文注入”原理表达式之所以能直接写username、count这类裸变量是因为编译器会做上下文注入。在 context.js 中addContext会把非保留字标识符改写为_.xxx_代表组件实例同时this会被改写为_所以组件脚本里this.count与模板里的{ count }指向同一数据字符串字面量、对象属性键、.或/之后的标识符跳过改写$event会被统一替换为$e作为事件对象占位符document、window、Math、JSON、console等 Web 平台保留字定义在 html5.js保持原样模板里可以直接调用。属性动态值、布尔属性与 class 处理属性使用与文本相同的表达式语法并在编译期被 parseAttributes 分类处理!-- attribute values -- time datetime{ date.toISOString() } !-- boolean attributes (falsy values remove the attribute) -- button disabled{ is_disabled } !-- class name interpolation -- div classgallery { type } !-- conditional classes -- div class[ is-active: isActive, has-error: hasError ] !-- combine static and dynamic -- div classgallery { type } [ is-active: isActive ]行为细节属性值表达式{ }内为任意 JavaScript 表达式编译后被包裹为(expr)形式参与字符串拼接见 parseExpression。布尔属性disabled、checked、selected、hidden、required、readonly等被列入 BOOLEAN 常量。真值时设置属性setAttribute(name, )假值时整个移除。渲染端在 setAttributes 中实现这一逻辑。条件 class[ 类名: 条件, ... ]语法会被编译为$concat({...})对象映射见 parseClassHelper$concat只保留条件为真的类名node.js。含连字符的类名自动加引号。类名安全校验渲染器会检查 class 中是否存在:、[、]等非法字符并提示 class 数量过多node.js避免调试条件 class 时留下难排查的脏数据。render.test.js 验证了 class 映射与函数条件[ active: isActive(), error: hasError() ]都能正确渲染。循环:each渲染列表:each指令把数组、对象条目渲染为重复元素!-- basic loop -- li :eachitem in items{ item.name }/li !-- with index -- li :eachitem, i in items { i }: { item.name } /li !-- destructuring -- li :each{ name, price } in products { name } costs { price } /li !-- loop objects -- li :each[key, val] in Object.entries(data) { key } { val } /li !-- template loops (no wrapper element) -- dl template :eachterm in glossary dt{ term.word }/dt dd{ term.definition }/dd /template /dl底层解析见 parseFor / parseForArgs支持in/of两种分隔符item, i形式中最后一个变量作为索引{ name, price }与[key, val]分别触发对象解构与 entries 模式。渲染循环在 renderLoop 中完成每次迭代克隆一份组件数据、写入循环变量含索引i再逐个渲染。template循环是免包装元素的关键——它不产生额外标签直接把内部节点平铺到父容器适合dl、table等不允许任意包裹元素的场景。条件:if/:else-if/:elsep :ifcount 100Too many!/p p :else-ifcount 10Getting there/p p :else{ count } items/p !-- combine with loops (condition evaluated first) -- ul :ifitems.length li :eachitem in items{ item }/li /ul p :elseNo items/p编译期相邻的条件元素会被合并为一个some分支组见 mergeConditionals保证:else永远挂在最近的:if兄弟上运行期 renderIf 从上到下求值命中第一个为真的分支渲染[Error]视为假。条件与循环组合时条件优先求值未命中则整块不渲染。组件可复用的 UI 单元组件即“带script自定义标签的文档片段”。定义、使用与传参!-- define a component -- product-card h3{ name }/h3 p{ price }/p script // default values this.name Untitled this.price 0 /script /product-card !-- use the component -- product-card/ !-- pass properties -- product-card :nameCoffee :price12/ !-- pass data variables -- product-card :nameproductName :priceproductPrice/ !-- shorthand (passes the name and price variables) -- product-card :name :price/ !-- regular attributes (no colon prefix) are rendered -- product-card idfeatured classhighlight/ !-- loop components -- product-card :eachitem in products :binditem/规则梳理属性传递:nameCoffee传字面量:nameproductName传变量:name简写等价于:namename源码见 attributes.js带冒号且无值时会自动补为同名变量。这些属性以is_data标记只进入组件数据、不渲染为 DOM 属性node.js。普通属性不带冒号的id、class等原样渲染在组件根元素上。:binditem把对象整体展开合并进组件数据getAttrData配合:each可把列表项整体注入组件。组件默认值script里this.xxx ...在组件实例化时执行node.js未传入的属性回落到默认值。组件根元素:is组件默认渲染为div包裹用:is换成任意标签!-- this component renders as figure -- image-card :isfigure img src{ url } figcaption{ caption }/figcaption /image-card从渲染路径看renderComponent 会优先采用:is指定的标签名作为根元素:is也用于把原生标签升级为交互组件如form :ismember-form见 html-file-types.md 的 DHTML 库示例。自定义标签含连字符或非 HTML5 标签由 ast.js 判定为is_custom触发组件渲染逻辑。事件处理客户端客户端专属——:on前缀为任意受支持事件绑定处理逻辑counter button :onclickcount{ count }/button script this.count 0 /script /counter !-- method handlers -- counter button :onclickincrement/button button :onclickdecrement-/button pCount: { count }/p pDouble: { double }/p script this.count 0 increment() { this.count } decrement() { if (this.count 0) this.count-- } // getter methods are supported get double() { return this.count * 2 } /script /counter !-- event object -- form :onsubmithandleSubmit script handleSubmit(e) { // forms automatically call e.preventDefault() console.log(Submitted:, e.target) } /script /form实现要点可绑定的事件集合定义在 EVENTS 常量click、submit、input、keydown、pointerdown、transitionend 等数十种。:onclick解析为click事件监听attributes.js。内联表达式:onclickcount直接写语句方法名:onclickincrement会自动补全为increment($e)调用同一段源码。脚本中的increment() { ... }方法写法会被编译器转换为this.increment function(...)见 convertFunctionsget double()则被转换为Object.defineProperty(this, double, { get() {...} })convertGetters因此 getter 也能直接在模板与事件中读取。表单事件submit事件会自动调用e.preventDefault()避免页面刷新node.js配合e.target可取回 FormData。每次事件触发后组件自动update()重渲染node.js数据变更即时反映到视图。生命周期方法客户端客户端专属——在关键时间点执行逻辑user-profile h2{ user.name }/h2 script // before mounting to DOM onmount() { console.log(About to mount) } // after mounting to DOM mounted() { console.log(Mounted!) } // before updating onupdate() { console.log(About to update) } // after updating updated() { console.log(Updated!) } /script /user-profile这些钩子在 node.js 中被显式调用onmount在插入 DOM 前触发、mounted在挂载后触发mount见 L47-L54onupdate在重渲染前触发若返回false可取消本次更新updated在更新完成后触发update见 L13-L25。组件作为子组件被渲染时同样会依次触发renderComponent。手动更新this.update(data)客户端专属——事件处理器之外的场景需要手动触发重渲染this.update(data)事件处理器触发后会自动更新视图但异步操作如fetch或外部事件如 WebSocket 消息结束后需要显式调用this.update()把新数据合并进组件并重渲染script async mounted() { const data await fetch(/api/user) const user await data.json() // Manual update required after async operations this.update({ user }) } /scriptupdate(values)先Object.assign合并数据再执行 onupdate → 渲染 → domdiff → updated 全流程node.js差异更新复用 diff.js 的 DOM 比对算法。动态挂载this.mount(name, target, data)客户端专属——在单页应用中按需把组件挂载到指定位置this.mount(name, target, data)参数说明参数类型说明namestring组件名targetDOM 元素或 CSS 选择器挂载目标字符串会被querySelector解析dataobject可选传给组件的数据my-app article/ script state.on(id, ({ id }) { this.mount(id ? user-details : user-list, article) }) /script /my-app实现上this.mount会在已加载的组件依赖opts.deps中按名字查找组件动态创建实例并挂载node.js。路由切换等场景的配套模式见 single-page-apps。共享脚本跨组件复用函数与常量顶层script中定义的函数、常量对所有组件可见!-- top-level script -- script // available to all components const TAX_RATE 0.08 function formatPrice(num) { return $ num.toFixed(2) } /script !-- use in components -- product-card p{ formatPrice(price) }/p pTax: { formatPrice(price * TAX_RATE) }/p script this.price 10 /script /product-card !-- another component definition -- shopping-cart p{ formatPrice(price) }/p script // ... /script /shopping-cart编译期 document.js 会把文档中的所有script内容合并到page.script再通过 parseNames 提取其中声明的变量与函数名作为后续表达式注入的“已知标识符”因此共享函数在任意组件模板中都能被直接调用。JavaScript 导入客户端客户端专属——导入外部模块并在模板中使用其导出script import { formatDistance } from ./utils.js import { store } from ./store.js /script !-- imported functions available in templates -- article time{ formatDistance(date) }/time pCart items: { store.cart.length }/p /article含import语句的文档会被 document.js 自动判定为动态 HTMLis_dhtml导入的名字同样进入parseNames的已知标识符列表模板可直接引用。这也是 html-file-types.md 中“自动检测 DHTML”的依据之一。Passthrough scripts服务端服务端专属——带type或src属性的script不参与 Nue 处理原样输出到客户端!-- these render as-is to the client -- script src/analytics.js/script script typemodule console.log(This runs on the client) /script区分逻辑在 tokenizer 中script标签连同属性整体被保留为原始 tokentokenizer.js而真正承载组件逻辑的裸script才会被提取执行。插槽slot/组合模式组件通过slot/声明内容插槽使用方填入的内容会被渲染到插槽位置!-- component with slot -- card div classcard slot/ /div /card !-- using the slot -- card h2This goes inside the card/h2 pSo does this/p /card !-- multiple instances -- card :eachpost in posts h2{ post.title }/h2 p{ post.excerpt }/p /card渲染端遇到slot节点时会回填父组件的子内容node.js。插槽与:each天然组合循环创建的每个card实例都会收到各自 posts 项的内容。这是 Nue 实现卡片、弹层、布局组件等组合型 UI 的基础配合 layout-system 可搭建完整的布局体系。CSS 变量设计令牌直通样式用--前缀属性把值写入元素的 CSS 变量无需内联样式!-- renders as style--spacing: 2rem -- section --spacing2rem style section { padding: var(--spacing); } /style /section !-- dynamic values -- div --columns{ columnCount }编译期--xxx属性被标记为is_var且剥掉前缀attributes.js渲染时收集所有 CSS 变量属性统一合并为一条style--name:value;...setAttributes。这样设计系统设计令牌可以保持单一事实来源不在标记里塞内联样式、不靠 class 堆叠覆盖主题、间距、栅格等令牌通过组件属性直接注入样式。与文档类型体系的衔接Nue 语法本身是“服务端与客户端同构”的{ }表达式、:each、:if、组件、插槽、CSS 变量在服务端构建与客户端运行时都可用事件处理、生命周期、this.update()、this.mount()、JS 导入则标记为客户端专属。页面的最终行为由文档类型决定!doctype html服务端渲染的静态页面!doctype dhtml客户端组件挂载后交互!html lib/!dhtml lib/!htmldhtml可复用组件库含同构组件。完整说明见 html-file-types.md。值得一提的是即使不写文档类型document.js 也会根据是否存在:on*事件处理器、import语句等自动检测 DHTML但显式声明!dhtml更为稳健清晰。小结Nue 的 HTML 语法是对标准 HTML 的“增量扩展”表达式与控制流解决数据渲染组件与插槽解决复用与组合事件与生命周期解决交互CSS 变量解决设计令牌传递。每条语法规则都能在 nuedom 的编译管线tokenize → parse → AST → render中找到对应实现并有 render.test.js 等测试用例背书。以此为起点你可以继续阅读 html-file-types.md 了解文档类型体系或通过 examples/nue-counter.html 看到一个最小的计数器组件示例。【免费下载链接】nueFastest way to build modern websites项目地址: https://gitcode.com/GitHub_Trending/nu/nue创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询