Browse Source

Copy over changes from dev_mode bootstrapping to examples/federated

After we shifted our focus from the federated example to the dev_mode, we continued to fix bugs and more subtle issues with the logic. This copies that logic back over to the example.

It also fixes a few things we noticed with the dev_mode index.js, such as it did not have the Promise.allSettled polyfill defined for older browsers.
Jason Grout 4 years ago
parent
commit
1119e4b5e5

+ 20 - 3
dev_mode/index.js

@@ -5,6 +5,24 @@
 
 import { PageConfig } from '@jupyterlab/coreutils';
 
+// 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,
+          }))
+      )
+    );
+}
+
 import './style.js';
 
 async function createModule(scope, module) {
@@ -118,7 +136,7 @@ export async function main() {
   // Handle the registered mime extensions.
   const mimeExtensions = [];
   {{#each jupyterlab_mime_extensions}}
-  if (queuedFederated.indexOf('{{@key}}') === -1) {
+  if (!queuedFederated.includes('{{@key}}')) {
     try {
       let ext = require('{{@key}}{{#if this}}/{{this}}{{/if}}');
       for (let plugin of activePlugins(ext)) {
@@ -144,7 +162,7 @@ export async function main() {
 
   // Handled the registered standard extensions.
   {{#each jupyterlab_extensions}}
-  if (queuedFederated.indexOf('{{@key}}') === -1) {
+  if (!queuedFederated.includes('{{@key}}')) {
     try {
       let ext = require('{{@key}}{{#if this}}/{{this}}{{/if}}');
       for (let plugin of activePlugins(ext)) {
@@ -208,4 +226,3 @@ export async function main() {
   }
 
 }
-

+ 32 - 0
examples/app/package.json

@@ -60,5 +60,37 @@
     "webpack": "^5.3.1",
     "webpack-cli": "^4.1.0",
     "whatwg-fetch": "^3.0.0"
+  },
+  "jupyterlab": {
+    "extensions": {
+      "@jupyterlab/application-extension": "",
+      "@jupyterlab/apputils-extension": "",
+      "@jupyterlab/codemirror-extension": "",
+      "@jupyterlab/completer-extension": "",
+      "@jupyterlab/console-extension": "",
+      "@jupyterlab/csvviewer-extension": "",
+      "@jupyterlab/docmanager-extension": "",
+      "@jupyterlab/filebrowser-extension": "",
+      "@jupyterlab/fileeditor-extension": "",
+      "@jupyterlab/help-extension": "",
+      "@jupyterlab/imageviewer-extension": "",
+      "@jupyterlab/inspector-extension": "",
+      "@jupyterlab/launcher-extension": "",
+      "@jupyterlab/mainmenu-extension": "",
+      "@jupyterlab/markdownviewer-extension": "",
+      "@jupyterlab/mathjax2-extension": "",
+      "@jupyterlab/notebook-extension": "",
+      "@jupyterlab/rendermime-extension": "",
+      "@jupyterlab/running-extension": "",
+      "@jupyterlab/settingeditor-extension": "",
+      "@jupyterlab/shortcuts-extension": "",
+      "@jupyterlab/statusbar-extension": "",
+      "@jupyterlab/terminal-extension": "^3.0.1",
+      "@jupyterlab/theme-dark-extension": "",
+      "@jupyterlab/theme-light-extension": "",
+      "@jupyterlab/tooltip-extension": "",
+      "@jupyterlab/translation-extension": "",
+      "@jupyterlab/ui-components-extension": ""
+    }
   }
 }

+ 21 - 7
examples/app/webpack.config.js

@@ -4,13 +4,27 @@ const data = require('./package.json');
 const webpack = require('webpack');
 const Build = require('@jupyterlab/builder').Build;
 
-const names = Object.keys(data.dependencies).filter(function (name) {
-  const packageData = require(name + '/package.json');
-  return packageData.jupyterlab !== undefined;
-});
+const jlab = data.jupyterlab;
+
+// Create a list of application extensions and mime extensions from
+// jlab.extensions
+const extensions = {};
+const mimeExtensions = {};
+
+for (const key of jlab.extensions) {
+  const {
+    jupyterlab: { extension, mimeExtension }
+  } = require(`${key}/package.json`);
+  if (extension !== undefined) {
+    extensions[key] = extension === true ? '' : extension;
+  }
+  if (mimeExtension !== undefined) {
+    mimeExtensions[key] = mimeExtension === true ? '' : mimeExtension;
+  }
+}
 
-const extras = Build.ensureAssets({
-  packageNames: names,
+const extensionAssetConfig = Build.ensureAssets({
+  packageNames: [...Object.keys(jlab.extensions ?? {}), ...Object.keys(jlab.mimeExtensions ?? {})],
   output: './build'
 });
 
@@ -76,4 +90,4 @@ module.exports = [
       })
     ]
   }
-].concat(extras);
+].concat(extensionAssetConfig);

+ 115 - 0
examples/federated/core_package/bootstrap.js

@@ -0,0 +1,115 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) Jupyter Development Team.
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+
+// We copy some of the pageconfig parsing logic in @jupyterlab/coreutils
+// below, since this must run before any other files are loaded (including
+// @jupyterlab/coreutils).
+
+/**
+ * Get global configuration data for the Jupyter application.
+ *
+ * @param name - The name of the configuration option.
+ *
+ * @returns The config value or an empty string if not found.
+ *
+ * #### Notes
+ * All values are treated as strings. For browser based applications, it is
+ * assumed that the page HTML includes a script tag with the id
+ * `jupyter-config-data` containing the configuration as valid JSON.
+ */
+let _CONFIG_DATA = null;
+function getOption(name) {
+  if (_CONFIG_DATA === null) {
+    let configData;
+    // Use script tag if available.
+    if (typeof document !== 'undefined' && document) {
+      const el = document.getElementById('jupyter-config-data');
+
+      if (el) {
+        configData = JSON.parse(el.textContent || '{}');
+      }
+    }
+    _CONFIG_DATA = configData ?? Object.create(null);
+  }
+
+  return _CONFIG_DATA[name] || '';
+}
+
+// eslint-disable-next-line no-undef
+__webpack_public_path__ = 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
+          })
+        )
+      )
+    );
+}
+
+function loadScript(url) {
+  return new Promise((resolve, reject) => {
+    const newScript = document.createElement('script');
+    newScript.onerror = reject;
+    newScript.onload = resolve;
+    newScript.async = true;
+    document.head.appendChild(newScript);
+    newScript.src = url;
+  });
+}
+
+async function loadComponent(url, scope) {
+  await loadScript(url);
+
+  // From https://webpack.js.org/concepts/module-federation/#dynamic-remote-containers
+  await __webpack_init_sharing__('default');
+  const container = window._JUPYTERLAB[scope];
+  // Initialize the container, it may provide shared modules and may need ours
+  await container.init(__webpack_share_scopes__.default);
+}
+
+void (async function bootstrap() {
+  // This is all the data needed to load and activate plugins. This should be
+  // gathered by the server and put onto the initial page template.
+  const extension_data = getOption('federated_extensions');
+
+  // We first load all federated components so that the shared module
+  // deduplication can run and figure out which shared modules from all
+  // components should be actually used. We have to do this before importing
+  // and using the module that actually uses these components so that all
+  // dependencies are initialized.
+  let labExtensionUrl = getOption('fullLabextensionsUrl');
+  const extensions = await Promise.allSettled(
+    extension_data.map(async data => {
+      await loadComponent(
+        `${labExtensionUrl}/${data.name}/${data.load}`,
+        data.name
+      );
+    })
+  );
+
+  extensions.forEach(p => {
+    if (p.status === 'rejected') {
+      // There was an error loading the component
+      console.error(p.reason);
+    }
+  });
+
+  // Now that all federated containers are initialized with the main
+  // container, we can import the main function.
+  let main = (await import('./index.out.js')).main;
+  window.addEventListener('load', main);
+})();

+ 25 - 56
examples/federated/core_package/index.js

@@ -21,28 +21,7 @@ if (Promise.allSettled === undefined) {
     );
 }
 
-require('./style.js')
-
-function loadScript(url) {
-  return new Promise((resolve, reject) => {
-    const newScript = document.createElement('script');
-    newScript.onerror = reject;
-    newScript.onload = resolve;
-    newScript.async = true;
-    document.head.appendChild(newScript);
-    newScript.src = url;
-  });
-}
-
-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
-  await __webpack_init_sharing__('default');
-  const container = window._JUPYTERLAB[scope];
-  // Initialize the container, it may provide shared modules and may need ours
-  await container.init(__webpack_share_scopes__.default);
-}
+import('./style.js');
 
 async function createModule(scope, module) {
   try {
@@ -64,39 +43,25 @@ async function main() {
   var ignorePlugins = [];
   var register = [];
 
+  const federatedExtensionPromises = [];
+  const federatedMimeExtensionPromises = [];
+  const federatedStylePromises = [];
+
   // This is all the data needed to load and activate plugins. This should be
   // gathered by the server and put onto the initial page template.
   const extension_data = JSON.parse(
     PageConfig.getOption('federated_extensions')
   );
 
-  const federatedExtensionPromises = [];
-  const federatedMimeExtensionPromises = [];
-  const federatedStylePromises = [];
-
-  // We first load all federated 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, data.load)}`,
-      data.name
-    );
-    return data;
-  }));
-
-  extensions.forEach(p => {
-    if (p.status === "rejected") {
-      // There was an error loading the component
-      console.error(p.reason);
-      return;
-    }
+  const queuedFederated = [];
 
-    const data = p.value;
+  extensions.forEach(data => {
     if (data.extension) {
+      queuedFederated.push(data.name);
       federatedExtensionPromises.push(createModule(data.name, data.extension));
     }
     if (data.mimeExtension) {
+      queuedFederated.push(data.name);
       federatedMimeExtensionPromises.push(createModule(data.name, data.mimeExtension));
     }
     if (data.style) {
@@ -137,12 +102,15 @@ async function main() {
   // Handle the registered mime extensions.
   const mimeExtensions = [];
   {{#each jupyterlab_mime_extensions}}
-  try {
-    for (let plugin of activePlugins(require('{{@key}}/{{this}}'))) {
-      mimeExtensions.push(plugin);
+  if (!queuedFederated.includes('{{@key}}')) {
+    try {
+      let ext = require('{{@key}}{{#if this}}/{{this}}{{/if}}');
+      for (let plugin of activePlugins(ext)) {
+        mimeExtensions.push(plugin);
+      }
+    } catch (e) {
+      console.error(e);
     }
-  } catch (e) {
-    console.error(e);
   }
   {{/each}}
 
@@ -160,12 +128,15 @@ async function main() {
 
   // Handled the registered standard extensions.
   {{#each jupyterlab_extensions}}
-  try {
-    for (let plugin of activePlugins(require('{{@key}}/{{this}}'))) {
-      register.push(plugin);
+  if (!queuedFederated.includes('{{@key}}')) {
+    try {
+      let ext = require('{{@key}}{{#if this}}/{{this}}{{/if}}');
+      for (let plugin of activePlugins(ext)) {
+        register.push(plugin);
+      }
+    } catch (e) {
+      console.error(e);
     }
-  } catch (e) {
-    console.error(e);
   }
   {{/each}}
 
@@ -205,5 +176,3 @@ async function main() {
     console.debug('Example started!');
   });
 }
-
-window.addEventListener('load', main);

+ 38 - 41
examples/federated/core_package/package.json

@@ -219,46 +219,43 @@
       "react",
       "react-dom"
     ],
-    "extensions": {
-      "@jupyterlab/application-extension": "",
-      "@jupyterlab/apputils-extension": "",
-      "@jupyterlab/celltags-extension": "",
-      "@jupyterlab/codemirror-extension": "",
-      "@jupyterlab/completer-extension": "",
-      "@jupyterlab/console-extension": "",
-      "@jupyterlab/csvviewer-extension": "",
-      "@jupyterlab/docmanager-extension": "",
-      "@jupyterlab/documentsearch-extension": "",
-      "@jupyterlab/extensionmanager-extension": "",
-      "@jupyterlab/filebrowser-extension": "",
-      "@jupyterlab/fileeditor-extension": "",
-      "@jupyterlab/help-extension": "",
-      "@jupyterlab/htmlviewer-extension": "",
-      "@jupyterlab/hub-extension": "",
-      "@jupyterlab/imageviewer-extension": "",
-      "@jupyterlab/inspector-extension": "",
-      "@jupyterlab/launcher-extension": "",
-      "@jupyterlab/logconsole-extension": "",
-      "@jupyterlab/mainmenu-extension": "",
-      "@jupyterlab/mathjax2-extension": "",
-      "@jupyterlab/notebook-extension": "",
-      "@jupyterlab/rendermime-extension": "",
-      "@jupyterlab/settingeditor-extension": "",
-      "@jupyterlab/shortcuts-extension": "",
-      "@jupyterlab/statusbar-extension": "",
-      "@jupyterlab/terminal-extension": "",
-      "@jupyterlab/theme-light-extension": "",
-      "@jupyterlab/tooltip-extension": "",
-      "@jupyterlab/translation-extension": "",
-      "@jupyterlab/ui-components-extension": "",
-      "@jupyterlab/vdom-extension": ""
-    },
-    "mimeExtensions": {
-      "@jupyterlab/javascript-extension": "",
-      "@jupyterlab/json-extension": "",
-      "@jupyterlab/pdf-extension": "",
-      "@jupyterlab/vega5-extension": ""
-    },
-    "externalExtensions": {}
+    "extensions": [
+      "@jupyterlab/application-extension",
+      "@jupyterlab/apputils-extension",
+      "@jupyterlab/celltags-extension",
+      "@jupyterlab/codemirror-extension",
+      "@jupyterlab/completer-extension",
+      "@jupyterlab/console-extension",
+      "@jupyterlab/csvviewer-extension",
+      "@jupyterlab/docmanager-extension",
+      "@jupyterlab/documentsearch-extension",
+      "@jupyterlab/extensionmanager-extension",
+      "@jupyterlab/filebrowser-extension",
+      "@jupyterlab/fileeditor-extension",
+      "@jupyterlab/help-extension",
+      "@jupyterlab/htmlviewer-extension",
+      "@jupyterlab/hub-extension",
+      "@jupyterlab/imageviewer-extension",
+      "@jupyterlab/inspector-extension",
+      "@jupyterlab/launcher-extension",
+      "@jupyterlab/logconsole-extension",
+      "@jupyterlab/mainmenu-extension",
+      "@jupyterlab/mathjax2-extension",
+      "@jupyterlab/notebook-extension",
+      "@jupyterlab/rendermime-extension",
+      "@jupyterlab/settingeditor-extension",
+      "@jupyterlab/shortcuts-extension",
+      "@jupyterlab/statusbar-extension",
+      "@jupyterlab/terminal-extension",
+      "@jupyterlab/theme-light-extension",
+      "@jupyterlab/tooltip-extension",
+      "@jupyterlab/translation-extension",
+      "@jupyterlab/ui-components-extension",
+      "@jupyterlab/vdom-extension",
+      "@jupyterlab/javascript-extension",
+      "@jupyterlab/json-extension",
+      "@jupyterlab/pdf-extension",
+      "@jupyterlab/vega5-extension"
+    ]
   }
 }

+ 11 - 20
examples/federated/core_package/webpack.config.js

@@ -10,12 +10,6 @@ const fs = require('fs-extra');
 const path = require('path');
 const Handlebars = require('handlebars');
 
-const names = Object.keys(data.dependencies).filter(function (name) {
-  const packageData = require(path.join(name, 'package.json'));
-  return packageData.jupyterlab !== undefined;
-});
-
-const jlab = data.jupyterlab;
 
 // Ensure a clear build directory.
 const buildDir = path.resolve(__dirname, 'build');
@@ -24,26 +18,24 @@ if (fs.existsSync(buildDir)) {
 }
 fs.ensureDirSync(buildDir);
 
-const extras = Build.ensureAssets({
-  packageNames: names,
+// Configuration to handle extension assets
+const extensionAssetConfig = Build.ensureAssets({
+  packageNames: jlab.extensions,
   output: buildDir
 });
 
 const singletons = {};
 
-data.jupyterlab.singletonPackages.forEach(element => {
+jlab.singletonPackages.forEach(element => {
   singletons[element] = { singleton: true };
 });
 
-// Handle the extensions.
-const extensions = jlab.extensions || {};
-const mimeExtensions = jlab.mimeExtensions || {};
-const externalExtensions = jlab.externalExtensions || {};
+// Create a list of application extensions and mime extensions from
+// jlab.extensions
+const extensions = {};
+const mimeExtensions = {};
 
-// go through each external extension
-// add to mapping of extension and mime extensions, of package name
-// to path of the extension.
-for (const key in externalExtensions) {
+for (const key of jlab.extensions) {
   const {
     jupyterlab: { extension, mimeExtension }
   } = require(`${key}/package.json`);
@@ -68,8 +60,7 @@ fs.writeFileSync(path.join(buildDir, 'index.out.js'), result);
 
 // Make a bootstrap entrypoint
 const entryPoint = path.join(buildDir, 'bootstrap.js');
-const bootstrap = 'import("./index.out.js");';
-fs.writeFileSync(entryPoint, bootstrap);
+fs.copySync('./bootstrap.js', entryPoint);
 
 if (process.env.NODE_ENV === 'production') {
   baseConfig.mode = 'production';
@@ -101,4 +92,4 @@ module.exports = [
       })
     ]
   })
-].concat(extras);
+].concat(extensionAssetConfig);