Search code examples
javascripthtmlwebpackrelative-pathwebpack-file-loader

How to set image links for a multipage webpack configuration when the landing page is not in a subdirectory?


I am trying my hand at a multipage webpack configuration and have a question about how to load images differently for a landing page compared to the other pages on the site. The landing page builds to the root directory, while the other pages build to their respective subfolders.

Webpack appends the correct relative path ../images/ for the other pages, however the landing page needs to remain as images/, as it is located on the root directory along with the images folder.

How can I configure webpack such that <img src="images/00.jpg"> stays the same for the landing page, but is updated to <img src="../images/00.jpg"> for all other pages?

Here is the source folder:

/ src /
   -home.html
   -about.html
   js/
      -home.js
      -about.js
   images/
      -00.jpg
      -01.jpg
   scss/
      -style.scss

Here is the build folder webpack generates:

/ public_html /
   -index.html   // relative links in this file become broken :(
   -bundle.js
   about/
      -index.html
      -bundle.js
   images/
      -00.jpg
      -01.jpg
   css/
      -style.css

Finally, here is the webpack configuration. Please excuse the wall of code, I decided to include the entire configuration in case there is a better way to set this up.

// webpack.config.js

const webpack = require('webpack');
const path = require('path');

const HtmlWebPackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
 entry: {
   home: './src/js/home.js',
   about: './src/js/about.js',
  },
 output: {
   filename: (pathData) => {
     return pathData.chunk.name === 'home' ? 'bundle.js' : '[name]/bundle.js';
   },
   path: path.resolve(__dirname, 'public_html')
 },
 module: {
   rules: [
     {
       test: /\.js$/,
       exclude: /node_modules/,
       use: {
         loader: "babel-loader"
       }
     },{
       test: /\.html$/,
       use: [
         {
           loader: "html-loader",
           options: { minimize: true }
         }
       ]
     },{
       test: /\.css$/,
       use: [MiniCssExtractPlugin.loader, "css-loader"]
     },{
       test: /\.sass$|\.scss$/,
       use: [
         MiniCssExtractPlugin.loader,
         { loader: 'css-loader' },
         { loader: 'sass-loader' },
       ],
     },{
       test: /\.(jpg|png|svg)$/,
       use: [
         {
           loader: 'file-loader',
           options: {
             name: '[name].[ext]',
             outputPath:'images/',
             publicPath:'../images/'    // how to exclude home.html ?
           }
         }
       ]
     }
   ]
 },
 plugins: [
    new HtmlWebPackPlugin({
      hash: true,
      filename: 'index.html',      // landing page remains in root directory
      template: 'src/index.html',
      chunks: ['home']
   }),
    new HtmlWebPackPlugin({
      hash: true,
      filename: 'about/index.html', // all other pages move to subdirectories
      template: 'src/about.html',
      chunks: ['about']
   }),
   new MiniCssExtractPlugin({
      filename: "css/style.css"
   }),
   new CleanWebpackPlugin()
 ]
};

Thank you! And also, let me know how you like this configuration file!


Solution

  • I have it working. horray!

    I never ended up using publicPath. Maybe there would have been a way to change it, but this ended up being a red herring. Instead I restructured my src directory to follow the pattern I was looking for, and removed html-loader so the paths wouldn't get changed during the build process.

    Here is my new source directory:

    / src /
      -home.html
      templates/
        -about.html
      js/
        -home.js
        -about.js
      images/
        -00.jpg
        -01.jpg
      scss/
        -style.scss
    

    You can see home.html is purposely on the main directory rather than in /templates. Image sources are referenced as images/ in home.html, and ../images/ elsewhere.

    Now instead of html-loader, I used copy-webpack-plugin to require / copy the images from the source directory to the build folder, as html-loader was changing the paths during the build process. It took me a few tries to figure out that html-loader was the culprit.

    Here is my final working webpack config for the record.

    // webpack.config.js
    
    const webpack = require('webpack');
    const path = require('path');
    
    const HtmlWebPackPlugin = require("html-webpack-plugin");
    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    const { CleanWebpackPlugin } = require('clean-webpack-plugin');
    const CopyPlugin = require('copy-webpack-plugin');
    
    module.exports = {
     context: path.resolve(__dirname, 'src'),
     entry: {
       home: './js/home.js',
       about: './js/about.js',
      },
     output: {
       filename: (pathData) => {
         return pathData.chunk.name === 'home' ? 'bundle.js' : '[name]/bundle.js';
       },
       path: path.resolve(__dirname, 'public_html')
     },
     module: {
       rules: [
         {
           test: /\.js$/,
           exclude: /node_modules/,
           use: {
             loader: "babel-loader"
           }
         },{
           test: /\.css$/,
           use: [MiniCssExtractPlugin.loader, "css-loader"]
         },{
           test: /\.sass$|\.scss$/,
           use: [
             MiniCssExtractPlugin.loader,
             { loader: 'css-loader' },
             { loader: 'sass-loader' },
           ],
         },{
           test: /\.(jpg|png|svg)$/,
           use: [
             {
               loader: 'file-loader',
               options: {
                 name: '[path][name].[ext]'
               }
             }
           ]
         }
       ]
     },
     plugins: [
        new CopyPlugin({
          patterns: [
            {from:'./image',to:'image'} 
          ],
        }),
        new HtmlWebPackPlugin({
          hash: true,
          filename: 'index.html',      // landing page remains in root directory
          template: 'index.html',
          chunks: ['home']
       }),
        new HtmlWebPackPlugin({
          hash: true,
          filename: 'about/index.html', // all other pages move to subdirectories
          template: 'templates/about.html',
          chunks: ['about']
       }),
       new MiniCssExtractPlugin({
          filename: "style.css"
       }),
       new CleanWebpackPlugin()
     ]
    };