nobone

A server library tries to understand what developers really need.

A server library tries to understand what developers really need.

The philosophy behind NoBone is providing possibilities rather than telling developers what they should do. All the default behaviors are just examples of how to use NoBone. All the APIs should work together without pain.


  • Code you program, not configure.
  • Built for performance.
  • Not only a good dev-tool, but also good at production.
  • Supports programmable plugins.
  • Cross platform.
  • Pure js, supports coffee by default.

Install as an dependency:

npm install nobone
 
# View a better nobone documentation than Github readme. 
node_modules/.bin/nobone --doc

Or you can install it globally:

npm i -g nobone
 
# View a better nobone documentation than Github readme. 
nb -d

  1. Why doesn't the auto-reaload work?

Check if the process.env.NODE_ENV is set to development.

  1. Why doesn't the compiler work properly?

Please delete the .nobone cache directory, and try again.

  1. How to view the documentation with TOC (table of contents) or offline?

If you have installed nobone globally, just execute nobone --doc or nobone -d. If you are on Windows or Mac, it will auto open the documentation.

If you have installed nobone with npm install nobone in current directory, execute node_modules/.bin/nobone -d.

  1. Why I can't execute the entrance file with nobone cli tool?

Don't execute nobone with a directory path when you want to start it with an entrance file.

  1. When serving jade or less, it doesn't work.

These are optinal packages, you have to install them first. For example, if you want nobone to support jade, please execute npm install -g jade.

  1. How to disable that annoying nobone update warn?

There's an option to do this: nb = nobone null, { checkUpgrade: false }.


process.env.NODE_ENV = 'development'
 
nobone = require 'nobone'
 
port = 8219
 
# If you want to init without a specific module, 
# for example 'db' and 'service' module, just exclude them: 
#   nobone { 
#       renderer: {} 
#   } 
# By default it only loads two modules: `service` and `renderer`. 
nb = nobone {
    db: { dbPath: './test.db' }
    proxy: {}
    renderer: {}
    service: {}
    lang: {
        langPath: 'examples/fixtures/lang'
        current: 'cn'
    }
}
 
# Service 
nb.service.get '/'(req, res) ->
    # Renderer 
    # It will auto-find the 'examples/fixtures/index.tpl', and render it to html. 
    # You can also render jade, coffee, stylus, less, sass,markdown, 
    # or define custom handlers. 
    # When you modify the `examples/fixtures/index.tpl`, the page will auto-reload. 
    nb.renderer.render('examples/fixtures/index.html')
    .then (tplFn) ->
        res.send tplFn({ name: 'nobone' })
 
# Launch express.js 
nb.service.listen port->
    # Kit 
    # A smarter log helper. 
    nb.kit.log 'Listen port ' + port
 
    # Open default browser. 
    nb.kit.xopen 'http://127.0.0.1:' + port
 
# Static folder for auto-service of coffeescript and stylus, etc. 
nb.service.use nb.renderer.static('examples/fixtures')
 
# Database 
# Nobone has a build-in file database. 
# Here we save 'a' as value 1. 
nb.db.loaded.then ->
    nb.db.exec (db) ->
        db.doc.a = 1
        db.save('DB OK')
    .then (data) ->
        nb.kit.log data
 
    # Get data 'a'. 
    nb.kit.log nb.db.doc.a
 
# Proxy 
# Proxy path to specific url. 
nb.service.get '/proxy.*'(req, res) ->
    # If you visit "http://127.0.0.1:8013/proxy.js", 
    # it'll return the "http://127.0.0.1:8013/main.js" from the remote server, 
    # though here we just use a local server for test. 
    nb.proxy.url reqres"http://127.0.0.1:#{port}/main." + req.params[0]
 
# Globalization 
nb.kit.log 'human'.# -> '人类' 
nb.kit.log 'open|formal'.# -> '开启' 
nb.kit.log nb.lang('find %s men'[10]'jp') # -> '10人が見付かる' 
 
close = ->
    # Release all the resources. 
    nb.close().then ->
        nb.kit.log 'Peacefully closed.'
 

See the examples.

You can use nobone as an alternative of node bin or coffee, it will auto detect file type and run it properly.

Such as nb app.js, nb app.coffee. It will run the script and if the script changed, it will automatically restart it.

You can use nb -w off app.js to turn off the watcher. You can pass a json to the watch list nb -w '["a.js", "b.js"]' app.js. Any of watched file changed, the program will be restarted.

Such as nb /home/, it will open a web server for you to browse the folder content. As you edit the html file in the folder, nobone will live reload the content for you. For css or image file change, it won't refresh the whole page, only js file change will trigger the page reload.

You can use url query ?source and url hash #L to view a source file. Such as http://127.0.0.1:8013/app.js?source#L10, it will open a html page with syntax highlight. Or full version http://127.0.0.1:8013/app.js?source=javascript#L10

You can use ?gotoDoc to open a dependencies' markdown file. Such as jdb/readme.md?gotoDoc. Nobone will use the node require's algorithm to search for the module recursively.


Install nobone globally: npm install -g nobone

# Help info
nb -h
 
# Use it as a static file server for current directory.
# Visit 'http://127.0.0.1/nobone' to see a better nobone documentation.
nb
 
# Use regex to filter the log info.
# Print out all the log if it contains '.ejs'
logReg='.ejs' nb
 
# Use custom logic to start up.
nb app.js
watchPersistent=off nb app.js
 
# Scaffolding helper
nb bone -h
 

This command is inherited from the nokit's. For more information:

# Run default task
no
 
# See help
no -h

Here I give a simple instruction. For a real example, see nobone-sync.

NoBone support a simple way to implement npm plugin. And your npm package doesn't have to waist time to install nobone dependencies. The package.json file can only have these properties:

{
  "name": "nobone-sample",
  "version": "0.0.1",
  "description": "A sample nobone plugin.",
  "main": "main.coffee"
}

The name of the plugin should prefixed with nobone-.

The main.coffee file may looks like:

{ kit } = require 'nobone'
kit.log 'sample plugin'

Suppose we have published the nobone-sampe plugin with npm.

Other people can use the plugin after installing it with either npm install nobone-sample or npm install -g nobone-sample.

To run the plugin simply use nobone sample.

You can use nb ls to list all installed plugins.


It's highly recommended reading the API doc locally by command nb --doc

  • Overview

    NoBone has several modules and a helper lib. All the modules are optional. Only the kit lib is loaded by default and is not optional.

    Most of the async functions are implemented with [Promise][Promise]. [Promise]: https://github.com/petkaantonov/bluebird

  • nobone

    Main constructor.

    • param: modules { Object }

      By default, it only load two modules, service and renderer:

      {
          service: {}
          renderer: {}
          db: null
          proxy: null
          lang: null
       
          langPath: null # language set directory 
      }
    • param: opts { Object }

      Defaults:

      {
          # Whether to auto-check the version of nobone. 
          checkUpgrade: true
       
      # Whether to enable the sse live reload. 
          autoReload: true
      }
    • return: { Object }

      A nobone instance.

  • close

    Release the resources.

    • return: { Promise }
  • version

    Get current nobone version string.

    • return: { String }
  • checkUpgrade

    Check if nobone need to be upgraded.

    • return: { Promise }
  • client

    The NoBone client helper.

    • static:

    • param: opts { Object }

      The options of the client, defaults:

      {
          autoReload: kit.isDevelopment()
          host: '' # The host of the event source. 
      }
    • param: useJs { Boolean }

      By default use html. Default is false.

    • return: { String }

      The code of client helper.

  • Overview

    It is just a Express.js wrap.

  • service

    Create a Service instance.

    • param: opts { Object }

      Defaults:

      {
          autoLog: kit.isDevelopment()
          enableRemoteLog: kit.isDevelopment()
          enableSse: kit.isDevelopment()
          express: {}
      }
    • return: { Service }

  • server

    The server object of the express object.

  • sse

    A Server-Sent Event Manager. The namespace of nobone sse is /nobone-sse. For more info see [Using server-sent events][Using server-sent events]. NoBone use it to implement the live-reload of web assets. [Using server-sent events]: https://developer.mozilla.org/en-US/docs/Server-sentEvents/UsingServer-sentEvents

    • type: { SSE }

    • property: sessions { Array }

      The sessions of connected clients.

    • property: retry { Integer }

      The reconnection time to use when attempting to send the event, unit is ms. Default is 1000ms. A session object is something like:

      {
          req  # The express.js req object. 
          res  # The express.js res object. 
      }
    • example:

      You browser code should be something like this:

      es = new EventSource('/nobone-sse')
      es.addEventListener('eventName'(e) ->
          msg = JSON.parse(e.data)
          console.log(msg)
  • e.sseConnected

    This event will be triggered when a sse connection started. The event name is a combination of sseConnected and req.path, for example: "sseConnected/test"

    • event: { sseConnected }

    • param: session { SSESession }

      The session object of current connection.

  • e.sseClose

    This event will be triggered when a sse connection closed.

    • event: { sseClose }

    • param: session { SSESession }

      The session object of current connection.

  • sse.create

    Create a sse session.

    • param: req { Express.req }

    • param: res { Express.res }

    • return: { SSESession }

  • session.emit

    Emit message to client.

    • param: event { String }

      The event name.

    • param: msg { Object | String }

      The message to send to the client.

  • sse.emit

    Broadcast a event to clients.

    • param: event { String }

      The event name.

    • param: msg { Object | String }

      The data you want to emit to session.

    • param: { String }

      [path] The namespace of target sessions. If not set, broadcast to all clients.

  • Overview

    An abstract renderer for any content, such as source code or image files. It automatically uses high performance memory cache. This renderer helps nobone to build a passive compilation architecture. You can run the benchmark to see the what differences it makes. Even for huge project the memory usage is negligible.

  • renderer

    Create a Renderer instance.

    • param: opts { Object }

      Defaults:

      {
          enableWatcher: kit.isDevelopment()
          autoLog: kit.isDevelopment()
       
          # If renderer detects this pattern, it will auto-inject `noboneClient.js` 
          # into the page. 
          injectClientReg: /<html[^<>]*>[\s\S]*</html>/i
       
          cacheDir: '.nobone/rendererCache'
          cacheLimit: 1024
       
          fileHandlers: {
              '.html': {
                  default: true
                  extSrc: ['.tpl','.ejs''.jade']
                  # Extra files to watch. 
                  extraWatch: { path1: 'comment1'path2: 'comment2'... }
                  encoding: 'utf8' # optional, default is 'utf8' 
                  dependencyReg: {
                      '.ejs': /<%[\n\r\s]*include\s+([^\r\n]+)\s*%>/g
                  }
                  compiler: (str, path, data) -> ...
              }
       
              # Simple coffee compiler 
              '.js': {
                  extSrc: '.coffee'
                  compiler: (str, path) -> ...
              }
       
              # Browserify a main entrance file. 
              '.jsb': {
                  type: '.js'
                  extSrc: '.coffee'
                  dependencyReg: /require\s+([^\r\n]+)/g
                  compiler: (str, path) -> ...
              }
              '.css': {
                  extSrc: ['.styl''.less''.sass''.scss']
                  compiler: (str, path) -> ...
              }
              '.md': {
                  type: 'html' # Force type, optional. 
                  extSrc: ['.md''.markdown']
                  compiler: (str, path) -> ...
              }
          }
      }
    • return: { Renderer }

  • fileHandlers

    You can access all the fileHandlers here. Manipulate them at runtime.

    • type: { Object }

    • example:

      { renderer } = nobone()
      renderer.fileHandlers['.css'].compiler = (str, path) ->
          stylus = kit.requireOptional 'stylus'__dirname
       
          compile = stylus(strdata).set 'filename'path
          # Take advantage of the syntax parser. 
          this.dependencyPaths = compile.deps()
          kit.promisify(compile.rendercompile)()
  • cachePool

    The cache pool of the result of fileHandlers.compiler

    • type: { Object }

      Key is the file path.

  • dir

    Set a service for listing directory content, similar with the serve-index project.

    • param: opts { String | Object }

      If it's a string it represents the rootDir.

    • return: { Middleware }

      Experss.js middleware.

  • static

    Set a static directory proxy. Automatically compile, cache and serve source files for both deveopment and production.

    • param: opts { String | Object }

      If it's a string it represents the rootDir. of this static directory. Defaults:

      {
          rootDir: '.'
       
          # Whether enable serve direcotry index. 
          index: kit.isDevelopment()
       
          injectClient: kit.isDevelopment()
       
          # Useful when mapping a normal path to a hashed file. 
          # Such as map 'lib/main.js' to 'lib/main-jk2x.js'. 
          reqPathHandler: decodeURIComponent
       
          # Check path such as '../../../../etc/passwd'. 
          isMalicious: ->
      }
    • return: { Middleware }

      Experss.js middleware.

  • staticEx

    An extra version of renderer.static. Better support for markdown and source file.

    • param: opts { String | Object }

      If it's a string it represents the rootDir. of this static directory. Defaults:

      {
          rootDir: '.'
       
          # Whether enable serve direcotry index. 
          index: kit.isDevelopment()
       
          injectClient: kit.isDevelopment()
       
          # Useful when mapping a normal path to a hashed file. 
          # Such as map 'lib/main.js' to 'lib/main-jk2x.js'. 
          reqPathHandler: decodeURIComponent
      }
    • return: { Middleware }

      Experss.js middleware.

  • render

    Render a file. It will auto-detect the file extension and choose the right compiler to handle the content.

    • param: path { String | Object }

      The file path. The path extension should be the same with the compiled result file. If it's an object, it can contain any number of following params.

    • param: ext { String }

      Force the extension. Optional.

    • param: data { Object }

      Extra data you want to send to the compiler. Optional.

    • param: isCache { Boolean }

      Whether to cache the result, default is true. Optional.

    • param: reqPath { String }

      The http request path. Support it will make auto-reload more efficient.

    • param: handler { FileHandler }

      A custom file handler.

    • return: { Promise }

      Contains the compiled content.

    • example:

      # The 'a.ejs' file may not exists, it will auto-compile 
      # the 'a.ejs' or 'a.html' to html. 
      renderer.render('a.html').then (html) -> kit.log(html)
       
      # if the content of 'a.ejs' is '<% var a = 10 %><%= a %>' 
      renderer.render('a.ejs''.html').then (html) -> html == '10'
      renderer.render('a.ejs').then (str) -> str == '<% var a = 10 %><%= a %>'
  • close

    Release the resources.

  • releaseCache

    Release memory cache of a file.

    • param: path { String }
  • e.compiled

    • event: { compiled }

    • param: path { String }

      The compiled file.

    • param: content { String }

      Compiled content.

    • param: handler { FileHandler }

      The current file handler.

  • e.compileError

    • event: { compileError }

    • param: path { String }

      The error file.

    • param: err { Error }

      The error info.

  • e.watchFile

    • event: { watchFile }

    • param: path { String }

      The path of the file.

    • param: curr { fs.Stats }

      Current state.

    • param: prev { fs.Stats }

      Previous state.

  • e.fileDeleted

    • event: { fileDeleted }

    • param: path { String }

      The path of the file.

  • e.fileModified

    • event: { fileModified }

    • param: path { String }

      The path of the file.

  • getCache

    Set handler cache.

    • param: handler { FileHandler }

    • return: { Promise }

  • genHandler

    Generate a file handler.

    • param: path { String }

    • param: handler { FileHandler }

    • return: { FileHandler }

  • Overview

    It use the renderer module to create some handy functions.

  • compiler

    The compiler can handle any type of file.

    • context: { FileHandler }

      Properties:

      {
          ext: String # The current file's extension. 
          opts: Object # The current options of renderer. 
       
          # The file dependencies of current file. 
          # If you set it in the `compiler`, the `dependencyReg` 
          # and `dependencyRoots` should be left undefined. 
          dependencyPaths: Array
       
          # The regex to match dependency path. Regex or Table. 
          dependencyReg: RegExp
       
          # The root directories for searching dependencies. 
          dependencyRoots: Array
       
          # The source map informantion. 
          # If you need source map support, the `sourceMap`property 
          # must be set during the compile process. 
          # If you use inline source map, this property shouldn't be set. 
          sourceMap: String or Object
      }
    • param: str { String }

      Source content.

    • param: path { String }

      For debug info.

    • param: data { Any }

      The data sent from the render function. when you call the render directly. Default is an object:

      {
          _: lodash
          injectClient: kit.isDevelopment()
      }
    • return: { Promise }

      Promise that contains the compiled content.

  • dir

    Folder middleware.

    • param: opts { Object }

    • return: { Function }

  • static

    Static middleware.

    • param: renderer { Renderer }

    • param: opts { Object }

    • return: { Function }

  • staticEx

    Static middleware. Don't use it in production.

    • param: renderer { Renderer }

    • param: opts { Object }

    • return: { Function }

  • Overview

    See my [jdb][jdb] project.

    Offline Documentation [jdb]: https://github.com/ysmood/jdb

  • db

    Create a JDB instance.

    • param: opts { Object }

      Defaults:

      {
          dbPath: './nobone.db'
      }
    • return: { Jdb }

  • jdb.loaded

    A promise object that help you to detect when the db is totally loaded.

    • type: { Promise }
  • Overview

    For test, page injection development. A cross-platform programmable Fiddler alternative.

  • proxy

    Create a Proxy instance.

    • param: opts { Object }

      Defaults: { }

    • return: { Proxy }

  • url

    Use it to proxy one url to another.

    • param: req { http.IncomingMessage }

      Also supports Express.js.

    • param: res { http.ServerResponse }

      Also supports Express.js.

    • param: url { String }

      The target url forced to. Optional. Such as force 'http://test.com/a' to 'http://test.com/b', force 'http://test.com/a' to 'http://other.com/a', force 'http://test.com' to 'other.com'.

    • param: opts { Object }

      Other options. Default:

      {
          # Limit the bandwidth byte per second. 
          bps: null
       
          # if the bps is the global bps. 
          globalBps: false
       
          agent: customHttpAgent
       
          # You can hack the headers before the proxy send it. 
          handleReqHeaders: (headers) -> headers
          handleResHeaders: (headers) -> headers
      }
    • param: err { Function }

      Custom error handler.

    • return: { Promise }

    • example:

      nobone = require 'nobone'
      { proxyservice } = nobone { proxy:{}service: {} }
       
      service.post '/a'(req, res) ->
          proxy.url reqres'a.com'(err) ->
              kit.log err
       
      service.get '/b'(req, res) ->
          proxy.url reqres'/c'
       
      service.get '/a.js'(req, res) ->
          proxy.url reqres'http://b.com/c.js'
       
      # Transparent proxy. 
      service.use proxy.url
  • connect

    Http CONNECT method tunneling proxy helper. Most times used with https proxing.

    • param: req { http.IncomingMessage }

    • param: sock { net.Socket }

    • param: head { Buffer }

    • param: host { String }

      The host force to. It's optional.

    • param: port { Int }

      The port force to. It's optional.

    • param: err { Function }

      Custom error handler.

    • example:

      nobone = require 'nobone'
      { proxyservice } = nobone { proxy:{}service: {} }
       
      # Directly connect to the original site. 
      service.server.on 'connect'proxy.connect
  • pac

    A pac helper.

    • param: currHost { String }

      The current host for proxy server. It's optional.

    • param: ruleHandler { Function }

      Your custom pac rules. It gives you three helpers.

      url # The current client request url. 
      host # The host name derived from the url. 
      currHost = 'PROXY host:port;' # Nobone server host address. 
      direct =  "DIRECT;"
      match = (pattern) -> # A function use shExpMatch to match your url. 
      proxy = (target) -> # return 'PROXY target;'. 
    • return: { Function }

      Express Middleware.

      nobone = require 'nobone'
      { proxyservice } = nobone { proxy:{}service: {} }
       
      service.get '/pac'proxy.pac ->
          if match 'http://a.com/*'
              currHost
          else if url == 'http://c.com'
              proxy 'd.com:8123'
          else
              direct
  • Overview

    An string helper for globalization.

  • self

    It will find the right key/value pair in your defined langSet. If it cannot find the one, it will output the key directly.

    • param: cmd { String }

      The original text.

    • param: args { Array }

      The arguments for string format. Optional.

    • param: name { String }

      The target language name. Optional.

    • return: { String }

    • example:

      { lang } = require('nobone')(lang: {})
      lang.langSet =
          human:
              cn: '人类'
              jp: '人間'
       
          open:
              cn:
                  formal: '开启' # Formal way to say 'open' 
                  casual: '打开' # Casual way to say 'open' 
       
          'find %s men': '%s人が見付かる'
       
      lang('human''cn'langSet) # -> '人类' 
      lang('open|casual''cn'langSet) # -> '打开' 
      lang('find %s men'[10]'jp'langSet) # -> '10人が見付かる' 
    • example:

      { lang } = require('nobone')(
          lang: { langPath: 'lang.coffee' }
          current: 'cn'
      )
       
      'human'.# '人类' 
      'Good weather.'.lang('jp') # '日和。' 
       
      lang.current = 'en'
      'human'.# 'human' 
      'Good weather.'.lang('jp') # 'Good weather.' 
  • langSet

    Language collections.

    • type: { Object }

    • example:

      { lang } = require('nobone')(lang: {})
      lang.langSet = {
          'cn': { 'human': '人类' }
      }
  • current

    Current default language.

    • type: { String }

    • default:

      'en'

  • load

    Load language set and save them into the langSet. Besides, it will also add properties l and lang to String.prototype.

    • param: filePath { String }

      js or coffee files.

    • example:

      { lang } = require('nobone')(lang: {})
      lang.load 'assets/lang'
      lang.current = 'cn'
      log 'test'.# -> '测试'. 
      log '%s persons'.lang([10]) # -> '10 persons' 

See the doc/changelog.md file.


npm test

Goto see benchmark


Decouple libs.

Better test coverage.


May 2014, Yad Smood