Gathering detailed insights and metrics for vanjs-htm
Gathering detailed insights and metrics for vanjs-htm
Gathering detailed insights and metrics for vanjs-htm
Gathering detailed insights and metrics for vanjs-htm
VanJS with HTM for JSX-like syntax in vanilla JavaScript using VanJS reactivity.
npm install vanjs-htm
Typescript
Module System
Node Version
NPM Version
TypeScript (94.15%)
CSS (3.55%)
HTML (2.02%)
JavaScript (0.28%)
Total Downloads
0
Last Day
0
Last Week
0
Last Month
0
Last Year
0
MIT License
84 Commits
1 Watchers
1 Branches
1 Contributors
Updated on Jun 30, 2025
Latest Version
1.2.0
Package Id
vanjs-htm@1.2.0
Unpacked Size
50.17 kB
Size
12.07 kB
File Count
14
NPM Version
11.4.0
Node Version
22.11.0
Published on
Jul 02, 2025
Cumulative downloads
Total Downloads
Last Day
0%
NaN
Compared to previous day
Last Week
0%
NaN
Compared to previous week
Last Month
0%
NaN
Compared to previous month
Last Year
0%
NaN
Compared to previous year
A flexible and lightweight (<900B gzipped minified) HTM integration for VanJS and optionally VanX, supporting control flow directives, automatic SVG namespace handling, and optional HTML entity decoding.
Here's a sample based on the simplified TODO App from VanJS.
for:each
, show:when
, and portal:mount
for SolidJS style declarative rendering. You can also combine show:when
with for:each
and portal:mount
to conditionally render lists and portals. Note: VanX is required only for the for:each
directive.vh:svg
directive for excluded or ambiguous elements.1// Script tags for including van and vanX 2// <script src="https://cdn.jsdelivr.net/gh/vanjs-org/van/public/van-latest.nomodule.min.js"></script> 3// <script src="https://cdn.jsdelivr.net/npm/vanjs-ext/dist/van-x.nomodule.min.js"></script> 4 5// Script tags for including htm and vanHTM 6// <script src="https://cdn.jsdelivr.net/npm/htm/dist/htm.js"></script> 7// <script src="https://cdn.jsdelivr.net/npm/vanjs-htm/dist/van-htm.js"></script> 8// The imports below can be replaced by the script tags above for htm and vanHTM 9import htm from 'htm'; 10import vanHTM from 'vanjs-htm'; 11 12// const { html, rmPortals } = vanHTM({ htm, van, vanX }); // This line and the one below are interchangeable 13const { html, rmPortals } = vanHTM({ htm, van, vanX: { list: vanX.list } }); 14 15const el = html` 16 <div> 17 Hello, 18 <b>world</b> 19 ! 20 </div> 21`; 22van.add(document.body, el);
The repository includes a sandbox environment for experimenting with VanHTM locally. To run it:
1npm install 2npm run sandbox
This will start a local development server where you can explore and test VanHTM features.
VanHTM provides several prebuilt bundles for browser usage, available via CDN (e.g., jsDelivr). You can choose the build that best fits your needs.
Build output structure:
dist/
default builds.dist/withDecoding/
builds that utilize HTML Entity Decoding (requires a HTML entities library like entities, he, html-entities, etc.).Each directory contains:
van-htm.module.js
(ESM, minified, ~870B gzipped)van-htm.js
(IIFE/global, minified, ~880B gzipped)van-htm.cjs
(CJS, minified)van-htm.dev.module.js
(ESM, unminified)van-htm.dev.js
(IIFE/global, unminified)VanHTM supports function components, allowing you to create reusable UI components that work seamlessly with all VanHTM features including control flow directives.
1// Define a reusable component 2const Card = (props, ...children) => html` 3 <div class="card"> 4 <h3>${props.title}</h3> 5 <div class="card-content">${children.length ? children : 'No children provided'}</div> 6 </div> 7`; 8 9const el = html` 10 <${Card} title="Welcome"> 11 <p>This is the card content</p> 12 <button>Click me</button> 13 <//> 14`; 15 16van.add(document.body, el);
Renders a list by looping over a reactive array or iterable. The value of for:each
should be a reactive list (e.g., from vanX.reactive
). The child function receives the current value, a deleter function, and the index/key.
Note: This directive requires VanX. If vanX
is not provided to vanHTM()
and you attempt to use for:each
, an error will occur.
1const items = vanX.reactive([1, 2, 3]); 2van.add( 3 document.body, 4 html` 5 <ul for:each=${items}> 6 ${(v, deleter, k) => 7 html` 8 <li>${v}</li> 9 `} 10 </ul> 11 ` 12);
See VanX docs: Reactive List for more details on the itemFunc
parameter.
Conditionally renders content based on a boolean, a VanJS state, or a function. If the condition is falsy, the show:fallback
value is rendered instead (can be a primitive, a state or a function if you need reactivity).
Note: Due to how HTM works, children are evaluated (eagerly) before the show:when
condition is checked. For complex children or performance-sensitive code, consider using a function to defer evaluation:
1// ❌ Expensive operation runs even when condition is false due to 2html`<div show:when=${condition}>${expensiveOperation()}</div>` 3 4// ✅ Use a function for complex/expensive children 5html`<div show:when=${condition}>${expensiveOperation}</div>` 6html`<div show:when=${condition}>${() => html`...complex children...`}</div>` 7 8// ✅ Or use a conditional function directly 9${() => condition ? html`<div>${expensiveOperation()}</div>` : ''} 10${() => condition ? html`<div>...complex children...</div>` : ''}
1const visible = van.state(true); 2const toggleButton = html` 3 <button onclick=${() => (visible.val = !visible.val)}>Toggle Visible</button> 4`; 5van.add( 6 document.body, 7 html` 8 <div> 9 ${toggleButton} 10 <div 11 show:when=${visible} 12 show:fallback=${() => 13 html` 14 <div><b>Fallback - ${visible}</b></div> 15 `} 16 > 17 Visible - ${visible} 18 </div> 19 </div> 20 ` 21);
show:when
: Accepts a boolean, a VanJS state, or a function returning a boolean.show:fallback
: (Optional) Content to render when the condition is falsy. Can be a primitive, a state or a function if you need reactivity.Renders the element into a different part of the DOM (a "portal"). The portal:mount
attribute determines where the content is rendered. It can be:
Node
#modal-root
)Note: For
rmPortals
to work correctly, portals should only be the direct child of their parent element. Nesting portals deeper will preventrmPortals
from removing them properly.
Implementation Detail: VanHTM automatically adds a
p:id
attribute to portaled elements for internal tracking. This attribute is used byrmPortals
to identify and remove the correct portal elements. You should not manually set or modify this attribute. See below for more information.
1const portalTarget = document.getElementById('portal-target'); 2const containerWithPortal = html` 3 <div> 4 <div>Some content before</div> 5 <div portal:mount=${portalTarget}>Content to Portal</div> 6 <div>Some content after</div> 7 <button onclick=${() => rmPortals(containerWithPortal, portalTarget)}>Remove Portal</button> 8 </div> 9`; 10van.add(document.body, containerWithPortal);
You can also use a selector:
1const portalTargetId = '#portal-target'; 2const containerWithPortal = html` 3 <div> 4 <div>Some content before</div> 5 <div portal:mount=${portalTargetId}>Content to Portal</div> 6 <div>Some content after</div> 7 <button onclick=${() => rmPortals(containerWithPortal, portalTargetId)}>Remove Portal</button> 8 </div> 9`; 10van.add(document.body, containerWithPortal);
1// Removes all portaled elements created from `parentContainer` that are mounted in `portalTarget`. 2// If no portalTarget is specified, it defaults to document.body. 3rmPortals(parentContainer, portalTarget?);
Parameters:
parentContainer
(Node): The container element that contains the portal placeholder commentsportalTarget
(Element | string, optional): The target where portal content was mounted. Can be:
'#modal-root'
, '.portal-container'
)document.body
Examples:
1// Remove portals mounted in a specific element 2rmPortals(containerWithPortal, document.getElementById('modal-root')); 3 4// Remove portals mounted using a CSS selector 5rmPortals(containerWithPortal, '#modal-root'); 6 7// Remove portals mounted in document.body (default behavior) 8rmPortals(containerWithPortal); 9// Equivalent to: 10rmPortals(containerWithPortal, document.body);
show:when
with for:each
and portal:mount
You can combine the show:when
directive with for:each
and portal:mount
on the same element to conditionally render lists or portaled elements. If the show:when
condition is falsy, neither the list nor the portal will be rendered, and the show:fallback
(if provided) will be used instead.
Example: Conditionally render a list
1const items = vanX.reactive([1, 2, 3]); 2const showList = van.state(true); 3 4van.add( 5 document.body, 6 html` 7 <button onclick=${() => (showList.val = !showList.val)}>Toggle List</button> 8 <button onclick=${() => items.push(Object.keys(items).length + 1)}>Add item</button> 9 <ul for:each=${items} show:when=${showList}> 10 ${(v) => 11 html` 12 <li>${v}</li> 13 `} 14 </ul> 15 ` 16);
Example: Conditionally render a portal
1const getTime = () => new Date().toLocaleTimeString(); 2const portalTarget = document.getElementById('portal-target'); 3const showPortal = van.state(true); 4const time = van.state(getTime()); 5 6const intervalId = setInterval(() => { 7 time.val = getTime(); 8}, 1000); 9 10const container = html` 11 <div> 12 <div portal:mount=${portalTarget} show:when=${showPortal}>Portaled Content ${time}</div> 13 <button onclick=${() => (showPortal.val = !showPortal.val)}>Toggle Portal</button> 14 </div> 15`; 16van.add(document.getElementById('main-content'), container);
VanHTM automatically handles SVG elements by applying the correct namespace when rendering. This ensures that SVG elements work properly without any additional configuration.
The following SVG elements are automatically rendered with the SVG namespace:
Shapes: circle
, ellipse
, line
, path
, polygon
, polyline
, rect
Container elements: svg
, g
, defs
, symbol
, use
Gradient and pattern elements: linearGradient
, radialGradient
, stop
, pattern
Text elements: text
, textPath
, tspan
Other common elements: clipPath
, desc
, filter
, foreignObject
, marker
, mask
1// Basic SVG with automatic namespace handling 2const radius = van.state(30); 3const basicSVG = html` 4 <svg width="100" height="100"> 5 <circle cx="50" cy="50" r=${radius} fill="lightblue" stroke="darkblue" stroke-width="2" /> 6 <text x="50" y="55" text-anchor="middle" fill="darkblue">SVG</text> 7 </svg> 8 <br /> 9 <input type="range" min="10" max="45" value=${radius} oninput=${(e) => (radius.val = parseInt(e.target.value))} /> 10 <span>Radius: ${radius}</span> 11 <br /> 12`; 13van.add(document.body, basicSVG); 14 15// Complex SVG with gradients and paths 16const complexSVG = html` 17 <svg width="200" height="100"> 18 <defs> 19 <linearGradient id="gradient"> 20 <stop offset="0%" stop-color="#f00" /> 21 <stop offset="100%" stop-color="#00f" /> 22 </linearGradient> 23 </defs> 24 <rect x="10" y="10" width="180" height="80" fill="url(#gradient)" rx="10" /> 25 <path d="M 50 50 L 150 50" stroke="white" stroke-width="3" /> 26 </svg> 27`; 28van.add(document.body, complexSVG);
To keep the bundle size small, some SVG elements are excluded from automatic namespace handling:
animate
, animateMotion
, animateTransform
, set
fe*
elements (e.g., feGaussianBlur
, feBlend
, feColorMatrix
, etc.)metadata
, mpath
, switch
, view
For excluded elements or when you need explicit control, use the vh:svg
directive to force SVG namespace:
1// Animated SVG using vh:svg directive for excluded elements 2const animatedCircle = html` 3 <svg width="200" height="200"> 4 <circle cx="100" cy="100" r="40" fill="purple"> 5 <!-- animate is excluded, so we need vh:svg --> 6 <animate vh:svg attributeName="r" values="40;60;40" dur="2s" repeatCount="indefinite" /> 7 </circle> 8 </svg> 9`; 10van.add(document.body, animatedCircle); 11 12// SVG with filter effects using vh:svg 13const blurredRect = html` 14 <svg width="200" height="200"> 15 <defs> 16 <filter id="blur"> 17 <!-- feGaussianBlur is excluded, so we need vh:svg --> 18 <feGaussianBlur vh:svg in="SourceGraphic" stdDeviation="5" /> 19 </filter> 20 </defs> 21 <rect x="50" y="50" width="100" height="100" fill="orange" filter="url(#blur)" /> 22 </svg> 23`; 24van.add(document.body, blurredRect);
Some elements exist in both HTML and SVG (a
, script
, style
, title
). These default to HTML namespace for compatibility:
1// Using both HTML and SVG styles 2const styledSVG = html` 3 <div> 4 <style> 5 .highlight { 6 fill: yellow; 7 } 8 </style> 9 <svg width="100" height="100"> 10 <style vh:svg> 11 .svgtext { 12 font-size: 20px; 13 } 14 </style> 15 <circle cx="50" cy="50" r="40" class="highlight" /> 16 <text x="50" y="55" text-anchor="middle" class="svgtext">Hi</text> 17 </svg> 18 </div> 19`; 20van.add(document.body, styledSVG);
1import { decode } from 'html-entities'; 2import vanHTM from 'vanjs-htm/withDecoding'; 3 4// const { html, rmPortals } = vanHTM({ htm, van, vanX, decode }); // This line and the one below are interchangeable 5const { html, rmPortals } = vanHTM({ htm, van, vanX: { list: vanX.list }, decode }); 6 7// Example below 8const el = html` 9 <div> 10 Hello, 11 <b>world</b> 12 ! 😎 13 </div> 14`; 15van.add(document.body, el);
vanHTM(options)
htm
: Required in all builds. The HTM instance.van
: Required in all builds. The VanJS instance.vanX
: Required only for the for:each
directive. The VanJS Extension instance or an object that contains a list
property set as vanX.list
. If not provided and for:each
is used, an error will occur.decode
: Required in builds that include HTML Entity Decoding (vanjs-htm/withDecoding
). The decode method from a HTML entities library like entities, he, html-entities, etc.Returns:
html
: The htm template tag.rmPortals(parentContainer: Node, portalTarget?: Element | string)
: Remove portaled elements created from parentContainer
. The portalTarget
parameter specifies where to look for the portal content:
document.body
if not providedfor:each
directive relies on VanX's list
function. Refer to VanX documentation for error handling behavior with invalid reactive data.portal:mount
doesn't match any element, VanJS will throw an error when attempting to mount the portal content.rmPortals
is called with an invalid selector or non-existent element, the function will silently return without performing any operations.vanX
is not provided to vanHTM()
and the for:each
directive is used, an error will occur.VanHTM explicitly disables HTM's template string caching mechanism by setting this[0] = 3
in the template processor. This ensures that each template evaluation creates fresh elements, which is necessary for proper VanJS reactivity and state management. Refer to HTM documentation on Caching for more information.
VanHTM automatically adds a p:id
attribute to portaled elements for internal tracking. This attribute uses an auto-incrementing counter (format: p-${counter}
) and is used by rmPortals
to identify and remove the correct portal elements. You should not manually set or modify this attribute.
MIT
No vulnerabilities found.
No security vulnerabilities found.