stratum-framework

0.6.3 • Public • Published

Stratum

NPM version

A framework and component library for web application development.

Designed to provide a small modular basis upon which to build web applications.

To this end, the whole framework is built around a single core mechanism, the modular inclusion functionality, in require.js.

Contents

Modularity

The require function:

  • Is compatible with CommonJS Module/1.1
  • Works with the file: protocol, for rapid prototyping
  • Works with cross site urls
  • Retains correct file and line number mapping for debugging/error handling.
  • Is tested in a wide variety of browsers
  • Passes CommonJS module unit tests (though two are passed in error, see Gotchas for details)

The define function:

  • Is compatible with require.js AMD
  • Is probably best suited to providing mock modules for testing purposes!

Install

Either directly through git:

git clone https://github.com/jka6502/stratum.git

Or via npm:

npm install stratum-framework

Quickstart

To use Stratum, include the require script first:

    <script src='./stratum/lib/require.js'></script>

To include a component, require it, much as you would using node/CommonJS:

    var someModule = require('someModule');

Remember to leave off the '.js' suffix, this is a module path, not a file reference.

Relative paths can also be used, using the *nix style . (current) and .. (parent) prefixes for the module:

    var other = require('./some/other/module');

To export functionality from a module, assign it to module.exports:

    module.exports = { ... exported object/function ... };

or the global exports variable:

    exports = { ... exported object/function ... };

Requiring modules even works in inline script tags!

    <script>
        var something = require('../library/something');
    </script>

Production

This code is designed to facilitate faster building of applications. It is pretty thoroughly tested and works in a wide variety of environments, but it is not designed to be used in production.

In production environments, you can bake your scripts into a safe, universally compatible format, using Browserify.

Testing

To run the supplied test suite:

npm install
npm test

Testing with CommonJS

Open the CommonJS test suite page to run the tests. Remember to checkout the submodules required first in vendor.

The tests need printStackTrace to allow referencing exports or module.exports after the first load and execution of a module, due to the design of the CommonJS unit tests.

This practise is not recommended. Although possible in the browser, through some arcane trickery, this practise is rather unintuitive - it makes assumptions about a global variable reacting differently depending on the source file it is referenced in. It is better to wrap a module in a function scope (you are doing that anyway, aren't you?), use local variables to maintain internal relationships, then explicitly export any functions or objects required.

Gotchas

The browser is not the ideal environment for the CommonJS require approach, so there are some potential pitfalls with this implementation:

  • Requiring a module that is not yet loaded throws an exception.

This is the fundamental trickery that makes the require function possible in the browser, but it has implications.

Any try{ ... }catch{ ... } in the current stack will catch the dependency abort exception, that is used to halt script execution until dependencies have been resolved.

To avoid this, either filter any exceptions caught, by calling require.filter(exception); in your handler, or avoid catching exceptions from your require calls entirely.

Note: For this reason, two of the tests in the CommonJS module test suite pass in error. They catch the 'dependency abort' exception, rather than the 'file missing' exception they expect. However, those tests would actually pass, if they filtered out that exception.

  • Code preceeding require calls may be executed multiple times

The exception abort/re-execute cycle also means that any code before, or between require calls can be executed multiple times, if the script aborts to load a dependency.

Also, for the same reason, wrapping a require in a try{ ... }finally{ ... } handler may well invoke the finally clause one or more times...

  • Inline scripts are not necessarily sequenced

If a page contains multiple inline scripts, the order they are executed in will be indeterminate (well, determinate, but according to complex rules).

This is because any script containing one or more require calls may, or may not, cause execution of that script to be deferred, depending on whether its dependencies have already been satisfied.

Requiring in one inline script will not ensure dependencies are met before executing subsequent scripts.

So the following pattern should be avoided:

<script>
    var something = require('something');
</script>
 
<script>
    // Assume the above ensures availability...
    something.confabulate();
</script>
  • Global variables cannot be correctly supplied after module load

The module and exports global variables are guaranteed to be correct during the initial execution of a newly loaded module, but are not guaranteed to point to the same values in closures created within that module.

There is no way to automatically wrap a remotely loaded script in a new scope, which would be needed to implement this cleanly, so the best approach is to only set module.exports or exports in the initial execution of a script, and reference locally cached copies, to use any exported features internally.

  • Avoiding problems

All of the multi-execution issues can be overcome by just avoiding mixing logic with require calls, and requiring any dependencies at the top/start of any file - which is generally good advice anyway.

If you absolutely must mix logic and require calls, be careful to cache state globally to ensure the same path is taken if the logic is executed a second (or nth) time. Basically, any logic preceeding your require calls must be idempotent - or odd things will happen.

For inline scripts, each script tag has its own dependency chain, read it as the dictionary definition, if you require something in an inline script, you must actually require it in that script, don't assume it is available. Just don't add hard coupling between code in distinct inline script tags - its icky.

Global module and exports access issues can be alleviated by including printStackTrace before the require script in the page, but if possible, a simpler approach is to avoid reading from module or exports directly after initialisation.

In summary

GOOD

(function() {
 
    var something = require('something'),
        other = require('some/module/other');
 
    function Implementation() {}
 
    Implementation.prototype = {
 
        other: other
 
    };
 
    Implementation.staticMethod = function(param) {
        Implementation.blah(something(param));
    };
 
    module.exports = Implementation;
 
})();

BAD

console.log('Echo'); // Executed *at least* once... woo!
 
var something = require('something'); // Globalised!
 
try{
    exports = {
        other: require('some/other/module').feature,
 
        closure: function(param) {
            exports.other(something(param)); // Which 'exports' will this be?
        }
    };
}catch(e) {
    console.log('File missing: ' + e); // File missing: Dependency Abort
    // This will also prevent the 'next' file load being queued...
}

Just... why?

Why not use one of the existing module loaders that tackle browser dependency management, you ask?

Well, basically:

  • I enjoyed writing it
  • I hate the automagic and boilerplate required for mechanisms like AMD
  • I personally find this approach makes for simple, clean, readable code
  • I find that build steps/manual dependency management slow down prototyping, and by doing so, inhibit my creativity and demotivate me
  • The gotchas listed above are easily avoided
  • I've convinced myself that anything that falls foul of the above is, in fact, a code smell anyway :-P

Package Sidebar

Install

npm i stratum-framework

Weekly Downloads

5

Version

0.6.3

License

MIT

Last publish

Collaborators

  • jka6502