webpack hot module replacement not working - javascript

Am trying to use HRM (Hot Module Replacement) in my webpack config, first I have set the --hot option within my package.jsong:
"scripts": {
"start": "webpack-dev-server --hot"
}
Also note that am using the HtmlWebpackPlugin in order to create an index.html file for me and put it in a "build" directory, here is my full webpack.config.js file:
const path = require('path');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const TerserJSPlugin = require('terser-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const WebpackManifestPlugin = require('webpack-manifest-plugin');
var HtmlWebpackPlugin = require('html-webpack-plugin');
// const webpack = require('webpack');
const mode = "development";
module.exports = {
mode: mode,
// watch: true,
devtool: "cheap-module-eval-source-map",
devServer: {
port: 9000,
// contentBase: path.resolve(__dirname, 'build')
},
entry: {
application: "./src/javascripts/index.js",
admin: "./src/javascripts/admin.js"
},
output: {
filename: mode === 'production' ? "[name]-[contenthash].js" : '[name].[hash].js',
path: path.resolve(__dirname, 'build'),
// publicPath: '/'
},
optimization: {
minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],
},
plugins: [
new MiniCssExtractPlugin({
filename: mode === 'production' ? "[name]-[contenthash].css" : "[name]-[hash].css",
hmr: mode === 'production' ? false : true
}),
new CleanWebpackPlugin(),
new WebpackManifestPlugin(),
new HtmlWebpackPlugin({
template: './src/template.html',
// filename: '../index.html'
})
// new webpack.HotModuleReplacementPlugin()
],
module: {
rules: [
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env']
}
}
},
{
test: /\.css$/i,
use: [
MiniCssExtractPlugin.loader,
{ loader: 'css-loader', options: { importLoaders: 1 } },
{
loader: 'postcss-loader',
options: {
plugins: [
require('autoprefixer')({
overrideBrowserslist: ['last 3 versions', 'ie > 9']
})
]
}
},
],
},
{
test: /\.scss$/i,
use: [
MiniCssExtractPlugin.loader,
{ loader: 'css-loader', options: { importLoaders: 1 } },
{
loader: 'postcss-loader',
options: {
plugins: [
require('autoprefixer')({
overrideBrowserslist: ['last 3 versions', 'ie > 9']
})
]
}
},
'sass-loader'
],
},
{
test: /\.(png|jpg|gif|svg)$/i,
use: [
{
loader: 'file-loader',
options: {
limit: 8192,
name: '[name].[hash:7].[ext]'
},
},
{
loader: 'image-webpack-loader'
}
],
}
]
}
}
As you see the entry I use mainly is ./src/javascripts/index.js where am importing a file called application.scss:
import application from "../stylesheets/application.scss"
the application.scss contains:
span{
background-color: blue;
}
So I want to hot reload the page whenever I change the background color of the span located in my html page.
when I run npm run start then I change the background color of the span the update works the first time (BUT IT DOES A FULL PAGE RELOAD) then if I try to change the background color again nothing get updated and all I see in the console is this:
[HMR] Nothing hot updated. log.js:24
[HMR] App is up to date.
Am not sure what am missing here, but someone can see what am doing wrong?

From the documentation,
it tries to update with HMR before trying to reload the whole page
So, presumably, if that fails, it will just reload the entire page, which is what you're seeing.
Have you tried including this at the end of your primary entry file?
if (module.hot) {
module.hot.accept(function (err) {
console.log('An error occurred while accepting new version');
});
}
Also, inspect the -hot.js file requests of network tab of Chrome's developer tools --- are they status 200, 404, 500? Maybe your server is crashing or not serving them correctly.
Ensure in your webpack that you also have
devServer: {
// contentBase: './dist', // this watches for html changes, apparently?
contentBase: path.resolve(__dirname, 'build') // in your specific case maybe?
hot: true,
},

Related

How to make webpack-dev-server pick up latest changes

I'm using Webpack 5 and cannot set dev-server to pick up the latest changes immediately. That's why when I have any changes in my project I should do yarn build first and then yarn dev every time. I've already seen the same question here Webpack-dev-server doesn't pick up latest changes, but writeToDisk: true shows an error in the console. I've attached my webpack.config.js file.
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const miniCss = require('mini-css-extract-plugin');
module.exports = {
entry: { main: './src/index.js' },
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'main.js',
publicPath: ''
},
mode: 'development',
devServer: {
static: {
directory: path.join(__dirname, "dist")
},
compress: true,
port: 8080,
devMiddleware: {
publicPath: "//localhost:8080",
},
hot: "only",
open: true
},
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader',
exclude: '/node_modules/'
},
{
test: /\.pug$/,
loader: 'pug-loader',
options: {
pretty: true
}
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, {
loader: 'css-loader'
}]
},
{
test: /\.(s*)css$/,
use: [
miniCss.loader,
'css-loader',
'sass-loader',
]
},
{
test: /\.(png|svg|jpg|gif|woff(2)?|eot|ttf|otf)$/,
type: 'asset/resource'
},
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/pages/page/page.pug',
filename: 'index.html'
}),
new MiniCssExtractPlugin(),
new miniCss(),
]
}
Your issue is most likely with hot: "only". According to https://webpack.js.org/configuration/dev-server/ you will not see a page refresh on build failures if it's not hot: true.
Other than that I don't see anything obviously wrong with your code. I suspect that if you change it to hot: true it should refresh the page and that you have some other build failure.

How do I access process.env.VAR in Webpack 5?

I've never used Webpack before and I'm working on a project that's just vanilla JS and HTML. I'm having an issue accessing the values I set in .env. Here's my config.
const path = require("path");
const dotenv = require('dotenv');
var webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const Dotenv = require('dotenv-webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = () => {
env = dotenv.config().parsed;
const envKeys = Object.keys(env).reduce((prev, next) => {
prev[`process.env.${next}`] = JSON.stringify(env[next]);
return prev;
}, {});
return {
entry: {
main: './src/index.js'
},
output: {
path: path.join(__dirname, '../build'),
filename: '[name].bundle.js'
},
mode: 'development',
devServer: {
contentBase: "./src/",
publicPath: "./src/",
compress: true,
port: 9000,
overlay: true,
disableHostCheck: true
},
devtool: 'inline-source-map',
resolve: {
alias: {
process: "process/browser"
}},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.(png|svg|jpe?g|gif)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'assets/'
}
}
]
},
{
test: /\.html$/,
use: {
loader: 'html-loader',
options: {
//attributes: ['img:src', ':data-src'],
minimize: true
}
}
}
]
},
plugins: [
new webpack.ProvidePlugin({
process: 'process/browser',
}),
new Dotenv(),
new webpack.DefinePlugin(envKeys),
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html'
}),
]
}
};
As you can see, I'm using dotEnv, definePlugin, and even the dotEnv-webpack plugin. Unfortunately, none of these solutions seem to allow me to access process.env.originURL.
originURL=https://localhost:3000
I'm not importing or requiring anything in my javascript file, index.js. I'm assuming this should work, but the console tells me that process is undefined.
console.log(process.env.originURL);
How can I access process.env.originURL in my index.js?
It should work. Maybe you could try this version:
new Dotenv({ systemvars: true })
If it still doesn't work, please show your package.json, as well as you .env file (randomize the values of course). Is .env at the root of you app?

Large console output produced by mini-css-extract-plugin

Been trying to research this but it doesn't seem as if any else has this, or see this as an issue.
I am using mini-css-extract-plugin(MiniCssExtractPlugin) in my webpack.config.js.
However, when I run webpack the console is littered with hundreds of instances of something similar to this...
Child mini-css-extract-plugin ../../../node_modules/css-loader/index.js??ref--6-1!../../../node_modules/postcss-loader/src/index.js!../../../node_modules/sass-loader/lib/loader.js!ui/radiolist-toggler/RadioListToggler.scss:
Entrypoint mini-css-extract-plugin = *
[../../../node_modules/css-loader/index.js?!../../../node_modules/postcss-loader/src/index.js!../../../node_modules/sass-loader/lib/loader.js!./ui/radiolist-toggler/RadioListToggler.scss] /Users/~~~/git/user-section/node_modules/css-loader??ref--6-1!/Users/~~~/git/user-section/node_modules/postcss-loader/src!/Users/~~/git/user-section/node_modules/sass-loader/lib/loader.js!./ui/radiolist-toggler/RadioListToggler.scss 5.33 KiB {mini-css-extract-plugin} [built]
+ 1 hidden module
I need to scroll up for a good few seconds to be able to see all my assets etc.
I am pretty new to webpack, so not exactly sure how to prevent this from being output to the console?
Below is my webpack.config.js
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCssAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const modernizr = require("modernizr");
const CopyWebpackPlugin = require('copy-webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin');
module.exports = {
context: path.resolve(__dirname, 'src/main/client'),
entry: './index',
devtool: 'cheap-module-source-map',
optimization: {
minimizer: [
new UglifyJsPlugin({
cache: true,
parallel: true,
uglifyOptions: {
mangle: true,
compress: true,
ecma: 6
},
sourceMap: true
}),
new OptimizeCssAssetsPlugin({}),
],
splitChunks: {
chunks: 'all'
}
},
plugins: [
new CompressionPlugin({
test: /\.js$|\.css$|\.html$|\.(png|svg|jpg|gif)$/,
cache: true,
filename: '[path].gz[query]',
algorithm: 'gzip',
threshold: 10240
}),
new CleanWebpackPlugin([
'./target/webapp'
]),
new HtmlWebpackPlugin({
template: './index.html',
filename: '../index.html',
xhtml: true
}),
new MiniCssExtractPlugin({
filename: "[name].css",
}),
new CopyWebpackPlugin([{
from: '../webapp/**/*',
to: '../'
}]),
new webpack.DefinePlugin({
'process.env.NODE_ENV': '"production"'
}),
],
output: {
publicPath: '/app/',
filename: '[name].bundle.js',
chunkFilename: '[id].js',
path: path.resolve(__dirname, 'target/webapp/app/')
},
module: {
rules: [{
loader: "webpack-modernizr-loader",
test: /\.modernizrrc\.js$/
},
{
test: /\.html$/,
exclude: /node_modules/,
use: {
loader: 'html-loader'
}
},
{
test: /\.s?css$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
importLoaders: 1
}
},
{
loader: 'postcss-loader'
},
{
loader: 'sass-loader'
}
]
},
{
test: /\.(png|svg|jpg|gif)$/,
use: [
'file-loader'
]
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: './assets/fonts/'
}
}]
},
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: ["#babel/env"]
}
}
],
},
resolve: {
alias: {
// You can add comment "Please do not delete this file" in this file
modernizr$: path.resolve(__dirname, "./.modernizrrc.js")
}
}
}
#mcclosa mentioned this as a comment but in case anyone should look at this question, see no answer and click away, the solution is to add the stats option to your webpack.config.js file as follows:
module.exports = {
stats: { children: false },
}
The above option uses the children: false option suggested by #mcclosa, which does successfully remove the junk output by mini-css-extract-plugin, but I find using the preset stats: "minimal" produces a much nicer overall output. Using:
module.exports = {
stats: "minimal",
}
..gives me the following tiny output whenever my build has no errors:
i 「wdm」: Compiling...
i 「wdm」: 69 modules
i 「wdm」: Compiled successfully.
..as opposed to dozens of lines of useless build data, but it will continue to give give error information when errors are present.
Unfortunately, mini-css-extract-loader does not have a setting to control the verbosity of its log output.
Setting stats.children to false or "minimal" in your webpack.config.js can remove a lot of other useful output like your bundle names and sizes, entry point information, time taken to build, legitimate warnings and errors from other plugins that you may want to keep, etc.
Instead it seems that we must add a plugin that executes on the compiler's done hook to remove items from the stats.compilation object associated with mini-css-extract-plugin.
This example plugin should work:
class CleanupMiniCssExtractPlugin {
apply(compiler) {
compiler.hooks.done.tap("CleanupMiniCssExtractPlugin", stats => {
if (this._children) {
const children = stats.compilation.children;
if (Array.isArray(children)) {
stats.compilation.children = children.filter(
child => child.name.indexOf("mini-css-extract-plugin") == -1
);
}
}
});
}
}
Or you can use this npm package: https://www.npmjs.com/package/cleanup-mini-css-extract-plugin

Webpack HMR not reloading HTML file

I have a simple HMR setup for reloading typescript files and postcss files. And they work perfectly well and modules reload without a page refresh. But when I change my HTML files, the website doesn't reload on it's own and the HTML content is not being hot-reloaded in.
This is my webpack config file:
const { resolve } = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
entry: resolve(__dirname, 'src/main.ts'),
output: {
path: resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
module: {
rules: [
{
test: /\.ts$/,
loader: 'awesome-typescript-loader',
exclude: /node_modules/,
},
{
test: /\.css$/,
exclude: /node_modules/,
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader',
options: {
importLoaders: 1,
},
},
{
loader: 'postcss-loader',
}
]
}
]
},
devtool: 'source-map',
mode: 'development',
plugins: [
new HtmlWebpackPlugin({
template: resolve(__dirname, './src/index.html'),
}),
new webpack.HotModuleReplacementPlugin(),
new CleanWebpackPlugin(resolve(__dirname, 'dist'))
],
devServer: {
contentBase: resolve(__dirname, 'dist'),
port: 9000,
hot: true,
open: true,
progress: true,
}
}
With webpack 5,this setting is work for me:
devServer: {
hot:true,
open:true,
watchFiles: ['src/**/*']
},
webpack-dev-server github issue #3881
Problem is that html-webpack-plugin doesn't react to change and doesn't trigger the hmr.
In order to achieve that you could try something like this:
const { resolve } = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
let devServer; // set below in devserver part
function reloadHtml() {
this.plugin('compilation',
thing => thing.plugin('html-webpack-plugin-after-emit', trigger));
const cache = {};
function trigger(data, callback) {
const orig = cache[data.outputName];
const html = data.html.source();
if (orig && orig !== html)
devServer.sockWrite(devServer.sockets, 'content-changed');
cache[data.outputName] = html;
callback();
}
}
module.exports = {
entry: resolve(__dirname, 'src/main.ts'),
output: {
path: resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
module: {
rules: [
{
test: /\.ts$/,
loader: 'awesome-typescript-loader',
exclude: /node_modules/,
},
{
test: /\.css$/,
exclude: /node_modules/,
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader',
options: {
importLoaders: 1,
},
},
{
loader: 'postcss-loader',
}
]
}
]
},
devtool: 'source-map',
mode: 'development',
plugins: [
reloadHtml,
new HtmlWebpackPlugin({
template: resolve(__dirname, './src/index.html'),
}),
new webpack.HotModuleReplacementPlugin(),
new CleanWebpackPlugin(resolve(__dirname, 'dist'))
],
devServer: {
before(app, server) {
devServer = server;
},
contentBase: resolve(__dirname, 'dist'),
port: 9000,
hot: true,
open: true,
progress: true,
}
}
If you get :
DeprecationWarning: Tapable.plugin is deprecated. Use new API on `.hooks` instead
You can change reloadHtml to this:
function reloadHtml() {
const cache = {}
const plugin = {name: 'CustomHtmlReloadPlugin'}
this.hooks.compilation.tap(plugin, compilation => {
compilation.hooks.htmlWebpackPluginAfterEmit.tap(plugin, data => {
const orig = cache[data.outputName]
const html = data.html.source()
if (orig && orig !== html) {
devServer.sockWrite(devServer.sockets, 'content-changed')
}
cache[data.outputName] = html
})
})
}
Not sure if this is the case, but if you only want to extend your current HMR config to do a browser reload when your outer html/view files changed, then you can do it with a few extra lines using chokidar.
Maybe you already have it, because webpack-dev-server is using chokidar internally, but if not found then install it first with npm or yarn:
npm install chokidar --save
yarn add -D chokidar
Then require it in your webpack config:
const chokidar = require('chokidar');
Then in your devServer config:
devServer: {
before(app, server) {
chokidar.watch([
'./src/views/**/*.html'
]).on('all', function() {
server.sockWrite(server.sockets, 'content-changed');
})
},
Also check the API for more options.
I'm using it with Webpack4. Don't know if it works with earlier versions...
Hope it helps you or others looking for this situation.

Updating webpack.dev.config from Webpack 1 to 4

For the open source site, ReForum, I'm trying to update the site to the latest components (ie: webpack 4, react 16, etc). I started with babel and it upgraded smoothly. Then moved to webpack. I spent more than 10 hours trying various configs until I finally got it to compile using the following:
/**
* module dependencies for webpack dev configuration
* Proper webpack dev setup:
* https://medium.freecodecamp.org/how-to-develop-react-js-apps-fast-using-webpack-4-3d772db957e4
*/
const path = require('path');
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const autoprefixer = require('autoprefixer');
// define paths
const nodeModulesPath = path.resolve(__dirname, '../node_modules');
const buildPath = path.resolve(__dirname, '../public', 'build');
const mainAppPath = path.resolve(__dirname, '../frontend', 'App', 'index.js');
const sharedStylesPath = path.resolve(__dirname, '../frontend', 'SharedStyles');
const componentsPath = path.resolve(__dirname, '../frontend', 'Components');
const containersPath = path.resolve(__dirname, '../frontend', 'Containers');
const viewsPath = path.resolve(__dirname, '../frontend', 'Views');
/**
* webpack development configuration
*/
module.exports = {
mode: 'development',
target: 'web',
devtool: 'inline-source-map',
entry: [
'webpack-hot-middleware/client',
mainAppPath,
],
output: {
filename: 'bundle.js',
path: buildPath,
publicPath: '/build/',
},
plugins: [
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: '[name].css',
chunkFilename: '[id].css',
}),
new webpack.HotModuleReplacementPlugin(),
],
module: {
rules: [
{
test: /\.js$/,
use: [ 'babel-loader' ],
exclude: [nodeModulesPath],
},
{
test: /\.(sa|sc|c)ss$/,
use: [
'style-loader',
'css-loader',
// 'postcss-loader',
{ loader: 'postcss-loader',
options: {
sourceMap: true,
plugins() {
return [autoprefixer('last 5 version')];
},
// plugins: () => [require('autoprefixer')({
// 'browsers': ['> 1%', 'last 5 versions'],
// })],
},
},
'sass-loader',
],
},
{ test: /\.(png|jpg)$/, use: ['url-loader?limit=8192'] },
{ test: /\.svg$/, use: ['url-loader?limit=10000&mimetype=image/svg+xml'] },
],
},
resolve : {
// automatically resolve file extensions (ie import File from '../path/file')
extensions: ['.js', '.css'],
// alias to call specified folders
alias: {
SharedStyles: sharedStylesPath,
Components: componentsPath,
Containers: containersPath,
Views: viewsPath,
},
},
};
Original Webpack 1 dev config
However, the React elements classname disappear, preventing the styles from being applied. It should be like:
But instead is:
Also, the head now have multiple <style>s .
Please help me get classnames to reappear and fix the multiple head style elements.
FYI, the only way I was able to get postcss-loader to run is by turning it into an object. It would fail with errors like "Error: No PostCSS Config found in ... "
Update 1:
Tried #noppa and #Alex Ilyaev suggestions the following but it didn't work.
{
test: /\.(sa|sc|c)ss$/,
use: [
'style-loader',
// 'css-loader',
{
loader: 'css-loader',
options: {
modules: true,
loaders: true,
importLoaders: 1,
localIndentName:'[name]__[local]___[hash:base64:5]',
},
},
// 'postcss-loader',
{ loader: 'postcss-loader',
options: {
sourceMap: 'inline',
options: {
ident: 'postcss',
plugins: (loader) => [
require('autoprefixer')(),
],
},
},
},
],
},
If you're using Webpack 4, start with no config file - that's right - no config file. WP4 includes sane defaults so most of your non webpack related issues will surface right there.
Regarding the multiple styles block, you need to switch to mini-css-extract-plugin (doesn't support HMR) or extract-css-chunks-webpack-plugin (supports HMR).
Also, note that during dev mode, you'll see multiple style blocks (for HMR). But production build should not have multiple style blocks.

Categories