index.js 7.6 KB

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