Webpack 4 为不同级别的文件夹结构中的 img 标签生成错误的 src 路径

Webpack 4 generates wrong src path for img tags in different levels of folder structure

我有一个 Webpack 4 项目来制作多语言 admin-dashboard 具有以下文件夹和文件结构:

admin-dashboard
|
|--build
|  |--assets
|  |  |--img
|  |  |--font
|  |--fa
|  |  |--index.html ----> rtl html output (gets wrong img src like "assets/img/img.png")
|  |--index.html    ----> ltr html output (gets right img src like "assets/img/img.png")
|  |--style.css
|  |--style-rtl.css
|  |--script.js
|  |--script-rtl.js
|
|--config   ---> containing my Webpack config files for production and development
|  |--webpack.dev.js
|  |--webpack.prod.js
|
|--node_modules
|--src
|  |--assets
|  |  |--img
|  |  |--font
|  |
|  |--i18n
|  |  |--fa
|  |  |  |--index.html ----> rtl html template ---> <img src="../../assets/img/img.png" />
|  |  |--index.html    ----> ltr html template ---> <img src="../assets/img/img.png" />
|  |
|  |--js
|  |--locales
|  |--scss
|  |--templates
|
|--.babelrc
|--package.json
|--postcss.config.js

我的 src 文件夹中的 i18n 文件夹中的图像路径在开发项目时是正确的,但是当我构建项目时,它在项目的根目录中创建了一个 build 文件夹它自己的资产文件夹。正如您在上面的文件结构中看到的,build 文件夹中的 ltr html 文件具有放置在 build 文件夹中的资产文件夹的正确路径,但 fa 文件夹中的 rtl 版本指的是相同的路径,这是错误的.这 2 个文件基于 src/i18n 中的 2 html 个文件制作。加上两个 html 文件的 header 中指向图标图像的所有链接在文件的构建版本中保持不变,这是另一个问题,但由 Webpack html 插件完成的其他注入链接全部正确注入html 文件中的路径。

这是我的生产模式的 webpack 配置: webpack.prod.js:

const webpack = require('webpack');
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const TerserJSPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');

module.exports = {
  entry: {
    main: './src/js/main.js',
    'main-rtl': './src/js/main-rtl.js',
  },
  output: {
    path: path.join(__dirname, '../build'),
    filename: '[name].[chunkhash:8].bundle.js',
    chunkFilename: '[name].[chunkhash:8].chunk.js',
  },
  mode: 'production',
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader', 
        },
      },
      {
        test: /\.(sa|sc|c)ss$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader', 
          'postcss-loader', 
          'sass-loader',
        ],
      },
      {
        test: /\.(png|svg|jpe?g|gif)$/i,
        use: [
          {
            loader: 'file-loader', 
            options: {
              name: '[name].[ext]',
              outputPath: 'assets/img',
              esModule: false,
            },
          },
        ],
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        use: [
          {
            loader: 'file-loader', 
            options: {
              name: '[name].[ext]',
              outputPath: 'assets/font',
            },
          },
        ],
      },
      {
        test: /\.html$/i,
        use: {
          loader: 'html-loader',
          options: {
            attributes: {
              list: [
                {
                  tag: 'img',
                  attribute: 'src',
                  type: 'src',
                },
                {
                  tag: 'img',
                  attribute: 'srcset',
                  type: 'srcset',
                },
                {
                  tag: 'img',
                  attribute: 'data-src',
                  type: 'src',
                },
                {
                  tag: 'img',
                  attribute: 'data-srcset',
                  type: 'srcset',
                },
              ],

            },
          },
        },
      },
    ],
  },
  optimization: {
    minimizer: [new TerserJSPlugin(), new OptimizeCSSAssetsPlugin()],
    splitChunks: {
      cacheGroups: {
        commons: {
          test: /[\/]node_modules[\/]/,
          name: 'vendors',
          chunks: 'all',
        },
      },
      chunks: 'all',
    },
    runtimeChunk: {
      name: 'runtime',
    },
  },
  plugins: [
    // load jQuery
    new webpack.ProvidePlugin({
      $: 'jquery',
      jQuery: 'jquery',
    }),

    new CleanWebpackPlugin(),

    new MiniCssExtractPlugin({
      filename: '[name].[chunkhash:8].bundle.css',
      chunkFilename: '[name].[chunkhash:8].chunk.css',
    }),

    new HtmlWebpackPlugin({
      chunks: ['main'],
      template: 'src/i18n/index.html',
      filename: 'index.html',
    }),
    new HtmlWebpackPlugin({
      chunks: ['main-rtl'],
      template: 'src/i18n/fa/index.html',
      filename: 'fa/index.html',
    }),

  ],
};

所以,问题是当我使用 webpack.prod.js 配置构建项目时,它在构建文件夹中为我提供了 2 个 html 文件,用于 ltr 和 rtl 方向,在 rtl 版本中 index.html 它为 img 标签生成了错误的 src 路径,并且在两个文件中它都没有改变 favicon 链接的路径,并且提到的链接与源 html 文件保持相同,但其他链接注入了 Webpack html 插件在每个 html 文件中都正确注入了正确的路径。

我需要 2 html 个文件,根据文件在文件夹结构中的任何级别生成正确的 img 标签 src 路径。

任何帮助将不胜感激。

我做了很多研究,没有找到对问题有帮助的东西。因此,我想到了为项目的 ltr 和 rtl 输出设置两个单独的生产 webpack 配置文件的想法。因此,我修改了 webpack.prod.js 文件,如下所示:

const webpack = require('webpack');
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const TerserJSPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');

module.exports = {
  entry: {
    main: './src/js/main.js' // ---> the only entry we have is main
  },
  output: {
    path: path.join(__dirname, '../build'), 
    filename: '[name].[chunkhash:8].bundle.js',
    chunkFilename: '[name].[chunkhash:8].chunk.js',
  },
  mode: 'production',
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader', 
        },
      },
      {
        test: /\.(sa|sc|c)ss$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader', 
          'postcss-loader', 
          'sass-loader',
        ],
      },
      {
        test: /\.(png|svg|jpe?g|gif)$/i,
        use: [
          {
            loader: 'file-loader', 
            options: {
              name: '[name].[ext]',
              outputPath: 'assets/img',
              esModule: false,
            },
          },
        ],
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        use: [
          {
            loader: 'file-loader', 
            options: {
              name: '[name].[ext]',
              outputPath: 'assets/font',
            },
          },
        ],
      },
      {
        test: /\.html$/i,
        use: {
          loader: 'html-loader',
          options: {
            attributes: {
              list: [
                {
                  tag: 'img',
                  attribute: 'src',
                  type: 'src',
                },
                {
                  tag: 'img',
                  attribute: 'srcset',
                  type: 'srcset',
                },
                {
                  tag: 'img',
                  attribute: 'data-src',
                  type: 'src',
                },
                {
                  tag: 'img',
                  attribute: 'data-srcset',
                  type: 'srcset',
                },
              ],

            },
          },
        },
      },
    ],
  },
  optimization: {
    minimizer: [new TerserJSPlugin(), new OptimizeCSSAssetsPlugin()],
    splitChunks: {
      cacheGroups: {
        commons: {
          test: /[\/]node_modules[\/]/,
          name: 'vendors',
          chunks: 'all',
        },
      },
      chunks: 'all',
    },
    runtimeChunk: {
      name: 'runtime',
    },
  },
  plugins: [
    // load jQuery
    new webpack.ProvidePlugin({
      $: 'jquery',
      jQuery: 'jquery',
    }),

    new CleanWebpackPlugin(),

    new MiniCssExtractPlugin({
      filename: '[name].[chunkhash:8].bundle.css',
      chunkFilename: '[name].[chunkhash:8].chunk.css',
    }),

    new HtmlWebpackPlugin({
      chunks: ['main'],
      template: 'src/i18n/index.html',
      filename: 'index.html',
    }),
    // new HtmlWebpackPlugin({
      // chunks: ['main-rtl'],
      // template: 'src/i18n/fa/index.html',    ----> this part removed too
      // filename: 'fa/index.html',
    // }),

  ],
};

然后我为项目的 rtl 输出创建了另一个名为 webpack.prod.rtl.js 的 webpack 配置文件,并更改了项目的 outputPath file-loader 并且我还修改了 filename我的 HtmlWebpackPlugin 如下所示:

const webpack = require('webpack');
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const TerserJSPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');

module.exports = {
  entry: {
    'main-rtl': './src/js/main-rtl.js', // main-rtl instead of main
  },
  output: {
    path: path.join(__dirname, '../build/fa'),
    filename: '[name].[chunkhash:8].bundle.js',
    chunkFilename: '[name].[chunkhash:8].chunk.js',
  },
  mode: 'production',
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader', 
        },
      },
      {
        test: /\.(sa|sc|c)ss$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader', 
          'postcss-loader', 
          'sass-loader',
        ],
      },
      {
        test: /\.(png|svg|jpe?g|gif)$/i,
        use: [
          {
            loader: 'file-loader', 
            options: {
              name: '[name].[ext]',
              outputPath: '../assets/img',
              esModule: false,
            },
          },
        ],
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        use: [
          {
            loader: 'file-loader', 
            options: {
              name: '[name].[ext]',
              outputPath: '../assets/font',
            },
          },
        ],
      },
      {
        test: /\.html$/i,
        use: {
          loader: 'html-loader',
          options: {
            attributes: {
              list: [
                {
                  tag: 'img',
                  attribute: 'src',
                  type: 'src',
                },
                {
                  tag: 'img',
                  attribute: 'srcset',
                  type: 'srcset',
                },
                {
                  tag: 'img',
                  attribute: 'data-src',
                  type: 'src',
                },
                {
                  tag: 'img',
                  attribute: 'data-srcset',
                  type: 'srcset',
                },
              ],

            },
          },
        },
      },
    ],
  },
  optimization: {
    minimizer: [new TerserJSPlugin(), new OptimizeCSSAssetsPlugin()],
    splitChunks: {
      cacheGroups: {
        commons: {
          test: /[\/]node_modules[\/]/,
          name: 'vendors',
          chunks: 'all',
        },
      },
      chunks: 'all',
    },
    runtimeChunk: {
      name: 'runtime',
    },
  },
  plugins: [
    // load jQuery
    new webpack.ProvidePlugin({
      $: 'jquery',
      jQuery: 'jquery',
    }),

    new CleanWebpackPlugin(),

    new MiniCssExtractPlugin({
      filename: '[name].[chunkhash:8].bundle.css',
      chunkFilename: '[name].[chunkhash:8].chunk.css',
    }),

    new HtmlWebpackPlugin({
      chunks: ['main-rtl'],
      template: 'src/i18n/fa/index.html',
      filename: 'index.html',  // ---> changed from fa/index.html to index.html
    }),

  ],
};

另外,我安装了一个名为 npm-run-all 的 npm 包,用于 运行 一些并行的 npm cli 命令。因此,当我同时需要项目的 ltr 和 rtl 版本时,我可以通过 package.json 文件的 script section 中的这些命令构建它们:

"scripts": {
    // other scripts,
    "build-ltr": "webpack --config=config/webpack.prod.js",
    "build-rtl": "webpack --config=config/webpack.prod.rtl.js",
    "build": "npm-run-all --parallel build-ltr build-rtl"
  },

例如,在 运行ning npm run build 之后,它为我提供了我在问题中提到的先前文件夹结构中的项目的 ltr 和 rtl 版本,但具有正确的图像 src 路径。

所以,我决定分享解决方案。肯定还有其他解决方案,但这是我现在想出的解决问题的办法。

希望这对其他人有所帮助。