browser-es-module-loader.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. import RegisterLoader from 'es-module-loader/core/register-loader.js';
  2. import { InternalModuleNamespace as ModuleNamespace } from 'es-module-loader/core/loader-polyfill.js';
  3. import { baseURI, global, isBrowser } from 'es-module-loader/core/common.js';
  4. import { resolveIfNotPlain } from 'es-module-loader/core/resolve.js';
  5. var loader;
  6. // <script type="module"> support
  7. var anonSources = {};
  8. if (typeof document != 'undefined' && document.getElementsByTagName) {
  9. var handleError = function(err) {
  10. // dispatch an error event so that we can display in errors in browsers
  11. // that don't yet support unhandledrejection
  12. if (window.onunhandledrejection === undefined) {
  13. try {
  14. var evt = new Event('error');
  15. } catch (_eventError) {
  16. var evt = document.createEvent('Event');
  17. evt.initEvent('error', true, true);
  18. }
  19. evt.message = err.message;
  20. if (err.fileName) {
  21. evt.filename = err.fileName;
  22. evt.lineno = err.lineNumber;
  23. evt.colno = err.columnNumber;
  24. } else if (err.sourceURL) {
  25. evt.filename = err.sourceURL;
  26. evt.lineno = err.line;
  27. evt.colno = err.column;
  28. }
  29. evt.error = err;
  30. window.dispatchEvent(evt);
  31. }
  32. // throw so it still shows up in the console
  33. throw err;
  34. }
  35. var ready = function() {
  36. document.removeEventListener('DOMContentLoaded', ready, false );
  37. var anonCnt = 0;
  38. var scripts = document.getElementsByTagName('script');
  39. for (var i = 0; i < scripts.length; i++) {
  40. var script = scripts[i];
  41. if (script.type == 'module' && !script.loaded) {
  42. script.loaded = true;
  43. if (script.src) {
  44. loader.import(script.src).catch(handleError);
  45. }
  46. // anonymous modules supported via a custom naming scheme and registry
  47. else {
  48. var uri = './<anon' + ++anonCnt + '>';
  49. if (script.id !== ""){
  50. uri = "./" + script.id;
  51. }
  52. var anonName = resolveIfNotPlain(uri, baseURI);
  53. anonSources[anonName] = script.innerHTML;
  54. loader.import(anonName).catch(handleError);
  55. }
  56. }
  57. }
  58. }
  59. // simple DOM ready
  60. if (document.readyState === 'complete')
  61. setTimeout(ready);
  62. else
  63. document.addEventListener('DOMContentLoaded', ready, false);
  64. }
  65. function BrowserESModuleLoader(baseKey) {
  66. if (baseKey)
  67. this.baseKey = resolveIfNotPlain(baseKey, baseURI) || resolveIfNotPlain('./' + baseKey, baseURI);
  68. RegisterLoader.call(this);
  69. var loader = this;
  70. // ensure System.register is available
  71. global.System = global.System || {};
  72. if (typeof global.System.register == 'function')
  73. var prevRegister = global.System.register;
  74. global.System.register = function() {
  75. loader.register.apply(loader, arguments);
  76. if (prevRegister)
  77. prevRegister.apply(this, arguments);
  78. };
  79. }
  80. BrowserESModuleLoader.prototype = Object.create(RegisterLoader.prototype);
  81. // normalize is never given a relative name like "./x", that part is already handled
  82. BrowserESModuleLoader.prototype[RegisterLoader.resolve] = function(key, parent) {
  83. var resolved = RegisterLoader.prototype[RegisterLoader.resolve].call(this, key, parent || this.baseKey) || key;
  84. if (!resolved)
  85. throw new RangeError('ES module loader does not resolve plain module names, resolving "' + key + '" to ' + parent);
  86. return resolved;
  87. };
  88. function xhrFetch(url, resolve, reject) {
  89. var xhr = new XMLHttpRequest();
  90. var load = function(source) {
  91. resolve(xhr.responseText);
  92. }
  93. var error = function() {
  94. reject(new Error('XHR error' + (xhr.status ? ' (' + xhr.status + (xhr.statusText ? ' ' + xhr.statusText : '') + ')' : '') + ' loading ' + url));
  95. }
  96. xhr.onreadystatechange = function () {
  97. if (xhr.readyState === 4) {
  98. // in Chrome on file:/// URLs, status is 0
  99. if (xhr.status == 0) {
  100. if (xhr.responseText) {
  101. load();
  102. }
  103. else {
  104. // when responseText is empty, wait for load or error event
  105. // to inform if it is a 404 or empty file
  106. xhr.addEventListener('error', error);
  107. xhr.addEventListener('load', load);
  108. }
  109. }
  110. else if (xhr.status === 200) {
  111. load();
  112. }
  113. else {
  114. error();
  115. }
  116. }
  117. };
  118. xhr.open("GET", url, true);
  119. xhr.send(null);
  120. }
  121. var WorkerPool = function (script, size) {
  122. script = document.currentScript.src.substr(0, document.currentScript.src.lastIndexOf("/")) + "/" + script;
  123. this._workers = new Array(size);
  124. this._ind = 0;
  125. this._size = size;
  126. this._jobs = 0;
  127. this.onmessage = undefined;
  128. this._stopTimeout = undefined;
  129. for (var i = 0; i < size; i++) {
  130. var wrkr = new Worker(script);
  131. wrkr._count = 0;
  132. wrkr._ind = i;
  133. wrkr.onmessage = this._onmessage.bind(this, wrkr);
  134. wrkr.onerror = this._onerror.bind(this);
  135. this._workers[i] = wrkr;
  136. }
  137. this._checkJobs();
  138. };
  139. WorkerPool.prototype = {
  140. postMessage: function (msg) {
  141. if (this._stopTimeout !== undefined) {
  142. clearTimeout(this._stopTimeout);
  143. this._stopTimeout = undefined;
  144. }
  145. var wrkr = this._workers[this._ind % this._size];
  146. wrkr._count++;
  147. this._jobs++;
  148. wrkr.postMessage(msg);
  149. this._ind++;
  150. },
  151. _onmessage: function (wrkr, evt) {
  152. wrkr._count--;
  153. this._jobs--;
  154. this.onmessage(evt, wrkr);
  155. this._checkJobs();
  156. },
  157. _onerror: function(err) {
  158. try {
  159. var evt = new Event('error');
  160. } catch (_eventError) {
  161. var evt = document.createEvent('Event');
  162. evt.initEvent('error', true, true);
  163. }
  164. evt.message = err.message;
  165. evt.filename = err.filename;
  166. evt.lineno = err.lineno;
  167. evt.colno = err.colno;
  168. evt.error = err.error;
  169. window.dispatchEvent(evt);
  170. },
  171. _checkJobs: function () {
  172. if (this._jobs === 0 && this._stopTimeout === undefined) {
  173. // wait for 2s of inactivity before stopping (that should be enough for local loading)
  174. this._stopTimeout = setTimeout(this._stop.bind(this), 2000);
  175. }
  176. },
  177. _stop: function () {
  178. this._workers.forEach(function(wrkr) {
  179. wrkr.terminate();
  180. });
  181. }
  182. };
  183. var promiseMap = new Map();
  184. var babelWorker = new WorkerPool('babel-worker.js', 3);
  185. babelWorker.onmessage = function (evt) {
  186. var promFuncs = promiseMap.get(evt.data.key);
  187. promFuncs.resolve(evt.data);
  188. promiseMap.delete(evt.data.key);
  189. };
  190. // instantiate just needs to run System.register
  191. // so we fetch the source, convert into the Babel System module format, then evaluate it
  192. BrowserESModuleLoader.prototype[RegisterLoader.instantiate] = function(key, processAnonRegister) {
  193. var loader = this;
  194. // load as ES with Babel converting into System.register
  195. return new Promise(function(resolve, reject) {
  196. // anonymous module
  197. if (anonSources[key]) {
  198. resolve(anonSources[key])
  199. anonSources[key] = undefined;
  200. }
  201. // otherwise we fetch
  202. else {
  203. xhrFetch(key, resolve, reject);
  204. }
  205. })
  206. .then(function(source) {
  207. // check our cache first
  208. var cacheEntry = localStorage.getItem(key);
  209. if (cacheEntry) {
  210. cacheEntry = JSON.parse(cacheEntry);
  211. // TODO: store a hash instead
  212. if (cacheEntry.source === source) {
  213. return Promise.resolve({key: key, code: cacheEntry.code, source: cacheEntry.source});
  214. }
  215. }
  216. return new Promise(function (resolve, reject) {
  217. promiseMap.set(key, {resolve: resolve, reject: reject});
  218. babelWorker.postMessage({key: key, source: source});
  219. });
  220. }).then(function (data) {
  221. // evaluate without require, exports and module variables
  222. // we leave module in for now to allow module.require access
  223. try {
  224. var cacheEntry = JSON.stringify({source: data.source, code: data.code});
  225. localStorage.setItem(key, cacheEntry);
  226. } catch (e) {
  227. if (window.console) {
  228. window.console.warn('Unable to cache transpiled version of ' + key + ': ' + e);
  229. }
  230. }
  231. (0, eval)(data.code + '\n//# sourceURL=' + data.key + '!transpiled');
  232. processAnonRegister();
  233. });
  234. };
  235. // create a default loader instance in the browser
  236. if (isBrowser)
  237. loader = new BrowserESModuleLoader();
  238. export default BrowserESModuleLoader;