2.2.0 • Published 6 years ago

hmls v2.2.0

Weekly downloads
2
License
ISC
Repository
github
Last release
6 years ago

Build Status Dependencies Coverage Status

HMLS

Join the chat at https://gitter.im/hmls-njs/Lobby

Go from 0 to webapp in seconds.

HMLS gives you, with zero configuration:

  • A full blown and easy-to-use web server with a ton of plugins; hapi
  • An awesome view layer via marko (easy-to-use taglibs, reusable custom components, et cetera)
  • Asset bundling (and serving) via lasso (complete with less CSS pre-processing)
  • Bi-directional communication is as easy as writing a few callbacks with socket.io, and is ready to go

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.

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.

HMLS really represents the VC in MVC.

Change Log

  • 2017-12-16 - As of v2.x HMLS is fully compatible with Hapi v17.x and async/await.

Installation

mkdir <project>

cd <project>

npm init

npm install --save hmls

Then make a few files:

index.js

'use strict'

const HMLS = require('hmls')

const vc = new HMLS()

!async function () {
 await vc.init()
 await vc.start()
 console.log('server started: %s', vc.server.info.uri)
}()
 .catch((err) => {
   console.error(err.message)
 })

routes/index.js

'use strict'

module.exports = {
  method: 'get',
  path: '/',
  handler: async function (req, h) {
    return 'Welcome to the home page!'
  }
}

Then node index.js and visit http://localhost:8080 to see your home page.

Too lazy for that noise?

Check this out, just a one liner that installs everything and starts your project:

$ mkdir my-project && cd my-project && npm i -g hmls && npm init && npm i -S hmls && hmls --scaffold && node index.js

Scaffolding

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.

First create a project and install HMLS globally:

$ mkdir <project>

$ cd <project>

$ npm init

$ npm install --save hmls

$ npm install --global hmls

Then:

$ hmls --scaffold && node index.js

Simple as that!

Related Plugins

I've written a few hapi-related plugins that I use all the time, mostly for authorization and authentication:

Methods, Attributes, and Options

new HMLS([options])

Instantiates a new hmls object. options has the following defaults:

{
  server: {
    host: 'localhost',
    port: '8080'
  },
  lasso: {
    outputDir: path.join(__dirname, '..', '..', 'static'),
    plugins: ['lasso-marko']
  },
  projectRoot: path.join(__dirname, '..', '..'),
  routesPath: [path.join(__dirname, '..', '..', 'routes')],
  assetsPath: path.join(__dirname, '..', '..', 'assets'),
  ioPath: path.join(__dirname, '..', '..', 'io')
 }
  • server - this object will be passed directly to hapi's constructor. See https://hapijs.com/api for full options.
  • 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.
  • 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.
  • assetsPath - HMLS will serve all files in this folder at /assets, useful for static resources like images.
  • 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.

async hmls.init()

Returns: Promise

Initializes everything (hapi, lasso, sockets, et cetera).

If this is not called explicitly it is called in hmls.start().

If you want to do things to the individual components prior to starting hapi (like manually adding routes to hapi) this is useful.

async hmls.start()

Returns: Promise

Starts the hapi server, will invoke hmls.init() if it has not already been invoked.

hmls.server

The hapi server object.

hmls.lasso

The lasso instance.

hmls.io

The socket.io instance.

Events

initialized

Emitted after async hmls.init() has completed.

started

Emitted 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.

Structure and Architecture

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.

Marko is fantastic at rendering dynamic content. Marko is used as HMLS' templating engine.

Lasso is fantastic at bundling resources together (client JS, CSS, et cetera). You can directly access the lasso instance with hmls.lasso.

socket.io is super neat and allows the client and the server to communicate via websockets.

(see where I am going here?)

All 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.

Project Structure

/routes

This 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.

An example of a trivial route file:

'use strict'

module.exports = [{
  method: 'get',
  path: '/',
  handler: async function (req, h) {
    return 'I get rendered to the browser!'
  }
}]

/static

This 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.

/assets

You 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.

/io

All files in this folder will be required. 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:

'use strict'

module.exports = function (io) {
  io.on('connection', function(socket) {
    console.log('%s connected', socket.id)
  })
}

Examples

With a simple marko template

index.js

'use strict'

const HMLS = require('hmls')

const vc = new HMLS()

vc.on('started', () => {
 console.log('server started at %s', vc.server.info.uri)
})

vc.init()
vc.start()

pages/slash/index.marko

<h1>
    The current <code>Date</code> is ${input.now}!
</h1>

routes/slash/index.js

'use strict'

module.exports = [{
  method: 'get',
  path: '/',
  handler: async function (req, h) {
    const page = require('~/pages/slash/index.marko')
    return page.stream(
      {
        now: new Date()
      }
    )
  }
}]

Bundling assets with lasso

You can use lasso's manifest file (browser.json) and its taglib in marko files to bundle assets.

index.js

'use strict'

const HMLS = require('hmls')

const vc = new HMLS()

vc.on('started', () => {
  console.log('server started at %s', vc.server.info.uri)
})

vc.init()
vc.start()

routes/slash/index.js

'use strict'

module.exports = [{
  method: 'get',
  path: '/',
  handler: async function (req, h) {
    try {
      const page = require('~/pages/slash/index.marko')
      return page.stream(
        {
          now: new Date()
        }
      )
    } catch (err) {
      console.error(err.message)
      return err.message
    }
  }
}]

pages/slash/index.marko

<lasso-page package-path="./browser.json"/>
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>/</title>
    <lasso-head/>
</head>
<body>
<h1>
    The current <code>Date</code> is ${input.now}!
</h1>
<lasso-body/>
</body>
</html>

pages/slash/browser.json

{
  "dependencies": [
    {
      "type": "js",
      "url": "//ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"
    },
    "./lib1.js",
    "./lib2.js",
    variables.css
  ]
}

pages/slash/lib1.js

console.log('from pages/slash/lib1.js');

pages/slash/lib2.js

console.log('from pages/slash/lib2.js');

pages/slash/style.css

body {
    background-color: #eee;
}

h1 {
    color: red;
}

Now the page source will look something like this:

<!doctype html>
<html lang="en">
   <head>
      <meta charset="UTF-8">
      <title>/</title>
      <link rel="stylesheet" href="/static/slash-4c51a8fb.css">
   </head>
   <body>
      <h1>The current <code>Date</code> is Sun May 14 2017 17:31:37 GMT-0400 (EDT)!</h1>
      <script src="//ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
      <script src="/static/slash-71d36ac2.js"></script>
   </body>
</html>

/static/slash-71d36ac2.js will look like this:

console.log('from pages/slash/lib1.js');
console.log('from pages/slash/lib2.js');

Notice that the two JS lib files have been combined into one resource, also notice the jQuery injection.

socket.io

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.

A trivial file in the /io folder:

'use strict'

module.exports = function (io) {
  io.on('connection', function(socket) {
    console.log('%s connected', socket.id)
  })
}

Then the view (the .marko file) should include the socket.io client JS library (simply paste <script src="/socket.io/socket.io.js"></script>).

For example:

pages/slash/index.marko

<lasso-page package-path="./browser.json"/>
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>/</title>
    <lasso-head/>
    <script src="/socket.io/socket.io.js"></script>
</head>
<body>
<h1>
    The current <code>Date</code> is ${input.now}!
</h1>
<lasso-body/>
</body>
</html>

io/slash/index.js

'use strict'

module.exports = function (io) {
  io.on('connection', function(socket) {
    console.log('%s connected', socket.id)
    socket.on('greetingAcknowledgement', function() {
      console.log('%s acknowledged `greeting`', socket.id)
    })
    console.log('sending `greeting` to %s', socket.id)
    socket.emit('greeting')
  })
}

Now you can interact with the server with client JS:

pages/slash/lib.js

var socket = io();

socket.on('greeting', function() {
  console.log('received `greeting` from server');
  socket.emit('greetingAcknowledgement');
});

Node console:

server started at http://localhost:8080
VwyAfRLa6cSJM3neAAAA connected
sending `greeting` to VwyAfRLa6cSJM3neAAAA
VwyAfRLa6cSJM3neAAAA acknowledged `greeting`

Browser console:

received `greeting` from server
2.2.0

6 years ago

2.1.6

6 years ago

2.1.5

6 years ago

2.1.4

6 years ago

2.1.3

6 years ago

2.1.2

6 years ago

2.1.1

6 years ago

2.1.0

6 years ago

2.0.2

6 years ago

2.0.1

6 years ago

2.0.0

6 years ago

1.1.11

7 years ago

1.1.10

7 years ago

1.1.9

7 years ago

1.1.8

7 years ago

1.1.7

7 years ago

1.1.6

7 years ago

1.1.5

7 years ago

1.1.4

7 years ago

1.1.3

7 years ago

1.1.2

7 years ago

1.1.1

7 years ago

1.1.0

7 years ago

1.0.20

7 years ago

1.0.13

7 years ago

1.0.12

7 years ago

1.0.11

7 years ago

1.0.10

7 years ago

1.0.9

7 years ago

1.0.8

7 years ago

1.0.7

7 years ago

1.0.6

7 years ago

1.0.5

7 years ago

1.0.4

7 years ago

1.0.3

7 years ago

1.0.2

7 years ago

1.0.1

7 years ago

1.0.0

7 years ago