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