jantimon/html-webpack-plugin

Generate a separate html file for every async chunk

theninthsky opened this issue · 2 comments

Description

When splitting the bundle to separate chunks (using dynamic import) we see that async chunks are fetched only after all sync scripts have been parsed (around the time DOMContentLoaded is fired).
This happens because the code couldn't have known about those chunks before it was executed.

image

My idea is to generate an html file for every async chunk, so that when the script realizes it needs that chuck - it will already be there.

This should look like this:

calendar.html

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>My Webapp</title>

    <script type="module" src="js/runtime.07ba85.js"></script>
    <script type="module" src="js/react-dom.e2d2a7.js"></script>
    <script type="module" src="js/recoil.b6b82c.js"></script>
    <script type="module" src="js/main.67f1c6.js"></script>
    <link href="css/main.0f5e10.css" rel="stylesheet">
    <script  type="module" src="js/calendar.bc68f0"></script>
    <script  type="module" src="js/date-fns.d0b9db"></script>
    <script  type="module" src="js/react-big-calendar.b22420"></script>
    <script  type="module" src="js/@popperjs.612791"></script>
</head>

<body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
</body>

calendar.bc68f0.js is the main part of this async chuck, it was generated like this:

import(/* webpackChunkName: "calendar" */ 'pages/Calendar')

It's dependencies are: date-fns, react-big-calendar and popper-js.

Manually adding those scripts works as expected - all chunks are downloaded simultaneously and so the page loads much faster (without an extra roundtrip).
However, I'd like html-webpack-plugin to generate these htmls for me.

Unfortunately, I'm having a hard time trying to figure out how to do that, plus I suspect that the fact that there are multiple assets for each lazily-loaded chunk makes it even harder.

Can you try to help me?

I managed to get halfway through the solution.

I generate multiple html files, one for each lazily-loaded page:

webpack.config.js

const routes = ['/edit-photos',  '/calendar',  '/reviews']
.
.
.
plugins: [
      new HtmlPlugin({ filename: 'index.html', scriptLoading: 'module', templateContent: () => htmlTemplate() }),
      ...routes.map(
        route =>
          new HtmlPlugin({
            filename: `${route.slice(1)}.html`,
            scriptLoading: 'module',
            templateContent: ({ compilation }) => {
              const assets = compilation.getAssets().map(({ name }) => name)
              const jsAssets = assets.filter(name => name.includes(route) && name.endsWith('js'))
              const cssAssets = assets.filter(name => name.includes(route) && name.endsWith('css'))

              return htmlTemplate(jsAssets, cssAssets)
            }
          })
      )
]

And this is the html template:

public/index.js

module.exports = (jsAssets = [], cssAssets = []) => `
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>My Webapp</title>

    ${jsAssets.map(asset => `<script type="module" src="${asset}"></script>`).join('')}
    ${cssAssets.map(asset => `<link href="${asset}" rel="stylesheet">`).join('')}
    
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>

    <div id="root"></div>
  </body>
</html>
`

This works great.
However, I could not find a way to deduce a chunk's dependencies, so they are not included in the html file and are still fetched in a later time:

calendar.html

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">

    <title>My Webapp</title>

    <script type="module" src="js/calendar.cb3940.js"></script>
    <link href="css/calendar.d6412b.css" rel="stylesheet">
    <script type="module" src="js/runtime.b6649e.js"></script>
    <script type="module" src="js/react-dom.e2d2a7.js"></script>
    <script type="module" src="js/recoil.a2a01a.js"></script>
    <script type="module" src="js/main.a9a28c.js"></script>
    <link href="css/main.c10de3.css" rel="stylesheet">
</head>

<body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
</body>
</html>

The html file is missing calendar's chuck dependencies (date-fns, react-big-calendar, popper-js).

Can you think of a way to figure out what are the chunk's dependencies through 'templateContent' parameters?

Please in such case use multi compiler mode, it is the better solution