nobitta
nobitta

Reputation: 305

webpack-dev-server not reloading

I am using webpack 5 and I currently have the following setup:

The issue I am currently facing is that I am unable to get webpack dev server live reloading to work (this apply to all file types). I've been through the docs but no luck so far.

As far as I understand, when in development mode, webpack runs stuff in memory rather than in disk (which is supposed to be faster and that is great!). For some reason it appears that the watcher is not reacting to changes in the files specified in the devServer.watchFiles config. I was expecting webpack to detect changes on a typescript file, compile it and reload, but that's not happening.

You can find the contents of both files below.

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",
    },
};

Upvotes: 5

Views: 3636

Answers (3)

Andrey.Kostyuchenko
Andrey.Kostyuchenko

Reputation: 502

For webpack 5 use

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

See details here https://webpack.js.org/configuration/dev-server/#devserverwatchfiles

Upvotes: 0

nobitta
nobitta

Reputation: 305

The issue was related with WSL. More specifically running webpack in WSL on Windows file system (e.g. at /mnt/c/Users/MyUser/Documents/MyProject).

After moving the project to WSL file system (e.g. at /home/MyUser/MyProject) I was able to get live reload to work.

Even though this question mentions Parcel and Webpack, it is similar. I found the answer provides good context around the issue: https://stackoverflow.com/a/72786450/3685587

Upvotes: 2

There are a few spots where I am a little bit blind because you didn't add some description about your tsconfig.json file and your package.json, but I will try to do my best to explain you the key points here and afterwards explain the solution.

The ts-module

It is the module that uses the typescript module for compiling the files, it requires the typescript node_module installed in the app and a proper tsconfig.json.

For practical purposes I pasted bellow the configuration that I used in previous projects -- super basic you can improve it depending on your project --; There is nothing special about the config, it will not affect the HMR but it is required for the compiling.

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

webpack-dev-server

The package for starting the dev server in your local environment, this is quite straight forward, this should be installed along webpack and webpack-cli.

Your package.json could have a start script pointing to your webpack.dev.js configuration:

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

In my understanding there are a few different ways to start the dev server, you can check the documentation

The Webpack configuration

There are a few missing things to consider before implementing a "Hot reload"

The resolver configuration

I pasted bellow a pre-define list that I did for one of my projects, you can modify it to accept less/more extensions, but the idea is to "tell webpack" which files will be able to compile.

This will allow you to imports files with those extensions.

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

After applying that, Webpack should already be able to compile the files; typescript and the ts-loader should be able to watch your files properly. But, this doesn't mean "Hot Module Reload", this is just reloading your browser when something changes, the dev server will do "its magic" by its own.

I hope that the gif shows the proof that the configuration works

enter image description here

The full webpack config looks like:

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",
  },
};

For proper Hot Module Reload -- stands for just update pieces of your changed code without reloading the whole browser --

A few more adjustments are required, there are a lot of resources in the internet about this that I will try to list at the end, but since you are not using babel -- that works differently -- you need to provide a few more configurations in your webpack and also remove other properties.

Update ts-loader configuration

First check the ts-loader docs about HMR https://github.com/TypeStrong/ts-loader#hot-module-replacement and the implications.

They advice to add a configuration called transpileOnly, but if you check the specifications about that option you will see that you loose some type checking in the next compilation, for that reason they advice to install another package

It's advisable to use transpileOnly alongside the fork-ts-checker-webpack-plugin to get full type checking again. To see what this looks like in practice then either take a look at our example.

Your ts-loader rule should look like:

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

And your plugins:

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

Enabling Hot Module Reload

This is easier than it looks, you need to consider two key points:

  1. Let webpack know that you want HMR
  2. Let your code know that will be Hot reloaded -- sound like funny but yes --

The first point, is super easy, just add to your webpack configuration the following configuration:

devServer: {
    hot: true,
},

But for the second point, it is a little bit tricky, with a simple tweaks you can do it; The easiest way is via creating a new file per entry point, the file can be named as you wish, but in this example I will call it hot.ts, and will look like the code pasted bellow:

// 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");
  });
}

Side Note: You will find yourself with type errors with the module global variable, to solve that you need to install the following types npm i --save -D @types/node @types/webpack-env

And in your dev configuration mode -- usually the HRM is intended for development not for production -- you need to adjust the entry points:

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

Don't override the HMR watcher

I didn't find a clear answer from the documentation, but seems like your watchFiles inside of your devServer overrides the HMR watcher, if you remove that -- even from the build dev, it doesn't makes a difference in this configuration -- the HMR should work smoothly.

After following the previous steps your webpack file should look like the following code:

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",
  },
};

Repository with the working example: https://github.com/nicolasjuarezn/webpack-ts-example

Example of configuration working: enter image description here

Useful links:

I hope that this helps you!

Upvotes: 1

Related Questions