"(insane)(parenthetical)(module)(pattern); // working name"
code still a bit untidy but working under tests
needed to exorcise code demons that disturbed sleep ~ https://gist.github.com/dfkaye/7390424
ideas and implementations and refactorings keep coming up
instead of commonjs require
var asyncModule = require('asyncModule')
var anotherModule = require('another-module')
asyncModule(arg1)(arg2)(arg3);
anotherModule('hi, module');
or AMD of the function parsing type ~ which is actually really really close
(see requirejs, seajs), but relies on the "magic" of function.toString()
to
parse out require
statements ~ which at first seems "cool" but turns out
really to be more wasteful indirection and fakery
define(__filename, function(module, require, exports) {
var a = require('a/path');
var b = require('b/path');
// ...rest of commonjs, etc.
module.exports = ...
});
…
I am advocating a functional chaining pattern for describing the whole module.
Each function call returns the same function in order to receive input for the
next step (either a dependency or an execution scope function). This pattern
works by pulling the dependency statements up and skipping the extra require
statement:
(define)(__filename) // pathname of current scope
('asyncModule') // dependency name or path
('another-module') // dependency name or path
(function () { // execution scope or factory function
asyncModule(arg1)(arg2)(arg3);
anotherModule('hi, module');
// ...rest of commonjs, etc.
module.exports = ...
});
Each dependency is declared in a single statement, removing the need for commas in a list.
Think of it as "configuration injection" at the module level that avoids the global config file business.
they're "injected" as variables in the callback indirectly via a Function()
call which writes out a new function object, including callback.toString().
as commonjs modules return an export rather than a name we have to alias them in
an assignment like var name = require('module-name');
that api is, however,
synchronous which means it doesn't play well in the asynchronous world of the
browser.
to handle that in this library, module names are automatically aliased.
a required dependency that exports something is assigned to an alias derived
from the filename. An export defined in a file referenced at
'./path/to/cool-module.js'
will be assigned to a camelCased variable named
coolModule
.
(define)(__filename)
('./path/to/cool-module')
(function() {
coolModule
// ...rest of commonjs, etc.
module.exports = ...
});
if more than one file is named 'cool-module'
, we need a way to avoid the name
clash on coolModule
that would result.
to do that, specify an alias and delimiter along with the path name:
(define)(__filename)
('./path/to/cool-module')
('./path/to/another/cool-module {as} alias') // {as} token denotes alias
(function() {
coolModule
alias
// ...rest of commonjs, etc.
module.exports = ...
});
re-thinking this one
if a file sets a global value rather than returning an export, you can detect it
from the global
scope:
(define)(__filename)
('../fixture/zuber')
(function () {
global.zuber('test').should.be.equal('[global-zuber]' + 'test');
});
seems unexpected to have to specify that something is global ~ better to enable straight references and disambiguate collisions by aliasing vs. global.whatever
or use an alias to avoid clobbering, e.g., 'path/name {as} {alias}'
this works but seems unnecessary
(define)(__filename)
('../fixture/zuber {as} {zuber}')
(function () {
zuber('test').should.be.equal('[global-zuber]' + 'test');
});
re-thinking this one: order may be confusing
for testing modules with mocks of their dependencies it makes sense to add configuration injection close to the actual use of the thing
(define)(__filename)
('./path/to/cool-module')
('./path/to/mock {as} ./path/to/dependency') // {as} token denotes name alias
(function() {
coolModule
dependency //=> mock
});
still being worked out
this means we force all downstream dependencies to load an aliased path.
I am not sure that's wise (though helpful for testing/mocking) as it moves the configuration out of local modules back to more global modules.
in progress
CSP headers allow clients to disable script evaluation by default,
which means Function()
can't be used, unless you declare 'unsafe-eval' in the
CSP response header or meta tag directive. See
https://developer.chrome.com/extensions/contentSecurityPolicy#relaxing-eval for
more information.
If you're under this restriction, the solution in place here as of 2 JUNE 2014 is to add the expected dependency aliases as parameter names in the callback function, in any order:
(define)(__filename)
('./path/to/some-module')
('./path/to/mock {as} ./path/to/dependency')
(function(dependency, someModule) { // <= params match aliases, any order
someModule
dependency //=> mock
});
This could also be mitigated by a build process/nightmare eventually.
we'd be able to run very minimal transformations on scripts written in this way.
we can replace the (define)(__filename)
statements in each node file with the
file's app-relative pathname for use in the browser, we could concat the files
in dependency order using the define
capability built in, without having to
re-wrap everything à la browserify or r.js, without having to
transform everything à la traceur or es6ify, or any of the other
trendy-but-wrong, might-as-well-be-coffeescript transpoilers™.
then we could be productive again on both browser and node.js and get back to work solving our real issues.
JSON (modified MIT)