index.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import { PageConfig, URLExt } from '@jupyterlab/coreutils';
  4. // Promise.allSettled polyfill, until our supported browsers implement it
  5. // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled
  6. if (Promise.allSettled === undefined) {
  7. Promise.allSettled = promises =>
  8. Promise.all(
  9. promises.map(promise =>
  10. promise
  11. .then(value => ({
  12. status: "fulfilled",
  13. value,
  14. }), reason => ({
  15. status: "rejected",
  16. reason,
  17. }))
  18. )
  19. );
  20. }
  21. // This must be after the public path is set.
  22. // This cannot be extracted because the public path is dynamic.
  23. require('./imports.css');
  24. function loadScript(url) {
  25. return new Promise((resolve, reject) => {
  26. const newScript = document.createElement('script');
  27. newScript.onerror = reject;
  28. newScript.onload = resolve;
  29. newScript.async = true;
  30. document.head.appendChild(newScript);
  31. newScript.src = url;
  32. });
  33. }
  34. async function loadComponent(url, scope) {
  35. await loadScript(url);
  36. // From MIT-licensed https://github.com/module-federation/module-federation-examples/blob/af043acd6be1718ee195b2511adf6011fba4233c/advanced-api/dynamic-remotes/app1/src/App.js#L6-L12
  37. await __webpack_init_sharing__('default');
  38. const container = window._JUPYTERLAB[scope];
  39. // Initialize the container, it may provide shared modules and may need ours
  40. await container.init(__webpack_share_scopes__.default);
  41. }
  42. async function createModule(scope, module) {
  43. try {
  44. const factory = await window._JUPYTERLAB[scope].get(module);
  45. return factory();
  46. } catch(e) {
  47. console.warn(`Failed to create module: package: ${scope}; module: ${module}`);
  48. throw e;
  49. }
  50. }
  51. /**
  52. * The main entry point for the application.
  53. */
  54. async function main() {
  55. var JupyterLab = require('@jupyterlab/application').JupyterLab;
  56. var disabled = [];
  57. var deferred = [];
  58. var ignorePlugins = [];
  59. var register = [];
  60. // This is all the data needed to load and activate plugins. This should be
  61. // gathered by the server and put onto the initial page template.
  62. const extension_data = JSON.parse(
  63. PageConfig.getOption('federated_extensions')
  64. );
  65. const federatedExtensionPromises = [];
  66. const federatedMimeExtensionPromises = [];
  67. const federatedStylePromises = [];
  68. // We first load all federated components so that the shared module
  69. // deduplication can run and figure out which shared modules from all
  70. // components should be actually used.
  71. const extensions = await Promise.allSettled(extension_data.map( async data => {
  72. await loadComponent(
  73. `${URLExt.join(PageConfig.getOption('fullLabextensionsUrl'), data.name, data.load)}`,
  74. data.name
  75. );
  76. return data;
  77. }));
  78. extensions.forEach(p => {
  79. if (p.status === "rejected") {
  80. // There was an error loading the component
  81. console.error(p.reason);
  82. return;
  83. }
  84. const data = p.value;
  85. if (data.extension) {
  86. federatedExtensionPromises.push(createModule(data.name, data.extension));
  87. }
  88. if (data.mimeExtension) {
  89. federatedMimeExtensionPromises.push(createModule(data.name, data.mimeExtension));
  90. }
  91. if (data.style) {
  92. federatedStylePromises.push(createModule(data.name, data.style));
  93. }
  94. });
  95. /**
  96. * Iterate over active plugins in an extension.
  97. *
  98. * #### Notes
  99. * This also populates the disabled, deferred, and ignored arrays.
  100. */
  101. function* activePlugins(extension) {
  102. // Handle commonjs or es2015 modules
  103. let exports;
  104. if (extension.hasOwnProperty('__esModule')) {
  105. exports = extension.default;
  106. } else {
  107. // CommonJS exports.
  108. exports = extension;
  109. }
  110. let plugins = Array.isArray(exports) ? exports : [exports];
  111. for (let plugin of plugins) {
  112. if (PageConfig.Extension.isDisabled(plugin.id)) {
  113. disabled.push(plugin.id);
  114. continue;
  115. }
  116. if (PageConfig.Extension.isDeferred(plugin.id)) {
  117. deferred.push(plugin.id);
  118. ignorePlugins.push(plugin.id);
  119. }
  120. yield plugin;
  121. }
  122. }
  123. // Handle the registered mime extensions.
  124. const mimeExtensions = [];
  125. {{#each jupyterlab_mime_extensions}}
  126. try {
  127. for (let plugin of activePlugins(require('{{@key}}/{{this}}'))) {
  128. mimeExtensions.push(plugin);
  129. }
  130. } catch (e) {
  131. console.error(e);
  132. }
  133. {{/each}}
  134. // Add the federated mime extensions.
  135. const federatedMimeExtensions = await Promise.allSettled(federatedMimeExtensionPromises);
  136. federatedMimeExtensions.forEach(p => {
  137. if (p.status === "fulfilled") {
  138. for (let plugin of activePlugins(p.value)) {
  139. mimeExtensions.push(plugin);
  140. }
  141. } else {
  142. console.error(p.reason);
  143. }
  144. });
  145. // Handled the registered standard extensions.
  146. {{#each jupyterlab_extensions}}
  147. try {
  148. for (let plugin of activePlugins(require('{{@key}}/{{this}}'))) {
  149. register.push(plugin);
  150. }
  151. } catch (e) {
  152. console.error(e);
  153. }
  154. {{/each}}
  155. // Add the federated extensions.
  156. const federatedExtensions = await Promise.allSettled(federatedExtensionPromises);
  157. federatedExtensions.forEach(p => {
  158. if (p.status === "fulfilled") {
  159. for (let plugin of activePlugins(p.value)) {
  160. register.push(plugin);
  161. }
  162. } else {
  163. console.error(p.reason);
  164. }
  165. });
  166. // Load all federated component styles and log errors for any that do not
  167. (await Promise.allSettled(federatedStylePromises)).filter(({status}) => status === "rejected").forEach(({reason}) => {
  168. console.error(reason);
  169. });
  170. const lab = new JupyterLab({
  171. mimeExtensions,
  172. disabled: {
  173. matches: disabled,
  174. patterns: PageConfig.Extension.disabled
  175. .map(function (val) { return val.raw; })
  176. },
  177. deferred: {
  178. matches: deferred,
  179. patterns: PageConfig.Extension.deferred
  180. .map(function (val) { return val.raw; })
  181. },
  182. });
  183. register.forEach(function(item) { lab.registerPluginModule(item); });
  184. lab.start({ ignorePlugins });
  185. lab.restored.then(() => {
  186. console.debug('Example started!');
  187. });
  188. }
  189. window.addEventListener('load', main);