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