webpack.config.js 6.4 KB

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