Webpack-dev-server无法自动更新页面

5

我正在使用webpack 5,并且目前已经设置如下:

  • webpack.prod.js - 我在这里为生产环境进行了一些特定的配置(例如图像压缩、devtool、CSS缩小、特定的meta标签值)
  • webpack.dev.js - 我在这里为开发环境进行了一些特定的配置(例如没有图像压缩,没有CSS缩小,特定的meta标签值)

目前我面临的问题是无法使webpack dev server的实时重新加载生效(适用于所有文件类型)。我查看了文档,但迄今为止没有运气。

据我所知,在开发模式下,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",
    },
};

3个回答

2

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

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

即使这个问题提到了Parcel和Webpack,但它很相似。我发现答案为该问题提供了良好的上下文:https://dev59.com/-XYOtIcB2Jgan1zn_Jch#72786450


1

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

ts-module

它是使用typescript模块编译文件的模块,需要在应用程序中安装typescript节点模块和适当的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",
}

在我看来,启动开发服务器有几种不同的方法,你可以查看文档

Webpack配置

在实现“热重载”之前,有一些需要考虑的缺失部分。

解析器配置

下面是我为其中一个项目预定义的列表,你可以修改它以接受更少/更多的扩展名,但是思路是“告诉webpack”哪些文件将能够编译。

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

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

应用上述步骤后,Webpack 应该已经能够编译文件了;typescript 和 ts-loader 应该能够正确地监视你的文件。但是,这并不意味着“热模块重载”,这只是在某些内容发生更改时重新加载你的浏览器,开发服务器将自行完成其工作。
我希望这个 gif 能证明配置已经生效。

enter image description here

完整的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 的配置,但如果您检查 该选项的规格说明,您将看到在下一次编译中会失去一些类型检查,因此他们建议安装另一个包。

建议同时使用 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观察器

我在文档中没有找到明确的答案,但似乎你在devServer中的watchFiles会覆盖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 配置示例:enter image description here
有用的链接:
- ts-config问题:https://github.com/TypeStrong/ts-loader/issues/352 - 模块var问题:Webpack TypeScript module.hot does not exist 希望这能帮到你!

我没有意识到,但我过于专注于开发服务器模式,对于生产配置,唯一的区别是您不使用热模块重新加载,但其余部分相同,您需要良好完成解析器配置并删除“watchFiles”。也许您希望以不同的方式捆绑代码,我建议您更多地尝试输出配置或优化配置(https://webpack.js.org/configuration/optimization/)或使用Terser(https://webpack.js.org/plugins/terser-webpack-plugin/)并选择一个好的服务器库。从那里开始,天空就是极限。 - Nicolás Juárez Nuño
你好,感谢联系并提供详细的解释!我尝试了你说的一切,但仍然无法正常工作。这时我注意到控制台中出现了奇怪的模式。我一直在收到有关webpack断开连接的控制台日志消息。我进行了一些研究,并发现这是因为我在Windows文件系统中使用WSL 2所导致的。将项目移动到WSL文件系统后,一切都开始正常工作了。现在它可以与我的配置和你的配置一起正常工作 :) - nobitta
嘿,说实话,很难知道原因,但我猜可能是你没有正确指示图像的URL。如果你在index.ejs或error.html中使用简单的html标签添加图像,它应该看起来像这样: <img src="/kitty.png" /> /代表公共路径,通常指向根目录中名为public的文件夹。你可以使用https://webpack.js.org/guides/public-path/修改该路径。我刚试过了,对我有效。刷新页面一切正常。 - Nicolás Juárez Nuño
因为我必须进行缓存破坏,所以我这样做:<img src="<%- require('../img/banner.png') %>" />,所以我不确定公共路径在这里是否起作用。因为在第一次加载时,图像加载得很好,但是任何后续刷新都会导致 404 - nobitta
嗯,我刚试了你的代码,在我的端也能运行,也许在你的配置里有些我看不到的东西...如果你发现其他问题,请在这里发帖。 - Nicolás Juárez Nuño
显示剩余2条评论

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接