Modules in The Console - Take One
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:
- single-file scripts
- 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.
-
the folder can contain various .js files
* these should be loaded as previously - one file is a one command
-
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:
- The state of importing node.js modules into Java - analyzing connection between Nashorn/Java and node.js modules
- REPL or even better - calling for a need of approach when script is loaded directly into JavaScript environment
- The Console: script management - yelling about grouping and sharing scripts