commit c375db3ddf25c7c7245e2bef68f3c89281f539ac Author: Loic Nageleisen Date: Mon Aug 19 18:48:06 2013 +0200 umodule.js: first public release diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a6c7c28 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.js diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..cefe519 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "uspec"] + path = uspec + url = https://github.com/lloeki/uspec-js.git diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f070a65 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2013, Loic Nageleisen +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..dc53a3a --- /dev/null +++ b/Makefile @@ -0,0 +1,22 @@ +all: release + +release: umodule.js + +%.js: %.coffee + coffee --compile $< + +uspec_module.js: uspec/uspec.js wrapper + ./wrap_module uspec $< > $@ + +run_spec.js: umodule.js uspec_module.js umodule_spec.js + cat $^ > $@ + +phantom_spec: run_spec.js + phantomjs $< + +spec: phantom_spec + +clean: + @rm -f *.js + +.PHONY: release node_spec phantom_spec spec diff --git a/README.mdown b/README.mdown new file mode 100644 index 0000000..a89c277 --- /dev/null +++ b/README.mdown @@ -0,0 +1,65 @@ +# µModule + +µModule is a minimalist CommonJS module definition and requirement implementation, squarely +aimed at browsers. + +## Usage + +Load `umodule.js` before any other: + +```html + + +... +``` + +Then in your application assets: + +```coffee +# umodule.js is itsef a module and only exports `require` globally. +define = require('module').define + +# define a module +define 'foo', (exports) -> + exports.hello = (w) -> 'hello, #{w}!' +``` + +Subsequently, in another asset: + +```coffee +foo = require('foo') + +console.log foo.hello() +``` + +There is no loader: all code is supposed to be loaded by an asset pipeline and +your typical user agent request. Therefore the goal is merely to isolate code +into modules and retrieve their exported interface in a given scope, using +CommonJS semantics. If you want a loader behavior, look into RequireJS. + + +## Examples + +Although available as a global, require is passed as a second argument, +shadowing the global and (NIY) allowing for relative imports: + +```coffee +Module = require('module') + +Module.define 'bob/alice', (exports, require) -> + foo = require 'foo' + exports.eve = -> foo.hello() +``` + +The module itself is passed as a third argument. + +```coffee +Module.define 'dave', (exports, require, module) -> + my_exports = require module.id +``` + +## Testing + +Run with `make spec`. Testing depends on [uspec][uspec], included as a git submodule, and runs on phantomjs. + +[uspec]: https://github.com/lloeki/uspec-js diff --git a/umodule.coffee b/umodule.coffee new file mode 100644 index 0000000..5990282 --- /dev/null +++ b/umodule.coffee @@ -0,0 +1,39 @@ +### +# umodule.js v0.5 +# (c) 2013 Loic Nageleisen +# Licensed under 3-clause BSD +### + +root = global ? window + + +require = (id) -> + target = Module.root + target = target[item] for item in id.split('/') + throw new Error("module not found: #{id}") if typeof target is 'undefined' + target.exports + + +class Module + constructor: (@id) -> + @exports = {} + + @define: (target, name, block) -> + if arguments.length < 3 + [target, name, block] = [Module.root, arguments...] + + top = target + target = target[item] or= new Module(item) for item in name.split '/' + block.call(target, target.exports, target.require, target) + + target + + require: -> require() + + +Module.root = new Module('root') +Module.root.exports = root +Module.root.module = new Module('module') +Module.root.module.exports = Module + +root.require = require diff --git a/umodule.js b/umodule.js new file mode 100644 index 0000000..ef7984f --- /dev/null +++ b/umodule.js @@ -0,0 +1,70 @@ +// Generated by CoffeeScript 1.6.2 +/* +# umodule.js v0.5 +# (c) 2013 Loic Nageleisen +# Licensed under 3-clause BSD +*/ + + +(function() { + var Module, require, root, + __slice = [].slice; + + root = typeof global !== "undefined" && global !== null ? global : window; + + require = function(id) { + var item, target, _i, _len, _ref; + + target = Module.root; + _ref = id.split('/'); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + item = _ref[_i]; + target = target[item]; + } + if (typeof target === 'undefined') { + throw new Error("module not found: " + id); + } + return target.exports; + }; + + Module = (function() { + function Module(id) { + this.id = id; + this.exports = {}; + } + + Module.define = function(target, name, block) { + var item, top, _i, _len, _ref, _ref1; + + if (arguments.length < 3) { + _ref = [Module.root].concat(__slice.call(arguments)), target = _ref[0], name = _ref[1], block = _ref[2]; + } + top = target; + _ref1 = name.split('/'); + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + item = _ref1[_i]; + target = target[item] || (target[item] = new Module(item)); + } + block.call(target, target.exports, target.require, target); + return target; + }; + + Module.prototype.require = function() { + return require(); + }; + + return Module; + + })(); + + Module.root = new Module('root'); + + Module.root.exports = root; + + Module.root.module = new Module('module'); + + Module.root.module.exports = Module; + + root.require = require; + +}).call(this); diff --git a/umodule_spec.coffee b/umodule_spec.coffee new file mode 100644 index 0000000..0f59fe8 --- /dev/null +++ b/umodule_spec.coffee @@ -0,0 +1,70 @@ +uspec = require('uspec') +describe = uspec.describe +pending = uspec.pending +assert = uspec.assert +assert_throws = uspec.assert_throws +Module = require('module') + + +describe 'require', + + # CommonJS + + 'should be a function': -> + assert -> require instanceof Function + + 'should accept a module identifier': -> pending('how to test that?') + + 'should return the exported API of the foreign module': -> + Module.define 'foo', (exports) -> exports.bar = 'bar' + assert -> require('foo').bar isnt undefined + assert -> require('foo').bar == 'bar' + + 'should throw an error if the module cannot be returned': -> + assert_throws Error, -> require 'nonexistent/module' + + +describe 'Module', + + # CommonJS + + 'should have an `export` var, that is an Object': -> + Module.define 'foo', (exports) -> + assert -> typeof exports == 'object' + + 'should have a `require` var, conformant with the spec': -> + Module.define 'foo', (exports, require) -> + assert -> typeof require == 'function' + + 'should have a `module` var': -> + Module.define 'foo', (exports, require, module) -> + assert -> module instanceof Module + assert -> module.id == 'foo' + + 'should export by adding to the exports var': -> + m = Module.define 'foo', (exports) -> exports.bar = 'bar' + assert -> m.exports.bar == 'bar' + + 'should have id property': -> + m = Module.define 'foo', -> + assert -> m.id isnt undefined + + 'should have its id set so that requiring it returns its exports': -> + m = Module.define 'foo', (exports) -> exports.bar = 'bar' + assert -> require(m.id) == m.exports + + # Other + + 'should define a Module class': -> + assert -> Module isnt undefined + + 'should define a root module': -> + assert -> Module.root isnt undefined + assert -> Module.root.id == 'root' + + +results = uspec.run() +rc = if uspec.summary(results) then 0 else 1 + +phantom.exit(rc) unless typeof phantom is 'undefined' +process.exit(rc) unless typeof process is 'undefined' diff --git a/uspec b/uspec new file mode 160000 index 0000000..ec516c1 --- /dev/null +++ b/uspec @@ -0,0 +1 @@ +Subproject commit ec516c106c35d2a66b124a2a5497e17d5377575b diff --git a/wrap_module b/wrap_module new file mode 100755 index 0000000..accaaff --- /dev/null +++ b/wrap_module @@ -0,0 +1,5 @@ +#!/bin/bash +sed -e "s/#MODULE_NAME#/$1/" -e "/#MODULE#/ { +r $2 +d +}" wrapper diff --git a/wrapper b/wrapper new file mode 100644 index 0000000..19084ee --- /dev/null +++ b/wrapper @@ -0,0 +1,6 @@ +(function () { + Module = require('module'); + Module.define('#MODULE_NAME#', function (exports, require, module) { +#MODULE# + }); +}).call(this);