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(modules, opts)

    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(opts, useJs)

    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.

    • example:

      When the client code is loaded on the browser, you can use the nokit.log to log anything to server's terminal. Nobone server will auto-format and log the information to the terminal. It's convinient for mobile development when remote debug is not possible.

      # The nokit is assigned to the "window" object. 
      nokit.log { a: 10 }
      nokit.log 10
  • Overview

    It is just a Express.js wrap.

  • service(opts)

    Create a Service instance.

    • param: opts { Object }

      Defaults:

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

  • server

    The server object of the express object.

  • 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(opts)

    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(opts)

    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(opts)

    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(opts)

    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(path, ext, data, isCache, reqPath, handler)

    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(path)

    Release memory cache of a file.

    • param: path { String }
  • e.compiled(path, content, handler)

    • event: { compiled }

    • param: path { String }

      The compiled file.

    • param: content { String }

      Compiled content.

    • param: handler { FileHandler }

      The current file handler.

  • e.compileError(path, err)

    • event: { compileError }

    • param: path { String }

      The error file.

    • param: err { Error }

      The error info.

  • e.watchFile(path, curr, prev)

    • event: { watchFile }

    • param: path { String }

      The path of the file.

    • param: curr { fs.Stats }

      Current state.

    • param: prev { fs.Stats }

      Previous state.

  • e.fileDeleted(path)

    • event: { fileDeleted }

    • param: path { String }

      The path of the file.

  • e.fileModified(path)

    • event: { fileModified }

    • param: path { String }

      The path of the file.

  • getCache(handler)

    Set handler cache.

    • param: handler { FileHandler }

    • return: { Promise }

  • genHandler(path, handler)

    Generate a file handler.

    • param: path { String }

    • param: handler { FileHandler }

    • return: { FileHandler }

  • Overview

    It use the renderer module to create some handy functions.

  • compiler(str, path, data)

    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(opts)

    Folder middleware.

    • param: opts { Object }

    • return: { Function }

  • static(renderer, opts)

    Static middleware.

    • param: renderer { Renderer }

    • param: opts { Object }

    • return: { Function }

  • staticEx(renderer, opts)

    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(opts)

    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

    An string helper for globalization.

  • self(cmd, args, name)

    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(filePath)

    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