
Product
Announcing Socket Fix 2.0
Socket Fix 2.0 brings targeted CVE remediation, smarter upgrade planning, and broader ecosystem support to help developers get to zero alerts.
@zavx0z/html-parser
Advanced tools
HTML template parser для MetaFor. Извлекает структуру, пути к данным и выражения из tagged template literals без их выполнения.
@zavx0z/html-parser
читает исходник вашей render‑функции (через toString
), забирает блок html
`...` и статически парсит его. Результат — нормализованное дерево с:
${...}
в тексте и атрибутахarray.map(...)
с корректным скоупом [item]?:
, &&
/ ||
), в т.ч. ${cond && "disabled"}
class
и др.), булевы, style/объект, события (on*
)meta-*
(в т.ч. динамические meta-${...}
)Парсер вычисляет пути к данным (/context/name
, /core/list
, [item]/id
, ../[item]/id
) и формирует унифицированные выражения (например, "Hello ${[0]}!"
), где индексы ссылаются на переменные из data
.
Ничего не исполняется. Работает в Node, Bun, браузерах и воркерах.
bun i @zavx0z/html-parser
# или
npm i @zavx0z/html-parser
# или
pnpm add @zavx0z/html-parser
# или
yarn add @zavx0z/html-parser
Требуется:
typescript@^5
. ESM пакет.
import { parse, type Node } from "@zavx0z/html-parser"
const tree: Node[] = parse(
({ html, context, core }) => html`
<div class=${context.userStatus}>
<h1>Привет ${context.userName}!</h1>
${core.items.map(
(g) => html`
<div class="card ${g.active && "active"}">
${g.title ? html`<h2>${g.title}</h2>` : html`<span>Без названия</span>`}
</div>
`
)}
<meta-list
onClick=${core.onClick}
style=${{ color: context.color, opacity: core.opacity }}
context=${context.userData}
core=${core.widgetConfig} />
</div>
`
)
// Node — это discriminated union:
// - Element: { type: "el", tag, child?, string?, array?, boolean?, style?, event?, core?, context? }
// - Text: { type: "text", value? | data?: string | string[], expr?: string }
// - Map: { type: "map", data: string, child: Node[] }
// - Cond: { type: "cond", data: string | string[], expr?: string, child: [Node, Node] }
// - Logical: { type: "log", data: string | string[], expr?: string, child: Node[] }
// - Meta: { type: "meta", tag: string | { data, expr }, child?, ...attrs }
Атрибуты группируются по смыслу:
string
— обычные атрибуты; значение строка или { data, expr }
array
— списковые (class
, rel
и т.п.); элементы — { value }
или { data, expr }
boolean
— булевые флаги: true/false
или { data, expr }
style
— объектный вид из style=${{ ... }}
: { ключ: строка | { data, expr } }
event
— on*
‑обработчики с разобранными { data, expr, upd? }
core
/ context
— объектные meta‑атрибуты; в значениях { data?, expr?, upd? }
Пути в data
:
/context/...
, /core/...
— абсолютные привязки[item]
— текущий элемент map
; ../
для выхода из вложенных map
Выражения унифицируются через плейсхолдеры ${[i]}
по порядку переменных.
// Парсит render-функцию в нормализованное дерево
function parse<C extends Context, I extends Core, S extends State>(
render: (params: {
html(strings: TemplateStringsArray, ...values: any[]): string
core: I
context: C
state: S
update(context: Partial<C>): void
}) => void
): Node[]
const nodes = parse(({ html, context }) => html` <div class="container">Привет, ${context.userName}!</div> `)
Результат:
[
{
"type": "el",
"tag": "div",
"string": { "class": "container" },
"child": [
{
"type": "text",
"data": "/context/userName",
"expr": "Привет, ${[0]}!"
}
]
}
]
const nodes = parse(
({ html, context }) => html`
<div>
${context.isLoggedIn
? html`<span>Добро пожаловать, ${context.userName}!</span>`
: html`<a href="/login">Войти</a>`}
</div>
`
)
Результат:
[
{
"tag": "div",
"type": "el",
"child": [
{
"type": "cond",
"data": "/context/isLoggedIn",
"child": [
{
"tag": "span",
"type": "el",
"child": [
{
"type": "text",
"data": "/context/userName",
"expr": "Добро пожаловать, ${[0]}!"
}
]
},
{
"tag": "a",
"type": "el",
"string": { "href": "/login" },
"child": [
{
"type": "text",
"value": "Войти"
}
]
}
]
}
]
}
]
const nodes = parse(
({ html, context, core }) => html`
<ul>
${core.postTitles.map((title) => html`<li>${title}</li>`)}
</ul>
`
)
Результат:
[
{
"tag": "ul",
"type": "el",
"child": [
{
"type": "map",
"data": "/core/postTitles",
"child": [
{
"tag": "li",
"type": "el",
"child": [
{
"type": "text",
"data": "[item]"
}
]
}
]
}
]
}
]
const nodes = parse(
({ html, context, core }) => html`
<div>
${context.hasNotifications &&
html`
<div class="notifications">${core.notificationMessages.map((message) => html`<div>${message}</div>`)}</div>
`}
</div>
`
)
Результат:
[
{
"tag": "div",
"type": "el",
"child": [
{
"type": "log",
"data": "/context/hasNotifications",
"child": [
{
"tag": "div",
"type": "el",
"string": { "class": "notifications" },
"child": [
{
"type": "map",
"data": "/core/notificationMessages",
"child": [
{
"tag": "div",
"type": "el",
"child": [
{
"type": "text",
"data": "[item]"
}
]
}
]
}
]
}
]
}
]
}
]
const nodes = parse(
({ html, context, core }) => html`
<my-component core=${core.widgetConfig} context=${core.userData} class="custom">
<p>Содержимое компонента</p>
</my-component>
`
)
Результат:
[
{
"tag": "my-component",
"type": "meta",
"core": {
"data": "/core/widgetConfig"
},
"context": {
"data": "/core/userData"
},
"string": { "class": "custom" },
"child": [
{
"tag": "p",
"type": "el",
"child": [
{
"type": "text",
"value": "Содержимое компонента"
}
]
}
]
}
]
const nodes = parse(
({ html, context, core }) => html`
<meta-${core.componentType} class="dynamic">
<p>Динамический компонент</p>
</meta-${core.componentType}>
`
)
Результат:
[
{
"tag": {
"data": "/core/componentType",
"expr": "meta-${[0]}"
},
"type": "meta",
"string": { "class": "dynamic" },
"child": [
{
"tag": "p",
"type": "el",
"child": [
{
"type": "text",
"value": "Динамический компонент"
}
]
}
]
}
]
const nodes = parse(
({ html, context, update }) => html`
<div>
<button onclick=${() => update({ count: context.count + 1 })}>Счетчик: ${context.count}</button>
<input onchange=${(e) => update({ name: e.target.value })} value=${context.name} />
</div>
`
)
Результат:
[
{
"tag": "div",
"type": "el",
"child": [
{
"tag": "button",
"type": "el",
"event": {
"onclick": {
"upd": "count",
"data": "/context/count",
"expr": "() => update({ count: ${[0]} + 1 })"
}
},
"child": [
{
"type": "text",
"data": "/context/count",
"expr": "Счетчик: ${[0]}"
}
]
},
{
"tag": "input",
"type": "el",
"event": {
"onchange": {
"upd": "name",
"expr": "(e) => update({ name: e.target.value })"
}
},
"string": {
"value": {
"data": "/context/name"
}
}
}
]
}
]
Полная документация с примерами доступна на GitHub Pages.
html\
...`` в исходнике render‑функции — держите шаблон рядом.meta-${...}
превращается в { tag: { data, expr } }
.# сборка
bun run build
# тесты
bun test
# документация
bun run docs
MIT © zavx0z
FAQs
HTML template parser for MetaFor
The npm package @zavx0z/html-parser receives a total of 456 weekly downloads. As such, @zavx0z/html-parser popularity was classified as not popular.
We found that @zavx0z/html-parser demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Product
Socket Fix 2.0 brings targeted CVE remediation, smarter upgrade planning, and broader ecosystem support to help developers get to zero alerts.
Security News
Socket CEO Feross Aboukhadijeh joins Risky Business Weekly to unpack recent npm phishing attacks, their limited impact, and the risks if attackers get smarter.
Product
Socket’s new Tier 1 Reachability filters out up to 80% of irrelevant CVEs, so security teams can focus on the vulnerabilities that matter.