瀏覽代碼

Port example app changes to dev_mod/index.js

Maciej Nowacki 4 年之前
父節點
當前提交
f0d4c30cb0
共有 1 個文件被更改,包括 123 次插入72 次删除
  1. 123 72
      dev_mode/index.js

+ 123 - 72
dev_mode/index.js

@@ -4,12 +4,31 @@
 |----------------------------------------------------------------------------*/
 
 import {
-  PageConfig
+  PageConfig,
+  URLExt,
 } from '@jupyterlab/coreutils';
 
 // eslint-disable-next-line no-undef
 __webpack_public_path__ = PageConfig.getOption('fullStaticUrl') + '/';
 
+// Promise.allSettled polyfill, until our supported browsers implement it
+// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled
+if (Promise.allSettled === undefined) {
+  Promise.allSettled = promises =>
+    Promise.all(
+      promises.map(promise =>
+        promise
+          .then(value => ({
+            status: "fulfilled",
+            value,
+          }), reason => ({
+            status: "rejected",
+            reason,
+          }))
+      )
+    );
+}
+
 // This must be after the public path is set.
 // This cannot be extracted because the public path is dynamic.
 require('./imports.css');
@@ -26,7 +45,7 @@ function loadScript(url) {
   });
 }
 
-async function loadComponent(url, scope, module) {
+async function loadComponent(url, scope) {
   await loadScript(url);
 
   // From MIT-licensed https://github.com/module-federation/module-federation-examples/blob/af043acd6be1718ee195b2511adf6011fba4233c/advanced-api/dynamic-remotes/app1/src/App.js#L6-L12
@@ -34,13 +53,17 @@ async function loadComponent(url, scope, module) {
   const container = window._JUPYTERLAB[scope];
   // Initialize the container, it may provide shared modules and may need ours
   await container.init(__webpack_share_scopes__.default);
-
-  const factory = await window._JUPYTERLAB[scope].get(module);
-  const Module = factory();
-  return Module;
 }
 
-
+async function createModule(scope, module) {
+  try {
+    const factory = await window._JUPYTERLAB[scope].get(module);
+    return factory();
+  } catch(e) {
+    console.warn(`Failed to create module: package: ${scope}; module: ${module}`);
+    throw e;
+  }
+}
 
 /**
  * The main entry point for the application.
@@ -57,97 +80,125 @@ async function main() {
   const extension_data = JSON.parse(
     PageConfig.getOption('dynamic_extensions')
   );
-  const mime_extension_data = JSON.parse(
-    PageConfig.getOption('dynamic_mime_extensions')
-  );
 
-  // Get dynamic plugins
-  const dynamicPromises = extension_data.map(data =>
-    loadComponent(
-      data.path,
-      data.name,
-      data.module
-    )
-  );
-  const dynamicPlugins = await Promise.all(dynamicPromises);
-
-  const dynamicMimePromises = mime_extension_data.map(data =>
-    loadComponent(
-      data.path,
-      data.name,
-      data.module
-    )
-  );
-  const dynamicMimePlugins = await Promise.all(dynamicMimePromises);
+  const dynamicPluginPromises = [];
+  const dynamicMimePluginPromises = [];
+  const dynamicStylePromises = [];
+
+  // We first load all dynamic components so that the shared module
+  // deduplication can run and figure out which shared modules from all
+  // components should be actually used.
+  const extensions = await Promise.allSettled(extension_data.map( async data => {
+    await loadComponent(
+      `${URLExt.join(PageConfig.getOption('fullLabextensionsUrl'), data.name, 'remoteEntry.js')}`,
+      data.name
+    );
+    return data;
+  }));
+
+  extensions.forEach(p => {
+    if (p.status === "error") {
+      // There was an error loading the component
+      console.error(p.reason);
+      return;
+    }
 
-  // Handle the registered mime extensions.
-  var mimeExtensions = [];
-  var extension;
-  var extMod;
-  var plugins = [];
-  {{#each jupyterlab_mime_extensions}}
-  try {
-    extMod = require('{{@key}}/{{this}}');
-    extension = extMod.default;
+    const data = p.value;
+    if (data.plugin) {
+      dynamicPluginPromises.push(createModule(data.name, data.plugin));
+    }
+    if (data.mimePlugin) {
+      dynamicMimePluginPromises.push(createModule(data.name, data.mimePlugin));
+    }
+    if (data.style) {
+      dynamicStylePromises.push(createModule(data.name, data.style));
+    }
+  });
 
-    // Handle CommonJS exports.
-    if (!extMod.hasOwnProperty('__esModule')) {
-      extension = extMod;
+  /**
+   * Iterate over active plugins in an extension.
+   * 
+   * #### Notes
+   * This also populates the disabled, deferred, and ignored arrays.
+   */
+  function* activePlugins(extension) {
+    // Handle commonjs or es2015 modules
+    let exports;
+    if (extension.hasOwnProperty('__esModule')) {
+      exports = extension.default;
+    } else {
+      // CommonJS exports.
+      exports = extension;
     }
 
-    plugins = Array.isArray(extension) ? extension : [extension];
-    plugins.forEach(function(plugin) {
+    let plugins = Array.isArray(exports) ? exports : [exports];
+    for (let plugin of plugins) {
+      if (PageConfig.Extension.isDisabled(plugin.id)) {
+        disabled.push(plugin.id);
+        continue;
+      }
       if (PageConfig.Extension.isDeferred(plugin.id)) {
         deferred.push(plugin.id);
         ignorePlugins.push(plugin.id);
       }
-      if (PageConfig.Extension.isDisabled(plugin.id)) {
-        disabled.push(plugin.id);
-        return;
-      }
+      yield plugin;
+    }
+  }
+
+  // Handle the registered mime extensions.
+  const mimeExtensions = [];
+  {{#each jupyterlab_mime_extensions}}
+  try {
+    for (let plugin of activePlugins(require('{{@key}}/{{this}}'))) {
       mimeExtensions.push(plugin);
-    });
+    }
   } catch (e) {
     console.error(e);
   }
   {{/each}}
 
-  // Add the dyanmic mime extensions.
-  dynamicMimePlugins.forEach(plugin => { mimeExtensions.push(plugin); });
+  // Add the dynamic mime extensions.
+  const dynamicMimePlugins = await Promise.allSettled(dynamicMimePluginPromises);
+  dynamicMimePlugins.forEach(p => {
+    if (p.status === "fulfilled") {
+      for (let plugin of activePlugins(p.value)) {
+        mimeExtensions.push(plugin);
+      }
+    } else {
+      console.error(p.reason);
+    }
+  });
 
   // Handled the registered standard extensions.
   {{#each jupyterlab_extensions}}
   try {
-    extMod = require('{{@key}}/{{this}}');
-    extension = extMod.default;
-
-    // Handle CommonJS exports.
-    if (!extMod.hasOwnProperty('__esModule')) {
-      extension = extMod;
-    }
-
-    plugins = Array.isArray(extension) ? extension : [extension];
-    plugins.forEach(function(plugin) {
-      if (PageConfig.Extension.isDeferred(plugin.id)) {
-        deferred.push(plugin.id);
-        ignorePlugins.push(plugin.id);
-      }
-      if (PageConfig.Extension.isDisabled(plugin.id)) {
-        disabled.push(plugin.id);
-        return;
-      }
+    for (let plugin of activePlugins(require('{{@key}}/{{this}}'))) {
       register.push(plugin);
-    });
+    }
   } catch (e) {
     console.error(e);
   }
   {{/each}}
 
   // Add the dynamic extensions.
-  dynamicPlugins.forEach(plugin => { register.push(plugin) });
+  const dynamicPlugins = await Promise.allSettled(dynamicPluginPromises);
+  dynamicPlugins.forEach(p => {
+    if (p.status === "fulfilled") {
+      for (let plugin of activePlugins(p.value)) {
+        register.push(plugin);
+      }
+    } else {
+      console.error(p.reason);
+    }
+  });
+
+  // Load all dynamic component styles and log errors for any that do not
+  (await Promise.allSettled(dynamicStylePromises)).filter(({status}) => status === "rejected").forEach(({reason}) => {
+    console.error(reason);
+  });
 
-  var lab = new JupyterLab({
-    mimeExtensions: mimeExtensions,
+  const lab = new JupyterLab({
+    mimeExtensions,
     disabled: {
       matches: disabled,
       patterns: PageConfig.Extension.disabled
@@ -160,7 +211,7 @@ async function main() {
     },
   });
   register.forEach(function(item) { lab.registerPluginModule(item); });
-  lab.start({ ignorePlugins: ignorePlugins });
+  lab.start({ ignorePlugins });
 
   // Expose global app instance when in dev mode or when toggled explicitly.
   var exposeAppInBrowser = (PageConfig.getOption('exposeAppInBrowser') || '').toLowerCase() === 'true';