Write a main.js
as the application entry point
var determine = require('./determine');
console.log(determine.isCool(['clux', 'lava']));
the required module determine.js
var cool = require('shared::cool'); // cross-domain require
exports.isCool = function (input) {
return input.filter(cool);
};
and finally its required cool.js
on the shared
domain ?
module.exports = function (name) {
return (name === 'clux');
};
To compile these files invoke modul8()
and chain on options
var modul8 = require('modul8');
modul8('./client/main.js')
.domains({'shared': './shared/'})
.compile('./out.js');
This will construct a single, browser compatible out.js
in your execution path, and the generated dependency tree will look as follows:
app::main
└──┬app::determine
└───shared::cool
The shared code is independent of the application and can be reused on the server.
Compilation can also be performed via the command line interface by typing
$ modul8 client/main.js -p shared:shared/ > out.js
from the path containing the shared/ and client/ folders.
To load the browser compatible output file from your site simply stick it in the HTML
<script src="/out.js"></script>
modul8 is an extensible CommonJS code packager and analyzer for JavaScript and AltJS web applications. Applications are recursively analyzed for dependencies from an entry point and will pull in + compile just what is needed.
Code can be shared with the server by isolating modules/libraries in shared domains. This means stand alone logic
can exist on the server and be referenced via a normal require(dir + 'module')
, but also be referenced via require('shared::module')
on the client.
To give you full overview and control over what code is pulled in, modul8 automatically generates a per-file depedency tree. This allows fast analysis and identification of extraneous links, and becomes a very useful tool for refactoring.
modul8 supports live extensions of certain exports containers via third party script loaders, and server side data injection at compile time.
Lastly, modul8 aims to eliminate most global variables from your code. It does so using the following approaches
- Encapsulate all exported data in the closure inhabited by
require()
- Incorporate globally available libraries into the module system via automatic arbiters
Additionally, node modules can be required (if installed) as if on the server! To dive in properly; consult the api docs.
- highly extensible client side require
- simple and safe code sharing between the server and the client
- dynamic resolution and compilation of dependencies server-side
- compatible with JavaScript, CoffeeScript or (configurable) AltJS languages
- low footprint: <2kB (minified/gzipped) output size inflation
- enforces modularity best practices and logs an npm style dependency tree
- can inject data to require dynamically from the server or live from the client
- can require npm installed modules
- easy to write, modular plugins allows super easy client extensions with server logic and data
- minimizes global usage, encapsulates exports in closures, absorbs library globals
- only rebuilds on repeat calls if necessary (files modified || options changed)
- ideal for single page web applications, 1 or 2 HTTP request to get all code
Install the library:
$ npm install modul8
Install the command line tool:
$ npm install -g modul8
Download the development version:
$ git clone git://github.com/clux/modul8
Basic use only only the path to the entry point and an output.
modul8('./client/app.js').compile('./out.js');
This compiles everything referenced explicitly through app.js
to the single browser compatible out.js
.
Every require()
call is tracked and the resulting dependency tree is loggable. Cross domain require()
s are namespaced
C++ style: require('shared::validation')
will look for a .js
then .coffee
file named validation
on the shared domain.
This extra domain must be configured using a chained .domains()
call:
modul8('./client/app.js')
.domains({'shared': './shared/'})
.compile('./out.js');
To ensure that the shared
domain here can work on the server and the client, any require()
calls within domains
should be relative and not pull in anything outside that folder.
As an example, a same-origin require of shared::defs
should be done with a ./ prefix: require('./defs')
.
The dependency analyzer will typically output something like this if configured
app::app
├──┬app::controllers/user
│ └───app::models/user
├──┬app::controllers/entries
│ └───app::models/entry
├──┬shared::validation
│ └───shared::defs
└───M8::jQuery
jQuery
can be seemlessly integrated (and will show up in the dependency tree as above) by using .arbiters()
Data can by injected at compile time from the server by specifying keys and serializable/pre-serialized data to be attatched on the specified key
modul8('./client/app.js')
.data({'models': {'user':'clux'}})
.compile('./out.js');
The data
domain is initialized from the server with every key specified to .data()
, but can be extended live on the client.
The data API is particularly useful for web applications that needs particular application data to always be bundled.
Anything that can be serialized (including pre-serialized javascript input) can be sent to the data domain.
Extending the data domain in conjunction with creating specialized domains to handle that data, is a popular method that can be employed by node modules to break browser code down into more managable chunks - while linking them to the server.
This is so useful that it has become the defacto plugin API.
modul8('./client/app.js')
.use(new Plugin(opts))
.compile('./out.js');
This will allow the Plugin to extend 'out.js' with data created in Plugin, as well as add a namespaced require domain on the browser.
Using a Plugin will inflate 'out.js' by the size of the data it creates plus only the size of the modules you explicitly require()
.
Thus, adding plugins is a remarkably safe, monitorable, and robust way, to get discrete units of code - that shares logic with the server - to the client.
Writing your own plugins is also really easy. Please share.
Finally, modul8 defines an external
domain for asynchronous script loaders to dump their results. This domain can only be used and extended from the client.
Both the data
and external
domains are only allowed to be modified through safe proxies. Objects residing on these domains can be referenced
with require()
without messing up the compile time code analysis, but they can still show up in the dependency tree if desirable.
The full documentation site should contain everything you could ever want to know about modul8 and probably more. Read it, try it out, and give feedback if you like or hate it / parts of it, or if you want to contribute.
modul8 is my first proper open source project. It was crafted out of necessity, but it has grown into something larger. Version 1.0 should be ready relatively soon - so the current code can be considered mostly stable.
Version 0.10.0 and up should work fine with node v0.6.
Compiled code will work with ES5 compatible browsers (recent browsers minus Opera) If you target older browsers, include ES5-shim.
Install development dependencies
$ npm install
Run the tests
$ npm test
modul8 is actively tested with the latest node 0.6 branch. Many thanks to Travis-CI.
MIT Licensed - See LICENSE file for details