notebook/app/index.template.js
Jeremy Tuloup 251e0e3606
Add the plugin manager (#7198)
* Add the pluginmanager

* iterate

* Parse info in app
2023-12-27 10:33:45 +01:00

233 lines
6.4 KiB
JavaScript

// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
// Inspired by: https://github.com/jupyterlab/jupyterlab/blob/master/dev_mode/index.js
import { PageConfig, URLExt } from '@jupyterlab/coreutils';
require('./style.js');
require('./extraStyle.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
// eslint-disable-next-line no-undef
await __webpack_init_sharing__('default');
const container = window._JUPYTERLAB[scope];
// Initialize the container, it may provide shared modules and may need ours
// eslint-disable-next-line no-undef
await container.init(__webpack_share_scopes__.default);
}
async function createModule(scope, module) {
try {
const factory = await window._JUPYTERLAB[scope].get(module);
const instance = factory();
instance.__scope__ = scope;
return instance;
} catch (e) {
console.warn(
`Failed to create module: package: ${scope}; module: ${module}`
);
throw e;
}
}
/**
* The main function
*/
async function main() {
const mimeExtensionsMods = [
{{#each notebook_mime_extensions}}
require('{{ @key }}'),
{{/each}}
];
const mimeExtensions = await Promise.all(mimeExtensionsMods);
// Load the base plugins available on all pages
let baseMods = [
{{#each notebook_plugins}}
{{#if (ispage @key '/')}}
{{{ list_plugins }}}
{{/if}}
{{/each}}
];
const page = `/${PageConfig.getOption('notebookPage')}`;
switch (page) {
{{#each notebook_plugins}}
{{#unless (ispage @key '/')}}
// list all the other plugins grouped by page
case '{{ @key }}': {
baseMods = baseMods.concat([
{{{ list_plugins }}}
]);
break;
}
{{/unless}}
{{/each}}
}
// populate the list of disabled extensions
const disabled = [];
const availablePlugins = [];
/**
* Iterate over active plugins in an extension.
*
* #### Notes
* This also populates the disabled
*/
function* activePlugins(extension) {
// Handle commonjs or es2015 modules
let exports;
if (Object.prototype.hasOwnProperty.call(extension, '__esModule')) {
exports = extension.default;
} else {
// CommonJS exports.
exports = extension;
}
let plugins = Array.isArray(exports) ? exports : [exports];
for (let plugin of plugins) {
const isDisabled = PageConfig.Extension.isDisabled(plugin.id);
availablePlugins.push({
id: plugin.id,
description: plugin.description,
requires: plugin.requires ?? [],
optional: plugin.optional ?? [],
provides: plugin.provides ?? null,
autoStart: plugin.autoStart,
enabled: !isDisabled,
extension: extension.__scope__
});
if (isDisabled) {
disabled.push(plugin.id);
continue;
}
yield plugin;
}
}
const extension_data = JSON.parse(
PageConfig.getOption('federated_extensions')
);
const mods = [];
const federatedExtensionPromises = [];
const federatedMimeExtensionPromises = [];
const federatedStylePromises = [];
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 data = p.value;
if (data.extension) {
federatedExtensionPromises.push(createModule(data.name, data.extension));
}
if (data.mimeExtension) {
federatedMimeExtensionPromises.push(
createModule(data.name, data.mimeExtension)
);
}
if (data.style && !PageConfig.Extension.isDisabled(data.name)) {
federatedStylePromises.push(createModule(data.name, data.style));
}
});
// Add the base frontend extensions
const baseFrontendMods = await Promise.all(baseMods);
baseFrontendMods.forEach(p => {
for (let plugin of activePlugins(p)) {
mods.push(plugin);
}
});
// Add the federated extensions.
const federatedExtensions = await Promise.allSettled(
federatedExtensionPromises
);
federatedExtensions.forEach(p => {
if (p.status === 'fulfilled') {
for (let plugin of activePlugins(p.value)) {
mods.push(plugin);
}
} else {
console.error(p.reason);
}
});
// Add the federated mime extensions.
const federatedMimeExtensions = await Promise.allSettled(
federatedMimeExtensionPromises
);
federatedMimeExtensions.forEach(p => {
if (p.status === 'fulfilled') {
for (let plugin of activePlugins(p.value)) {
mimeExtensions.push(plugin);
}
} else {
console.error(p.reason);
}
});
// Load all federated component styles and log errors for any that do not
(await Promise.allSettled(federatedStylePromises))
.filter(({ status }) => status === 'rejected')
.forEach(({ reason }) => {
console.error(reason);
});
// Set the list of base notebook multi-page plugins so the app is aware of all
// its built-in plugins even if they are not loaded on the current page.
// For example this is useful so the Settings Editor can list the debugger
// plugin even if the debugger is only loaded on the notebook page.
PageConfig.setOption('allPlugins', '{{{ json notebook_plugins }}}');
const NotebookApp = require('@jupyter-notebook/application').NotebookApp;
const app = new NotebookApp({ mimeExtensions, availablePlugins });
app.registerPluginModules(mods);
// Expose global app instance when in dev mode or when toggled explicitly.
const exposeAppInBrowser =
(PageConfig.getOption('exposeAppInBrowser') || '').toLowerCase() === 'true';
if (exposeAppInBrowser) {
window.jupyterapp = app;
}
await app.start();
}
window.addEventListener('load', main);