Namek Dev
a developer's log
NamekDev

Modules in The Console - Take One

April 20, 2016

In previous posts (linked in the Summary) I discussed a topic of implementing modules in The Console. This time I’m going to describe my first step in this direction.

Currently The Console supports simple scripts - one .js file is one script, i.e. scripts/info/currency.js  is just a currency  command.

But that’s not enough. I want the modules, too. Here’s how I make them real.

The (modular) goal

I want to have both of two worlds:

  1. single-file scripts
  2. modules which are built of multiple files

I want to be able to reload both of them without restarting The Console.

In future I foresee efforts to bring npm modules.

The load algorithm

The Console should now analyze scripts  folder by scanning per-folder instead per-file.

  1. the folder can contain various .js files

    * these should be loaded as previously - one file is a one command
    
  2. however (!), if there’s index.js  or package.json  among the files:

    * treat the folder as a whole module
    * that means, read only package.json  (if exists!) or index.js
    

No module is loaded until scripts  folder is scanned throughout.

package.json?

What are those package jsons? That’s simple. **npm has them **and now The Console has them, too. package.json  defines a few things for a module:

  • meta info, e.g. module name, author of module, description, some URLs, etc.
  • version of module - this one is pretty important
  • dependencies - what other modules are needed to work with this one?
  • main - path to file that contains entry point to module, by default it is index.js

Load module into environment

When I deal with a module and I know name of entry file (it may be index.js by default) then I want to load it. However, currently, I just read whole script file into String in Java and then execute the script using JavaScript eval()  (kind of) when command is invoked.

Approach for modules will be different than eval() . Authors of scripts may want to import modules and I don’t want to ask Java for the code of module. That would not work with any npm module because some modules cache it’s settings or import more modules. Of course, I could inject my own require() or module.exports everytime but that doesn’t sound easy, rather hacky.

Now I’m going to load my modules directly through JavaScript environment by using load()  function which is provided by Nashorn. This is what I’ll use for that:

(function (context) {'use strict';

  if ('require' in context) return

  var settings = Object.create(null)
  var cache = Object.create(null)

  function executeAndCache(file) {
    var exports = {}, module = {exports: exports, id: file}

    Function('require', 'exports', 'module', read(file))
      .call(exports, require, exports, module)

    cache[file] = module.exports
    return cache[file]
  }

  function read(filePath) {
    if (settings.localDir)
      filePath = settings.localDir + '/' + filePath

    return new java.lang.String(
      java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(filePath))
    )
  }

  context.require = require

  function require(file) {
    if (file.slice(-3) !== '.js') file += '.js'
    return cache[file] || executeAndCache(file)
  }

  return settings
}(this))

Reload module into environment

There’s another trick to be done. Now, script files are loaded and then watched. Any modification of script resulted in reload of the script. During command invocation it will always use a fresh version of script.

With modules loaded by JavaScript (using require()  shown above) I have to tell JavaScript to reload a module. And here I can’t go without manually manipulating cache inside of require() . That’s tricky, but doable.

Modules, modules, modules…

I have discussed the topic of modules a few times before:

Daj Się Poznać, the-console
comments powered by Disqus