webpack.config.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. const path = require('path');
  4. const fs = require('fs-extra');
  5. const Handlebars = require('handlebars');
  6. const Build = require('@jupyterlab/builder').Build;
  7. const webpack = require('webpack');
  8. const merge = require('webpack-merge').default;
  9. const baseConfig = require('@jupyterlab/builder/lib/webpack.config.base');
  10. const { ModuleFederationPlugin } = webpack.container;
  11. const packageData = require('./package.json');
  12. // Handle the extensions.
  13. const jlab = packageData.jupyterlab;
  14. // Create a list of application extensions and mime extensions from
  15. // jlab.extensions
  16. const extensions = {};
  17. const mimeExtensions = {};
  18. for (const key of jlab.extensions) {
  19. const {
  20. jupyterlab: { extension, mimeExtension }
  21. } = require(`${key}/package.json`);
  22. if (extension !== undefined) {
  23. extensions[key] = extension === true ? '' : extension;
  24. }
  25. if (mimeExtension !== undefined) {
  26. mimeExtensions[key] = mimeExtension === true ? '' : mimeExtension;
  27. }
  28. }
  29. const extensionPackages = jlab.extensions;
  30. // Ensure a clear build directory.
  31. const buildDir = path.resolve(jlab.buildDir);
  32. if (fs.existsSync(buildDir)) {
  33. fs.removeSync(buildDir);
  34. }
  35. fs.ensureDirSync(buildDir);
  36. const outputDir = path.resolve(jlab.outputDir);
  37. if (fs.existsSync(outputDir)) {
  38. fs.removeSync(outputDir);
  39. }
  40. fs.ensureDirSync(outputDir);
  41. // Configuration to handle extension assets
  42. const extensionAssetConfig = Build.ensureAssets({
  43. packageNames: extensionPackages,
  44. output: outputDir
  45. });
  46. if (outputDir !== buildDir) {
  47. fs.moveSync(
  48. path.join(outputDir, 'style.js'),
  49. path.join(buildDir, 'style.js')
  50. )
  51. }
  52. // Create the entry point and other assets in build directory.
  53. const source = fs.readFileSync('index.template.js').toString();
  54. const template = Handlebars.compile(source);
  55. const extData = {
  56. jupyterlab_extensions: extensions,
  57. jupyterlab_mime_extensions: mimeExtensions
  58. };
  59. fs.writeFileSync(path.join(buildDir, 'index.js'), template(extData));
  60. // Create the bootstrap file that loads federated extensions and calls the
  61. // initialization logic in index.js
  62. const entryPoint = path.join(buildDir, 'bootstrap.js');
  63. fs.copySync('./bootstrap.js', entryPoint);
  64. function createShared(packageData) {
  65. // Set up module federation sharing config
  66. const shared = {};
  67. const extensionPackages = packageData.jupyterlab.extensions;
  68. // Make sure any resolutions are shared
  69. for (let [pkg, requiredVersion] of Object.entries(packageData.resolutions)) {
  70. shared[pkg] = { requiredVersion };
  71. }
  72. // Add any extension packages that are not in resolutions (i.e., installed from npm)
  73. for (let pkg of extensionPackages) {
  74. if (!shared[pkg]) {
  75. shared[pkg] = {
  76. requiredVersion: require(`${pkg}/package.json`).version
  77. };
  78. }
  79. }
  80. // Add dependencies and sharedPackage config from extension packages if they
  81. // are not already in the shared config. This means that if there is a
  82. // conflict, the resolutions package version is the one that is shared.
  83. const extraShared = [];
  84. for (let pkg of extensionPackages) {
  85. let pkgShared = {};
  86. let {
  87. dependencies = {},
  88. jupyterlab: { sharedPackages = {} } = {}
  89. } = require(`${pkg}/package.json`);
  90. for (let [dep, requiredVersion] of Object.entries(dependencies)) {
  91. if (!shared[dep]) {
  92. pkgShared[dep] = { requiredVersion };
  93. }
  94. }
  95. // Overwrite automatic dependency sharing with custom sharing config
  96. for (let [dep, config] of Object.entries(sharedPackages)) {
  97. if (config === false) {
  98. delete pkgShared[dep];
  99. } else {
  100. if ('bundled' in config) {
  101. config.import = config.bundled;
  102. delete config.bundled;
  103. }
  104. pkgShared[dep] = config;
  105. }
  106. }
  107. extraShared.push(pkgShared);
  108. }
  109. // Now merge the extra shared config
  110. const mergedShare = {};
  111. for (let sharedConfig of extraShared) {
  112. for (let [pkg, config] of Object.entries(sharedConfig)) {
  113. // Do not override the basic share config from resolutions
  114. if (shared[pkg]) {
  115. continue;
  116. }
  117. // Add if we haven't seen the config before
  118. if (!mergedShare[pkg]) {
  119. mergedShare[pkg] = config;
  120. continue;
  121. }
  122. // Choose between the existing config and this new config. We do not try
  123. // to merge configs, which may yield a config no one wants
  124. let oldConfig = mergedShare[pkg];
  125. // if the old one has import: false, use the new one
  126. if (oldConfig.import === false) {
  127. mergedShare[pkg] = config;
  128. }
  129. }
  130. }
  131. Object.assign(shared, mergedShare);
  132. // Transform any file:// requiredVersion to the version number from the
  133. // imported package. This assumes (for simplicity) that the version we get
  134. // importing was installed from the file.
  135. for (let [pkg, { requiredVersion }] of Object.entries(shared)) {
  136. if (requiredVersion && requiredVersion.startsWith('file:')) {
  137. shared[pkg].requiredVersion = require(`${pkg}/package.json`).version;
  138. }
  139. }
  140. // Add singleton package information
  141. for (let pkg of packageData.jupyterlab.singletonPackages) {
  142. shared[pkg].singleton = true;
  143. }
  144. return shared;
  145. }
  146. const plugins = [
  147. new ModuleFederationPlugin({
  148. library: {
  149. type: 'var',
  150. name: ['_JUPYTERLAB', 'CORE_LIBRARY_FEDERATION']
  151. },
  152. name: 'CORE_FEDERATION',
  153. shared: createShared(packageData)
  154. })
  155. ];
  156. module.exports = [
  157. merge(baseConfig, {
  158. mode: 'development',
  159. devtool: 'source-map',
  160. entry: ['./publicpath.js', entryPoint],
  161. output: {
  162. path: path.resolve(outputDir),
  163. library: {
  164. type: 'var',
  165. name: ['_JUPYTERLAB', 'CORE_OUTPUT']
  166. },
  167. filename: 'bundle.js'
  168. },
  169. optimization: {
  170. splitChunks: {
  171. chunks: 'all',
  172. cacheGroups: {
  173. jlab_core: {
  174. test: /[\\/]node_modules[\\/]@(jupyterlab|lumino)[\\/]/,
  175. name: 'jlab_core'
  176. }
  177. }
  178. }
  179. },
  180. plugins
  181. })
  182. ].concat(extensionAssetConfig);
  183. // For debugging, write the config out
  184. const logPath = path.join(buildDir, 'build_log.json');
  185. fs.writeFileSync(logPath, JSON.stringify(module.exports, null, ' '));