{"id":13827210,"url":"https://github.com/WebReflection/viperHTML","last_synced_at":"2025-07-09T03:31:09.024Z","repository":{"id":57392725,"uuid":"84357939","full_name":"WebReflection/viperHTML","owner":"WebReflection","description":"Isomorphic hyperHTML","archived":true,"fork":false,"pushed_at":"2020-03-05T13:17:02.000Z","size":496,"stargazers_count":314,"open_issues_count":1,"forks_count":10,"subscribers_count":17,"default_branch":"master","last_synced_at":"2024-11-10T03:02:50.316Z","etag":null,"topics":["html","isomorphic","js","lightweight","manipulation","performance","template","template-literals","vanilla"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/WebReflection.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-03-08T19:25:53.000Z","updated_at":"2024-07-13T00:32:50.000Z","dependencies_parsed_at":"2022-09-26T16:51:29.966Z","dependency_job_id":null,"html_url":"https://github.com/WebReflection/viperHTML","commit_stats":null,"previous_names":[],"tags_count":63,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WebReflection%2FviperHTML","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WebReflection%2FviperHTML/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WebReflection%2FviperHTML/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WebReflection%2FviperHTML/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/WebReflection","download_url":"https://codeload.github.com/WebReflection/viperHTML/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225481277,"owners_count":17481172,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["html","isomorphic","js","lightweight","manipulation","performance","template","template-literals","vanilla"],"created_at":"2024-08-04T09:01:52.093Z","updated_at":"2024-11-20T06:31:03.432Z","avatar_url":"https://github.com/WebReflection.png","language":"JavaScript","readme":"# viperHTML\n\n\u003cimg alt=\"viperHTML logo\" src=\"https://webreflection.github.io/hyperHTML/logo/viperhtml.svg\" width=\"116\" height=\"81\"\u003e\n\n[![donate](https://img.shields.io/badge/$-donate-ff69b4.svg?maxAge=2592000\u0026style=flat)](https://github.com/WebReflection/donate) [![License: ISC](https://img.shields.io/badge/License-ISC-yellow.svg)](https://opensource.org/licenses/ISC) [![Build Status](https://travis-ci.org/WebReflection/hyperHTML.svg?branch=master)](https://travis-ci.org/WebReflection/viperHTML) [![Coverage Status](https://coveralls.io/repos/github/WebReflection/viperHTML/badge.svg?branch=master)](https://coveralls.io/github/WebReflection/viperHTML?branch=master) ![Blazing Fast](https://img.shields.io/badge/speed-blazing%20🔥-brightgreen.svg) [![Greenkeeper badge](https://badges.greenkeeper.io/WebReflection/viperHTML.svg)](https://greenkeeper.io/)\n\n## Warning\n\nDue [low popularity](https://www.npmjs.com/package/viperhtml) of this project after all these years, and because I have shifted focus to better alternatives, this **project is currently in maintenance mode**, meaning that 100% _feature parity_ with _hyperHTML_ is not anymore an achievement, and only the simple/obvious will be fixed.\n\nAmong various changes happened to _hyperHTML_, the **sparse attributes** will likely **not** be **supported**, as the refactoring needed here would easily outperform the benefits.\n\nAs _sparse attributes_ are never really been a must have, you can simply use `attr=${...}` and concatenate in there anything you want.\n\nThis is also true for most modern alternatives of mine, such as [ucontent](https://github.com/WebReflection/ucontent#readme), but if that's the only deal breaker, have a look at [heresy-ssr](https://github.com/WebReflection/heresy-ssr#readme), which is 1:1 based on [lighterhtml](https://github.com/WebReflection/lighterhtml#readme), hence likely always 100% in features parity with it, included sparse attributes.\n\n- - -\n\n[hyperHTML](https://github.com/WebReflection/hyperHTML) lightness, ease, and performance, for the server.\n\n- - -\nDon't miss the [viperHTML](https://github.com/WebReflection/viperHTML) version of **Hacker News**\n\nLive: https://viperhtml-164315.appspot.com/\n\nRepo: https://github.com/WebReflection/viper-news\n- - -\n\n### Similar API without DOM constrains\nSimilar to its browser side counterpart, `viperHTML` parses the template string once, decides what is an attribute, what is a callback, what is text and what is HTML, and any future call to the same render will only update parts of that string.\n\nThe result is a blazing fast template engine that makes templates and renders shareable between the client and the server.\n\n### Seamlessly Isomorphic\nNo matter if you use ESM or CommonJS, you can use [hypermorphic](https://github.com/WebReflection/hypermorphic#hypermorphic-)\nto load same features on both client and server.\n\n```js\n// ESM example (assuming bundlers/ESM loaders in place)\nimport {bind, wire} from 'hypermorphic';\n\n// CommonJS example\nconst {bind, wire} = require('hypermorphic');\n```\n\n\n### Automatically Sanitized HTML\nBoth attributes and text nodes are safely escaped on each call.\n```js\nconst viperHTML = require('viperhtml');\n\nvar output = render =\u003e render`\n  \u003c!-- attributes and callbacks are safe --\u003e\n  \u003ca\n    href=\"${a.href}\"\n    onclick=\"${a.onclick}\"\n  \u003e\n    \u003c!-- also text is always safe --\u003e\n    ${a.text}\n    \u003c!-- use Arrays to opt-in HTML --\u003e\n    \u003cspan\u003e\n      ${[a.html]}\n    \u003c/span\u003e\n  \u003c/a\u003e\n`;\n\nvar a = {\n  text: 'Click \"Me\"',\n  html: '\u003cstrong\u003e\"HTML\" Me\u003c/strong\u003e',\n  href: 'https://github.com/WebReflection/viperHTML',\n  onclick: (e) =\u003e e.preventDefault()\n};\n\n// associate the link to an object of info\n// or simply use viperHTML.wire();\nvar link = viperHTML.bind(a);\n\nconsole.log(output(link).toString());\n```\n\nThe resulting output will be the following one:\n```html\n  \u003c!-- attributes and callbacks are safe --\u003e\n  \u003ca\n    href=\"https://github.com/WebReflection/viperHTML\"\n    onclick=\"return ((e) =\u0026gt; e.preventDefault()).call(this, event)\"\n  \u003e\n    \u003c!-- also text is always safe --\u003e\n    Click \u0026quot;Me\u0026quot;\n    \u003c!-- HTML goes in as it is --\u003e\n    \u003cspan\u003e\u003cstrong\u003e\"HTML\" Me\u003c/strong\u003e\u003c/span\u003e\n  \u003c/a\u003e\n```\n\n### Usage Example\n```js\nconst viperHTML = require('viperhtml');\n\nfunction tick(render) {\n  return render`\n    \u003cdiv\u003e\n      \u003ch1\u003eHello, world!\u003c/h1\u003e\n      \u003ch2\u003eIt is ${new Date().toLocaleTimeString()}.\u003c/h2\u003e\n    \u003c/div\u003e\n  `;\n}\n\n// for demo purpose only,\n// stop showing tick result after 6 seconds\nsetTimeout(\n  clearInterval,\n  6000,\n  setInterval(\n    render =\u003e console.log(tick(render).toString()),\n    1000,\n    // On a browser, you'll need to bind\n    // the content to a generic DOM node.\n    // On the server, you can directly use a wire()\n    // which will produce an updated result each time\n    // it'll be used through a template literal\n    viperHTML.wire()\n  )\n);\n```\n\n\n\n\n### The Extra viperHTML Feature: Asynchronous Partial Output\n\nClients and servers inevitably have different needs,\nand the ability to serve chunks on demand, instead of a whole page at once,\nis once of these very important differences that wouldn't make much sense on the client side.\n\nIf your page content might arrive on demand and is asynchronous,\n`viperHTML` offers an utility to both obtain performance boots,\nand intercepts all chunks of layout, as soon as this is available.\n\n\n#### viperHTML.async()\n\nSimilar to a wire, `viperHTML.async()` returns a callback that *must be invoked* right before the template string,\noptionally passing a callback that will be invoked per each available chunk of text, as soon as this is resolved.\n\n```js\n// the view\nconst pageLayout = (render, model) =\u003e\nrender`\u003c!doctype html\u003e\n\u003chtml\u003e\n  \u003chead\u003e${model.head}\u003c/head\u003e\n  \u003cbody\u003e${model.body}\u003c/body\u003e\n\u003c/html\u003e`;\n\n// the viper async render\nconst asyncRender = viperHTML.async();\n\n// dummy server for one view only\nrequire('http')\n  .createServer((req, res) =\u003e {\n    res.writeHead( 200, {\n      'Content-Type': 'text/html'\n    });\n    pageLayout(\n\n      // res.write(chunk) while resolved\n      asyncRender(chunk =\u003e res.write(chunk)),\n\n      // basic model example with async content\n      {\n        head: Promise.resolve({html: '\u003ctitle\u003eright away\u003c/title\u003e'}),\n        body: new Promise(res =\u003e setTimeout(\n          res, 1000,\n          // either:\n          //  {html: '\u003cdiv\u003elater on\u003c/div\u003e'}\n          //  ['\u003cdiv\u003elater on\u003c/div\u003e']\n          //  wire()'\u003cdiv\u003elater on\u003c/div\u003e'\n          viperHTML.wire()`\u003cdiv\u003elater on\u003c/div\u003e`\n        ))\n      }\n    )\n    .then(() =\u003e res.end())\n    .catch(err =\u003e { console.error(err); res.end(); });\n  })\n  .listen(8000);\n```\n\n\n### Handy Patterns\nFollowing a list of handy patterns to solve common issues.\n\n#### HTML in template literals doesn't get highlighted\nTrue that, but if you follow a simple `(render, model)` convetion,\nyou can just have templates as html files.\n```html\n\u003c!-- template/tick.html --\u003e\n\u003cdiv\u003e\n  \u003ch1\u003eHello, ${model.name}!\u003c/h1\u003e\n  \u003ch2\u003eIt is ${new Date().toLocaleTimeString()}.\u003c/h2\u003e\n\u003c/div\u003e\n```\nAt this point, you can generate as many views as you want through the following step\n```sh\n#!/usr/bin/env bash\n\nmkdir -p view\n\nfor f in $(ls template); do\n  echo 'module.exports = (render, model) =\u003e render`' \u003e \"view/${f:0:-4}js\"\n  cat template/$f \u003e\u003e \"view/${f:0:-4}js\"\n  echo '`;' \u003e\u003e \"view/${f:0:-4}js\"\ndone\n```\n\nAs result, the folder `view` will now contain a `tick.js` file as such:\n```js\nmodule.exports = (render, model) =\u003e render`\n\u003c!-- template/tick.html --\u003e\n\u003cdiv\u003e\n  \u003ch1\u003eHello, ${model.name}!\u003c/h1\u003e\n  \u003ch2\u003eIt is ${new Date().toLocaleTimeString()}.\u003c/h2\u003e\n\u003c/div\u003e\n`;\n```\n\nYou can now use each view as modules.\n```js\nconst view = {\n  tick: require('./view/tick')\n};\n\n// show the result in console\nconsole.log(view.tick(\n  viperHTML.wire(),\n  {name: 'user'}\n));\n```\n","funding_links":[],"categories":["Libraries","JavaScript"],"sub_categories":["HTML"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FWebReflection%2FviperHTML","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FWebReflection%2FviperHTML","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FWebReflection%2FviperHTML/lists"}