{"id":15371618,"url":"https://github.com/charlesread/hmls","last_synced_at":"2025-08-03T04:32:09.086Z","repository":{"id":57264666,"uuid":"91267010","full_name":"charlesread/hmls","owner":"charlesread","description":"Build a node webapp in seconds, wires together hapi, marko, lasso, and socket.io","archived":false,"fork":false,"pushed_at":"2018-02-04T17:38:51.000Z","size":811,"stargazers_count":2,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-11-15T04:16:37.678Z","etag":null,"topics":["hapi","hapijs","http","lasso","marko","node","nodejs","scaffolding","socket-io","socketio","webapp"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/charlesread.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-05-14T19:39:14.000Z","updated_at":"2019-06-24T08:50:09.000Z","dependencies_parsed_at":"2022-08-25T02:52:16.208Z","dependency_job_id":null,"html_url":"https://github.com/charlesread/hmls","commit_stats":null,"previous_names":[],"tags_count":32,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/charlesread%2Fhmls","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/charlesread%2Fhmls/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/charlesread%2Fhmls/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/charlesread%2Fhmls/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/charlesread","download_url":"https://codeload.github.com/charlesread/hmls/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228522624,"owners_count":17932833,"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":["hapi","hapijs","http","lasso","marko","node","nodejs","scaffolding","socket-io","socketio","webapp"],"created_at":"2024-10-01T13:48:06.350Z","updated_at":"2024-12-06T20:42:41.032Z","avatar_url":"https://github.com/charlesread.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build Status](https://travis-ci.org/charlesread/hmls.svg?branch=master)](https://travis-ci.org/charlesread/hmls)\n[![Dependencies](https://david-dm.org/charlesread/hmls.svg)](https://david-dm.org/charlesread/hmls)\n[![Coverage Status](https://coveralls.io/repos/github/charlesread/hmls/badge.svg?branch=master)](https://coveralls.io/github/charlesread/hmls?branch=master)\n\n# HMLS\n\n[![Join the chat at https://gitter.im/hmls-njs/Lobby](https://badges.gitter.im/hmls-njs/Lobby.svg)](https://gitter.im/hmls-njs/Lobby?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge\u0026utm_content=badge)\n\nGo from 0 to webapp in seconds.\n\nHMLS gives you, with _zero_ configuration:\n\n* A full blown and easy-to-use web server with a ton of plugins; `hapi`\n* An awesome view layer via `marko` (easy-to-use taglibs, reusable custom components, et cetera)\n* Asset bundling (and serving) via `lasso` (complete with `less` CSS pre-processing)\n* Bi-directional communication is as easy as writing a few callbacks with `socket.io`, and is ready to go\n\n`hapi`, `marko`, `lasso`, and `socket.io` are extremely efficient and powerful.  So `HMLS` just wires all of that up for you so that you can be up and running with a webapp in two shakes of a lamb's tail.\n \n`HMLS` doesn't \"dumb it down\" or hide things.  The individual components are easily accessed and altered, giving you all of the flexibility that you'd ever need, or at least the same flexibility that you'd have if you wired this together yourself.\n\n`HMLS` really represents the _VC_ in _MVC_.\n\n\u003c!-- toc --\u003e\n\n- [Change Log](#change-log)\n- [Installation](#installation)\n  * [index.js](#indexjs)\n  * [routes/index.js](#routesindexjs)\n  * [Too lazy for that noise?](#too-lazy-for-that-noise)\n- [Scaffolding](#scaffolding)\n- [Related Plugins](#related-plugins)\n- [Methods, Attributes, and Options](#methods-attributes-and-options)\n  * [`new HMLS([options])`](#new-hmlsoptions)\n  * [`async hmls.init()`](#async-hmlsinit)\n  * [`async hmls.start()`](#async-hmlsstart)\n  * [`hmls.server`](#hmlsserver)\n  * [`hmls.lasso`](#hmlslasso)\n  * [`hmls.io`](#hmlsio)\n- [Events](#events)\n  * [willInitialize](#willinitialize)\n  * [initialized](#initialized)\n  * [willRegisterRoutes](#willregisterroutes)\n  * [routesRegistered](#routesregistered)\n  * [willStart](#willstart)\n  * [started](#started)\n- [Structure and Architecture](#structure-and-architecture)\n  * [Project Structure](#project-structure)\n    + [/routes](#routes)\n    + [/static](#static)\n    + [/assets](#assets)\n    + [/io](#io)\n- [Examples](#examples)\n  * [With a simple `marko` template](#with-a-simple-marko-template)\n    + [index.js](#indexjs-1)\n    + [pages/slash/index.marko](#pagesslashindexmarko)\n    + [routes/slash/index.js](#routesslashindexjs)\n  * [Bundling assets with `lasso`](#bundling-assets-with-lasso)\n    + [index.js](#indexjs-2)\n    + [routes/slash/index.js](#routesslashindexjs-1)\n    + [pages/slash/index.marko](#pagesslashindexmarko-1)\n    + [pages/slash/browser.json](#pagesslashbrowserjson)\n    + [pages/slash/lib1.js](#pagesslashlib1js)\n    + [pages/slash/lib2.js](#pagesslashlib2js)\n    + [pages/slash/style.css](#pagesslashstylecss)\n  * [socket.io](#socketio)\n    + [pages/slash/index.marko](#pagesslashindexmarko-2)\n    + [io/slash/index.js](#ioslashindexjs)\n    + [pages/slash/lib.js](#pagesslashlibjs)\n\n\u003c!-- tocstop --\u003e\n\n## Change Log\n\n* 2017-12-16 - As of v2.x `HMLS` is fully compatible with Hapi v17.x and `async/await`.\n\n## Installation\n \n`mkdir \u003cproject\u003e`\n \n`cd \u003cproject\u003e`\n \n`npm init`\n \n`npm install --save hmls`\n \nThen make a few files:\n\n### index.js\n \n ```js\n'use strict'\n\nconst HMLS = require('hmls')\n\nconst vc = new HMLS()\n\n!async function () {\n  await vc.init()\n  await vc.start()\n  console.log('server started: %s', vc.server.info.uri)\n}()\n  .catch((err) =\u003e {\n    console.error(err.message)\n  })\n```\n \n### routes/index.js\n \n```js\n'use strict'\n\nmodule.exports = {\n  method: 'get',\n  path: '/',\n  handler: async function (req, h) {\n    return 'Welcome to the home page!'\n  }\n}\n```\n\nThen `node index.js` and visit http://localhost:8080 to see your home page.\n\n### Too lazy for that noise?\n\nCheck this out, just a one liner that installs `hmls`, scaffolds out a simple project, and starts it up:\n\n```bash\n$ mkdir my-project \u0026\u0026 cd my-project \u0026\u0026 npm init \u0026\u0026 npm i -S hmls \u0026\u0026 node node_modules/hmls/bin/hmls.js -s \u0026\u0026 node index.js\n```\n\n## Scaffolding\n\n`HMLS` comes with a CLI that can be used to create all of the files and folders (along with small examples) necessary to run  immediately.\n\nFirst create a project and install `HMLS` globally:\n\n`$ mkdir \u003cproject\u003e`\n\n`$ cd \u003cproject\u003e`\n \n`$ npm init`\n \n`$ npm install --save hmls`\n\n`$ npm install --global hmls`\n\nThen:\n\n`$ hmls --scaffold \u0026\u0026 node index.js`\n\nSimple as that!\n\n## Related Plugins\n\nI've written a few `hapi`-related plugins that I use all the time, mostly for authorization and authentication:\n\n* [hapi-acl-auth](https://www.npmjs.com/package/hapi-acl-auth)\n* [hapi-auth-auth0](https://www.npmjs.com/package/hapi-auth-auth0)\n* [hapi-auth-fb](https://www.npmjs.com/package/hapi-auth-fb)\n\n## Methods, Attributes, and Options\n\n### `new HMLS([options])`\n\nInstantiates a new `hmls` object.  `options` has the following defaults:\n\n```js\n{\n  server: {\n    host: 'localhost',\n    port: '8080'\n  },\n  lasso: {\n    outputDir: path.join(__dirname, '..', '..', 'static'),\n    plugins: ['lasso-marko']\n  },\n  projectRoot: path.join(__dirname, '..', '..'),\n  routesPath: [path.join(__dirname, '..', '..', 'routes')],\n  assetsPath: [path.join(__dirname, '..', '..', 'assets')],\n  ioPath: path.join(__dirname, '..', '..', 'io')\n }\n```\n\n* `server` - this object will be passed _directly_ to `hapi`'s constructor.  See https://hapijs.com/api for full options.\n* `lasso` - this object will be passed _directly_ to `lasso`'s `lasso.configure()` method.  `lasso.outpurDir` must be set, at a minimum, this specifies the folder where `lasso` will output bundled resources.  It defaults to `/static`.  `HMLS` will automatically use `inert` to serve this folder.\n* `routesPath` - `HMLS` will search this folder, or array of folders, for `hapi` routes. More precisely said, it will add each file's exported object to `hapi`'s route table.  _ALL_ files in this folder must export an object, or an array of objects, that are `hapi` routes.\n* `assetsPath` - `HMLS` will serve all files in this folder, or array of folders, at `/assets`, useful for static resources like images.\n* `ioPath` - `HMLS` wires up `socket.io`, any file in this folder is expected to export a function with the signature `function(io) {}`, where `io` is the `socket.io` instance.\n\n### `async hmls.init()`\n\nReturns: `Promise`\n\nInitializes everything (`hapi`, `lasso`, `sockets`, et cetera).\n\nIf this is not called explicitly it is called in `hmls.start()`.\n\nIf you want to do things to the individual components prior to starting `hapi` (like manually adding routes to `hapi`) this is useful.\n\n### `async hmls.start()`\n\nReturns: `Promise`\n\nStarts the `hapi` server, will invoke `hmls.init()` if it has not already been invoked.\n\n### `hmls.server`\n\nThe `hapi` server object.\n\n### `hmls.lasso`\n\nThe `lasso` instance.\n\n### `hmls.io`\n\nThe `socket.io` instance.\n\n## Events\n\n`HMLS` emits several events, they are emitted in this order:\n\n### willInitialize\n\nEmitted when `async hmls.init()` is about to start.\n\n### initialized\n\nEmitted after `async hmls.init()` has completed.\n\n### willRegisterRoutes\n\nEmitted right before routes in `routesPath` are added.\n\n### routesRegistered\n\nEmitted after all routes are added.\n\n### willStart\n\nEmitted just before `async hmls.start()` starts.\n\n### started\n\nEmitted after `async hmls.start()` has completed.  Useful for things like rigging `browser-refresh` or other things that require that all of the \"initial work\" has been done and the app is ready to go.\n\n## Structure and Architecture\n \n`Hapi` is _fantastic_ at serving static content (well, really any content).  `Hapi` is the webserver used in `HMLS`.  You can directly access the `hapi` instance with `hmls.server`.\n\n`Marko` is _fantastic_ at rendering dynamic content.  `Marko` is used as `HMLS`' templating engine.\n\n`Lasso` is _fantastic_ at bundling resources together (client JS, CSS, et cetera).  You can directly access the `lasso` instance with `hmls.lasso`.\n\n`socket.io` is super neat and allows the client and the server to communicate via websockets.  \n\n(see where I am going here?)\n\nAll `HMLS` really does is wire all of these pieces together, while exposing each piece, so you can get as hardcore with each piece as you like.\n\n### Project Structure\n\n#### /routes\n\nThis folder should contain JS files that export `hapi` routes.  By default it is the `routes` folder in your project root.  Change this with `options.routesPath`.\n\nAn example of a trivial route file:\n\n```js\n'use strict'\n\nmodule.exports = [{\n  method: 'get',\n  path: '/',\n  handler: async function (req, h) {\n    return 'I get rendered to the browser!'\n  }\n}]\n```\n\n#### /static\n\nThis folder is where `lasso` will output bundled resources.  By default it is the `static` folder in your project root.  Change this with `options.lasso.outputDir`.\n\n#### /assets\n\nYou can put anything in here that you'd like to be served, like images or other resources. By default it is the `assets` folder in your project root.  Change this with `options.assetsPath`.\n\n#### /io\n\nAll files in this folder will be `require`d.  It is assumed that each file will export a single function whose one parameter is the HMLS `socket.io` instance (accessible anytime via `hmls.io`).  Then you can do whatever you like via `socket.io`.  Here's an example of a file in the `/io` folder:\n\n```js\n'use strict'\n\nmodule.exports = function (io) {\n  io.on('connection', function(socket) {\n    console.log('%s connected', socket.id)\n  })\n}\n```\n\n## Examples\n\n### With a simple `marko` template\n\n#### index.js\n \n ```js\n'use strict'\n\nconst HMLS = require('hmls')\n\nconst vc = new HMLS()\n\n!async function () {\n  await vc.init()\n  await vc.start()\n  console.log('server started: %s', vc.server.info.uri)\n}()\n  .catch((err) =\u003e {\n    console.error(err.message)\n  })\n```\n\n#### pages/slash/index.marko\n\n```html\n\u003ch1\u003e\n    The current \u003ccode\u003eDate\u003c/code\u003e is ${input.now}!\n\u003c/h1\u003e\n```\n\n#### routes/slash/index.js\n\n```js\n'use strict'\n\nmodule.exports = [{\n  method: 'get',\n  path: '/',\n  handler: async function (req, h) {\n    const page = require('~/pages/slash/index.marko')\n    return page.stream(\n      {\n        now: new Date()\n      }\n    )\n  }\n}]\n```\n\n### Bundling assets with `lasso`\n\nYou can use `lasso`'s manifest file (`browser.json`) and its taglib in `marko` files to bundle assets.\n\n#### index.js\n\n```js\n'use strict'\n\nconst HMLS = require('hmls')\n\nconst vc = new HMLS()\n\nvc.on('started', () =\u003e {\n  console.log('server started at %s', vc.server.info.uri)\n})\n\nvc.start()\n  .catch(function(err) {\n    console.error(err.message)\n  })\n```\n\n#### routes/slash/index.js\n\n```js\n'use strict'\n\nmodule.exports = [{\n  method: 'get',\n  path: '/',\n  handler: async function (req, h) {\n    try {\n      const page = require('~/pages/slash/index.marko')\n      return page.stream(\n        {\n          now: new Date()\n        }\n      )\n    } catch (err) {\n      console.error(err.message)\n      return err.message\n    }\n  }\n}]\n```\n\n#### pages/slash/index.marko\n\n```html\n\u003classo-page package-path=\"./browser.json\"/\u003e\n\u003c!doctype html\u003e\n\u003chtml lang=\"en\"\u003e\n\u003chead\u003e\n    \u003cmeta charset=\"UTF-8\"\u003e\n    \u003ctitle\u003e/\u003c/title\u003e\n    \u003classo-head/\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n\u003ch1\u003e\n    The current \u003ccode\u003eDate\u003c/code\u003e is ${input.now}!\n\u003c/h1\u003e\n\u003classo-body/\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\n#### pages/slash/browser.json\n\n```json\n{\n  \"dependencies\": [\n    {\n      \"type\": \"js\",\n      \"url\": \"//ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js\"\n    },\n    \"./lib1.js\",\n    \"./lib2.js\",\n    \"./style.css\"\n  ]\n}\n```\n\n#### pages/slash/lib1.js\n\n```js\nconsole.log('from pages/slash/lib1.js');\n```\n\n#### pages/slash/lib2.js\n\n```js\nconsole.log('from pages/slash/lib2.js');\n```\n\n#### pages/slash/style.css\n\n```css\nbody {\n    background-color: #eee;\n}\n\nh1 {\n    color: red;\n}\n```\n\nNow the page source will look something like this:\n\n```html\n\u003c!doctype html\u003e\n\u003chtml lang=\"en\"\u003e\n   \u003chead\u003e\n      \u003cmeta charset=\"UTF-8\"\u003e\n      \u003ctitle\u003e/\u003c/title\u003e\n      \u003clink rel=\"stylesheet\" href=\"/static/slash-4c51a8fb.css\"\u003e\n   \u003c/head\u003e\n   \u003cbody\u003e\n      \u003ch1\u003eThe current \u003ccode\u003eDate\u003c/code\u003e is Sun May 14 2017 17:31:37 GMT-0400 (EDT)!\u003c/h1\u003e\n      \u003cscript src=\"//ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js\"\u003e\u003c/script\u003e\n      \u003cscript src=\"/static/slash-71d36ac2.js\"\u003e\u003c/script\u003e\n   \u003c/body\u003e\n\u003c/html\u003e\n```\n\n`/static/slash-71d36ac2.js` will look like this:\n\n```js\nconsole.log('from pages/slash/lib1.js');\nconsole.log('from pages/slash/lib2.js');\n```\n\n_Notice that the two JS lib files have been combined into one resource, also notice the jQuery injection._\n \n### socket.io\n\n`socket.io` is all wired up.  To add sockets to `HMLS` add JS files to the `/io` folder (or whichever folder) set with `options.ioPath`.\n\nA trivial file in the `/io` folder:\n\n```js\n'use strict'\n\nmodule.exports = function (io) {\n  io.on('connection', function(socket) {\n    console.log('%s connected', socket.id)\n  })\n}\n```\n\nThen the view (the `.marko` file) should include the `socket.io` client JS library (simply paste `\u003cscript src=\"/socket.io/socket.io.js\"\u003e\u003c/script\u003e`).\n\nFor example:\n\n#### pages/slash/index.marko\n\n```html\n\u003classo-page package-path=\"./browser.json\"/\u003e\n\u003c!doctype html\u003e\n\u003chtml lang=\"en\"\u003e\n\u003chead\u003e\n    \u003cmeta charset=\"UTF-8\"\u003e\n    \u003ctitle\u003e/\u003c/title\u003e\n    \u003classo-head/\u003e\n    \u003cscript src=\"/socket.io/socket.io.js\"\u003e\u003c/script\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n\u003ch1\u003e\n    The current \u003ccode\u003eDate\u003c/code\u003e is ${input.now}!\n\u003c/h1\u003e\n\u003classo-body/\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\n#### io/slash/index.js\n\n```js\n'use strict'\n\nmodule.exports = function (io) {\n  io.on('connection', function(socket) {\n    console.log('%s connected', socket.id)\n    socket.on('greetingAcknowledgement', function() {\n      console.log('%s acknowledged `greeting`', socket.id)\n    })\n    console.log('sending `greeting` to %s', socket.id)\n    socket.emit('greeting')\n  })\n}\n```\n\nNow you can interact with the server with client JS:\n\n#### pages/slash/lib.js\n\n```js\nvar socket = io();\n\nsocket.on('greeting', function() {\n  console.log('received `greeting` from server');\n  socket.emit('greetingAcknowledgement');\n});\n```\n\nNode console:\n\n```js\nserver started at http://localhost:8080\nVwyAfRLa6cSJM3neAAAA connected\nsending `greeting` to VwyAfRLa6cSJM3neAAAA\nVwyAfRLa6cSJM3neAAAA acknowledged `greeting`\n```\n\nBrowser console:\n\n```js\nreceived `greeting` from server\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcharlesread%2Fhmls","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcharlesread%2Fhmls","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcharlesread%2Fhmls/lists"}