Skip to main content

nmbl

Write and edit NMBL templates correctly — .nmbl files, <template lang="nmbl"> blocks in Vue/Svelte/Astro components, and nmbl`…` tagged literals in React/Solid/Qwik. Use whenever generating or modifying NMBL syntax. Covers the syntax, per-framework control flow, the whitespace pitfalls that cause compile errors, and how to verify output.

跳到安装

来源信息

仓库
theoephraim/nmbl
最近来源活动
2026年6月17日 23:59
检测到的 SKILL.md 语言
英语
星标
3
分支
1

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
nmbl
description
Write and edit NMBL templates correctly — .nmbl files, <template lang="nmbl"> blocks in Vue/Svelte/Astro components, and nmbl`…` tagged literals in React/Solid/Qwik. Use whenever generating or modifying NMBL syntax. Covers the syntax, per-framework control flow, the whitespace pitfalls that cause compile errors, and how to verify output.
# Writing NMBL NMBL is an indentation-based shorthand for HTML (think Pug, minus the template engine) that compiles to your framework's native template syntax. Docs: https://nmbl.tools ## Core syntax ``` div#app.card.dark → <div id="app" class="card dark"> .box → <div class="box"> (implicit div) a.btn(href="/x" target="_blank") Go → <a class="btn" href="/x" target="_blank">Go</a> li > a(href="/") Home → <li><a href="/">Home</a></li> (block expansion) input(type="email" required) → boolean attrs supported; void elements never get children MyComponent(:prop="v") → PascalCase = component, kept as-is p Inline text after the tag head p | explicit text line (pipe) | use pipes for multi-line text <!DOCTYPE html> → lines starting with < pass through verbatim ``` **Leave SVG as raw HTML.** Don't translate `<svg>…</svg>` blocks (icons, logos) into NMBL nodes — keep them verbatim under their parent (every line starts with `<`, so they pass through). SVG is generated/pasted markup, not authored structure; converting it gains nothing and churns diffs. The same goes for any pasted markup blob you wouldn't hand-edit. One constraint: all lines of the raw block must sit at the SAME indentation — a raw line indented deeper than the previous one is a parse error, so flatten the block's internal indentation: ``` .icon <svg viewBox="0 0 24 24"> <path d="…"/> </svg> ``` - **Nesting is indentation** (2 spaces). Children go on indented lines below the parent. - **Attributes**: whitespace-separated inside parens; parens may span multiple lines; values: `"str"`, `'str'`, `` `template ${x}` ``, bare (`colspan=2`), or raw expression `onClick={() => f(x)}`. - **Bound shorthand**: `:name` alone expands to `:name="name"`. ## Comments (two tiers) ``` // stripped from output (dev notes — safe for TODOs) //! rendered as an HTML comment in output button( type="submit" // disabled ← comment out individual attributes /* aria-label="x" */ ) Save ``` ## Content blocks (raw bodies) A **glued** trailing `:` or `:mode` makes all more-indented lines raw text: ``` script: const x = 1 < 2; // not parsed as NMBL style: .a { color: red } article:md ## markdown (transformed only if a filter is registered) ``` ### Markdown sections (`:md`) `:md` works out of the box in EVERY integration — prefer it over stacks of inline `<h3>`/`<p>`/`<code>` for prose-heavy sections: ``` .prose:md ### A heading A paragraph with [links](/x), `code`, and **bold**. Blank lines separate paragraphs. Raw inline HTML works too (<a target="_blank" …>). ``` - **Astro** (`@nmbl-lang/astro`): renders through the project's own Astro markdown pipeline — same remark/rehype plugins and syntax highlighting as `.md` files. - **Vue / Svelte / `.nmbl` files / `nmbl`…`` JSX templates**: a default CommonMark+GFM renderer (`@nmbl-lang/core/markdown`) is built in. Override with `filters: { md }` (vite plugin options accept async filters; svelte via `compiler.filters`). - The rendered HTML is spliced into the host template, so `{expr}` / `{{ expr }}` interpolation and `<Component />` tags still work inside the prose (MDX-like); braces inside code spans/fences are auto-escaped so they never parse as host expressions. - **JSX targets**: a content-mode body compiles to `dangerouslySetInnerHTML` on the host element (raw HTML is not JSX). A bare `:md` block with no host element is a compile error in jsx — attach it to an element (`div:md`). - Body is dedented relative to the block — relative indentation (nested lists) is preserved. - Core `compile()` takes sync `filters`; the default md filter is importable: `import { mdFilter } from '@nmbl-lang/core/markdown'`. ## Control flow — one notation, host-native output ``` @if(cond) @elseif(other) @else @each(item of items :key="item.id") @each(item, i of items :key="item.id") ← optional index ``` - The `@each` expression also accepts Svelte's form (`items as item, i (item.id)`); both compile to whatever the host needs. Prefer the `of` form. - `@if` conditions are plain host-JS, passed through verbatim. | Target (framework option) | Output | |---|---| | `svelte` | `{#if}…{:else}…{/if}`, `{#each items as item, i (key)}`; also `@await`/`@key`/`@snippet`, `{@html x}` | | `vue` | `<template v-if="…">…</template>`, `<template v-for="… of …" :key="…">` | | `astro` / `jsx` | `{cond ? (…) : (…)}`, `{items.map((item, i) => (…))}`; jsx puts `key={…}` on the loop's single root element | | `html` | no @-blocks (compile error) | **Errors, not silent wrongness**: `@await`/`@key`/`@snippet` outside Svelte, `{@html}` in Vue (use a `v-html` attribute), multi-root `@each` body with `:key` in jsx — all hard compile errors with hints. Trust the error messages. ## Interpolation is the HOST's syntax `{{ msg }}` in Vue, `{expr}` in Svelte/Astro, and inside `nmbl\`…\`` JSX literals either `${hole}` holes or plain `{expr}` text. NMBL never parses these — it passes them through. ## Pitfalls (these cause real compile errors) 1. **Attribute parens must be GLUED to the tag head**: `a(href="/")` ✓ — `a (href="/")` is the text `(href="/")`. 2. **Selectors must be glued**: `div.card` ✓ — `div .card` is text. 3. **A glued trailing `:` at end of line opens a content block.** `note:` alone on a line captures its children as raw text. Inline text ending in a colon is fine when there's a space earlier on the line (`label Size:` is text), but a single word + `:` is always a content-block introducer. 4. **Text lines need `|`** (or must start with `<` for raw HTML). A bare text line produces a diagnostic. 5. **Block expansion `>` needs surrounding spaces**: `li > a`. A `>` later in inline text passes through as-is (only a `>` directly after the tag head/attrs is block expansion — and only when a valid element follows). 6. **Don't mix tabs and spaces**; pick 2-space indentation. 7. In Vue files, plain `v-if`/`v-for`/`@click.stop`/`#slot` attributes also pass straight through — @-blocks are optional sugar. ## Verify your output NMBL fails loudly with exact source positions — compile to check, don't eyeball: ```js // in a project depending on @nmbl-lang/core import { compile, decompile } from '@nmbl-lang/core'; const { html, errors } = compile(src, { framework: 'vue' }); // 'svelte' | 'astro' | 'jsx' | 'html' // errors: [{ message, span: { start: { line, column } } }] ``` Or just run the project's build/dev — every integration surfaces NMBL errors at exact positions. When converting existing HTML, don't hand-translate: `decompile(html)` produces idiomatic NMBL.
在 GitHub 查看