Reputation: 305
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
Reputation: 502
For webpack 5 use
devServer: {
watchFiles: ['src/**/*.php', 'public/**/*'],
},
See details here https://webpack.js.org/configuration/dev-server/#devserverwatchfiles
Upvotes: 0
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
Reputation: 196
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.
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
There are a few missing things to consider before implementing a "Hot reload"
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
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",
},
};
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.
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(),
],
This is easier than it looks, you need to consider two key points:
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",
},
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:
Useful links:
I hope that this helps you!
Upvotes: 1