Cannot combine bundle chunks with webpack

Recently updated to babylon 7.54.1.
My project creates a webpack bundle of babylon to be consumed in other applications as an npm package. After updating I noticed a ton of chunk.js files being added when I ran an app that consumed my package (see images)


I found this resource in the docs Code splitting and Chunks using known bundlers | Babylon.js Documentation and have tried both setting up the optimizations as described (I copied the optimizations property from the example) or flat out limiting the chunk count to 1 in my webpack.config.base.js file. Neither of these made any change to the chunks when I inspect in the web or run the app.

Both reducing the chunk to a single file or disabling chunks would be acceptable solutions, but I can’t seem to get either to work as the docs describe it should.

Any direction/advice on what Im doing wrong would help a ton.
Thank you.

hey hey,

could you share your webpack configuration? I assume you are using webpack 5?

This is the webpack config I tried last with just disabling the chunks all together.
My webpack versions are:

"webpack": "^5.82.1",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.15.0",
"webpack-merge": "^5.8.0"
import path from 'path';
import { fileURLToPath } from 'url';

// eslint-disable-next-line
import CopyPlugin from 'copy-webpack-plugin';
import webpack from 'webpack';

import scopeUIPlugin from './postcss/postcss-scope-ui.js';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

export default env => {
    var external_values = [
        {
            oimo: true,
            earcut: true,
            cannon: true,
        },
    ];

    // If --env=BABYLON=true is not found then ignore packing babylon
    if (env.BABYLON === undefined) {
        external_values.push(/^@babylonjs.*$/);
    }

    return {
        entry: './src/index.ts',
        module: {
            rules: [
                {
                    test: /\.tsx?$/,
                    exclude: /node_modules/,
                    use: [
                        {
                            loader: 'ts-loader',
                            options: {
                                configFile: 'tsconfig.esm.json',
                            },
                        },
                    ],
                },
                {
                    test: /\.js$/,
                    exclude: /node_modules/,
                    loader: 'esbuild-loader',
                    options: {
                        target: 'ES2022',
                    },
                },
                {
                    test: /\.css$/i,
                    use: [
                        'style-loader',
                        {
                            loader: 'css-loader',
                            options: {
                                modules: {
                                    localIdentName: 'viewer__[local]',
                                },
                            },
                        },
                        {
                            loader: 'postcss-loader',
                            options: {
                                postcssOptions: {
                                    plugins: ['@tailwindcss/postcss', scopeUIPlugin],
                                },
                            },
                        },
                    ],
                },
                {
                    test: /\.(gif|svg|jpe?g|png|eot|woff|woff2|ttf)$/,
                    type: 'asset/inline',
                },
            ],
        },
        resolve: {
            extensions: ['.tsx', '.ts', '.js'],
        },
        plugins: [
            new CopyPlugin({
                patterns: [
                    {
                        from: 'glsl/*.fx',
                        to: path.resolve(__dirname, 'dist'),
                    },
                ],
            }),
            new webpack.optimize.LimitChunkCountPlugin({
                maxChunks: 1,
            }),
        ],
        externals: external_values,
        performance: {
            maxEntrypointSize: 2000000,
            maxAssetSize: 2000000,
        },
        output: {
            path: path.resolve(__dirname, 'dist/lib/esm'),
            filename: 'viewer.bundle.js',
            library: {
                type: 'module',
            },
            clean: true,
        },
        experiments: {
            outputModule: true,
        },
        devtool: 'source-map',
        devServer: {
            static: {
                directory: './samples',
            },
            devMiddleware: {
                publicPath: '/dist/',
            },
            open: true,
        },
    };
};

You can probably customize how Webpack should build your chunks. For example, here babylonjs and other vendors was collected per folders. This can be useful for future updates when using cdn.

config.optimization = {
            // optimizer settings

            splitChunks:{
                chunks: 'all',

                minSize: 350 * 1024,
                maxSize: 3500 * 1024,
                minRemainingSize:3500 * 1024,
                maxInitialRequests:5,
                minChunks: 2,
                automaticNameDelimiter: '/',
                usedExports:true,
                cacheGroups: {  

// chunks of your project

                    default: {
                        test: /[\\/]((?!(node_modules)).*)[\\/]/, 
                        reuseExistingChunk: true,
                        priority: 5,
                        minChunks: 1,
                        maxInitialRequests:5,
                        minSize: 1024,
                        maxSize: 1000 * 1024,
                        name:'prj',
                    },

// VENDORS !!!

                    vendors: {
                        test: /[\\/]node_modules[\\/].*/,
                        name:'vendors/misc',
                        chunks: 'all',
                        enforce: true,
                        maxInitialRequests:5,
                        minSize: 200 * 1024,
                        maxSize: 1000 * 1024,
                        priority: 10, 
                        reuseExistingChunk: true,
                    },
                    babylon: {
                        test: /[\\/]node_modules[\\/]@babylonjs[\\/]/,
                        name:'vendors/babylon',
                        chunks: 'all',
                        enforce: true,
                        priority: 20,
                        minSize: 200 * 1024,
                        maxSize: 3500 * 1024,
                        maxInitialRequests:5,
                        reuseExistingChunk: true,
                    },
                    vue:{
                        test: /[\\/]node_modules[\\/]@?vue3?(\\-.*)?[\\/]/,
                        name:'vendors/vue/vue',
                        chunks: 'all',
                        enforce: true,
                        priority: 15,
                        minSize:  200 * 1024,
                        maxSize:  3500 * 1024,
                        maxInitialRequests:5,
                        minChunks: 1,
                        reuseExistingChunk: true,
                    }
// ...
                }
            }
        };

I had tried using cache groups before from the doc example with

    optimization: {
        splitChunks: {
            cacheGroups: {
                webgpuShaders: {
                    name: "webgpu-shaders",
                    chunks: "all",
                    priority: 50,
                    enforce: true,
                    test: (module) => /\/ShadersWGSL\//.test(module.resource),
                },
                webglShaders: {
                    name: "webgl-shaders",
                    chunks: "all",
                    priority: 50,
                    enforce: true,
                    test: (module) => /\/Shaders\//.test(module.resource),
                },
                webgpuExtensions: {
                    name: "webgpu-extensions",
                    chunks: "all",
                    priority: 50,
                    enforce: true,
                    test: (module) => /\/WebGPU\//.test(module.resource),
                },
                babylonBundle: {
                    name: "babylonBundle",
                    chunks: "all",
                    priority: 30,
                    reuseExistingChunk: true,
                    test: (module) => /\/node_modules\/@babylonjs\//.test(module.resource),
                },
            },
        },
        usedExports: true,
        minimize: true,
    },

But I saw no change in my chunk output at all

Hm… and your test function actually was called?

How do I make sure the test functions are being called?

I also tried setting the cache groups to only

babylon: {
    test: /[\\/]node_modules[\\/]@babylonjs[\\/]/,
    chunks: 'all',
    enforce: true,
    priority: 20,
    minSize: 200 * 1024,
    maxSize: 3500 * 1024,
    maxInitialRequests:5,
    reuseExistingChunk: true,
},

from your example and also saw no different results

I would also like to note now that a lot of the shaders Babylon is chunking I do not use at all in my project.

This is well documented and was clear when we made the change.
I am not sure why your project configuration is not working - a reproduction (the project) will be very helpful. You can also use the template - GitHub - RaananW/babylonjs-webpack-es6: Babylon.js basic scene with typescript, webpack, es6 modules, editorconfig, eslint, hot loading and more. Will even make coffee if you ask nicely. , which has the chunks configured and working.

1 Like

Ok. I cloned the webpack-es6 repo and ran an install and build with no changes. Based on the docs (Babylon.js docs)

Let’s get a simple webpack configuration that takes all WebGL shaders and packs them all together in a single file. First, let’s change slightly the output configuration:

I should be seeing only 4 chunks in the dist/js (webgpu-shaders, webgl-shaders, webgpu-extensions, babylonBundle) but I still produced a bunch of individual chunk files instead in the dist.

Is this expected or am I doing something wrong with the build? I just ran npm i && npm run build in the repo

Is it possible the chunking is showing due to a framework issue? I am installing my built package into angular. I had one project using this package that used the angular webpack builder dep instead of the devkit/build-angular dep and it saw many fewer chunks being loaded.

I’ll check that later today, that shouldn’t happen. Are you sure the dist folder was cleaned before the build?

It was a fresh clone, dist folder did not exist until the build

BTW - my honest opinion to this is - why would anyone care about chunks. They only help in the long run, they have no real overhead, and they keep your base package minimal. All you need to do is take the dist folder, upload it to a web server, and everything else is done for you.

However, it should be configurable, i totally agree.

The reason is I was experiencing some network issues. I am sometimes running my app in a limited network environment and the amount of chunks it is currently producing is around ~800 files, which makes the app take much longer to load overall due to browser parallel request limits. When it was just a single 3MB bundle it was able to do it much quicker.

(Also 800 chunk files feels a little extreme and not correct for my app)

Then I also assume you are importing directly from @babylonjs/core and not from the files directly.

I have a mix of imports throughout the project. I do import generally in a lot of places just from @babylonjs/core though.

I don’t use any dynamic imports though in my project. I always import as the top of the file.

if you import directly from @babylonjs/core you will load the entire framework, and not only what you are using. This is documented here - Babylon.js docs

1 Like

So then I should see a decrease in my chunks if I replace the import X from '@babylonjs/core' to file specific imports from core and the other packages?

I replaced every direct import from @babylonjs/core (ex @babylonjs/core/Maths/math.vector now instead of just core) in the project with a file specific import instead, but my angular app still shows hundreds of chunk files being loaded with my installed package.