@merkur/integration 0.37.0 → 0.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.cjs +308 -116
- package/lib/index.es9.cjs +224 -84
- package/lib/index.es9.mjs +222 -85
- package/lib/index.js +308 -116
- package/lib/index.mjs +306 -117
- package/lib/index.umd.js +1 -1
- package/package.json +3 -3
- package/server/__tests__/indexSpec.js +272 -30
- package/server/index.js +25 -3
- package/server/__tests__/__snapshots__/indexSpec.js.snap +0 -19
package/lib/index.cjs
CHANGED
|
@@ -57,165 +57,357 @@ const exported = {
|
|
|
57
57
|
test,
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
const isLoadedSymbol = Symbol.for('isLoaded');
|
|
61
|
+
const loadingPromiseSymbol = Symbol.for('loadingPromise');
|
|
62
|
+
|
|
63
|
+
function _attachElementToAsset(asset, element) {
|
|
64
|
+
return {
|
|
65
|
+
...asset,
|
|
66
|
+
element,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
63
69
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
70
|
+
function _handleAssetError({
|
|
71
|
+
asset,
|
|
72
|
+
message = `Error loading asset ${asset.source}.`,
|
|
73
|
+
}) {
|
|
74
|
+
if (asset.optional) {
|
|
75
|
+
console.warn(message);
|
|
76
|
+
|
|
77
|
+
return _attachElementToAsset(asset, null);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const error = new Error(message);
|
|
81
|
+
error.asset = asset;
|
|
82
|
+
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
68
85
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
86
|
+
function _addListenersToAssetElement(asset, element, resolve, reject) {
|
|
87
|
+
element.addEventListener('load', () => {
|
|
88
|
+
resolve(_attachElementToAsset(asset, element));
|
|
89
|
+
element[isLoadedSymbol] = true;
|
|
90
|
+
delete element[loadingPromiseSymbol];
|
|
91
|
+
});
|
|
92
|
+
element.addEventListener('error', () => {
|
|
93
|
+
if (element.parentNode) {
|
|
94
|
+
element.remove();
|
|
75
95
|
}
|
|
76
96
|
|
|
77
|
-
|
|
97
|
+
try {
|
|
98
|
+
resolve(_handleAssetError({ asset }));
|
|
99
|
+
} catch (error) {
|
|
100
|
+
reject(error);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
78
104
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
105
|
+
function _loadStyle(asset, root) {
|
|
106
|
+
if (asset.type === 'inlineStyle') {
|
|
107
|
+
const style = document.createElement('style');
|
|
108
|
+
style.innerHTML = asset.source;
|
|
109
|
+
root.appendChild(style);
|
|
110
|
+
|
|
111
|
+
return _attachElementToAsset(asset, style);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const link = document.createElement('link');
|
|
115
|
+
|
|
116
|
+
link[loadingPromiseSymbol] = new Promise((resolve, reject) => {
|
|
117
|
+
_addListenersToAssetElement(asset, link, resolve, reject);
|
|
118
|
+
link.rel = 'stylesheet';
|
|
119
|
+
link.href = asset.source;
|
|
120
|
+
|
|
121
|
+
root.appendChild(link);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
return link[loadingPromiseSymbol];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function loadStyleAssets(assets, root = document.head) {
|
|
128
|
+
const styleElements = Array.from(root.querySelectorAll('style'));
|
|
129
|
+
|
|
130
|
+
return Promise.all(
|
|
131
|
+
assets.map((asset) => {
|
|
132
|
+
if (
|
|
133
|
+
!['stylesheet', 'inlineStyle'].includes(asset.type) ||
|
|
134
|
+
!asset.source
|
|
135
|
+
) {
|
|
136
|
+
return _attachElementToAsset(asset, null);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (asset.type === 'stylesheet') {
|
|
140
|
+
const link = root.querySelector(`link[href='${asset.source}']`);
|
|
141
|
+
|
|
142
|
+
if (link) {
|
|
143
|
+
if (link[loadingPromiseSymbol]) {
|
|
144
|
+
return link[loadingPromiseSymbol];
|
|
145
|
+
}
|
|
84
146
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
147
|
+
return _attachElementToAsset(asset, link);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (asset.type === 'inlineStyle') {
|
|
152
|
+
const inlineStyle = styleElements.find(
|
|
153
|
+
(element) => element.innerHTML === asset.source,
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
if (inlineStyle) {
|
|
157
|
+
return _attachElementToAsset(asset, inlineStyle);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return _loadStyle(asset, root);
|
|
162
|
+
}),
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function _findScriptElement(scriptElements, asset) {
|
|
167
|
+
if (asset.type === 'json') {
|
|
168
|
+
return scriptElements.find(
|
|
169
|
+
(element) => element.dataset.src === asset.source,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (!['script', 'inlineScript', 'inlineJson'].includes(asset.type)) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const attributeKey = asset.type === 'script' ? 'src' : 'textContent';
|
|
178
|
+
const source =
|
|
179
|
+
asset.type === 'inlineJson' ? JSON.stringify(asset.source) : asset.source;
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
scriptElements.find((element) => element[attributeKey] === source) || null
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function _loadScript(asset, root) {
|
|
187
|
+
const script = document.createElement('script');
|
|
188
|
+
|
|
189
|
+
if (asset.type === 'inlineScript') {
|
|
190
|
+
script.textContent = asset.source;
|
|
191
|
+
root.appendChild(script);
|
|
192
|
+
|
|
193
|
+
return _attachElementToAsset(asset, script);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
script[loadingPromiseSymbol] = new Promise((resolve, reject) => {
|
|
197
|
+
script.defer = true;
|
|
198
|
+
_addListenersToAssetElement(asset, script, resolve, reject);
|
|
199
|
+
script.src = asset.source;
|
|
200
|
+
|
|
201
|
+
const { attr } = asset;
|
|
202
|
+
if (attr && Object.keys(attr).length) {
|
|
203
|
+
for (const name in attr) {
|
|
204
|
+
const value = attr[name];
|
|
205
|
+
|
|
206
|
+
if (typeof value === 'boolean') {
|
|
207
|
+
if (value) {
|
|
208
|
+
script.setAttribute(name, '');
|
|
100
209
|
} else {
|
|
101
|
-
script.
|
|
210
|
+
script.removeAttribute(name);
|
|
102
211
|
}
|
|
212
|
+
} else {
|
|
213
|
+
script.setAttribute(name, value);
|
|
103
214
|
}
|
|
104
215
|
}
|
|
105
|
-
} else {
|
|
106
|
-
script.text = asset.source;
|
|
107
|
-
resolve();
|
|
108
216
|
}
|
|
109
217
|
|
|
110
218
|
root.appendChild(script);
|
|
111
219
|
});
|
|
112
|
-
}
|
|
113
220
|
|
|
114
|
-
|
|
115
|
-
return new Promise((resolve, reject) => {
|
|
116
|
-
if (asset.type === 'stylesheet') {
|
|
117
|
-
const link = document.createElement('link');
|
|
118
|
-
link.onload = resolve;
|
|
119
|
-
link.onerror = reject;
|
|
120
|
-
link.rel = 'stylesheet';
|
|
121
|
-
link.href = asset.source;
|
|
122
|
-
|
|
123
|
-
root.appendChild(link);
|
|
124
|
-
} else {
|
|
125
|
-
const style = document.createElement('style');
|
|
126
|
-
style.innerHTML = asset.source;
|
|
127
|
-
|
|
128
|
-
root.appendChild(style);
|
|
129
|
-
resolve();
|
|
130
|
-
}
|
|
131
|
-
});
|
|
221
|
+
return script[loadingPromiseSymbol];
|
|
132
222
|
}
|
|
133
223
|
|
|
134
|
-
function
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
((asset.type
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
224
|
+
async function loadScriptAssets(assets, root = document.head) {
|
|
225
|
+
const scriptElements = Array.from(root.querySelectorAll('script'));
|
|
226
|
+
|
|
227
|
+
return Promise.all(
|
|
228
|
+
assets.map((asset) => {
|
|
229
|
+
if (!['script', 'inlineScript'].includes(asset.type) || !asset.source) {
|
|
230
|
+
return _attachElementToAsset(asset, null);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const { source } = asset;
|
|
234
|
+
const _asset = Object.assign({}, asset);
|
|
235
|
+
|
|
236
|
+
if (source === Object(source)) {
|
|
237
|
+
if (source.es13 && exported.isES13Supported()) {
|
|
238
|
+
_asset.source = source.es13;
|
|
239
|
+
} else if (source.es11 && exported.isES11Supported()) {
|
|
240
|
+
_asset.source = source.es11;
|
|
241
|
+
} else if (source.es9 && exported.isES9Supported()) {
|
|
242
|
+
_asset.source = source.es9;
|
|
243
|
+
} else {
|
|
244
|
+
_asset.source = null;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (!_asset.source) {
|
|
248
|
+
return _handleAssetError({
|
|
249
|
+
asset: _asset,
|
|
250
|
+
message: `Asset '${_asset.name}' is missing ES variant and could not be loaded.`,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (_asset.test && exported.test(_asset.test)) {
|
|
256
|
+
return _attachElementToAsset(
|
|
257
|
+
_asset,
|
|
258
|
+
_findScriptElement(scriptElements, _asset),
|
|
259
|
+
);
|
|
260
|
+
}
|
|
150
261
|
|
|
151
|
-
|
|
262
|
+
const script = _findScriptElement(scriptElements, _asset);
|
|
263
|
+
|
|
264
|
+
if (script && _asset.type === 'script') {
|
|
265
|
+
if (script[loadingPromiseSymbol]) {
|
|
266
|
+
return script[loadingPromiseSymbol];
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (script[isLoadedSymbol]) {
|
|
270
|
+
return _attachElementToAsset(_asset, script);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return new Promise((resolve, reject) =>
|
|
274
|
+
_addListenersToAssetElement(_asset, script, resolve, reject),
|
|
275
|
+
);
|
|
276
|
+
} else if (script && _asset.type === 'inlineScript') {
|
|
277
|
+
return _attachElementToAsset(_asset, script);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return _loadScript(_asset, root);
|
|
281
|
+
}),
|
|
282
|
+
);
|
|
152
283
|
}
|
|
153
284
|
|
|
154
|
-
async function
|
|
155
|
-
const
|
|
156
|
-
const scriptsToRender = assets.reduce((scripts, asset) => {
|
|
157
|
-
const { source } = asset;
|
|
158
|
-
const _asset = Object.assign({}, asset);
|
|
285
|
+
async function _fetchData(source) {
|
|
286
|
+
const response = await fetch(source);
|
|
159
287
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
288
|
+
if (!response.ok) {
|
|
289
|
+
throw new Error(
|
|
290
|
+
`Failed to fetch from '${source}' with status ${response.status} ${response.statusText}.`,
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return response.text();
|
|
295
|
+
}
|
|
163
296
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
} else if (source.es9 && exported.isES9Supported()) {
|
|
170
|
-
_asset.source = source.es9;
|
|
171
|
-
} else {
|
|
172
|
-
_asset.source = null;
|
|
297
|
+
function _removeElementAfterTimeout(element, timeout) {
|
|
298
|
+
if (timeout) {
|
|
299
|
+
setTimeout(() => {
|
|
300
|
+
if (element.parentNode) {
|
|
301
|
+
element.remove();
|
|
173
302
|
}
|
|
303
|
+
}, timeout);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function _loadJsonAsset(asset, root) {
|
|
308
|
+
const script = document.createElement('script');
|
|
309
|
+
script.type = 'application/json';
|
|
310
|
+
|
|
311
|
+
if (asset.type === 'inlineJson') {
|
|
312
|
+
script.textContent = JSON.stringify(asset.source);
|
|
313
|
+
root.appendChild(script);
|
|
314
|
+
_removeElementAfterTimeout(script, asset.ttl);
|
|
315
|
+
|
|
316
|
+
return _attachElementToAsset(asset, script);
|
|
317
|
+
}
|
|
174
318
|
|
|
175
|
-
|
|
176
|
-
|
|
319
|
+
script[loadingPromiseSymbol] = new Promise((resolve, reject) => {
|
|
320
|
+
script.dataset.src = asset.source;
|
|
321
|
+
root.appendChild(script);
|
|
177
322
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
323
|
+
(async () => {
|
|
324
|
+
try {
|
|
325
|
+
const textContent = await _fetchData(asset.source);
|
|
326
|
+
script.textContent = textContent;
|
|
327
|
+
delete script[loadingPromiseSymbol];
|
|
328
|
+
_removeElementAfterTimeout(script, asset.ttl);
|
|
329
|
+
resolve(_attachElementToAsset(asset, script));
|
|
330
|
+
} catch (error) {
|
|
331
|
+
script.remove();
|
|
181
332
|
|
|
182
|
-
|
|
333
|
+
try {
|
|
334
|
+
resolve(
|
|
335
|
+
_handleAssetError({
|
|
336
|
+
asset,
|
|
337
|
+
message: `Error loading JSON asset '${asset.name}': ${error.message}`,
|
|
338
|
+
}),
|
|
339
|
+
);
|
|
340
|
+
} catch (error) {
|
|
341
|
+
reject(error);
|
|
183
342
|
}
|
|
343
|
+
}
|
|
344
|
+
})();
|
|
345
|
+
});
|
|
184
346
|
|
|
185
|
-
|
|
186
|
-
|
|
347
|
+
return script[loadingPromiseSymbol];
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async function loadJsonAssets(assets, root = document.head) {
|
|
351
|
+
const scriptElements = Array.from(
|
|
352
|
+
root.querySelectorAll('script[type="application/json"]'),
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
return Promise.all(
|
|
356
|
+
assets.map((asset) => {
|
|
357
|
+
if (!['json', 'inlineJson'].includes(asset.type) || !asset.source) {
|
|
358
|
+
return _attachElementToAsset(asset, null);
|
|
187
359
|
}
|
|
188
|
-
}
|
|
189
360
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
361
|
+
const script = _findScriptElement(scriptElements, asset);
|
|
362
|
+
|
|
363
|
+
if (script) {
|
|
364
|
+
if (script[loadingPromiseSymbol]) {
|
|
365
|
+
return script[loadingPromiseSymbol];
|
|
194
366
|
}
|
|
195
367
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
) {
|
|
200
|
-
return scripts;
|
|
201
|
-
}
|
|
368
|
+
if (script.textContent) {
|
|
369
|
+
return _attachElementToAsset(asset, script);
|
|
370
|
+
}
|
|
202
371
|
|
|
203
|
-
|
|
372
|
+
return _handleAssetError({
|
|
373
|
+
asset,
|
|
374
|
+
message: `JSON asset '${asset.name}' is missing textContent and could not be loaded.`,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
204
377
|
|
|
205
|
-
|
|
206
|
-
|
|
378
|
+
return _loadJsonAsset(asset, root);
|
|
379
|
+
}),
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function _mergeResults(results) {
|
|
384
|
+
return results.reduce((acc, results) => {
|
|
385
|
+
results.forEach((result, index) => {
|
|
386
|
+
if (!acc[index]) {
|
|
387
|
+
acc[index] = result;
|
|
388
|
+
} else if (result.element) {
|
|
389
|
+
acc[index] = result;
|
|
390
|
+
}
|
|
391
|
+
});
|
|
207
392
|
|
|
208
|
-
|
|
393
|
+
return acc;
|
|
394
|
+
}, []);
|
|
209
395
|
}
|
|
210
396
|
|
|
211
|
-
function loadAssets(assets, root) {
|
|
212
|
-
|
|
397
|
+
async function loadAssets(assets, root) {
|
|
398
|
+
const results = await Promise.all([
|
|
213
399
|
loadScriptAssets(assets, root),
|
|
214
400
|
loadStyleAssets(assets, root),
|
|
401
|
+
loadJsonAssets(assets, root),
|
|
215
402
|
]);
|
|
403
|
+
|
|
404
|
+
return _mergeResults(results);
|
|
216
405
|
}
|
|
217
406
|
|
|
407
|
+
exports.isLoadedSymbol = isLoadedSymbol;
|
|
218
408
|
exports.loadAssets = loadAssets;
|
|
409
|
+
exports.loadJsonAssets = loadJsonAssets;
|
|
219
410
|
exports.loadScriptAssets = loadScriptAssets;
|
|
220
411
|
exports.loadStyleAssets = loadStyleAssets;
|
|
412
|
+
exports.loadingPromiseSymbol = loadingPromiseSymbol;
|
|
221
413
|
exports.testScript = exported;
|