{"id":14967523,"url":"https://github.com/noway/htb","last_synced_at":"2025-10-25T20:31:23.276Z","repository":{"id":239863827,"uuid":"800971471","full_name":"noway/htb","owner":"noway","description":"Htb.js — a 50-line HTML template engine that uses JavaScript syntax.","archived":false,"fork":false,"pushed_at":"2024-05-15T11:24:29.000Z","size":11,"stargazers_count":13,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-05T00:52:43.344Z","etag":null,"topics":["ejs","handlebars","html","html-templates","jbuilder","json2html","mustache","pug","template-engine","templates","templating"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/htb","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/noway.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-05-15T10:56:34.000Z","updated_at":"2024-08-12T18:05:20.000Z","dependencies_parsed_at":"2024-05-15T21:06:09.059Z","dependency_job_id":null,"html_url":"https://github.com/noway/htb","commit_stats":null,"previous_names":["noway/htb"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/noway/htb","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noway%2Fhtb","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noway%2Fhtb/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noway%2Fhtb/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noway%2Fhtb/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/noway","download_url":"https://codeload.github.com/noway/htb/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noway%2Fhtb/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":281015445,"owners_count":26430086,"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","status":"online","status_checked_at":"2025-10-25T02:00:06.499Z","response_time":81,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["ejs","handlebars","html","html-templates","jbuilder","json2html","mustache","pug","template-engine","templates","templating"],"created_at":"2024-09-24T13:38:11.021Z","updated_at":"2025-10-25T20:31:23.019Z","avatar_url":"https://github.com/noway.png","language":"TypeScript","readme":"![htb-js-logo](https://github.com/noway/htb/assets/2031472/05536b73-047a-40f3-9d3d-de902aea8716)\n\n# HTML Template Builder \u0026emsp; [![latest version badge]][npm] [![license badge]][license] [![downloads badge]][npm]\n\n[latest version badge]: https://img.shields.io/npm/v/htb\n[license badge]: https://img.shields.io/npm/l/htb\n[downloads badge]: https://img.shields.io/npm/dw/htb\n[npm]: https://www.npmjs.com/package/htb\n[license]: https://github.com/noway/htb/blob/main/LICENSE\n\nHTML Template Builder (Htb.js) is a 50-line HTML template engine that uses JavaScript syntax.\n\nHtb.js was not invented. Htb.js was discovered at the intersection of HTML and JavaScript. Htb.js takes inspiration from Ruby's Jbuilder and JavaScript's json2html, and can be seen as a synthesis of the two, or a 'what if Jbuilder and json2html were combined'.\n\n## Examples\n\n### Getting started\n\n```typescript\nconst htb = Htb\n  ('!DOCTYPE', { html: true })\n  ('html', {}, () =\u003e [\n    Htb('head', {}, () =\u003e [\n      Htb('title', {}, 'Simple Example'),\n      Htb('meta', { name: 'viewport', content: 'width=device-width, initial-scale=1' }),\n      Htb('style', { type: 'text/css' }, 'p { font-family: Arial, Helvetica, sans-serif; }')\n    ]),\n    Htb('body', {}, () =\u003e [\n      Htb('h1', {}, () =\u003e \n        'Hello, John Doe!'\n      ),\n      Htb('p', {}, () =\u003e [\n        Htb('b', {}, 'This is bold text. '),\n        Htb('i', {}, 'This is italic text. '),\n        Htb('u', {}, 'This is underlined text.')\n      ])\n    ])\n  ]);\nconsole.log(htb.html)\n```\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003ctitle\u003eSimple Example\u003c/title\u003e\n    \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\"\u003e\n    \u003cstyle type=\"text/css\"\u003e\n      p {\n        font-family: Arial, Helvetica, sans-serif;\n      }\n    \u003c/style\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003ch1\u003eHello, John Doe!\u003c/h1\u003e\n    \u003cp\u003e\u003cb\u003eThis is bold text.\u003c/b\u003e\u003ci\u003eThis is italic text.\u003c/i\u003e\u003cu\u003eThis is underlined text.\u003c/u\u003e\u003c/p\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n### Conditionals \u0026 variables\n\n```typescript\nconst user = {\n  isAdmin: false,\n  isMember: true,\n  name: 'John Doe'\n};\nconst htb = Htb\n  ('!DOCTYPE', { html: true })\n  ('html', {}, () =\u003e [\n    Htb('head', {}, () =\u003e [\n      Htb('title', {}, 'Conditionals Example'),\n      Htb('meta', { name: 'viewport', content: 'width=device-width, initial-scale=1' }),\n      Htb('style', { type: 'text/css' }, 'p { font-family: Arial, Helvetica, sans-serif; }')\n    ]),\n    Htb('body', {}, () =\u003e [\n      Htb('h1', {}, 'User Profile'),\n      user.isAdmin\n        ? Htb('p', {}, `Welcome, Admin ${user.name}!`)\n        : user.isMember\n          ? Htb('p', {}, `Welcome, Member ${user.name}!`)\n          : Htb('p', {}, 'Welcome, Guest!')\n    ])\n  ]);\nconsole.log(htb.html)\n```\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003ctitle\u003eConditionals Example\u003c/title\u003e\n    \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\"\u003e\n    \u003cstyle type=\"text/css\"\u003e\n      p {\n          font-family: Arial, Helvetica, sans-serif;\n      }\n    \u003c/style\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003ch1\u003eUser Profile\u003c/h1\u003e\n    \u003cp\u003eWelcome, Member John Doe!\u003c/p\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n### Nested Loops\n\n```typescript\nconst categories = [\n  { name: 'Fruits', items: ['Apple', 'Banana', 'Orange'] },\n  { name: 'Vegetables', items: ['Carrot', 'Broccoli', 'Spinach'] }\n];\nconst htb = Htb\n  ('!DOCTYPE', { html: true })\n  ('html', {}, () =\u003e [\n    Htb('head', {}, () =\u003e [\n      Htb('title', {}, 'Nested Loops Example'),\n      Htb('meta', { name: 'viewport', content: 'width=device-width, initial-scale=1' }),\n      Htb('style', { type: 'text/css' }, 'p { font-family: Arial, Helvetica, sans-serif; }')\n    ]),\n    Htb('body', {}, () =\u003e {\n      const elements = [Htb('h1', {}, 'Categories and Items')];\n      for (const category of categories) {\n        elements.push(Htb('h2', {}, category.name)\n          ('ul', {}, () =\u003e\n            category.items.map(item =\u003e\n              Htb('li', {}, item)\n            )\n          ));\n      }\n      return elements;\n    })\n  ]);\nconsole.log(htb.html)\n```\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003ctitle\u003eNested Loops Example\u003c/title\u003e\n    \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\"\u003e\n    \u003cstyle type=\"text/css\"\u003e\n      p {\n          font-family: Arial, Helvetica, sans-serif;\n      }\n    \u003c/style\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003ch1\u003eCategories and Items\u003c/h1\u003e\n    \u003ch2\u003eFruits\u003c/h2\u003e\n    \u003cul\u003e\n      \u003cli\u003eApple\u003c/li\u003e\n      \u003cli\u003eBanana\u003c/li\u003e\n      \u003cli\u003eOrange\u003c/li\u003e\n    \u003c/ul\u003e\n    \u003ch2\u003eVegetables\u003c/h2\u003e\n    \u003cul\u003e\n      \u003cli\u003eCarrot\u003c/li\u003e\n      \u003cli\u003eBroccoli\u003c/li\u003e\n      \u003cli\u003eSpinach\u003c/li\u003e\n    \u003c/ul\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n### Partial Templates\n\n```typescript\nconst headerTemplate = Htb\n  ('header', {}, () =\u003e [\n    Htb('h1', {}, 'My Website'),\n    Htb('nav', {}, () =\u003e [\n      Htb('a', { href: '/' }, 'Home'),\n      Htb('a', { href: '/about' }, 'About'),\n      Htb('a', { href: '/contact' }, 'Contact')\n    ])\n  ]);\nconst footerTemplate = Htb\n  ('footer', {}, () =\u003e [\n    Htb('p', {}, 'Made with \u003c3 and coffee.')\n  ]);\nconst htb = Htb\n  ('!DOCTYPE', { html: true })\n  ('html', {}, () =\u003e [\n    Htb('head', {}, () =\u003e [\n      Htb('title', {}, 'Partial Templates Example'),\n      Htb('meta', { name: 'viewport', content: 'width=device-width, initial-scale=1' }),\n      Htb('style', { type: 'text/css' }, 'p { font-family: Arial, Helvetica, sans-serif; }')\n    ]),\n    Htb('body', {}, () =\u003e [\n      headerTemplate,\n      Htb('main', {}, () =\u003e [\n        Htb('h2', {}, 'Page Content'),\n        Htb('p', {}, 'Welcome to the page!')\n      ]),\n      footerTemplate\n    ])\n  ]);\nconsole.log(htb.html)\n```\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003ctitle\u003ePartial Templates Example\u003c/title\u003e\n    \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\"\u003e\n    \u003cstyle type=\"text/css\"\u003e\n      p {\n          font-family: Arial, Helvetica, sans-serif;\n      }\n    \u003c/style\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003cheader\u003e\n      \u003ch1\u003eMy Website\u003c/h1\u003e\n      \u003cnav\u003e\u003ca href=\"/\"\u003eHome\u003c/a\u003e\u003ca href=\"/about\"\u003eAbout\u003c/a\u003e\u003ca href=\"/contact\"\u003eContact\u003c/a\u003e\u003c/nav\u003e\n    \u003c/header\u003e\n    \u003cmain\u003e\n      \u003ch2\u003ePage Content\u003c/h2\u003e\n      \u003cp\u003eWelcome to the page!\u003c/p\u003e\n    \u003c/main\u003e\n    \u003cfooter\u003e\n      \u003cp\u003eMade with \u0026lt;3 and coffee.\u003c/p\u003e\n    \u003c/footer\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n### Numbers\n\n```typescript\nconst htb = Htb\n  ('!DOCTYPE', { html: true })\n  ('html', {}, () =\u003e [\n    Htb('head', {}, () =\u003e [\n      Htb('title', {}, 'Arithmetic Operations Example'),\n      Htb('meta', { name: 'viewport', content: 'width=device-width, initial-scale=1' }),\n      Htb('style', { type: 'text/css' }, 'p { font-family: Arial, Helvetica, sans-serif; }')\n    ]),\n    Htb('body', {}, () =\u003e [\n      Htb('h1', {}, 'Basic Arithmetic'),\n      Htb('p', {}, [\n        'Sum of ', Htb('span', {}, '10'), ' and ', Htb('span', {}, '5'), ': ',\n        Htb('span', {}, `${10 + 5}`) // a number\n      ]),\n      Htb('p', {}, [\n        'Product of ', Htb('span', {}, '7'), ' and ', Htb('span', {}, '3'), ': ',\n        Htb('span', {}, `${7 * 3}`) // a number\n      ]),\n      Htb('p', {}, [\n        'Division of ', Htb('span', {}, '20'), ' by ', Htb('span', {}, '4'), ': ',\n        Htb('span', {}, `${20 / 4}`) // a number\n      ])\n    ])\n  ]);\nconsole.log(htb.html)\n```\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003ctitle\u003eArithmetic Operations Example\u003c/title\u003e\n    \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\"\u003e\n    \u003cstyle type=\"text/css\"\u003e\n      p {\n          font-family: Arial, Helvetica, sans-serif;\n      }\n    \u003c/style\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003ch1\u003eBasic Arithmetic\u003c/h1\u003e\n    \u003cp\u003eSum of \u003cspan\u003e10\u003c/span\u003e and \u003cspan\u003e5\u003c/span\u003e: \u003cspan\u003e15\u003c/span\u003e\u003c/p\u003e\n    \u003cp\u003eProduct of \u003cspan\u003e7\u003c/span\u003e and \u003cspan\u003e3\u003c/span\u003e: \u003cspan\u003e21\u003c/span\u003e\u003c/p\u003e\n    \u003cp\u003eDivision of \u003cspan\u003e20\u003c/span\u003e by \u003cspan\u003e4\u003c/span\u003e: \u003cspan\u003e5\u003c/span\u003e\u003c/p\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n## FAQ\n\n### 1. Is this JSX?\n\nJSX is a syntactic extension of JavaScript. Htb.js is closer to `React.createElement()`, although without the React part.\n\n### 2. Is JSX to Htb.js compiler planned?\n\nNo.\n\n### 3. Should I use this in production?\n\nFortune favours the brave.\n\n### 4. How to use this in my project?\n\nIt's best to copy paste the [`htb.ts`](https://github.com/noway/htb/blob/main/htb.ts) file into your repo. Although an [NPM package](https://www.npmjs.com/package/htb) is also available.\n\n### 5. Is there a way to generate Htb.js code from HTML?\n\nUse this LLM prompt: [`llm-prompt.txt`](https://github.com/noway/htb/raw/main/llm-prompt.txt)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnoway%2Fhtb","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnoway%2Fhtb","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnoway%2Fhtb/lists"}