【问题标题】:webpack-dev-server not reloadingwebpack-dev-server 没有重新加载
【发布时间】:2022-12-04 16:19:45
【问题描述】:

我正在使用 webpack 5,我目前有以下设置:

  • webpack.prod.js - 我有一些特定的生产配置(例如图像压缩、devtool、CSS 缩小、特定的元标记值)
  • webpack.dev.js - 我有一些特定的开发配置(例如没有图像压缩,没有 CSS 缩小,特定的元标记值)

我目前面临的问题是我无法获得webpack 开发服务器实时重新加载工作(这适用于所有文件类型)。我已经阅读了文档,但到目前为止还没有运气。

据我所知,在开发模式下,webpack 在内存中而不是在磁盘中运行(这应该更快,这太棒了!)。出于某种原因,观察者似乎没有对 devServer.watchFiles 配置中指定的文件中的更改做出反应。我期待 webpack 检测 typescript 文件的更改,编译它并重新加载,但这并没有发生。

您可以在下面找到这两个文件的内容。

webpack.prod.js:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserPlugin = require('terser-webpack-plugin');
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');
const buildPath = path.resolve(__dirname, 'dist');

module.exports = {
  //devtool: 'source-map',
  entry: {
    index: "./src/index/index.ts",
    error: "./src/error/error.ts",
  },
  output: {
    filename: "js/[name].[contenthash].js",
    path: buildPath,
    clean: true,
  },
  module: {
    rules: [{
        test: /\.ts$/i,
        exclude: /node_modules/,
        use: "ts-loader",
      },
      {
        test: /\.html$/i,
        exclude: /node_modules/,
        use: "html-loader",
      },
      {
        test: /\.css$/i,
        exclude: /node_modules/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
        ]
      },
      {
        test: /\.png$/i,
        exclude: /node_modules/,
        type: "asset/resource",
        generator: {
          filename: "img/[name].[contenthash][ext]",
        },
      },
      {
        test: /\.(woff|woff2|ttf)$/i,
        exclude: /node_modules/,
        type: "asset/resource",
        generator: {
          filename: "fonts/[name].[contenthash][ext]",
        },
      },
      {
        test: /\.mp3$/i,
        exclude: /node_modules/,
        type: "asset/resource",
        generator: {
          filename: "[name].[contenthash][ext]",
        },
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "./src/index/index.ejs",
      inject: "body",
      chunks: ["index"],
      filename: "index.html",
      meta: {
        "robots": {
          name: "robots",
          content: "index,follow"
        },
      },
    }),
    new HtmlWebpackPlugin({
      template: "./src/error/error.html",
      inject: "body",
      chunks: ["error"],
      filename: "error.html",
    }),
    new MiniCssExtractPlugin({
      filename: "css/[name].[contenthash].css",
      chunkFilename: "css/[id].[contenthash].css",
    }),
    new CopyPlugin({
      patterns: [{
        from: "src/robots.txt",
        to: "robots.txt",
      }, ],
    }),
  ],
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        parallel: true,
      }),
      new CssMinimizerPlugin(),
      new ImageMinimizerPlugin({
        minimizer: {
          implementation: ImageMinimizerPlugin.imageminMinify,
          options: {
            plugins: [
              ["imagemin-pngquant", {
                quality: [0.5, 0.9]
              }],
            ],
          },
        },
      }),
    ],
  },
};

webpack.dev.js:

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
    mode: "development",
    devtool: "eval-cheap-module-source-map",
    entry: {
        index: "./src/index/index.ts",
        error: "./src/error/error.ts",
    },
    devServer: {
        watchFiles: [path.resolve(__dirname, "src/**/*")],
        open: true,
    },
    module: {
        rules: [
            {
                test: /\.ts$/i,
                exclude: /node_modules/,
                use: "ts-loader",
            },
            {
                test: /\.html$/i,
                exclude: /node_modules/,
                use: "html-loader",
            },
            {
                test: /\.css$/i,
                exclude: /node_modules/,
                use: ["style-loader", "css-loader"]
            },
            {
                test: /\.png$/i,
                exclude: /node_modules/,
                type: "asset/resource",
                generator: {
                    filename: "img/[name].[contenthash][ext]",
                },
            },
            {
                test: /\.(woff|woff2|ttf)$/i,
                exclude: /node_modules/,
                type: "asset/resource",
                generator: {
                    filename: "fonts/[name].[contenthash][ext]",
                },
            },
            {
                test: /\.mp3$/i,
                exclude: /node_modules/,
                type: "asset/resource",
                generator: {
                    filename: "[name].[contenthash][ext]",
                },
            },
        ],
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index/index.ejs",
            inject: "body",
            chunks: ["index"],
            filename: "index.html",
            meta: {
                "robots": { name: "robots", content: "noindex, nofollow" },
            },
        }),
        new HtmlWebpackPlugin({
            template: "./src/error/error.html",
            inject: "body",
            chunks: ["error"],
            filename: "error.html"
        }),
    ],
    optimization: {
        runtimeChunk: "single",
    },
};

【问题讨论】:

    标签: webpack webpack-dev-server webpack-5 webpack-hmr


    【解决方案1】:

    有几个地方我有点盲目,因为你没有添加一些关于你的tsconfig.json文件和你的package.json的描述,但我会尽力在这里向你解释关键点,然后再解释解决方案。

    ts-module

    它是使用 typescript 模块编译文件的模块,它需要在应用程序中安装 typescript node_module 和适当的 tsconfig.json

    出于实际目的,我将我在以前的项目中使用的配置粘贴在下面——非常基本,你可以根据你的项目改进它——;配置没有什么特别的,它不会影响 HMR 但它是编译所必需的。

    {
      "compilerOptions": {
        "noImplicitAny": true,
        "removeComments": true,
        "allowSyntheticDefaultImports": true,
        "esModuleInterop": true,
        "outDir": "./dist/",
        "sourceMap": true,
        "module": "commonjs",
        "target": "es6",
        "allowJs": true,
      },
      "includes": ["/**/*"]
    }
    

    webpack-dev-server

    用于在您的本地环境中启动开发服务器的包,这非常简单,应该与webpackwebpack-cli 一起安装。

    你的 package.json 可能有一个指向你的 webpack.dev.js 配置的 start 脚本:

    "scripts" {
       "start": "webpack-dev-server --config ./webpack.dev.js",
    }
    

    据我了解,有几种不同的方法可以启动开发服务器,您可以check the documentation

    Webpack 配置

    在实施“热重载”之前需要考虑一些遗漏的事情

    resolver configuration

    我在我为我的一个项目所做的预定义列表下面粘贴了,您可以修改它以接受更少/更多的扩展,但我的想法是“告诉 webpack”哪些文件将能够编译。

    这将允许您导入具有这些扩展名的文件。

    resolve: {
          extensions: [
            ".js",
            ".jsx",
            ".ts",
            ".tsx",
            ".less",
            ".css",
            ".json",
            ".mjs",
          ],
        }
    

    应用之后,Webpack 应该已经可以编译文件了; typescriptts-loader 应该能够正确观看您的文件。但是,这并不意味着“热模块重新加载”,这只是在发生变化时重新加载浏览器,开发服务器将自行发挥“魔力”。

    我希望 gif 显示配置有效的证明

    完整的 webpack 配置如下所示:

    const HtmlWebpackPlugin = require("html-webpack-plugin");
    
    
    module.exports = {
      mode: "development",
      devtool: "eval-cheap-module-source-map",
      entry: {
        index: "./src/index/index.ts",
        error: "./src/error/error.ts",
      },
      resolve: {
        extensions: [
          ".js",
          ".jsx",
          ".ts",
          ".tsx",
          ".less",
          ".css",
          ".json",
          ".mjs",
        ],
      },
      module: {
        rules: [
          {
            test: /.ts$/i,
            exclude: /node_modules/,
            use: [
              {
                loader: "ts-loader",
              },
            ],
          },
          {
            test: /.html$/i,
            exclude: /node_modules/,
            use: "html-loader",
          },
          {
            test: /.css$/i,
            exclude: /node_modules/,
            use: ["style-loader", "css-loader"],
          },
          {
            test: /.png$/i,
            exclude: /node_modules/,
            type: "asset/resource",
            generator: {
              filename: "img/[name].[contenthash][ext]",
            },
          },
          {
            test: /.(woff|woff2|ttf)$/i,
            exclude: /node_modules/,
            type: "asset/resource",
            generator: {
              filename: "fonts/[name].[contenthash][ext]",
            },
          },
          {
            test: /.mp3$/i,
            exclude: /node_modules/,
            type: "asset/resource",
            generator: {
              filename: "[name].[contenthash][ext]",
            },
          },
        ],
      },
      plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index/index.ejs",
            inject: "body",
            chunks: ["index"],
            filename: "index.html",
            meta: {
                "robots": { name: "robots", content: "noindex, nofollow" },
            },
        }),
        new HtmlWebpackPlugin({
            template: "./src/error/error.html",
            inject: "body",
            chunks: ["error"],
            filename: "error.html"
        })
      ],
      optimization: {
        runtimeChunk: "single",
      },
    };
    
    

    对于正确的热模块重新加载——代表只更新你更改的代码片段而不重新加载整个浏览器——

    还需要进行一些调整,互联网上有很多关于此的资源,我将在最后列出,但由于您没有使用 babel——工作方式不同——您需要提供更多配置在您的 webpack 中并删除其他属性。

    更新 ts-loader 配置

    首先查看ts-loader 关于 HMR https://github.com/TypeStrong/ts-loader#hot-module-replacement 及其含义的文档。

    他们建议添加一个名为transpileOnly 的配置,但是如果你检查specifications about that option,你会发现你在下一次编译中丢失了一些类型检查,因此他们建议安装另一个包

    建议将 transpileOnly 与 fork-ts-checker-webpack-plugin 一起使用以再次进行完整的类型检查。要了解这在实践中是什么样子,请查看我们的示例。

    您的 ts-loader 规则应如下所示:

    rules: [
          {
            test: /.ts$/i,
            exclude: /node_modules/,
            use: [
              {
                loader: "ts-loader",
                options: {
                  transpileOnly: true,
                },
              },
            ],
          },
    

    还有你的插件:

    plugins: [
        ..., // Your HTML plugins
        new ForkTsCheckerWebpackPlugin(),
      ],
    

    启用热模块重新加载

    这比看起来容易,您需要考虑两个关键点:

    1. 让 webpack 知道你想要 HMR
    2. 让您的代码知道将被热重载——听起来很有趣但是是的——

      第一点,非常简单,只需将以下配置添加到您的 webpack 配置中:

      devServer: {
          hot: true,
      },
      

      但对于第二点,这有点棘手,通过简单的调整就可以做到;最简单的方法是通过为每个入口点创建一个新文件,该文件可以根据需要命名,但在本例中我将其命名为hot.ts,并且看起来像下面粘贴的代码:

      // Inside of your index folder
      require("./index");
      
      if (module.hot) {
        module.hot.accept("./index.ts", function () {
          console.log("Hot reloading index");
          require("./index");
        });
      }
      
      // Inside of your error folder
      require("./error");
      
      if (module.hot) {
        module.hot.accept("./error.ts", function () {
          console.log("Hot reloading error");
          require("./error");
        });
      }
      

      边注:你会发现自己的module全局变量存在类型错误,要解决你需要安装以下类型npm i --save -D @types/node @types/webpack-env

      在您的开发配置模式下——通常 HRM 用于开发而非生产——您需要调整入口点:

      entry: {
          index: "./src/index/hot.ts",
          error: "./src/error/hot.ts",
      },
      

      不要覆盖 HMR 观察者

      我没有从文档中找到明确的答案,但看起来你的 watchFiles 在你的 devServer 中覆盖了 HMR 观察者,如果你删除它 - 即使从构建开发中,它也没有什么不同此配置——HMR 应该可以顺利运行。

      执行完前面的步骤后,您的 webpack 文件应该类似于以下代码:

      const HtmlWebpackPlugin = require("html-webpack-plugin");
      const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
      
      
      module.exports = {
        mode: "development",
        devtool: "eval-cheap-module-source-map",
        entry: {
          index: "./src/index/hot.ts",
          error: "./src/error/hot.ts",
        },
        devServer: {
          hot: true,
        },
        resolve: {
          extensions: [
            ".js",
            ".jsx",
            ".ts",
            ".tsx",
            ".less",
            ".css",
            ".json",
            ".mjs",
          ],
        },
        module: {
          rules: [
            {
              test: /.ts$/i,
              exclude: /node_modules/,
              use: [
                {
                  loader: "ts-loader",
                  options: {
                    transpileOnly: true,
                  },
                },
              ],
            },
            {
              test: /.html$/i,
              exclude: /node_modules/,
              use: "html-loader",
            },
            {
              test: /.css$/i,
              exclude: /node_modules/,
              use: ["style-loader", "css-loader"],
            },
            {
              test: /.png$/i,
              exclude: /node_modules/,
              type: "asset/resource",
              generator: {
                filename: "img/[name].[contenthash][ext]",
              },
            },
            {
              test: /.(woff|woff2|ttf)$/i,
              exclude: /node_modules/,
              type: "asset/resource",
              generator: {
                filename: "fonts/[name].[contenthash][ext]",
              },
            },
            {
              test: /.mp3$/i,
              exclude: /node_modules/,
              type: "asset/resource",
              generator: {
                filename: "[name].[contenthash][ext]",
              },
            },
          ],
        },
        plugins: [
          new HtmlWebpackPlugin({
              template: "./src/index/index.ejs",
              inject: "body",
              chunks: ["index"],
              filename: "index.html",
              meta: {
                  "robots": { name: "robots", content: "noindex, nofollow" },
              },
          }),
          new HtmlWebpackPlugin({
              template: "./src/error/error.html",
              inject: "body",
              chunks: ["error"],
              filename: "error.html"
          }),
          new ForkTsCheckerWebpackPlugin(),
        ],
        optimization: {
          runtimeChunk: "single",
        },
      };
      

      带有工作示例的存储库:https://github.com/nicolasjuarezn/webpack-ts-example

      配置工作示例:

      有用的链接:

      我希望这对你有帮助!

    【讨论】:

    • 我没有意识到,但我太专注于开发服务器模式,对于生产配置,唯一的区别是你不使用热模块重新加载,但其余的都是一样的,你需要做好解析器配置和删除watchFiles。也许你会想要以不同的方式捆绑代码,我建议你尝试更多的输出配置或优化 conf (webpack.js.org/configuration/optimization) 或使用 Terser (webpack.js.org/plugins/terser-webpack-plugin) 并选择一个好的服务器库。从那里天空是极限。
    • 您好,感谢您的联系和详细的解释!我尝试了你所说的一切,但仍然没有用。那时我注意到控制台中出现了一个奇怪的模式。我不断收到有关 webpack 断开连接的控制台日志消息。我研究了一下,发现这是因为我在 Windows 文件系统中使用 WSL 2。将项目移动到 WSL 文件系统后,一切都开始正常工作。它现在适用于我的和你的配置:)
    • 关于图像仍然发生了一件事,即使我使用你的配置。当我刷新页面时,某些图像无法加载并显示 404。任何线索说明为什么会发生这种情况?将 png 模块规则类型从 asset/resource 更改为 asset/inline 足以解决它。
    • 嘿,说实话,有点难知道原因,但我猜你可能没有正确指示图像的 URL。如果您使用简单的 html 标记在 index.ejs 或 error.html 中添加图像,它应该如下所示:<img src="/kitty.png" />/ 表示通常指向名为 @987654372 的文件夹的公共路径@ 在你的根目录中。您可以使用 webpack.js.org/guides/public-path 修改该路径。我刚刚尝试过并为我工作。刷新页面和一切。
    • 由于缓存破坏,我不得不这样做:<img src="<%- require('../img/banner.png') %>" />,所以我不确定公共路径是否在这里起作用。因为在第一次加载时图像加载得很好,但任何后续刷新都会导致 404
    【解决方案2】:

    该问题与 WSL 有关。更具体地说,在 Windows 文件系统上的 WSL 中运行 webpack(例如/mnt/c/Users/MyUser/Documents/MyProject)。

    将项目移动到 WSL 文件系统后(例如/home/MyUser/MyProject),我能够实时重新加载工作。

    尽管this question 提到了 Parcel 和 Webpack,但它们是相似的。我发现答案提供了围绕该问题的良好背景:https://*.com/a/72786450/3685587

    【讨论】:

      【解决方案3】:

      对于 webpack 5 使用

        devServer: {
          watchFiles: ['src/**/*.php', 'public/**/*'],
        },
      

      在这里查看详细信息https://webpack.js.org/configuration/dev-server/#devserverwatchfiles

      【讨论】: