From e7cdc2271fcef47589b5ffd7e0cb396dc8eeb5f8 Mon Sep 17 00:00:00 2001 From: Eric Vergnaud Date: Thu, 3 Sep 2015 23:40:06 +0800 Subject: [PATCH] Largely rewritten to support usage in web worker thread without breaking compatibility with NodeJS require --- runtime/JavaScript/src/lib/require.js | 299 ++++++++++++++++++++++++++ 1 file changed, 299 insertions(+) create mode 100644 runtime/JavaScript/src/lib/require.js diff --git a/runtime/JavaScript/src/lib/require.js b/runtime/JavaScript/src/lib/require.js new file mode 100644 index 000000000..b7d0da392 --- /dev/null +++ b/runtime/JavaScript/src/lib/require.js @@ -0,0 +1,299 @@ +// [The "BSD license"] +// Copyright (c) 2015 Eric Vergnaud +// All rights reserved. +// Fragments from Torben Haase and Steven Levithan +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. 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. +// 3. The name of the author may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +// INFO Standalone require() +// This is a largely rewritten standalone version of the require function. +// The original purpose is to provide a require function compatible with NodeJS +// So that the same code can run in both NodeJS and browsers +// This cannot be achieved using RequireJS and comparable alternatives +// because they all have a prototype not compatible with NodeJS's require +// It is also that the code be able to run without a package builder +// such as RequireJS, WebPack or other alternatives, since they slow dow development + +// This code was largely inspired by the following libraries and authors: + +// Smoothie, by Torben Haase, Flowy Apps (torben@flowyapps.com) +// But unfortunately Smoothie's require cannot run in web workers +// So I had to rewrite a lot of stuff, although 50% of the code is unchanged + +// parseUri, by Steven Levithan + +// NOTE The load parameter points to the function, which prepares the +// environment for each module and runs its code. Scroll down to the end of +// the file to see the function definition. + + +(function(load) { 'use strict'; + + var RequireError = function(message, fileName, lineNumber) { + this.name = "RequireError"; + this.message = message; + } + RequireError.prototype = Object.create(Error.prototype); + + // INFO RequireOptions + // The values can be set by defining a object called RequireOptions, which + // contains properties of the same name as the options to be changed. + // NOTE The RequireOptions object has to be defined before this script is loaded + // Changing the values in the RequireOptions object will have no effect afterwards! + + // NOTE Global module paths + var paths = self.RequireOptions && self.RequireOptions.paths!==undefined ? self.RequireOptions.paths.slice(0) : ['./']; + + // INFO Current module paths + // pwd[0] contains the path of the currently loading module + // pwd[1] contains the path its parent module and so on. + var pwd = Array(''); + + // INFO URI parser + function parseUri (str) { + var o = parseUri.options, + m = o.parser[o.strictMode ? "strict" : "loose"].exec(str), + uri = {}, + i = 14; + + while (i--) uri[o.key[i]] = m[i] || ""; + + uri[o.q.name] = {}; + uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) { + if ($1) uri[o.q.name][$1] = $2; + }); + + return uri; + }; + + parseUri.options = { + strictMode: true, + key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], + q: { + name: "queryKey", + parser: /(?:^|&)([^&=]*)=?([^&]*)/g + }, + parser: { + strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/, + loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/ + } + }; + + // INFO splitDirs + // function used to split a full directory path into its components + // takes car of leading and trailing path separators + function splitDirs(str) { + var dirs = str.split("/"); + if(!dirs[0]) dirs = dirs.slice(1); + if(!dirs[dirs.length-1]) dirs = dirs.slice(0,-1); + return dirs; + } + + // INFO resolveUrl + // function used to build a full and plain url given a relative uri + // takes an optional location parameter when called recursively + // otherwise uses the location of the loading page, or script in workers + // the function also takes car of relative path fragments, such as ./ and ../ + // even when they are included in a full path + function resolveUrl (str, location) { + location = location || self.location; + var full = parseUri(location); + var uri = parseUri(str); + if(uri.host) { + // cater for relative path components, the below takes care of both /./ and /../ + var rel = uri.path.indexOf("/."); + if (rel > 0) { + location = full.protocol + "://" + full.authority + uri.path.slice(0, rel + 1); // includes the trailing slash + str = uri.path.slice(rel + 1); + return resolveUrl(str, location); + } else { + return uri; + } + } else { + if(uri.path.startsWith("/")) { + str = full.protocol + "://" + full.authority + uri.path; + return resolveUrl(str); + } else if(uri.path.startsWith("./")) { + var paths = splitDirs(uri.path).slice(1); + str = full.protocol + "://" + full.authority + full.directory + paths.join("/"); + return resolveUrl(str); + } else if(uri.path.startsWith("..")) { + var dirs = splitDirs(full.directory); + var paths = splitDirs(uri.path); + while (paths[0] === "..") { + dirs = dirs.slice(0, -1); + paths = paths.slice(1); + } + str = full.protocol + "://" + full.authority + "/" + dirs.join("/") + "/" + paths.join("/"); + return resolveUrl(str); + } else + throw "Unsupported"; + } + } + + // INFO Module cache + // Contains getter functions for the exports objects of all the loaded modules. + // The getter for the module 'mymod' is named '$mymod' to prevent collisions with + // predefined object properties (see note below). As long as a module has not been + // loaded the getter is either undefined or contains the module code as a function + var cache = new Object(); + var locks = new Object(); + + // INFO Module getter + // Takes a module identifier, resolves it and gets the module code via an + // AJAX request from the module URI. If this was successful the code and + // some environment variables are passed to the load function. The return + // value is the module's `exports` object. If the cache already contains + // an object for the module id, this object is returned directly. + // NOTE If a callback function has been passed, the AJAX request is asynchronous + // and the mpdule exports are passed to the callback function after the + // module has been loaded. + + function require(identifier, callback) { + + var descriptor = resolveId(identifier); + var cacheid = '$'+descriptor.id; + + if (cache[cacheid]) { + if (typeof cache[cacheid] === 'string') + load(descriptor, cache, pwd, cache[cacheid]); + // NOTE The callback should always be called asynchronously to + // ensure that a cached call won't differ from an uncached one. + callback && setTimeout(function(){callback(cache[cacheid])}, 0); + return cache[cacheid]; + } + + var request = new XMLHttpRequest(); + + // NOTE IE8 doesn't support the onload event, therefore we use onreadystatechange + // as a fallback here. However, onreadystatechange shouldn't be used for all browsers, + // since at least mobile Safari seems to have an issue where onreadystatechange + // is called twice for readyState 4. + callback && (request[request.onload===null?'onload':'onreadystatechange'] = onLoad); + request.open('GET', descriptor.uri, !!callback); + // NOTE Sending the request causes the event loop to continue. Therefore pending + // AJAX load events for the same url might be executed before this synchronous onLoad + // is executed. This should be no problem, but in Chrome the responseText of the sneaked + // in load events will be empty. Therefore we have to lock the loading while executong send(). + locks[cacheid] = locks[cacheid]++||1; + request.send(); + locks[cacheid]--; + !callback && onLoad(); + return cache[cacheid]; + + function onLoad() { + if (request.readyState != 4) + return; + if (request.status != 200 && request.status != 0 ) // 0 for Safari with file protocol + throw new RequireError('unable to load '+descriptor.id+" ("+request.status+" "+request.statusText+")"); + if (locks[cacheid]) { + console.warn("module locked: " + descriptor.id); + callback && setTimeout(onLoad, 0); + return; + } + if (!cache[cacheid]) + load(descriptor, cache, pwd, 'function(){\n'+request.responseText+'\n}'); + callback && callback(cache[cacheid]); + } + } + + // INFO Module resolver + // Takes a module identifier and resolves it to a module id and URI. + // Both values are returned as a module descriptor, which can then + // be passed to `fetch` to load a module, and cache it by id. + + function resolveId(identifier) { + // NOTE Matches [1]:[..]/[path/to/][file][.js] + var m = identifier.match(/^(?:([^:\/]+):)?(\.\.?)?\/?((?:.*\/)?)([^\.]+)?(\..*)?$/); + // NOTE Matches [1]:[/path/to] + var p = pwd[0].match(/^(?:([^:\/]+):)?(.*)/); + var root = m[2] ? paths[p[1]?parseInt(p[1]):0] : paths[m[1]?parseInt(m[1]):0]; + var url = resolveUrl((m[2]?root+p[2]+m[2]+'/':root)+m[3]+(m[4]?m[4]:'index')); + var id = url.source.replace(/^[^:]*:\/\/[^\/]*\/|\/(?=\/)/g, ''); + var uri = "/"+id+(m[5]?m[5]:'.js'); + root.replace(/[^\/]+\//g, function(r) { + id = (id.substr(0, r.length) == r) ?id.substr(r.length) : id = '../'+id; + }); + return {'id':id,'uri':uri}; + } + + // INFO Exporting require to global scope + if (self.require !== undefined) + throw new RequireError('\'require\' already defined in global scope'); + + try { + Object.defineProperty(self, 'require', {'value':require}); + Object.defineProperty(self.require, 'resolveUrl', {'value':resolveUrl}); + Object.defineProperty(self.require, 'paths', {'get':function(){return paths.slice(0);}}); + } + catch (e) { + // NOTE IE8 can't use defineProperty on non-DOM objects, so we have to fall back to unsave property assignments in this case. + self.require = require; + self.require.resolve = resolve; + self.require.paths = paths.slice(0); + // NOTE We definitely need a getter for the cache, so we make the cache a DOM-object in IE8. + // NOTE This won't work in IE8 web workers, but as of writing, it's an acceptable trade-off + cache = document.createElement('DIV'); + } + + // INFO Adding preloaded modules to cache + for (var id in (self.RequireOptions && self.RequireOptions.preloaded)) + cache['$'+id] = self.RequireOptions.preloaded[id].toString(); + + // INFO Parsing module root paths + for (var i=0; i