Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I use multiple entries in Webpack alongside multiple HTML files in HtmlWebpackPlugin?

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.

like image 594
Caleb Jay Avatar asked Dec 13 '22 14:12

Caleb Jay


1 Answers

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.

like image 65
Caleb Jay Avatar answered Dec 16 '22 05:12

Caleb Jay