reun
v0.2.7
Published
client-side nodejs-like require using unpkg
Downloads
20
Readme
TODO: unit testing TODO: documentaiton, - merge into source
REUN - require(unpkg)
Reun is:
- 100% client-side nodejs-like
require
for the browser. - using https://unpkg.com/.
- dynamic, just
require(...)
whatever module you want from your source file. No need forpackage.json
, - versions can be passed to require, i.e.require('[email protected]')
. - pretending to be a synchronous, even though it is asynchrounous. Tries to work in the typical cases, and will always fail in certain documented edge cases. Pragmatic, and not standard compliant.
- able to directly load many nodejs modules, that has not been packaged for the browser.
- adding custom functionality when desired, i.e.
module.meta
API
reun.eval(code, [opt])
executecode
, wherecode
is either a function, or the string source of a module.require()
is available and is pretending to be synchronous, and done relative to theopt.uri
. Returns a promise of the function result or module-exports.reun.require(module, [opt])
loads a module, path is relative to thelocation.href
if available. Returns a promise.
Usage example
index.html
:
<!DOCTYPE html>
<html>
<body>
<script src=https://unpkg.com/reun></script>
<script>reun.require('./example.js');</script>
</body>
</html>
example.js
:
var uniq = require('uniq');
console.log(uniq([1,4,2,8,4,2,1,3,2]));
Extensions
require('[email protected]')
allows you to require a specific versionmodule.meta
allows you to set meta information about your module, - this may later be used to automatically package the module for npm, cordova, ...
Incompatibilities
The implementation is a hack. We want to pretend to be synchronous, but we also do not want to block the main thread. Instead require
throws an exception when a module is not loaded yet. When we run a file, we catch this exception, load the module asynchrounously, and then rerun the file. Later on we might also search the source for require("...")
, or require('...')
and try to preload these modules, but this is not implemented yet.
Also we just resolve the module name as 'https://unpkg.com/' + module_name
. To be more compatible with node modules, we may check the package.json
in the future to make sure that the relative paths in the require works.
- Custom exceptions from
require
should not caught. - Code before a require, may be executed multiple times, - should be side-effect free.
require
may fail within callbacks, if the module has not been loaded before.- If the source lives in a subdirectory, and the module is not packaged for the web, and contains relative paths, - the paths are wrongly resolved. A workaround is to
require('module/lib/index.js')
instead ofrequire('module')
. - It does obviously not work with every module.
In spite of these limitations, it is still possible to require
many nodejs module directly to the web.
Project setup
(function() { 'use strict';
var da = typeof direape !== 'undefined' ? direape : require('direape');
da.testSuite('reun');
var reun = self.reun || {};
var modules = {
reun: reun,
direape: da
};
reun.eval(src|fn, opt);
Functions will be called as a module with require
, exports
, and module
as parameters, - similar to http://requirejs.org/docs/commonjs.html
var runQueue = new Promise((resolve) => da.ready(() => resolve()));
reun.eval = (fn, opt) => {
runQueue = runQueue.then(() => do_eval(fn, opt))
.catch((e) => da.nextTick(() => { throw e; }));
return runQueue;
};
da.handle('reun:eval', (fn, opt) =>
reun.eval(fn, opt).then(da.jsonify));
reun.require(module-name, opt);
reun.require = (name, opt) =>
reun.eval('module.exports = require("' + name + '",' +
JSON.stringify(opt || {}) + ');',
Object.assign({uri: self.location && self.location.href || './'}, opt));
da.handle('reun:require', reun.require);
Implementation details
moduleUrl
Convert a require-address to a url.
path is baseurl used for mapping relative file paths (./hello.js
) to url.
function moduleUrl(module, opt) {
var path = opt.uri || '';
if(module.slice(0,4) === 'reun') {
return 'reun';
}
if(module.startsWith('https:') ||
module.startsWith('http:')) {
return module;
}
path = path.replace(/[?#].*/, '');
path = (module.startsWith('.')
? path.replace(/[/][^/]*$/, '/')
: 'https://unpkg.com/');
path = path + module;
while(path.indexOf('/./') !== -1) {
path = path.replace('/./', '/');
}
var prevPath;
do {
prevPath = path;
path = path.replace(/[/][^/]*[/][.][.][/]/g, '/');
} while(path !== prevPath);
return path;
}
do_eval
function do_eval(fn, opt) {
opt = opt || {};
if(typeof fn === 'string') {
fn = stringToFunction(fn, opt);
}
return executeModule(fn, opt);
}
executeModule
function executeModule(fn, opt) {
opt.uri = opt.uri || '';
var require = (name, opt) => reun_require(name, opt, module);
var module = {
require: require,
uri: opt.uri,
id: opt.uri.replace('https://unpkg.com/', '').replace(/@[^/]*/, ''),
exports: {}
};
if(opt.main) {
require.main = module;
}
return rerunModule(fn, module);
}
rerunModule
function rerunModule(fn, module) {
var result;
try {
fn(module.require, module.exports, module);
result = module.exports;
} catch (e) {
if(e.constructor !== RequireError) {
throw e;
}
return da.call(da.nid, 'da:GET', e.url)
.catch(() => {
throw new Error('require could not load "' + e.url + '" ' +
'Possibly module incompatible with http://reun.solsort.com/'); })
.then((moduleSrc) => executeModule(stringToFunction(moduleSrc, e.opt),
e.opt))
.then((exports) => assignModule(e.url, exports))
.then(() => rerunModule(fn, module));
}
return Promise.resolve(result);
}
shortName(uri)
function assignModule(uri, exports) {
modules[uri] = exports;
Find the short name of the module, and remember it by that alias, to make sure that later requires for the module without version/url returns the already loaded module.
if(exports.meta && exports.meta.id) {
modules[exports.meta.id] = exports;
}
var name = uri
.replace('https://unpkg.com/', '')
.replace(/[@/].*/, '');
if(!modules[name]) {
modules[name] = exports;
}
}
reun_require
function reun_require(name, opt, parentModule) {
if(modules[name]) {
return modules[name];
}
var url = moduleUrl(name, parentModule);
if(!modules[url]) {
throw new RequireError(name, url, opt);
}
return modules[url];
}
stringToFunction
function stringToFunction(src, opt) {
var wrappedSrc = '(function(require,exports,module){' +
src + '})//# sourceURL=' + opt.uri;
return eval(wrappedSrc);
}
RequireError
When trying to load at module, that is not loaded yet, we throw this error:
function RequireError(module, url, opt) {
this.module = module;
this.url = url;
opt = opt || {};
opt.uri = url;
this.opt = opt;
}
RequireError.prototype.toString = function() {
return 'RequireError:' + this.module +
' url:' + this.url;
};
Main / test runner
da.ready(() => {
if((da.isNodeJs() && require.main === module && process.argv[2] === 'test') ||
(self.REUN_RUN_TESTS)) {
da.runTests('reun')
.then(() => da.isNodeJs() && process.exit(0))
.catch(() => da.isNodeJs() && process.exit(1));
}
});
if(typeof module === 'object') {
module.exports = reun;
} else {
self.reun = reun;
}
end
})();
License
This software is copyrighted solsort.com ApS, and available under GPLv3, as well as proprietary license upon request.
Versions older than 10 years also fall into the public domain.