4

We'd like to have two outputs from Webpack - our entire app with all of its dependencies, and a single different page with only one dependency (that isn't shared by the main app).

It seems the way to do this is to leverage the entry property of a Webpack config. However, that's not enough, as we also use HtmlWebpackPlugin to output our HTML file with the build.js that Webpack compiled dynamically added (as well as compiled LESS, etc). According to the HtmlWebpackPlugin docs:

If you have multiple Webpack entry points, they will all be included with script tags in the generated HTML.

That won't work for us, so I need to leverage their filterChunks option. This GitHub issue response states it most succinctly:

module.exports = {
  entry: {
    'page1': './apps/page1/scripts/main.js',
    'page2': './apps/page2/src/main.js'
  },
  output: {
    path: __dirname,
    filename: "apps/[name]/build/bundle.js"
  },
  plugins: [
    new HtmlWebpackPlugin({
      inject: false,
      chunks: ['page1'],
      filename: 'apps/page1/build/index.html'
    }),
    new HtmlWebpackPlugin({
      inject: false,
      chunks: ['page2'],
      filename: 'apps/page2/build/index.html'
    })
  ]
};

(in the HtmlWebpackPlugin docs, this is under the "filtering chunks" section)

So, I modified our code like so:

module.exports = {
    entry: {
        app: './public/js/ide.js',
        resetPassword: './public/js/reset_password.js'
    },
    output: {
        path: path.resolve(__dirname, '../build'),
        filename: '[name].js',
        publicPath: '/'
    },
    ...
    plugins: [
        new HtmlWebpackPlugin({
            filename: 'index.html',
            template: 'public/html/ide.html',
            inject: true,
            chunks: ['app']
        }),
        new HtmlWebpackPlugin({
            filename: 'reset_password.html',
            template: 'public/html/reset_password.html',
            inject: true,
            chunks: ['resetPassword']
        }),
    ],
}

Now, when I rebuild the project (just trying with WebpackDevServer for now) and navigate to /index.html, I can see in the network tab the massive bundle file, the contents of index.html (based off the ide.html template), as well as requests for various external resources. However, no actual JavaScript will run (say, a console.log in ide.js). All the HTML in the file shows.

For reset_password.html, all HTML shows, and the reset_password.js file shows, but none of the javascript within runs.

How can I ensure the JavaScript in my entry files runs?

EDIT: I have gotten ide.js working, because I hadn't realized the following was a "chunk":

optimization: {
        splitChunks: {
            cacheGroups: {
                commons: {
                    test: /[\\/]node_modules[\\/]/,
                    name: 'vendor',
                    chunks: 'all'
                }
            }
        }
    },

So, I added vendor to the index.html HtmlWebpackPlugin chunks property. Now, it looks like this:

new HtmlWebpackPlugin({
    filename: 'index.html',
    template: 'public/html/ide.html',
    inject: true,
    chunks: ['app', 'vendor']
}),

reset_password doesn't need anything in the node_modules folder, and this also doesn't explain why no JavaScript at all would run inside of ide.js, so I'm still quite confused. Also, reset_password is still non-functional.

EDIT2: Looking through the apparently attached reset_password.js file when I load reset_password.html, I can see this line

eval("\n\nconsole.log('DRAGONHELLO');//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vLi9wdWJsaWMvanMvcmVzZXRfcGFzc3dvcmQuanM/ZjY5ZSJdLCJuYW1lcyI6WyJjb25zb2xlIiwibG9nIl0sIm1hcHBpbmdzIjoiOztBQUNBQSxRQUFRQyxHQUFSLENBQVksYUFBWiIsImZpbGUiOiIuL3B1YmxpYy9qcy9yZXNldF9wYXNzd29yZC5qcy5qcyIsInNvdXJjZXNDb250ZW50IjpbIlxuY29uc29sZS5sb2coJ0RSQUdPTkhFTExPJylcbiJdLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///./public/js/reset_password.js\n");

So, clearly my console.log('DRAGONHELLO') is "seen", but I have no idea why it isn't running.

EDIT3: Adding vendor to chunks for reset_password.html causes the JavaScript to run, but I have no idea why, and this is non-ideal because the whole point of the exercise was to have two different bundles, one which was very minimal and didn't need all of our node_modules.

EDIT4: I ran Webpack with profile:true, and I can see I'm not getting the "Chunk Names" wrong:

                 js/app.3d18b43294ebd54ed083.js   1.34 MiB       0  [emitted]  [big]  app
       js/resetPassword.198485be2b163cc258ed.js   1.02 KiB       1  [emitted]         resetPassword
                   js/2.e7f92193ea3c611a0b36.js   2.23 MiB       2  [emitted]  [big]  vendor
             js/app.3d18b43294ebd54ed083.js.map   2.71 MiB       0  [emitted]         app
   js/resetPassword.198485be2b163cc258ed.js.map   4.57 KiB       1  [emitted]         resetPassword
               js/2.e7f92193ea3c611a0b36.js.map   7.12 MiB       2  [emitted]         vendor

EDIT5: I tried both

module.exports = {
  //...
  optimization: {
    runtimeChunk: {
      name: entrypoint => `runtime~${entrypoint.name}`
    }
  }
};

and

module.exports = {
  //...
  optimization: {
    runtimeChunk: true
  }
};

Based on PlayMa256's comment and the webpack docs on runtimeChunk. Neither caused the JavaScript to execute.

Caleb Jay
  • 2,159
  • 3
  • 32
  • 66
  • 1
    you need runtimeChunk: true in order to execute the bundles. And that has to be included in both places – PlayMa256 Sep 21 '18 at 00:31
  • can you clarify? In which both places? – Caleb Jay Sep 21 '18 at 16:28
  • Please see edit5. Based on what I read up on your suggestion, I tried some minor experimentation, none of which worked. I would be very grateful if you could elucidate where else I should add the runtimeChunk option. – Caleb Jay Sep 21 '18 at 16:35

2 Answers2

9

This is a multipart issue.

First, there is a bug in Html-Webpack-Plugin that makes it incompatible with Webpack4 and multiple entrypoints. It must be upgraded to v4.0.0-alpha.2 at least to work.

Second, in the new version, you needn't use use optimization.splitChunks.cacheGroups to manually separate out node_modules. Doing optimization.splitChunks.chunks = 'all' is enough to result in a given entrypoint only getting in its vendors-app-{{chunk_name}} chunk the node_modules it actually imports.

So if you do

optimization: {
    splitChunks: {
        chunks: 'all'
    },
},

Combined with

plugins: [
    new HtmlWebpackPlugin({
        filename: 'index.html',
        template: 'public/html/ide.html',
        inject: true,
        chunks: ['app']
    }),
    new HtmlWebpackPlugin({
        filename: 'reset_password.html',
        template: 'public/html/reset_password.html',
        inject: true,
        chunks: ['resetPassword']
    }),
]

Combined with

entry: {
    app: './public/js/ide.js',
    resetPassword: './public/js/reset_password.js'
},

Then, your webpack output will have

  1. app
  2. resetPassword
  3. vendors~app~resetPassword
  4. vendors~app
  5. vendors~resetPassword

Unless you have no imports in your resetPassword.js file, in which case it will look like

  1. app
  2. resetPassword
  3. vendors~app~resetPassword (necessary webpack vendor packages)
  4. vendors~app

More information, images, and conversation at https://github.com/jantimon/html-webpack-plugin/issues/1053

Note that chunksSortMode: is no longer a valid option on the HtmlWebpackPlugin object for the newest version, it is apparently done by default in webpack4.

Caleb Jay
  • 2,159
  • 3
  • 32
  • 66
  • This issue i had was with using HtmlWebpackPlugin i could not for the life of me get the code to execute. All imports were correct, but the chunk specificed in the HtmlWebpackPlugin config wouldn't actually run. Tried using runtime flag as well. The only solution was to name the splitChunks, which whilst it worked, i felt defeated the point of splitChunks. – Emile Sep 12 '19 at 14:20
  • Thanks much for mentioning that the plugin version before V4 had a bug, its 2020 and still the default install had installed V3.2.0 which I had to upgrade. – K DawG Apr 12 '20 at 06:16
3

When you set:

module.exports = {
  //...
  optimization: {
    runtimeChunk: true
  }
};

It generates a runtime chunk. What is a runtime? Runtime is where ALL the code that webpack uses to load other files are. This is the heart of webpack when you run build.

Since you have 2 separate bundle files: resetPassword and app.

Ok, you have all the files you need. You maybe need vendors on both too, since vendor in your case contains everything from node_modules. So basically you will have:

html 1: app, vendor, runtimeChunk.

html 2: reset_password, vendor, runtimeChunk.

By doing that, you application should run.

PlayMa256
  • 6,603
  • 2
  • 34
  • 54
  • According to https://gist.github.com/sokra/1522d586b8e5c0f5072d7565c2bee693 , "optimization.runtimeChunk: true adds an additonal chunk to each entrypoint containing only the runtime.". You're saying "runtime" is webpack's code for loading files? Hmm. Adding `runtimeChunk: true` to `optimization` caused both the `app` and `reset_password` to not work – Caleb Jay Sep 21 '18 at 16:51
  • but did you add runtime to both of htmls? In the top of the file – PlayMa256 Sep 21 '18 at 16:54
  • Oh, no let me try that – Caleb Jay Sep 21 '18 at 16:54
  • ``` new HtmlWebpackPlugin({ filename: 'index.html', template: 'public/html/ide.html', inject: true, chunks: ['app', 'vendor', 'runtimeChunk'] }), new HtmlWebpackPlugin({ filename: 'reset_password.html', template: 'public/html/reset_password.html', inject: true, chunks: ['resetPassword', 'runtimeChunk'], }), ``` – Caleb Jay Sep 21 '18 at 16:58
  • Still didn't work. I'm building right now with `profile` enabled, to see what the name of the chunks are. One moment. – Caleb Jay Sep 21 '18 at 16:58
  • manually add them in order to test, you don't need to bundle them everytime – PlayMa256 Sep 21 '18 at 16:59
  • You mean put like – Caleb Jay Sep 21 '18 at 17:00
  • Ah, hold on, in the bundle, looks like they're named `runtime~app` and `runtime~reset_password`. And I shouldn't do it manually because I need to deploy this to jenkins and other people's computers, for one thing lol. But I appreciate your help so far, this will likely be the correct answer. – Caleb Jay Sep 21 '18 at 17:01
  • You don't need to do that manually all the time... do that just for the sake of testing... when you figure out if it works you do that correctly. – PlayMa256 Sep 21 '18 at 17:03
  • So, manual testing I believe is unecessary. With your recommendation, I added `runtime-app` to and `runtime-~reset_password` to each of the HtmlWebpackPlugin places. Now, `app` works (root url), however, /reset_password.html still does not run javascript. The reason manually testing is unecessary is I can properly see both javascript files injected in the html in the inspector: `` – Caleb Jay Sep 21 '18 at 17:05
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/180538/discussion-between-playma256-and-caleb-jay). – PlayMa256 Sep 21 '18 at 17:07