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

发布时间:2026/11/11 22:35:43

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),仅供参考
延伸阅读

更多相关文章

2026/11/8 2:29:11

Qt集成BP神经网络:数据流组织、多线程训练与误差曲线绘制

简介:基于QT框架的BP神经网络实现,面向希望在图形界面中学习、演示反向传播算法的开发者,适合初次接触QT与机器学习结合的C用户。资源包共6个文件,以C源码为主:NeuralNet.cpp与main.cpp实现网络训练与界面逻辑&#xf…

2026/11/11 22:33:02

证书过期了不知道怎么复审延期?河北省电工证培训在哪考最靠谱

证书过期了不知道怎么复审延期?河北省电工证培训在哪考最靠谱 证书过期了,不知道怎么复审延期?很多人一到年底就慌了神,特别是特种作业操作证,像电工证这类,一旦过期不及时处理,就等于失去了从业资格。你是不是也遇到过这种情况?别急,这篇内容就专门…

2026/11/11 22:33:02

义乌电工证好考吗?学历不高也能考,全国通用怎么操作?

义乌电工证好考吗?学历不高也能考,全国通用怎么操作? 你是不是也担心学历不高,怕考不了义乌电工证?其实,电工证考试对学历要求并不高,只要你是年满18岁、身体健康,具备初中及以上学历,就能报考。而且, 义乌电工证是全国通用的…

2026/11/11 22:33:02

邢台哪里可考电工证的继续教育学时要求别忽视,上岗必备怎么拿?

邢台哪里可考电工证的继续教育学时要求别忽视,上岗必备怎么拿? 工地太忙根本没时间复习考试,电工证的继续教育又不是可有可无的事。尤其是在邢台,很多工人在一线跑项目,时间一长,证书的学时要求、复审流程这些细节反而被忽视。其实,这不仅影响你能不能…

2026/11/11 22:28:01

中牟考电工证哪里报名怎么报名?工地太忙根本没时间复习考试

中牟考电工证哪里报名怎么报名?工地太忙根本没时间复习考试 工地太忙根本没时间复习考试,这种感觉我太懂了。在焦作转岗做电工那会儿,每天扛着工具跑工地,晚上还得熬夜看图纸,哪还有精力花时间复习考试?直到那次因为没考过电工证被项目负责人训了一顿,…

2026/11/11 12:42:59

电工证丢失还能补吗?千万别踩坑,丢了证等于失业

电工证丢失还能补吗?千万别踩坑,丢了证等于失业 你正在找工作,但手里的电工证突然丢了,这下怎么办?别急,先别慌, 电工证丢失还能补吗 ?答案是肯定的,但 千万别踩坑 ,补办流程如果不了解,可能会耽误你上岗、甚至影响到你的收入。…

2026/11/11 0:02:26

考低压电工证通过率考试真题与备考技巧

低压电工证通过率高不高?学历不高也能考,值不值得考? 你是不是总听说电工证能帮你找高薪工作,但又担心自己学历不够,考不上?其实,低压电工证考试门槛并没有你想象中那么高,关键是方法对不对。这篇文章就告诉你, 考低压电工证通过率到底有多高 ,…

2026/11/11 0:02:26

低压电工证学时千万别踩坑,濮阳毕业生怎么避坑报名

低压电工证学时千万别踩坑,濮阳毕业生怎么避坑报名 你是不是也和我一样,刚毕业就听说低压电工证学时必须达标才能上岗?可一打听,发现报名机构五花八门,价格参差不齐, 不知道去哪报名怕被中介坑…

2026/11/11 20:27:01

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/10 10:29:37

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑