@module-federation/runtime 0.0.0-next-20231225041300 → 0.0.0-next-20231225073141

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.
@@ -3,7 +3,7 @@
3
3
  var share = require('./share.cjs.js');
4
4
 
5
5
  const ShareUtils = {
6
- getGlobalShare: share.getGlobalShare,
6
+ getRegisteredShare: share.getRegisteredShare,
7
7
  getGlobalShareScope: share.getGlobalShareScope
8
8
  };
9
9
  const GlobalUtils = {
@@ -1,7 +1,7 @@
1
- import { l as getGlobalShare, x as getGlobalShareScope, F as nativeGlobal, H as resetFederationGlobalInfo, A as getGlobalFederationInstance, C as setGlobalFederationInstance, B as getGlobalFederationConstructor, z as setGlobalFederationConstructor, m as getInfoWithoutType, r as getGlobalSnapshot, I as getTargetSnapshotInfoByModuleInfo, p as getGlobalSnapshotInfoByModuleInfo, q as setGlobalSnapshotInfoByModuleInfo, J as addGlobalSnapshot, c as getRemoteEntryExports, E as registerGlobalPlugins, g as getGlobalHostPlugins, n as getPreloaded, o as setPreloaded } from './share.esm.js';
1
+ import { l as getRegisteredShare, t as getGlobalShareScope, F as nativeGlobal, H as resetFederationGlobalInfo, A as getGlobalFederationInstance, C as setGlobalFederationInstance, B as getGlobalFederationConstructor, z as setGlobalFederationConstructor, m as getInfoWithoutType, r as getGlobalSnapshot, I as getTargetSnapshotInfoByModuleInfo, p as getGlobalSnapshotInfoByModuleInfo, q as setGlobalSnapshotInfoByModuleInfo, J as addGlobalSnapshot, c as getRemoteEntryExports, E as registerGlobalPlugins, g as getGlobalHostPlugins, n as getPreloaded, o as setPreloaded } from './share.esm.js';
2
2
 
3
3
  const ShareUtils = {
4
- getGlobalShare,
4
+ getRegisteredShare,
5
5
  getGlobalShareScope
6
6
  };
7
7
  const GlobalUtils = {
package/dist/index.cjs.js CHANGED
@@ -121,6 +121,24 @@ async function loadEntryScript({ name, globalName, entry, createScriptHook }) {
121
121
  if (remoteEntryExports) {
122
122
  return remoteEntryExports;
123
123
  }
124
+ if (typeof document === 'undefined') {
125
+ return sdk.loadScriptNode(entry, {
126
+ attrs: {
127
+ name,
128
+ globalName
129
+ },
130
+ createScriptHook
131
+ }).then(()=>{
132
+ const { remoteEntryKey, entryExports } = share.getRemoteEntryExports(name, globalName);
133
+ share.assert(entryExports, `
134
+ Unable to use the ${name}'s '${entry}' URL with ${remoteEntryKey}'s globalName to get remoteEntry exports.
135
+ Possible reasons could be:\n
136
+ 1. '${entry}' is not the correct URL, or the remoteEntry resource or name is incorrect.\n
137
+ 2. ${remoteEntryKey} cannot be used to get remoteEntry exports in the window object.
138
+ `);
139
+ return entryExports;
140
+ });
141
+ }
124
142
  return sdk.loadScript(entry, {
125
143
  attrs: {},
126
144
  createScriptHook
@@ -194,6 +212,10 @@ let Module = class Module {
194
212
  const res = this.loaderHook.lifecycle.createScript.emit({
195
213
  url
196
214
  });
215
+ if (typeof document === 'undefined') {
216
+ //todo: needs real fix
217
+ return res;
218
+ }
197
219
  if (res instanceof HTMLScriptElement) {
198
220
  return res;
199
221
  }
@@ -209,15 +231,16 @@ let Module = class Module {
209
231
  const { loadFactory = true } = options || {
210
232
  loadFactory: true
211
233
  };
234
+ this.hostInfo.name;
212
235
  // Get remoteEntry.js
213
236
  const remoteEntryExports = await this.getEntry();
214
237
  if (!this.inited) {
215
- const globalShareScope = share.Global.__FEDERATION__.__SHARE__;
238
+ const localShareScopeMap = this.shareScopeMap;
216
239
  const remoteShareScope = this.remoteInfo.shareScope || 'default';
217
- if (!globalShareScope[remoteShareScope]) {
218
- globalShareScope[remoteShareScope] = {};
240
+ if (!localShareScopeMap[remoteShareScope]) {
241
+ localShareScopeMap[remoteShareScope] = {};
219
242
  }
220
- const shareScope = globalShareScope[remoteShareScope];
243
+ const shareScope = localShareScopeMap[remoteShareScope];
221
244
  // TODO: compat logic , it could be moved after providing startup hooks
222
245
  const remoteEntryInitOptions = {
223
246
  version: this.remoteInfo.version || '',
@@ -227,10 +250,18 @@ let Module = class Module {
227
250
  remoteEntryExports.init(shareScope, [], remoteEntryInitOptions);
228
251
  const federationInstance = share.Global.__FEDERATION__.__INSTANCES__.find((i)=>i.options.id === sdk.composeKeyWithSeparator(this.remoteInfo.name, this.remoteInfo.buildVersion));
229
252
  if (federationInstance) {
230
- federationInstance.initOptions(_extends$4({}, remoteEntryInitOptions, {
231
- remotes: [],
232
- name: this.remoteInfo.name
233
- }));
253
+ // means the instance is prev vmok instance
254
+ if (!federationInstance.releaseNumber || Number(federationInstance.releaseNumber) <= 100) {
255
+ // 兼容旧的生产者传参
256
+ federationInstance.initOptions(_extends$4({}, remoteEntryInitOptions, {
257
+ remotes: [],
258
+ name: this.remoteInfo.name
259
+ }));
260
+ if (!__FEDERATION__.__SHARE__['default'] && this.shareScopeMap && this.shareScopeMap['default']) {
261
+ // @ts-ignore compat prev logic , and it will be optimized by supporting startup hook
262
+ __FEDERATION__.__SHARE__['default'] = this.shareScopeMap['default'];
263
+ }
264
+ }
234
265
  }
235
266
  }
236
267
  this.lib = remoteEntryExports;
@@ -245,7 +276,7 @@ let Module = class Module {
245
276
  return exposeContent;
246
277
  }
247
278
  // loading: Record<string, undefined | Promise<RemoteEntryExports | void>> = {};
248
- constructor({ hostInfo, remoteInfo, shared, loaderHook }){
279
+ constructor({ hostInfo, remoteInfo, shared, loaderHook, shareScopeMap }){
249
280
  this.inited = false;
250
281
  this.shared = {};
251
282
  this.lib = undefined;
@@ -253,6 +284,7 @@ let Module = class Module {
253
284
  this.remoteInfo = remoteInfo;
254
285
  this.shared = shared;
255
286
  this.loaderHook = loaderHook;
287
+ this.shareScopeMap = shareScopeMap;
256
288
  }
257
289
  };
258
290
 
@@ -779,9 +811,9 @@ function generatePreloadAssets(origin, preloadOptions, remote, globalSnapshot, r
779
811
  if (!shareInfo) {
780
812
  return;
781
813
  }
782
- const globalShare = share.getGlobalShare(shared.sharedName, shareInfo);
814
+ const registeredShared = share.getRegisteredShare(origin.shareScopeMap, shared.sharedName, shareInfo, origin.hooks.lifecycle.resolveShare);
783
815
  // If the global share does not exist, or the lib function does not exist, it means that the shared has not been loaded yet and can be preloaded.
784
- if (globalShare && typeof globalShare.lib === 'function') {
816
+ if (registeredShared && typeof registeredShared.lib === 'function') {
785
817
  shared.assets.js.sync.forEach((asset)=>{
786
818
  loadedSharedJsAssets.add(asset);
787
819
  });
@@ -1108,6 +1140,13 @@ function _object_without_properties_loose(source, excluded) {
1108
1140
  return target;
1109
1141
  }
1110
1142
  class FederationHost {
1143
+ _setGlobalShareScopeMap() {
1144
+ const globalShareScopeMap = share.getGlobalShareScope();
1145
+ const identifier = this.options.id || this.options.name;
1146
+ if (identifier && !globalShareScopeMap[identifier]) {
1147
+ globalShareScopeMap[identifier] = this.shareScopeMap;
1148
+ }
1149
+ }
1111
1150
  initOptions(userOptions) {
1112
1151
  this.registerPlugins(userOptions.plugins);
1113
1152
  const options = this.formatOptions(this.options, userOptions);
@@ -1122,6 +1161,11 @@ class FederationHost {
1122
1161
  // 2. Searches globally for a matching share, if found, it uses it directly
1123
1162
  // 3. If not found, it retrieves it from the current share and stores the obtained share globally.
1124
1163
  const shareInfo = Object.assign({}, (_this_options_shared = this.options.shared) == null ? void 0 : _this_options_shared[pkgName], customShareInfo);
1164
+ if (shareInfo == null ? void 0 : shareInfo.scope) {
1165
+ shareInfo.scope.forEach((shareScope)=>{
1166
+ this.initializeSharing(shareScope, shareInfo.strategy);
1167
+ });
1168
+ }
1125
1169
  const loadShareRes = await this.hooks.lifecycle.beforeLoadShare.emit({
1126
1170
  pkgName,
1127
1171
  shareInfo,
@@ -1132,30 +1176,42 @@ class FederationHost {
1132
1176
  // Assert that shareInfoRes exists, if not, throw an error
1133
1177
  share.assert(shareInfoRes, `Cannot find ${pkgName} Share in the ${this.options.name}. Please ensure that the ${pkgName} Share parameters have been injected`);
1134
1178
  // Retrieve from cache
1135
- const globalShare = share.getGlobalShare(pkgName, shareInfoRes);
1136
- if (globalShare && globalShare.lib) {
1137
- share.addUniqueItem(globalShare.useIn, this.options.name);
1138
- return globalShare.lib;
1139
- } else if (globalShare && globalShare.loading) {
1140
- const factory = await globalShare.loading;
1141
- share.addUniqueItem(globalShare.useIn, this.options.name);
1179
+ const registeredShared = share.getRegisteredShare(this.shareScopeMap, pkgName, shareInfoRes, this.hooks.lifecycle.resolveShare);
1180
+ const addUseIn = (shared)=>{
1181
+ if (!shared.useIn) {
1182
+ shared.useIn = [];
1183
+ }
1184
+ share.addUniqueItem(shared.useIn, this.options.name);
1185
+ };
1186
+ if (registeredShared && registeredShared.lib) {
1187
+ addUseIn(registeredShared);
1188
+ return registeredShared.lib;
1189
+ } else if (registeredShared && registeredShared.loading && !registeredShared.loaded) {
1190
+ const factory = await registeredShared.loading;
1191
+ registeredShared.loaded = true;
1192
+ if (!registeredShared.lib) {
1193
+ registeredShared.lib = factory;
1194
+ }
1195
+ addUseIn(registeredShared);
1142
1196
  return factory;
1143
- } else if (globalShare) {
1197
+ } else if (registeredShared) {
1144
1198
  const asyncLoadProcess = async ()=>{
1145
- const factory = await globalShare.get();
1199
+ const factory = await registeredShared.get();
1146
1200
  shareInfoRes.lib = factory;
1147
- share.addUniqueItem(shareInfoRes.useIn, this.options.name);
1148
- const gShared = share.getGlobalShare(pkgName, shareInfoRes);
1201
+ shareInfoRes.loaded = true;
1202
+ addUseIn(shareInfoRes);
1203
+ const gShared = share.getRegisteredShare(this.shareScopeMap, pkgName, shareInfoRes, this.hooks.lifecycle.resolveShare);
1149
1204
  if (gShared) {
1150
1205
  gShared.lib = factory;
1206
+ gShared.loaded = true;
1151
1207
  }
1152
1208
  return factory;
1153
1209
  };
1154
1210
  const loading = asyncLoadProcess();
1155
1211
  this.setShared({
1156
1212
  pkgName,
1157
- loaded: true,
1158
- shared: shareInfoRes,
1213
+ loaded: false,
1214
+ shared: registeredShared,
1159
1215
  from: this.options.name,
1160
1216
  lib: null,
1161
1217
  loading
@@ -1168,17 +1224,19 @@ class FederationHost {
1168
1224
  const asyncLoadProcess = async ()=>{
1169
1225
  const factory = await shareInfoRes.get();
1170
1226
  shareInfoRes.lib = factory;
1171
- share.addUniqueItem(shareInfoRes.useIn, this.options.name);
1172
- const gShared = share.getGlobalShare(pkgName, shareInfoRes);
1227
+ shareInfoRes.loaded = true;
1228
+ addUseIn(shareInfoRes);
1229
+ const gShared = share.getRegisteredShare(this.shareScopeMap, pkgName, shareInfoRes, this.hooks.lifecycle.resolveShare);
1173
1230
  if (gShared) {
1174
1231
  gShared.lib = factory;
1232
+ gShared.loaded = true;
1175
1233
  }
1176
1234
  return factory;
1177
1235
  };
1178
1236
  const loading = asyncLoadProcess();
1179
1237
  this.setShared({
1180
1238
  pkgName,
1181
- loaded: true,
1239
+ loaded: false,
1182
1240
  shared: shareInfoRes,
1183
1241
  from: this.options.name,
1184
1242
  lib: null,
@@ -1194,16 +1252,16 @@ class FederationHost {
1194
1252
  loadShareSync(pkgName) {
1195
1253
  var _this_options_shared;
1196
1254
  const shareInfo = (_this_options_shared = this.options.shared) == null ? void 0 : _this_options_shared[pkgName];
1197
- const globalShare = share.getGlobalShare(pkgName, shareInfo);
1198
- if (globalShare && typeof globalShare.lib === 'function') {
1199
- share.addUniqueItem(globalShare.useIn, this.options.name);
1200
- if (!globalShare.loaded) {
1201
- globalShare.loaded = true;
1202
- if (globalShare.from === this.options.name) {
1255
+ const registeredShared = share.getRegisteredShare(this.shareScopeMap, pkgName, shareInfo, this.hooks.lifecycle.resolveShare);
1256
+ if (registeredShared && typeof registeredShared.lib === 'function') {
1257
+ share.addUniqueItem(registeredShared.useIn, this.options.name);
1258
+ if (!registeredShared.loaded) {
1259
+ registeredShared.loaded = true;
1260
+ if (registeredShared.from === this.options.name) {
1203
1261
  shareInfo.loaded = true;
1204
1262
  }
1205
1263
  }
1206
- return globalShare.lib;
1264
+ return registeredShared.lib;
1207
1265
  }
1208
1266
  if (shareInfo.lib) {
1209
1267
  if (!shareInfo.loaded) {
@@ -1274,7 +1332,8 @@ class FederationHost {
1274
1332
  remoteInfo,
1275
1333
  shared: this.options.shared || {},
1276
1334
  plugins: this.options.plugins,
1277
- loaderHook: this.loaderHook
1335
+ loaderHook: this.loaderHook,
1336
+ shareScopeMap: this.shareScopeMap
1278
1337
  };
1279
1338
  if (!module) {
1280
1339
  module = new Module(moduleOptions);
@@ -1354,14 +1413,9 @@ class FederationHost {
1354
1413
  * It accepts one argument, the name of the share scope.
1355
1414
  * If the share scope does not exist, it creates one.
1356
1415
  */ // eslint-disable-next-line @typescript-eslint/member-ordering
1357
- initializeSharing(shareScopeName = share.DEFAULT_SCOPE) {
1358
- const shareScopeLoading = share.Global.__FEDERATION__.__SHARE_SCOPE_LOADING__;
1359
- const shareScope = share.Global.__FEDERATION__.__SHARE__;
1416
+ initializeSharing(shareScopeName = share.DEFAULT_SCOPE, strategy) {
1417
+ const shareScope = this.shareScopeMap;
1360
1418
  const hostName = this.options.name;
1361
- // Executes only once
1362
- if (shareScopeLoading[shareScopeName]) {
1363
- return shareScopeLoading[shareScopeName];
1364
- }
1365
1419
  // Creates a new share scope if necessary
1366
1420
  if (!shareScope[shareScopeName]) {
1367
1421
  shareScope[shareScopeName] = {};
@@ -1369,21 +1423,27 @@ class FederationHost {
1369
1423
  // Executes all initialization snippets from all accessible modules
1370
1424
  const scope = shareScope[shareScopeName];
1371
1425
  const register = (name, shared)=>{
1426
+ var _activeVersion_shareConfig;
1372
1427
  const { version, eager } = shared;
1373
1428
  scope[name] = scope[name] || {};
1374
1429
  const versions = scope[name];
1375
1430
  const activeVersion = versions[version];
1376
- const activeVersionEager = Boolean(activeVersion && (activeVersion.eager || activeVersion.shareConfig.eager));
1431
+ const activeVersionEager = Boolean(activeVersion && (activeVersion.eager || ((_activeVersion_shareConfig = activeVersion.shareConfig) == null ? void 0 : _activeVersion_shareConfig.eager)));
1377
1432
  if (!activeVersion || !activeVersion.loaded && (Boolean(!eager) !== !activeVersionEager ? eager : hostName > activeVersion.from)) {
1378
1433
  versions[version] = shared;
1379
1434
  }
1380
1435
  };
1381
1436
  const promises = [];
1437
+ const initFn = (mod)=>mod && mod.init && mod.init(shareScope[shareScopeName]);
1382
1438
  const initRemoteModule = async (key)=>{
1383
1439
  const { module } = await this._getRemoteModuleAndOptions(key);
1384
- const initFn = (mod)=>mod && mod.init && mod.init(shareScope[shareScopeName]);
1385
- const entry = await module.getEntry();
1386
- initFn(entry);
1440
+ if (module.getEntry) {
1441
+ const entry = await module.getEntry();
1442
+ if (!module.inited) {
1443
+ initFn(entry);
1444
+ module.inited = true;
1445
+ }
1446
+ }
1387
1447
  };
1388
1448
  Object.keys(this.options.shared).forEach((shareName)=>{
1389
1449
  const shared = this.options.shared[shareName];
@@ -1391,15 +1451,17 @@ class FederationHost {
1391
1451
  register(shareName, shared);
1392
1452
  }
1393
1453
  });
1394
- this.options.remotes.forEach((remote)=>{
1395
- if (remote.shareScope === shareScopeName) {
1396
- promises.push(initRemoteModule(remote.name));
1397
- }
1398
- });
1399
- if (!promises.length) {
1400
- return shareScopeLoading[shareScopeName] = true;
1454
+ if (strategy === 'version-first') {
1455
+ this.options.remotes.forEach((remote)=>{
1456
+ if (remote.shareScope === shareScopeName) {
1457
+ promises.push(initRemoteModule(remote.name));
1458
+ }
1459
+ });
1401
1460
  }
1402
- return shareScopeLoading[shareScopeName] = Promise.all(promises).then(()=>shareScopeLoading[shareScopeName] = true);
1461
+ return promises;
1462
+ }
1463
+ initShareScopeMap(scopeName, shareScope) {
1464
+ this.shareScopeMap[scopeName] = shareScope;
1403
1465
  }
1404
1466
  formatOptions(globalOptions, userOptions) {
1405
1467
  const formatShareOptions = share.formatShareConfigs(userOptions.shared || {}, userOptions.name);
@@ -1439,16 +1501,17 @@ class FederationHost {
1439
1501
  }
1440
1502
  return res;
1441
1503
  }, globalOptionsRes.remotes);
1442
- // register shared include lib
1504
+ // register shared in shareScopeMap
1443
1505
  const sharedKeys = Object.keys(formatShareOptions);
1444
1506
  sharedKeys.forEach((sharedKey)=>{
1445
1507
  const sharedVal = formatShareOptions[sharedKey];
1446
- const globalShare = share.getGlobalShare(sharedKey, sharedVal);
1447
- if (!globalShare && sharedVal && sharedVal.lib) {
1508
+ const registeredShared = share.getRegisteredShare(this.shareScopeMap, sharedKey, sharedVal, this.hooks.lifecycle.resolveShare);
1509
+ if (!registeredShared && sharedVal && sharedVal.lib) {
1448
1510
  this.setShared({
1449
1511
  pkgName: sharedKey,
1450
1512
  lib: sharedVal.lib,
1451
1513
  get: sharedVal.get,
1514
+ loaded: true,
1452
1515
  shared: sharedVal,
1453
1516
  from: userOptions.name
1454
1517
  });
@@ -1483,7 +1546,6 @@ class FederationHost {
1483
1546
  ]);
1484
1547
  }
1485
1548
  setShared({ pkgName, shared, from, lib, loading, loaded, get }) {
1486
- const target = share.getGlobalShareScope();
1487
1549
  const { version, scope = 'default' } = shared, shareInfo = _object_without_properties_loose(shared, [
1488
1550
  "version",
1489
1551
  "scope"
@@ -1492,23 +1554,23 @@ class FederationHost {
1492
1554
  scope
1493
1555
  ];
1494
1556
  scopes.forEach((sc)=>{
1495
- if (!target[sc]) {
1496
- target[sc] = {};
1557
+ if (!this.shareScopeMap[sc]) {
1558
+ this.shareScopeMap[sc] = {};
1497
1559
  }
1498
- if (!target[sc][pkgName]) {
1499
- target[sc][pkgName] = {};
1560
+ if (!this.shareScopeMap[sc][pkgName]) {
1561
+ this.shareScopeMap[sc][pkgName] = {};
1500
1562
  }
1501
- if (target[sc][pkgName][version]) {
1563
+ if (this.shareScopeMap[sc][pkgName][version]) {
1502
1564
  share.warn(// eslint-disable-next-line max-len
1503
1565
  `The share \n ${share.safeToString({
1504
1566
  scope: sc,
1505
1567
  pkgName,
1506
1568
  version,
1507
- from: target[sc][pkgName][version].from
1569
+ from: this.shareScopeMap[sc][pkgName][version].from
1508
1570
  })} has been registered`);
1509
1571
  return;
1510
1572
  }
1511
- target[sc][pkgName][version] = _extends({
1573
+ this.shareScopeMap[sc][pkgName][version] = _extends({
1512
1574
  version,
1513
1575
  scope: [
1514
1576
  'default'
@@ -1519,7 +1581,7 @@ class FederationHost {
1519
1581
  loading
1520
1582
  });
1521
1583
  if (get) {
1522
- target[sc][pkgName][version].get = get;
1584
+ this.shareScopeMap[sc][pkgName][version].get = get;
1523
1585
  }
1524
1586
  });
1525
1587
  }
@@ -1534,11 +1596,13 @@ class FederationHost {
1534
1596
  errorLoadRemote: new AsyncHook('errorLoadRemote'),
1535
1597
  beforeLoadShare: new AsyncWaterfallHook('beforeLoadShare'),
1536
1598
  loadShare: new AsyncHook(),
1599
+ resolveShare: new SyncWaterfallHook('resolveShare'),
1537
1600
  beforePreloadRemote: new AsyncHook(),
1538
1601
  generatePreloadAssets: new AsyncHook('generatePreloadAssets'),
1539
1602
  afterPreloadRemote: new AsyncHook()
1540
1603
  });
1541
- this.version = '0.0.4';
1604
+ this.releaseNumber = `4`;
1605
+ this.version = `0.0.4`;
1542
1606
  this.moduleCache = new Map();
1543
1607
  this.loaderHook = new PluginSystem({
1544
1608
  // FIXME: may not be suitable
@@ -1546,7 +1610,6 @@ class FederationHost {
1546
1610
  createScript: new SyncHook(),
1547
1611
  fetch: new AsyncHook('fetch')
1548
1612
  });
1549
- this.loadingShare = {};
1550
1613
  // TODO: Validate the details of the options
1551
1614
  // Initialize options with default values
1552
1615
  const defaultOptions = {
@@ -1562,6 +1625,8 @@ class FederationHost {
1562
1625
  };
1563
1626
  this.name = userOptions.name;
1564
1627
  this.options = defaultOptions;
1628
+ this.shareScopeMap = {};
1629
+ this._setGlobalShareScopeMap();
1565
1630
  this.snapshotHandler = new SnapshotHandler(this);
1566
1631
  this.registerPlugins([
1567
1632
  ...defaultOptions.plugins,
@@ -1611,6 +1676,10 @@ function preloadRemote(...args) {
1611
1676
  share.setGlobalFederationConstructor(FederationHost);
1612
1677
 
1613
1678
  exports.registerGlobalPlugins = share.registerGlobalPlugins;
1679
+ Object.defineProperty(exports, 'loadScript', {
1680
+ enumerable: true,
1681
+ get: function () { return sdk.loadScript; }
1682
+ });
1614
1683
  exports.FederationHost = FederationHost;
1615
1684
  exports.init = init;
1616
1685
  exports.loadRemote = loadRemote;