From 7b7b25e05a148be4c02a8a82dccc3ce2d642b924 Mon Sep 17 00:00:00 2001 From: Sergey Petushkov Date: Tue, 3 Jun 2025 18:51:18 +0200 Subject: [PATCH 1/2] chore(web): we have chunk splitting at home It's really hard to make webpack to bundle both for target web and use require imports for its internal module system, to work around that we add this manual chunk splitting logic for the production build. --- packages/compass-web/webpack.config.js | 74 +++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/packages/compass-web/webpack.config.js b/packages/compass-web/webpack.config.js index 8e04551c39e..e6053818976 100644 --- a/packages/compass-web/webpack.config.js +++ b/packages/compass-web/webpack.config.js @@ -15,6 +15,64 @@ function localPolyfill(name) { return path.resolve(__dirname, 'polyfills', ...name.split('/'), 'index.ts'); } +function normalizeDepName(name) { + return name.replaceAll('@', '').replaceAll('/', '__'); +} + +function resolveDepEntry(name) { + const monorepoPackagesDir = path.resolve(__dirname, '..'); + const resolvedPath = require.resolve(name); + if (resolvedPath.startsWith(monorepoPackagesDir)) { + const packageJson = require(`${name}/package.json`); + const packageRoot = path.dirname(require.resolve(`${name}/package.json`)); + const entrypoint = path.join( + packageRoot, + packageJson['compass:main'] ?? packageJson['main'] + ); + return entrypoint; + } + return require.resolve(name); +} + +/** + * Takes in a webpack configuration for a library package and creates a + * multi-compiler config that splits the library into multiple parts that can be + * properly processed by another webpack compilation. + * + * This is opposed to using a webpack chunk splitting feature that will generate + * the code that uses internal webpack module runtime that will not be handled + * by any other bundler. This custom code splitting is way less advanced, but + * works well for leaf node dependencies of the package. + * + * NB: This naive implementation works well only for leaf dependencies with a + * single export file. It can be updated to support multiple exports packages, + * but we can skip this for now + */ +function createSiblingBundleFromLeafDeps( + config, + deps, + moduleType = 'commonjs2' +) { + const siblings = Object.fromEntries( + deps.map((depName) => { + return [depName, `${moduleType} ./${normalizeDepName(depName)}.js`]; + }) + ); + const baseConfig = merge(config, { externals: siblings }); + const configs = [baseConfig].concat( + deps.map((depName) => { + return merge(baseConfig, { + entry: resolveDepEntry(depName), + output: { + filename: `${normalizeDepName(depName)}.js`, + library: { type: moduleType }, + }, + }); + }) + ); + return configs; +} + /** * Atlas Cloud uses in-flight compression that doesn't compress anything that is * bigger than 10MB, we want to make sure that compass-web assets stay under the @@ -222,7 +280,7 @@ module.exports = (env, args) => { }, }; - return merge(config, { + const compassWebConfig = merge(config, { externals: { react: 'commonjs2 react', 'react-dom': 'commonjs2 react-dom', @@ -258,4 +316,18 @@ module.exports = (env, args) => { }, ], }); + + // Split production bundle into more chunks to make sure it's easier for us to + // stay under the max chunk limit. Be careful when adding new packages here, + // make sure you're only selecting big packages with the smallest amount of + // shared dependencies possible + const bundles = createSiblingBundleFromLeafDeps(compassWebConfig, [ + '@mongodb-js/compass-components', + 'bson-transpilers', + // bson is not that big, but is a shared dependency of compass-web, + // compass-components and bson-transpilers, so splitting it out + 'bson', + ]); + + return bundles; }; From 832b59a91aebb0d161e196b2a032d79d740ed495 Mon Sep 17 00:00:00 2001 From: Sergey Petushkov Date: Wed, 11 Jun 2025 10:24:21 +0200 Subject: [PATCH 2/2] chore(web): add todo --- packages/compass-web/webpack.config.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/compass-web/webpack.config.js b/packages/compass-web/webpack.config.js index e6053818976..e71d2f01f47 100644 --- a/packages/compass-web/webpack.config.js +++ b/packages/compass-web/webpack.config.js @@ -41,12 +41,16 @@ function resolveDepEntry(name) { * * This is opposed to using a webpack chunk splitting feature that will generate * the code that uses internal webpack module runtime that will not be handled - * by any other bundler. This custom code splitting is way less advanced, but - * works well for leaf node dependencies of the package. + * by any other bundler (see TODO). This custom code splitting is way less + * advanced, but works well for leaf node dependencies of the package. * - * NB: This naive implementation works well only for leaf dependencies with a - * single export file. It can be updated to support multiple exports packages, - * but we can skip this for now + * TODO(COMPASS-9445): This naive implementation works well only for leaf + * dependencies with a single export file. A better approach would be to coerce + * webpack to produce a require-able web bundle, which in theory should be + * possible with a combination of `splitChunks`, `chunkFormat: 'commonjs'`, and + * `target: 'web'`, but in practice produced bundle doesn't work due to webpack + * runtime exports not being built correctly. We should investigate and try to + * fix this to remove this custom chunk splitting logic. */ function createSiblingBundleFromLeafDeps( config, @@ -323,6 +327,7 @@ module.exports = (env, args) => { // shared dependencies possible const bundles = createSiblingBundleFromLeafDeps(compassWebConfig, [ '@mongodb-js/compass-components', + 'ag-grid-community', 'bson-transpilers', // bson is not that big, but is a shared dependency of compass-web, // compass-components and bson-transpilers, so splitting it out