Fully configurable pages with Webpack 4

Hi.

My project has a particular use-case which I am not sure how to configure using Webpack 4. I’m not sure anymore if it is possible to configure it using Webpack 4.

In my application, pages are composed by the server at runtime, based on configuratin.
As a result, I do not know which bundles will be required until the server actually gets a request and selects them.

Essentially, there are two kinds of modules that make up my app:

  1. Library modules: these are some utility modules for my app. All pages created will require these.
    E.g. Ajax sender, sorting and searching, etc.
  2. Generic modules: these are the modules which the server selects based on the page’s configuration.
    E.g. chat feature, ads popup, data table, etc.

Note that I am the maintainer for both these kinds of modules, so I have full flexibility on how to structure them.
Vendor modules are mainatined by third-parties (e.g. jQuery).

Generic modules depend on Library modules, but not vice-verca. Generic modules do not have dependencies on each other.

Folder structuring of application:

app-root/
    - libs/*.js
    - generics/*.js

My application request flow looks like this:

  1. Server gets a request for a particular page configuration. It determines that it needs Generic modules X, Y, Z to satisfy this configuration.
  2. Server creates <script> tags to serve all Library modules at once (my-libs.bundle.js).
  3. Server creates a separate <script> tag for each Generic module (A.bundle.js, B.bundle.js, etc).

How do I set up Webpack to generate the bundles in this way?


Current progress using Webpack 4:

  1. Entry points: all modules are entry points.

    entry: ['libs/*.js', 'generics/*.js']
    
  2. Cache groups:

    optimization: {
        splitChunks: {
            cacheGroups: [
                {
                    test: /libs\/*.js/,
                    chunks: "normal",
                    name: "libs",
                    enforce: true
                },
                {
                    test: /generics\/A.js/,
                    chunks: "normal",
                    name: "A",
                    enforce: true
                },
                {
                    test: /generics\/B.js/,
                    chunks: "normal",
                    name: "B",
                    enforce: true
                },
                {
                    test: /generics\/C.js/,
                    chunks: "normal",
                    name: "C",
                    enforce: true
                },
                ...etc
            ]
        }
    }
    

    The Regex pattern matching may not be perfect, but you get the idea.

  3. Output:

    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, 'build/js')
    },
    

Problems faced:

When I run the above setup I get the following bundles:

  1. main.bundle.js: contains Webpack runtime, list of modules (manifest?), etc.
  2. libs.bundle.js
  3. A.bundle.js
  4. B.bundle.js
  5. C.bundle.js
  6. …etc

So far, so good.

Now, I get my server to generate HTML pages in the following way (pseudocode):

<script src="build/js/main.bundle.js"></script>
<script src="build/js/libs.bundle.js"></script>

for (modulePath of requestedGenericModulePaths) {
    console.log(`<script src="${modulePath}"></script>`);
}

...

(Ignore syntax, CDN loading etc…this is just an example).

So if the request is for a page with Generic modules X and Y, the following HTML will be output:

<script src="build/js/main.bundle.js"></script>
<script src="build/js/libs.bundle.js"></script>

<script src="build/js/generics/X.bundle.js"></script>
<script src="build/js/generics/Y.bundle.js"></script>

...

So far, so good.

However, this is where I am stuck. The above HTML does not work; if I put logging statements in X and Y, they do not show on the browser console.

I looked into main.bundle.js. From what I understand, it seems like ALL generic modules are being required in main.bundle.js.

I am not sure how to proceed. Should I set chunks: "async" in each cacheGroup? Should I make all my generic modules externals? Should I play around with the Manifest?

Author: Fantashit

1 thought on “Fully configurable pages with Webpack 4

  1. Set optimization.runtimeChunk: "single": this allows to use multiple entrypoints in a single html page.

    Don’t fiddle around with optimization.splitChunks until you got the stuff working without it. It doesn’t change the behavior! It’s only an optimization.

    Create an entrypoint per thing you want to include at runtime.

    Example:

    entry: {
      "chat": "...",
      "ad-popup": "...",
      "data-table": ..."
    }

    Read needed script tags per entry from the Stats.
    Example:

    assetsByChunkName: {
      "chat": ["runtime.js", "chat.js"],
      "ad-popup": ["runtime.js", "ad-popup.js"],
      "data-table": ["runtime.js", "data-table.js"]
    }

    These arrays will contain more entries when using optimization.splitChunks.

    Dedupe all scripts. i. e. runtime.js only need to be added once.

    Add script tags.

Comments are closed.