// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. // Heavily inspired (and slightly tweaked) from: // https://github.com/jupyterlab/jupyterlab/blob/master/examples/federated/core_package/webpack.config.js const fs = require('fs-extra'); const path = require('path'); const webpack = require('webpack'); const merge = require('webpack-merge').default; const { ModuleFederationPlugin } = webpack.container; const BundleAnalyzerPlugin = require('webpack-bundle-analyzer') .BundleAnalyzerPlugin; const Build = require('@jupyterlab/builder').Build; const baseConfig = require('@jupyterlab/builder/lib/webpack.config.base'); const data = require('./package.json'); const names = Object.keys(data.dependencies).filter(name => { const packageData = require(path.join(name, 'package.json')); return packageData.jupyterlab !== undefined; }); // Ensure a clear build directory. const buildDir = path.resolve(__dirname, 'build'); if (fs.existsSync(buildDir)) { fs.removeSync(buildDir); } fs.ensureDirSync(buildDir); // Copy extra files const index = path.resolve(__dirname, 'index.js'); const cssImports = path.resolve(__dirname, 'style.js'); fs.copySync(index, path.resolve(buildDir, 'index.js')); fs.copySync(cssImports, path.resolve(buildDir, 'extraStyle.js')); const extras = Build.ensureAssets({ packageNames: names, output: buildDir }); /** * Create the webpack ``shared`` configuration */ function createShared(packageData) { // Set up module federation sharing config const shared = {}; const extensionPackages = packageData.jupyterlab.extensions; // Make sure any resolutions are shared for (let [pkg, requiredVersion] of Object.entries(packageData.resolutions)) { shared[pkg] = { requiredVersion }; } // Add any extension packages that are not in resolutions (i.e., installed from npm) for (let pkg of extensionPackages) { if (!shared[pkg]) { shared[pkg] = { requiredVersion: require(`${pkg}/package.json`).version }; } } // Add dependencies and sharedPackage config from extension packages if they // are not already in the shared config. This means that if there is a // conflict, the resolutions package version is the one that is shared. const extraShared = []; for (let pkg of extensionPackages) { let pkgShared = {}; let { dependencies = {}, jupyterlab: { sharedPackages = {} } = {} } = require(`${pkg}/package.json`); for (let [dep, requiredVersion] of Object.entries(dependencies)) { if (!shared[dep]) { pkgShared[dep] = { requiredVersion }; } } // Overwrite automatic dependency sharing with custom sharing config for (let [dep, config] of Object.entries(sharedPackages)) { if (config === false) { delete pkgShared[dep]; } else { if ('bundled' in config) { config.import = config.bundled; delete config.bundled; } pkgShared[dep] = config; } } extraShared.push(pkgShared); } // Now merge the extra shared config const mergedShare = {}; for (let sharedConfig of extraShared) { for (let [pkg, config] of Object.entries(sharedConfig)) { // Do not override the basic share config from resolutions if (shared[pkg]) { continue; } // Add if we haven't seen the config before if (!mergedShare[pkg]) { mergedShare[pkg] = config; continue; } // Choose between the existing config and this new config. We do not try // to merge configs, which may yield a config no one wants let oldConfig = mergedShare[pkg]; // if the old one has import: false, use the new one if (oldConfig.import === false) { mergedShare[pkg] = config; } } } Object.assign(shared, mergedShare); // Transform any file:// requiredVersion to the version number from the // imported package. This assumes (for simplicity) that the version we get // importing was installed from the file. for (let [pkg, { requiredVersion }] of Object.entries(shared)) { if (requiredVersion && requiredVersion.startsWith('file:')) { shared[pkg].requiredVersion = require(`${pkg}/package.json`).version; } } // Add singleton package information for (let pkg of packageData.jupyterlab.singletonPackages) { if (shared[pkg]) { shared[pkg].singleton = true; } } return shared; } // Make a bootstrap entrypoint const entryPoint = path.join(buildDir, 'bootstrap.js'); const bootstrap = 'import("./index.js");'; fs.writeFileSync(entryPoint, bootstrap); if (process.env.NODE_ENV === 'production') { baseConfig.mode = 'production'; } if (process.argv.includes('--analyze')) { extras.push(new BundleAnalyzerPlugin()); } module.exports = [ merge(baseConfig, { mode: 'development', entry: ['./publicpath.js', './' + path.relative(__dirname, entryPoint)], output: { path: path.resolve(__dirname, '..', 'retrolab/static/'), library: { type: 'var', name: ['_JUPYTERLAB', 'CORE_OUTPUT'] }, filename: 'bundle.js' }, plugins: [ new ModuleFederationPlugin({ library: { type: 'var', name: ['_JUPYTERLAB', 'CORE_LIBRARY_FEDERATION'] }, name: 'CORE_FEDERATION', shared: createShared(data) }) ] }) ].concat(extras);