127

I'm trying to do something in a project that I'm not sure if it is possible, I am in a wrong way or misunderstanding something. We are using webpack, and the idea is to serve more than one html file.

localhost:8181 -> serves index.html
localhost:8181/example.html -> serves example.html

I'm trying to do it by setting multiple entry points, following the documentation:

The folder structure is:

/
|- package.json
|- webpack.config.js
  /src
   |- index.html
   |- example.html
   | /js
      |- main.js
      |- example.js

Webpack.config.js:

...
entry: {
    main: './js/main.js',
    exampleEntry: './js/example.js'
},
output: {
    path: path.resolve(__dirname, 'build', 'target'),
    publicPath: '/',
    filename: '[name].bundle.js',
    chunkFilename: '[id].bundle_[chunkhash].js',
    sourceMapFilename: '[file].map'
},
...

index.html

<!DOCTYPE html>
<html
<head>
    ...
    <link type="text/css" href="/style/default.css">
</head>
<body>
    <div id="container"></div>
    <script src="/main.bundle.js"></script>
</body>
</html>

example.html:

<!DOCTYPE html>
<html
<head>
    ...
    <link type="text/css" href="/style/default.css">
</head>
<body>
    ...
    <script src="/example.bundle.js"></script>
</body>
</html>

Somebody knows what I'm doing wrong?

Thank you.

miguelitomp
  • 1,413
  • 2
  • 10
  • 7

8 Answers8

180

See an entrypoint as the root of a tree that references many other assets like javascript modules, images, templates and so on. When you define more than one entrypoint, you basically split your assets into so called chunks to not have all your code and assets in one single bundle.

What I think you want to achieve is to have more than one "index.html" for different apps that also refer to different chunks of your assets which you already defined with your entrypoints.

Copying an index.html file or even generating one with references to these entrypoints is not handled by the entrypoint mechanism - it is the other way round. A basic approach for handling html pages is using the html-webpack-plugin which not only can copy html files but also has an extensive mechanism for templating. This is especially helpful if you want to have your bundles suffixed with a bundle hash that is pretty to avoid browser caching issues when you update your app.

As you have defined a name pattern as [id].bundle_[chunkhash].js you can no longer reference your javascript bundle as main.bundle.js as it will be called something like main.bundle_73efb6da.js.

Have a look at the html-webpack-plugin. Especially relevant for your use case:

You should probably have something like that in the end (warning: not tested)

plugins: [
  new HtmlWebpackPlugin({
    filename: 'index.html',
    template: 'src/index.html',
    chunks: ['main']
  }),
  new HtmlWebpackPlugin({
    filename: 'example.html',
    template: 'src/example.html',
    chunks: ['exampleEntry']
  })
]

Please be aware to reference the name of the entrypoint in the chunks array, so in your example this should be exampleEntry. Probably it's also a good idea to move your templates into a specific folder instead of having them in directly inside the root src folder.

starball
  • 20,030
  • 7
  • 43
  • 238
Andreas Jägle
  • 11,632
  • 3
  • 31
  • 31
45

To use Multiple HTML files in Webpack using HtmlWebpackPlugin :

Modify the webpack.config.js by directly embedding the below code.

const HtmlWebpackPlugin = require('html-webpack-plugin');

let htmlPageNames = ['example1', 'example2', 'example3', 'example4'];
let multipleHtmlPlugins = htmlPageNames.map(name => {
  return new HtmlWebpackPlugin({
    template: `./src/${name}.html`, // relative path to the HTML files
    filename: `${name}.html`, // output HTML files
    chunks: [`${name}`] // respective JS files
  })
});

module.exports = {
  entry: {
    main: './js/main.js',
    example1: './js/example1.js',
    //... repeat until example 4
  },
  module: { 
       //.. your rules
  };
  plugins: [
    new HtmlWebpackPlugin({
      template: "./src/index.html",
      chunks: ['main']
    })
  ].concat(multipleHtmlPlugins)
  
};

You can add as many HTML pages as required to the htmlPageNames array. Ensure that each HTML and corresponding JS file have the same name (The above code assumes that).

RICHARD ABRAHAM
  • 2,218
  • 20
  • 26
  • 1
    I had to do `excludeChunks: ["main"]` to prevent bundle meant for my index.html from appearing in my other html too. – kyw Nov 02 '21 at 01:32
  • 1
    Thank you so much. This worked for me with the changes that @kyw suggested and also, by using placeholder in output, like so: output: { path: path.resolve(__dirname, 'dist'), filename: '[name].js', publicPath: '' }, – Shraddha Feb 14 '22 at 16:24
3

You can also use Copy Webpack Plugin if you don't need two different builds, i.e., assuming that you just want to serve a different HTML with the same main.bundle.js.

The plugin is really dead simple (only tested in webpack v4):

const CopyWebpackPlugin = require('copy-webpack-plugin');

const config = {
  plugins: [
    new CopyWebpackPlugin([
      { from: './src/example.html', to: './example.html' }
    ])
  ]
}

Then in example.html you can load the build from index.html. E.g.:

<!DOCTYPE html>
<html
<head>
    ...
    <title>Example</title>
</head>
<body>
    <div id="container"> Show an example </div>
    <script src="main.bundle.js"></script>
</body>
</html>
F Lekschas
  • 12,481
  • 10
  • 60
  • 72
  • 1
    is there any other way possible to use CopyWebpackPlugin and add bundle.js file to html file through webpack instead of directly giving script reference in the html file itself ? – Sritam Jagadev Jul 24 '18 at 11:02
  • @SritamJagadev No, CopyWebpackPlugin just copies files literally. You need WebpackHtmlPlugin if you want to modify the HTML, like injecting script tags. – Thomas Oct 17 '21 at 11:47
3

RICHARD ABRAHAM's solution worked well for me i also added fsreaddir function for detect html files

let htmlPageNames = [];
const pages = fs.readdirSync('./src')
pages.forEach(page => {
    if (page.endsWith('.html')) {
        htmlPageNames.push(page.split('.html')[0])
    }
})
console.log(htmlPageNames);
Akif Kara
  • 442
  • 6
  • 14
1

There is another solution, assuming Webpack ^4.44.1. That is, importing the HTML in your JS/TS app.

Sample webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');


module.exports = {
    entry: { app: './src/index.ts' },

    mode: 'development',
    devtool: 'inline-source-map',
    plugins: [
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin({
            title: 'Development',
            template: path.join(path.resolve(__dirname, 'src'), 'index.ejs')
        }),
    ],
    module: {
        rules: [
            {
                test: /\.ts$/,
                use: 'ts-loader',
                include: [path.resolve(__dirname, 'src')],
                exclude: /node_modules/,
            },
            {
                test: /\.html$/i,
                use: [
                    {
                        loader: 'file-loader',
                        options: {
                            name: '[name].[ext]'
                        }
                    }
                ],
                // this exclude is required
                exclude: path.join(path.resolve(__dirname, 'src'), 'index.html')
            }
        ],
    },
    resolve: {
        extensions: ['.ts', '.js'],
    },
    devServer: {
        contentBase: path.join(__dirname, 'dist'),
        compress: true,
        port: 3900
    },
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist'),
    },
};

Corresponding app

import './about.html';
    
console.log('this is a test'); 

index.ejs

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Question</title>
</head>
<body>
     <a href="./about.html">About</a>
</body>
</html>

about.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>About</title>
</head>
<body>
    <p>This is an about page</p>
</body>
</html>

Webpack will copy about.html to the corresponding output folder.

Alex Nolasco
  • 18,750
  • 9
  • 86
  • 81
1
plugins: [
  ...templates.map(template => new HtmlWebpackPlugin(template))
]

This code would help if you have a lot of templates :)

  • Can you write out the whole code? It's not really clear. – RockyK Jun 30 '21 at 04:17
  • @RockyKev I don't remember the context, but the main point is to have a variable to store information about your templates. Then you can run your templates trough webpack and compile them if you need to. ` const templates = [ { template: './src/hbs/pages/index.hbs', filename: './index.html' }, { template: './src/hbs/pages/index.hbs', filename: './articles/index.html' } ]` (sorry for bad formatting) – Pavel Rodionov Jul 01 '21 at 13:45
1

Going back to @andreas-jägle point. Use 'html-webpack-plugin':html-webpack-plugin html-webpack-plugin. However optimise your code to avoid duplication of files:

  plugins: ['index', 'page1', 'page2'].map(
    (file) =>
      new HtmlWebpackPlugin({
        template: './src/' + file + '.html',
        inject: true,
        chunks: ['index', 'main'],
        filename: './' + file + '.html' //relative to root of the application
      })
  )

rottitime
  • 1,653
  • 17
  • 29
0

in 2023 can be used the html-bundler-webpack-plugin instead of html-webpack-plugin. This plugin extracts JS, CSS, assets from source files used in HTML. Using this plugin, the entrypoint is the HTML file and all used source resources are extracted automatically.

You can load individual source scripts, styles and images directly in HTML. The path to source file must be relative by HTML file or an Webpack alias.

index.html

<!DOCTYPE html>
<html
<head>
    ...
    <!-- load source file of style -->
    <link rel="stylesheet" href="./style/default.scss">
</head>
<body>
    <h1>Home</h1>
    <!-- load source file of image -->
    <img src="./images/homepage.png">
    <!-- load source file of script -->
    <script src="./js/main.js"></script>
</body>
</html>

example.html

<!DOCTYPE html>
<html
<head>
    ...
    <!-- load source file of style -->
    <link rel="stylesheet" href="./style/default.scss">
</head>
<body>
    <h1>Example</h1>
    <!-- load source file of script -->
    <script src="./js/example.js"></script>
</body>
</html>

Install plugin:

npm install html-bundler-webpack-plugin --save-dev

Change your webpack.config.js according to the following minimal configuration:

const path = require('path');
const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');

module.exports = {
  output: {
    path: path.join(__dirname, 'dist/'),
    publicPath: '/',
  },

  entry: {
    // define HTML files here
    index: './src/index.html',  // => dist/index.html
    example: './src/example.html', // => dist/example.html
    // ...
  },

  plugins: [
    new HtmlBundlerPlugin({
      js: {
        // output filename of extracted JS from source script loaded in HTML via `<script>` tag
        filename: 'assets/js/[name].[contenthash:8].js',
      },
      css: {
        // output filename of extracted CSS from source style loaded in HTML via `<link>` tag
        filename: 'assets/css/[name].[contenthash:8].css',
      },
    }),
  ],

  module: {
    rules: [
      // Note: enable processing of HTML files from entry
      {
        test: /\.html$/,
        loader: HtmlBundlerPlugin.loader, // HTML loader
      },
      // styles
      {
        test: /\.(css|sass|scss)$/,
        use: ['css-loader', 'sass-loader'],
      },
      // images
      {
        test: /\.(png|jpe?g|svg|ico)/,
        type: 'asset/resource',
        generator: {
          filename: 'assets/img/[name].[hash:8][ext]',
        },
    },
    ],
  },
};

The generated HTML contains output hashed filenames. Source styles, scripts and images are automatically processed and placed in the output directory dist/.

The generated index.html:

<!DOCTYPE html>
<html>
<head>
    ...
    <link href="/assets/css/default.f57966f4.css" rel="stylesheet">
</head>
<body>
    <h1>Home</h1>
    <img src="/assets/img/homepage.d2f4b855.png">
    <script src="/assets/js/main.b855d8f4.js"></script>
</body>
</html>

Note

No longer needed to import styles in JavaScript.
No longer needed to define a JS file as the entrypoint.
Now it works intuitively, right in the HTML.

biodiscus
  • 365
  • 3
  • 8