FCM: Failed to register SERVICE WORKER in ReactJS with WEBPACK - reactjs

I am trying to implement push notifications through FCM in reactJS with WebPack v.5 and facing with this error:
An error occurred while retrieving token. FirebaseError: Messaging: We are unable to register the default service worker. Failed to register a ServiceWorker for scope ('http://localhost:4000/firebase-cloud-messaging-push-scope') with script ('http://localhost:4000/firebase-messaging-sw.js'): A bad HTTP response code (404) was received when fetching the script. (messaging/failed-service-worker-registration).
This is my firebase file Code:
importScripts(
"https://www.gstatic.com/firebasejs/9.0.0/firebase-app-compat.js"
);
importScripts(
"https://www.gstatic.com/firebasejs/9.0.0/firebase-messaging-compat.js"
);
// Initialize the Firebase app in the service worker by passing the generated config
const firebaseConfig = {
apiKey: "",
authDomain: "",
projectId: "",
storageBucket: "",
messagingSenderId: "",
appId: "",
measurementId: "",
};
firebase.initializeApp(firebaseConfig);
// Retrieve firebase messaging
const messaging = firebase.messaging();
messaging.onBackgroundMessage(function (payload) {
console.log("Received background message ", payload);
const notificationTitle = payload.notification.title;
const notificationOptions = {
body: payload.notification.body,
};
self.registration.showNotification(notificationTitle, notificationOptions);
});
if ("serviceWorker" in navigator) {
navigator.serviceWorker
.register("../firebase-messaging-sw.js")
.then(function (registration) {
console.log("Registration successful, scope is:", registration.scope);
})
.catch(function (err) {
console.log("Service worker registration failed, error:", err);
});
}
Here is my WebPack file code:
const path = require("path");
const ReactRefreshWebpackPlugin = require("#pmmmwh/react-refresh-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const FaviconsWebpackPlugin = require("favicons-webpack-plugin");
// const ServiceWorkerWebpackPlugin = require("serviceworker-webpack-plugin");
// const runtime = require("serviceworker-webpack-plugin/lib/runtime");
const { IgnorePlugin } = require("webpack");
let mode = "development";
let target = "web";
const plugins = [
new CleanWebpackPlugin(),
new MiniCssExtractPlugin(),
new HtmlWebpackPlugin({
template: "./public/index.html",
}),
new FaviconsWebpackPlugin({
logo: "./public/favicon.ico", // svg works too!
}),
new IgnorePlugin({
resourceRegExp: /^#progress$/,
}),
// new ServiceWorkerWebpackPlugin({
// entry: path.join(__dirname, "./public/firebase-messaging-sw.js"),
// }),
];
// if ("serviceWorker" in navigator) {
// const registration = runtime.register();
// }
if (process.env.NODE_ENV === "production") {
mode = "production";
// Temporary workaround for 'browserslist' bug that is being patched in the near future
target = "browserslist";
}
if (process.env.SERVE) {
// We only want React Hot Reloading in serve mode
plugins.push(new ReactRefreshWebpackPlugin());
}
module.exports = {
// mode defaults to 'production' if not set
mode: mode,
// This is unnecessary in Webpack 5, because it's the default.
// However, react-refresh-webpack-plugin can't find the entry without it.
entry: "./src/index.js",
output: {
// output path is required for `clean-webpack-plugin`
path: path.resolve(__dirname, "dist"),
// this places all images processed in an image folder
assetModuleFilename: "images/[hash][ext][query]",
publicPath: "/",
},
module: {
rules: [
{
test: /\.(s[ac]|c)ss$/i,
use: [
{
loader: MiniCssExtractPlugin.loader,
// This is required for asset imports in CSS, such as url()
options: { publicPath: "" },
},
"css-loader",
"postcss-loader",
// according to the docs, sass-loader should be at the bottom, which
// loads it first to avoid prefixes in your sourcemaps and other issues.
"sass-loader",
],
},
{
test: /\.(png|jpe?g|gif|svg)$/i,
/**
* The `type` setting replaces the need for "url-loader"
* and "file-loader" in Webpack 5.
*
* setting `type` to "asset" will automatically pick between
* outputing images to a file, or inlining them in the bundle as base64
* with a default max inline size of 8kb
*/
type: "asset",
/**
* If you want to inline larger images, you can set
* a custom `maxSize` for inline like so:
*/
// parser: {
// dataUrlCondition: {
// maxSize: 30 * 1024,
// },
// },
},
{
test: /\.(woff|woff2|ttf|otf|eot)$/,
use: [
{
loader: "file-loader",
options: { name: "[path][name]-[hash:8].[ext]" },
},
],
},
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: {
// without additional settings, this will reference .babelrc
loader: "babel-loader",
options: {
/**
* From the docs: When set, the given directory will be used
* to cache the results of the loader. Future webpack builds
* will attempt to read from the cache to avoid needing to run
* the potentially expensive Babel recompilation process on each run.
*/
cacheDirectory: true,
},
},
},
],
},
plugins: plugins,
target: target,
devtool: "source-map",
resolve: {
extensions: [".js", ".jsx"],
},
// required if using webpack-dev-server
devServer: {
contentBase: "./dist",
historyApiFallback: true,
hot: true,
port: 4000,
},
};

Related

deploying officejs fabric react word add in

I have a word add in written in typescript using Officejs and office-ui-fabric-react. Everything works fine with the server running locally. I'd like to deploy into AWS S3 with Cloudfront. I'm building with npm run build, and npm run deploy (using deploy command "aws --profile profile-name s3 sync dist/ s3://bucketname". Static web site hosting is enabled in the S3 bucket. All files from the dist directory are seen in the bucket. After inserting the add in into Word with a manifest.xml file that points to the cloudfront endpoint I'm getting the error "Uncaught ReferenceError: React is not defined". The same error occurs when I point directly to the S3 static web endpoint. To see if I've missed anything I deployed a generic create-react-app using the steps above and it runs fine. I'm assuming that the problem lies with my webpack config so I've included that here (common, and prod). I'd be happy to include anything else that's needed. I'm also open to other deployment options if using AWS is causing the problem.
webpack.common.js:
const webpack = require('webpack');
const path = require('path');
const package = require('../package.json');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const autoprefixer = require('autoprefixer');
const build = (() => {
const timestamp = new Date().getTime();
return {
name: package.name,
version: package.version,
timestamp: timestamp,
author: package.author
};
})();
const entry = {
vendor: [
'react',
'react-dom',
'core-js',
'office-ui-fabric-react'
],
app: [
'react-hot-loader/patch',
'./index.tsx',
],
'function-file': '../function-file/function-file.ts'
};
const rules = [
{
test: /\.tsx?$/,
use: [
'react-hot-loader/webpack',
'ts-loader'
],
exclude: /node_modules/
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader']
},
{
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
use: {
loader: 'file-loader',
query: {
name: 'assets/[name].[ext]'
}
}
}
];
const output = {
path: path.resolve('dist'),
publicPath: '/',
filename: '[name].[hash].js',
chunkFilename: '[id].[hash].chunk.js'
};
const WEBPACK_PLUGINS = [
new webpack.NamedModulesPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
new webpack.BannerPlugin({ banner: `${build.name} v.${build.version} (${build.timestamp}) © ${build.author}` }),
new webpack.DefinePlugin({
ENVIRONMENT: JSON.stringify({
build: build
})
}),
new webpack.LoaderOptionsPlugin({
options: {
postcss: [
autoprefixer({ browsers: ['Safari >= 8', 'last 2 versions'] }),
],
htmlLoader: {
minimize: true
}
}
})
];
module.exports = {
context: path.resolve('./src'),
entry,
output,
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx', '.scss', '.css', '.html']
},
module: {
rules,
},
optimization: {
splitChunks: {
chunks: 'async',
minChunks: Infinity,
name: 'vendor'
}
},
plugins: [
...WEBPACK_PLUGINS,
new ExtractTextPlugin('[name].[hash].css'),
new HtmlWebpackPlugin({
title: 'letterConfig',
filename: 'index.html',
template: './index.html',
chunks: ['app', 'vendor', 'polyfills']
}),
new HtmlWebpackPlugin({
title: 'letterConfig',
filename: 'function-file/function-file.html',
template: '../function-file/function-file.html',
chunks: ['function-file']
}),
new CopyWebpackPlugin([
{
from: '../assets',
ignore: ['*.scss'],
to: 'assets',
}
])
]
};
webpack.prod.js:
const webpack = require('webpack');
const webpackMerge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');
const ENV = process.env.NODE_ENV = process.env.ENV = 'development';
module.exports = webpackMerge(commonConfig, {
devtool: 'source-map',
externals: {
'react': 'React',
'react-dom': 'ReactDOM'
},
performance: {
hints: "warning"
},
optimization: {
minimize: true
}
});
index.tsx:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { AppContainer } from 'react-hot-loader';
import { initializeIcons } from 'office-ui-fabric-react/lib/Icons';
import App from './components/App';
import './styles.less';
import 'office-ui-fabric-react/dist/css/fabric.min.css';
initializeIcons();
let isOfficeInitialized = false;
const title = 'letterConfig';
const render = (Component) => {
ReactDOM.render(
<AppContainer>
<Component title={title} isOfficeInitialized={isOfficeInitialized} />
</AppContainer>,
document.getElementById('container')
);
};
/* Render application after Office initializes */
Office.initialize = () => {
console.log('init');
isOfficeInitialized = true;
render(App);
};
/* Initial render showing a progress bar */
render(App);
if ((module as any).hot) {
(module as any).hot.accept('./components/App', () => {
const NextApp = require('./components/App').default;
render(NextApp);
});
}
It turned out that it was looking for a globally defined "React", and so not resolving via an npm module. In the webpack.prod.js file removing the following solved the problem:
externals: {
'react': 'React',
'react-dom': 'ReactDOM'
},

express-typescript-react: 404 (not found) frontend bundle file

I am making a full stack application, with Express(written in Typescript) and React. I am using webpack to bundle both backend and frontend.
I have two separate configs for webpack. One for frontend and the other one for backend.
Frontend config (webpack-fe.config.js)
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpack = require('clean-webpack-plugin');
const FRONTENDSRC = path.resolve(__dirname, 'frontend/src');
module.exports = {
target: 'web',
// #babel/polyfill is needed to use modern js functionalities in old browsers.
entry: ['#babel/polyfill', path.resolve(FRONTENDSRC, 'index.js')],
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle-fe.js'
},
module: {
rules: [
{
test: /\.jsx$|.js$/,
use: {
loader: 'babel-loader',
options: {
// To process async functions.
plugins: ['#babel/plugin-transform-async-to-generator']
}
},
exclude: /(node_modules|bower_components)/
},
{
test: /\.scss$|.sass/,
loaders: ['style-loader', 'css-loader', 'sass-loader']
}
]
},
resolve: {
modules: ['node_modules', FRONTENDSRC],
extensions: ['.js', '.jsx', '.ts', '.tsx', '.css', '.scss']
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(FRONTENDSRC, 'index.html')
}),
new CleanWebpack(['./dist/bundle-be.js', './dist/index.html'])
],
watch: true,
mode: 'development',
devServer: {
contentBase: path.resolve(__dirname, 'dist'),
compress: true,
port: 9000
}
};
Backend config (webpack-be.config.js)
const path = require('path');
const CleanWebpack = require('clean-webpack-plugin');
const nodeExternals = require('webpack-node-externals');
const projectDir = 'string value used to define path';
module.exports = {
context: projectDir,
target: 'node',
// #babel/polyfill is needed to use modern js functionalities in old browsers.
entry: [
'#babel/polyfill',
path.join(projectDir, 'backend', 'src', 'index.ts')
],
output: {
path: path.join(projectDir, 'dist'),
filename: 'bundle-be.js',
publicPath: path.join(projectDir, 'dist')
},
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
// To process async functions.
plugins: ['#babel/plugin-transform-async-to-generator']
}
},
exclude: /(node_modules|bower_components)/
},
{
test: /.ts$/,
loaders: ['ts-loader'],
exclude: /(node_modules|bower_components)/
}
]
},
resolve: {
modules: ['node_modules', path.join(projectDir, 'backend', 'src')],
extensions: ['.js', 'web.js', 'webpack.js', '.ts', '.tsx']
},
plugins: [new CleanWebpack([path.join(projectDir, 'dist', 'bundle-be.js')])],
watch: true,
externals: [nodeExternals()],
mode: 'development',
devtool: 'inline-source-map'
};
webpack.config.js
const feConfig = require('./webpack-fe.config');
const beConfig = require('./webpack-be.config');
module.exports = [feConfig, beConfig];
Here is the code for Server Initialization (index.ts)
import http from 'http';
import debug from 'debug';
import webpack from 'webpack';
import webpackDevMiddleware from 'webpack-dev-middleware';
import App from './server';
const config = require('../../webpack-be.config.js');
const compiler = webpack(config);
debug('ts-express:server');
class InitServer {
private port: number | boolean | string;
private server: any;
constructor() {
this.port = this.normalizePort(process.env.port || 7000);
App.set('port', this.port);
App.use(
webpackDevMiddleware(compiler, {
publicPath: config.output.publicPath
})
);
this.server = http.createServer(App);
this.server.listen(this.port);
this.server.on('error', this.onError);
this.server.on('listening', this.onListening);
}
private normalizePort = (val: number | string): number | string | boolean => {
let port: number = typeof val === 'string' ? parseInt(val, 10) : val;
if (isNaN(port)) return val;
else if (port >= 0) return port;
else return false;
};
private onError = (error: NodeJS.ErrnoException): void => {
if (error.syscall !== 'listen') throw error;
let bind =
typeof this.port === 'string' ? 'Pipe ' + this.port : 'Port ' + this.port;
switch (error.code) {
case 'EACCES':
console.error(`${bind} requires elevated privileges`);
process.exit(1);
break;
case 'EADDRINUSE':
console.error(`${bind} is already in use`);
process.exit(1);
break;
default:
throw error;
}
};
private onListening = (): void => {
console.log(`listening on ${this.port}`);
let addr = this.server.address();
let bind = typeof addr === 'string' ? `pipe ${addr}` : `port ${addr.port}`;
debug(`Listening on ${bind}`);
};
}
new InitServer();
Here is the server config file(server.ts)
import express from 'express';
import bodyParser from 'body-parser';
import path from 'path';
import { projectDir } from './shared/constants';
class App {
public express: express.Application;
constructor() {
this.express = express();
this.middleware();
this.routes();
}
private middleware(): void {
this.express.use(function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', '*');
next();
});
this.express.use(bodyParser.json());
this.express.use(bodyParser.urlencoded({ extended: false }));
}
private routes(): void {
this.express.get('/', function(req, res) {
res.sendFile(path.join(projectDir, 'dist', 'index.html'));
});
}
}
export default new App().express;
I use the following commands in the npm scripts:
"bundle": "webpack",
"serve": "node dist/bundle-be.js"
When I start the server, it serves my index.html file from the dist folder, but it gives me a 404 error for bundle-fe.js. I have checked that bundle-fe.js is generated in the dist folder. So why does it give me a 404 for bundle-fe.js ?
Thanks in advance!
Found it, I had to change the config file that I was using in index.ts file.
const config = require('../../webpack-fe.config.js'); // instead of webpack-be.config.js
Lol ^^ !!

How do you configure Webpack to clear the React Warning for production minification?

I am sure everyone has seen this error but here it is again:
Warning: It looks like you're using a minified copy of the development build of React. When deploying React apps to production, make sure to use the production build which skips development warnings and is faster. See https://facebook.github.io/react/docs/optimizing-performance.html#use-the-production-build for more details.
So of course I followed the instructions in the link provided, yet though I have made all the necessary updates to my code, I am still getting this error.
According to some other answers I have seen on StackOverflow and Github, the process.env.NODE_ENV being set to production through the Webpack plugin DefinePlugin tells React to build using the minified version. So I logged process.env.NODE_ENV in my main application component and it is in fact being set to production by the plugin and still I am getting the warning.
So even though the environment variable is being set to production, I am getting the warning and my React Dev Tools says:
This page is using the development build of React. 🚧
Note that the development build is not suitable for production.
Make sure to use the production build before deployment.
I cannot seem to isolate where the problem might be here since I have done all the necessary changes to get the production build to work.
Here is my webpack.config.js file:
const webpack = require('webpack');
const resolve = require('path').resolve;
const SRC_BASE = resolve(__dirname, 'src');
const merge = require('webpack-merge');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const argv = require('yargs').argv;
const HtmlWebpackPlugin = require('html-webpack-plugin');
const definePlugin = new webpack.DefinePlugin({
__DEV__: JSON.stringify(JSON.parse(process.env.BUILD_DEV || 'true')),
__PRERELEASE__: JSON.stringify(JSON.parse(process.env.BUILD_PRERELEASE || 'false')),
__PRODUCTION__: JSON.stringify(JSON.parse(process.env.NODE_ENV === 'production' || 'false')),
'process.env': {
NODE_ENV: process.env.NODE_ENV === 'production' ? // set NODE_ENV to production or development
JSON.stringify('production') : JSON.stringify('development'),
},
});
const loaderOptionsPlugin = new webpack.LoaderOptionsPlugin({ options: { context: __dirname } });
const commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js');
const cssOutput = new ExtractTextPlugin({ filename: 'style.css', allChunks: true });
const sourceMapPlugin = new webpack.SourceMapDevToolPlugin({ filename: '[name].map' });
const htmlPlugin = new HtmlWebpackPlugin({
template: `${SRC_BASE}/index.template.ejs`,
filename: '../index.html', // relative to public/build/ so this is public/index.html
inject: true,
hash: true,
});
let config = {
cache: true,
entry: {
main: ['babel-polyfill', resolve(SRC_BASE, 'index')],
},
output: {
path: resolve(__dirname, 'public/build'),
filename: '[name].bundle.js',
publicPath: '/build/',
sourceMapFilename: '[name].map',
},
resolve: {
modules: [
SRC_BASE,
'node_modules',
],
extensions: ['.js', '.jsx'],
},
module: {
rules: [
{
test: [/\.jsx$/, /\.js$/],
loader: 'babel-loader',
exclude: /(local_modules|node_modules|bower_components)/,
query: {
presets: [
'react',
'es2015',
'stage-1',
],
},
},
{
test: /\.(eot|woff|woff2|ttf|svg|png|jpe?g|gif)(\?\S*)?$/,
loader: 'file-loader',
},
{
test: /\.scss$/,
loader: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: 'css-loader!sass-loader',
}),
},
],
},
node: {
fs: 'empty',
},
plugins: [
definePlugin,
commonsPlugin,
cssOutput,
htmlPlugin,
loaderOptionsPlugin,
sourceMapPlugin,
],
};
// Only load dashboard if we're watching the code
if (argv.watch) {
const DashboardPlugin = require('webpack-dashboard/plugin');
config = merge(config, { plugins: [new DashboardPlugin()] });
}
if (process.env.NODE_ENV === 'production') {
console.log('******* I AM MERGING PRODUCTION CONFIGS ******');
console.log(`process.env.NODE_ENV = ${process.env.NODE_ENV}`);
config = merge(config, {
devtool: 'cheap-module-source-map',
plugins: [
new webpack.LoaderOptionsPlugin({
minimize: true,
debug: false,
}),
new webpack.optimize.UglifyJsPlugin(),
],
module: {
rules: [
{ test: /redux-logger/, loader: 'null-loader' },
],
},
});
}
module.exports = config;
And here is my gulpfile.js tasks that runs gulp build --production:
/* Production Builds use this task */
gulp.task('webpack', (done) => {
if (argv.production) {
process.env.BUILD_DEV = false;
process.env.NODE_ENV = 'production';
}
const buildConfig = require('./webpack.config');
const compiler = webpack(buildConfig);
const tag = '[webpack]';
const info = gutil.colors.green;
const error = gutil.colors.red;
const warning = gutil.colors.yellow;
const filterStackTraces = err =>
err.toString().split(/[\r\n]+/).filter(line => ! line.match(/^\s+at Parser/)).join(EOL);
if (argv.watch) {
compiler.watch({}, (err, stats) => {
const statDetails = stats.toJson();
if (err) {
gutil.log(error(tag), err.toString({ colors: true }));
}
else if (stats.hasErrors()) {
statDetails.errors.forEach(ex => gutil.log(error(tag), filterStackTraces(ex)));
}
else if (stats.hasWarnings()) {
statDetails.warnings.forEach(wx => gutil.log(warning(tag), filterStackTraces(wx)));
}
else {
statDetails.chunks.forEach(chunk => {
if (chunk.entry) gutil.log(info(tag), `Built ${chunk.files[0]} (${chunk.size} bytes)`);
});
gutil.log(info(tag), 'Build complete');
}
});
}
else {
compiler.run((err, stats) => {
if (err) {
return done(new gutil.PluginError('webpack', err));
}
if (stats.hasErrors()) {
const statDetails = stats.toJson();
statDetails.errors.forEach(ex => gutil.log(error(tag), filterStackTraces(ex)));
return done(new gutil.PluginError('webpack', 'Parse/ build error(s)'));
}
gutil.log(gutil.colors.green(tag), stats.toString({ colors: true }));
done();
});
}
});
gulp.task('build', ['webpack']);
After stumbling around the interweb for some different ways to configure webpack to overcome this problem I found a configuration option in the UglifyJsPlugin that cleared the error.
plugins: [
new webpack.optimize.UglifyJsPlugin({
include: /\.min\.js$/, <------This option fixed it
})
]
All though this cleared the warning in the console, I am still seeing my React Dev Tools saying that it is not using the production version.
I've had the same problem, but it doesn't seem to be a problem with webpack- it's likely something to do with Gulp. Have you tried running webpack with the config directly from the command line (i.e. webpack --config webpack-build.config.js or whatever) ? That produced a build without the warning quite happily. Here's my webpack config, cobbled together from various sources:
const path = require('path')
const webpack = require('webpack')
module.exports = {
devtool: 'cheap-module-source-map',
entry: './src/scripts/app',
output: {
path: path.join(__dirname, 'dist/scripts'),
filename: 'bundle.js',
publicPath: '/static/'
},
module: {
loaders: [
{
test: /\.js$/,
loader: 'babel-loader',
query: {
presets: ['env', 'react']
}
}
]
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('production')
}
}),
new webpack.LoaderOptionsPlugin({
minimize: true,
debug: false
}),
new webpack.optimize.UglifyJsPlugin({
beautify: false,
mangle: {
screw_ie8: true,
keep_fnames: true
},
compress: {
screw_ie8: true
},
comments: false
})
]
}
If I track down what's going on with gulp + webpack I'll write an update.

Build performance for webpack 2

I need some help in improving my build times. I upgraded my webpack config file to version 2 but the build times are worse compared to before. It takes atleast 3min to complete the build.
Here's my webpack file
/**
* React Starter Kit (https://www.reactstarterkit.com/)
*
* Copyright © 2014-2016 Kriasoft, LLC. All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE.txt file in the root directory of this source tree.
*/
require('dotenv').config();
import path from 'path';
import webpack from 'webpack';
import extend from 'extend';
import AssetsPlugin from 'assets-webpack-plugin';
import ExtractTextPlugin from 'extract-text-webpack-plugin';
import bundles from '../src/bundles'
import merge from 'lodash/merge'
import fs from 'fs'
const DEBUG = !process.argv.includes('--release');
const VERBOSE = process.argv.includes('--verbose');
const GLOBALS = {
'process.env.NODE_ENV': DEBUG ? '"development"' : '"production"',
'process.env.MORTGAGE_CALCULATOR_API': process.env.MORTGAGE_CALCULATOR_API ? `"${process.env.MORTGAGE_CALCULATOR_API}"` : null,
'process.env.API_HOST': process.env.BROWSER_API_HOST ? `"${process.env.BROWSER_API_HOST}"` : process.env.API_HOST ? `"${process.env.API_HOST}"` : null,
'process.env.GOOGLE_ANALYTICS_ID': process.env.GOOGLE_ANALYTICS_ID ? `"${process.env.GOOGLE_ANALYTICS_ID}"` : null,
'process.env.OMNITURE_SUITE_ID': process.env.OMNITURE_SUITE_ID ? `"${process.env.OMNITURE_SUITE_ID}"` : null,
'process.env.COOKIE_DOMAIN': process.env.COOKIE_DOMAIN ? `"${process.env.COOKIE_DOMAIN}"` : null,
'process.env.FEATURE_FLAG_BAZAAR_VOICE': process.env.FEATURE_FLAG_BAZAAR_VOICE ? `"${process.env.FEATURE_FLAG_BAZAAR_VOICE}"` : null,
'process.env.FEATURE_FLAG_REALTIME_RATING': process.env.FEATURE_FLAG_REALTIME_RATING ? `"${process.env.FEATURE_FLAG_REALTIME_RATING}"` : null,
'process.env.SALE_RESULTS_PAGE_FLAG': process.env.SALE_RESULTS_PAGE_FLAG ? `"${process.env.SALE_RESULTS_PAGE_FLAG}"` : null,
'process.env.SALE_RELOADED_RESULTS_PAGE_FLAG': process.env.SALE_RELOADED_RESULTS_PAGE_FLAG ? `"${process.env.SALE_RELOADED_RESULTS_PAGE_FLAG}"` : null,
'process.env.TRACKER_DOMAIN': process.env.TRACKER_DOMAIN ? `"${process.env.TRACKER_DOMAIN}"` : null,
'process.env.USER_SERVICE_ENDPOINT': process.env.USER_SERVICE_ENDPOINT ? `"${process.env.USER_SERVICE_ENDPOINT}"` : null,
__DEV__: DEBUG
};
//
// Common configuration chunk to be used for both
// client-side (client.js) and server-side (server.js) bundles
// -----------------------------------------------------------------------------
const config = {
output: {
publicPath: '/blaze-assets/',
sourcePrefix: ' ',
},
cache: DEBUG,
stats: {
colors: true,
reasons: DEBUG,
hash: VERBOSE,
version: VERBOSE,
timings: true,
chunks: VERBOSE,
chunkModules: VERBOSE,
cached: VERBOSE,
cachedAssets: VERBOSE,
},
plugins: [
new ExtractTextPlugin({
filename: DEBUG ? '[name].css' : '[name].[chunkhash].css',
allChunks: true,
}),
new webpack.LoaderOptionsPlugin({
debug: DEBUG,
})
],
resolve: {
extensions: ['.webpack.js', '.web.js', '.js', '.jsx', '.json'],
modules: [
'./src',
'node_modules',
]
},
module: {
rules: [
{
test: /\.jsx?$/,
include: [
path.resolve(__dirname, '../src'),
],
loader: 'babel-loader',
}, {
test: /\.(scss|css)$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
'css-loader',
'sass-loader',
]
})
}, {
test: /\.txt$/,
loader: 'raw-loader',
}, {
test: /\.(otf|png|jpg|jpeg|gif|svg|woff|woff2)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: 'url-loader?limit=10000',
}, {
test: /\.(eot|ttf|wav|mp3)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: 'file-loader',
}, {
test: /\.jade$/,
loader: 'jade-loader',
}
],
},
};
//
// Configuration for the client-side bundles
// -----------------------------------------------------------------------------
let clientBundles = {}
Object.keys(bundles).forEach(function (bundle) {
clientBundles[bundle] = [
'bootstrap-loader',
`./src/bundles/${bundle}/index.js`
]
})
merge(
clientBundles,
{
'embedWidget': ['./src/components/Widgets/EmbedWidget/widgetLoader.js']
}
)
const clientConfig = extend(true, {}, config, {
entry: clientBundles,
output: {
path: path.join(__dirname, '../build/public/blaze-assets/'),
filename: DEBUG ? '[name].js' : '[name].[chunkhash].js',
chunkFilename: DEBUG ? '[name].chunk.js' : '[name].[chunkhash:8].chunk.js',
},
node: {
fs: "empty"
},
// Choose a developer tool to enhance debugging
// http://webpack.github.io/docs/configuration.html#devtool
devtool: DEBUG ? 'cheap-module-source-map' : false,
plugins: [
...config.plugins,
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
new webpack.DefinePlugin({
...GLOBALS,
'process.env.BROWSER': true
}),
...(!DEBUG ? [
new webpack.optimize.UglifyJsPlugin({
compress: {
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
screw_ie8: true,
// jscs:enable requireCamelCaseOrUpperCaseIdentifiers
warnings: VERBOSE,
},
}),
new webpack.optimize.AggressiveMergingPlugin(),
] : []),
new AssetsPlugin({
path: path.join(__dirname, '../build'),
filename: 'assets.js',
processOutput: x => `module.exports = ${JSON.stringify(x)};`,
}),
],
});
//
// Configuration for the server-side bundle (server.js)
// -----------------------------------------------------------------------------
var srcDirs = {};
fs.readdirSync('src').forEach(function(path) {
srcDirs[path] = true
});
function isExternalFile(context, request, callback) {
var isExternal = request.match(/^[#a-z][a-z\/\.\-0-9]*$/i) && !srcDirs[request.split("/")[0]]
callback(null, Boolean(isExternal));
}
const serverConfig = extend(true, {}, config, {
entry: './src/server.js',
output: {
path: path.join(__dirname, '../build/public/blaze-assets/'),
filename: '../../server.js',
libraryTarget: 'commonjs2',
},
target: 'node',
externals: [
/^\.\/assets$/,
isExternalFile
],
node: {
console: false,
global: false,
process: false,
Buffer: false,
__filename: false,
__dirname: false,
},
devtool: DEBUG ? 'cheap-module-source-map' : 'source-map',
plugins: [
...config.plugins,
new webpack.DefinePlugin({
...GLOBALS,
'process.env.BROWSER': false,
'process.env.API_HOST': process.env.API_HOST ? `"${process.env.API_HOST}"` : null
}),
new webpack.NormalModuleReplacementPlugin(/\.(scss|css|eot|ttf|woff|woff2)$/, 'node-noop'),
new webpack.BannerPlugin({
banner: `require('dotenv').config(); require('newrelic'); require('source-map-support').install();`,
raw: true,
entryOnly: false
})
],
});
export default [clientConfig, serverConfig];
here's my start.js file
/**
* React Starter Kit (https://www.reactstarterkit.com/)
*
* Copyright © 2014-2016 Kriasoft, LLC. All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE.txt file in the root directory of this source tree.
*/
import Browsersync from 'browser-sync'
import webpack from 'webpack'
import webpackMiddleware from 'webpack-middleware'
import webpackHotMiddleware from 'webpack-hot-middleware'
import run from './run'
import runServer from './runServer'
import webpackConfig from './webpack.config'
import clean from './clean'
import copy from './copy'
const DEBUG = !process.argv.includes('--release')
/**
* Launches a development web server with "live reload" functionality -
* synchronizing URLs, interactions and code changes across multiple devices.
*/
async function start () {
await run(clean)
await run(copy.bind(undefined, { watch: true }))
await new Promise((resolve) => {
// Patch the client-side bundle configurations
// to enable Hot Module Replacement (HMR) and React Transform
webpackConfig.filter((x) => x.target !== 'node').forEach((config) => {
/* eslint-disable no-param-reassign */
Object.keys(config.entry).forEach((entryKey) => {
if (!Array.isArray(config.entry[entryKey])) {
config.entry[entryKey] = [config.entry[entryKey]]
}
config.entry[entryKey].unshift('webpack-hot-middleware/client')
})
if (config.output.filename) {
config.output.filename = config.output.filename.replace('[chunkhash]', '[hash]')
}
if (config.output.chunkFilename) {
config.output.chunkFilename = config.output.chunkFilename.replace('[chunkhash]', '[hash]')
}
config.plugins.push(new webpack.HotModuleReplacementPlugin())
config.plugins.push(new webpack.NoEmitOnErrorsPlugin())
config
.module
.rules
.filter((x) => x.loader === 'babel-loader')
.forEach((x) => (x.query = {
...x.query,
// Wraps all React components into arbitrary transforms
// https://github.com/gaearon/babel-plugin-react-transform
plugins: [
...(x.query ? x.query.plugins : []),
['react-transform', {
transforms: [
{
transform: 'react-transform-hmr',
imports: ['react'],
locals: ['module'],
}, {
transform: 'react-transform-catch-errors',
imports: ['react', 'redbox-react'],
},
],
},
],
],
}))
/* eslint-enable no-param-reassign */
})
const bundler = webpack(webpackConfig)
const wpMiddleware = webpackMiddleware(bundler, {
// IMPORTANT: webpack middleware can't access config,
// so we should provide publicPath by ourselves
publicPath: webpackConfig[0].output.publicPath,
// Pretty colored output
stats: webpackConfig[0].stats,
// For other settings see
// https://webpack.github.io/docs/webpack-dev-middleware
})
const hotMiddlewares = bundler
.compilers
.filter((compiler) => compiler.options.target !== 'node')
.map((compiler) => webpackHotMiddleware(compiler))
let handleServerBundleComplete = () => {
runServer((err, host) => {
if (!err) {
const bs = Browsersync.create()
bs.init({
...(DEBUG ? {} : { notify: false, ui: false }),
proxy: {
target: host,
middleware: [wpMiddleware, ...hotMiddlewares],
},
open: false,
// no need to watch '*.js' here, webpack will take care of it for us,
// including full page reloads if HMR won't work
files: ['build/content/**/*.*'],
}, resolve)
handleServerBundleComplete = runServer
}
})
}
bundler.plugin('done', () => handleServerBundleComplete())
})
}
export default start
I am using react-starter-kit but it is slightly modified version of the original. I have tried degrading css-loader to 0.14.5 to see improvements but no difference. Also, removed sourceMap from UglifyJSPlugin. Any help is appreaciated!

Webpack hot module replacement lists updated React components in console but does not update them

After changing some text in my About component and saving the file, I can see the following logs in my console:
client.js?6a8d:123 [HMR] bundle rebuilding
client.js?6a8d:126 [HMR] bundle rebuilt in 3786ms
process-update.js:27 [HMR] Checking for updates on the server...
bootstrap 5e8b103…:45 XHR finished loading: GET "http://localhost:3001/dist/5e8b1032c40f91ebd6ce.hot-update.json".hotDownloadManifest # bootstrap 5e8b103…:45hotCheck # bootstrap 5e8b103…:264check # process-update.js:64module.exports # process-update.js:28processMessage # client.js?6a8d:139handleMessage # client.js?6a8d:65
index.js:81 [React Transform HMR] Patching Marketing
process-update.js:100 [HMR] Updated modules:
process-update.js:102 [HMR] - ./src/containers/Marketing/About.js
process-update.js:102 [HMR] - ./src/containers/Marketing/index.js
process-update.js:107 [HMR] App is up to date.
However, the DOM is not updated and the only way to get the updated text is to do a full browser refresh.
Any ideas on how to solve this? I am fairly sure this was setup correctly in the past and I'm not sure what I did which made it stop working. I can only recall adding code splitting in a few places recently but the about component is part of the main build.
Here's the content of ...-hot-update.json:
{"h":"c6abfe651b7516cb5169","c":[0]}
...-hot-update.js appears to contain the code of the modified component.
Here's my development webpack config:
require('babel-polyfill')
// Webpack config for development
var fs = require('fs')
var path = require('path')
var webpack = require('webpack')
var assetsPath = path.resolve(__dirname, '../static/dist')
var host = (process.env.HOST || 'localhost')
var port = (+process.env.PORT + 1) || 3001
// const { CommonsChunkPlugin } = webpack.optimize
// https://github.com/halt-hammerzeit/webpack-isomorphic-tools
var WebpackIsomorphicToolsPlugin = require('webpack-isomorphic-tools/plugin')
var webpackIsomorphicToolsPlugin = new WebpackIsomorphicToolsPlugin(require('./webpack-isomorphic-tools'))
var babelrc = fs.readFileSync('./.babelrc')
var babelrcObject = {}
try {
babelrcObject = JSON.parse(babelrc)
} catch (err) {
console.error('==> ERROR: Error parsing your .babelrc.')
console.error(err)
}
var babelrcObjectDevelopment = babelrcObject.env && babelrcObject.env.development || {}
// merge global and dev-only plugins
var combinedPlugins = babelrcObject.plugins || []
combinedPlugins = combinedPlugins.concat(babelrcObjectDevelopment.plugins)
var babelLoaderQuery = Object.assign({}, babelrcObjectDevelopment, babelrcObject, {plugins: combinedPlugins})
delete babelLoaderQuery.env
// Since we use .babelrc for client and server, and we don't want HMR enabled on the server, we have to add
// the babel plugin react-transform-hmr manually here.
// make sure react-transform is enabled
babelLoaderQuery.plugins = babelLoaderQuery.plugins || []
var reactTransform = null
for (var i = 0; i < babelLoaderQuery.plugins.length; ++i) {
var plugin = babelLoaderQuery.plugins[i]
if (Array.isArray(plugin) && plugin[0] === 'react-transform') {
reactTransform = plugin
}
}
if (!reactTransform) {
reactTransform = ['react-transform', {transforms: []}]
babelLoaderQuery.plugins.push(reactTransform)
}
if (!reactTransform[1] || !reactTransform[1].transforms) {
reactTransform[1] = Object.assign({}, reactTransform[1], {transforms: []})
}
// make sure react-transform-hmr is enabled
reactTransform[1].transforms.push({
transform: 'react-transform-hmr',
imports: ['react'],
locals: ['module']
})
module.exports = {
devtool: 'inline-source-map',
context: path.resolve(__dirname, '..'),
entry: {
'main': [
'webpack-hot-middleware/client?path=http://' + host + ':' + port + '/__webpack_hmr',
'bootstrap-sass!./src/styles/theme/bootstrap.config.js',
// 'font-awesome-webpack!./src/styles/theme/font-awesome.config.js',
'./src/client.js',
]
},
output: {
path: assetsPath,
filename: '[name]-[hash].js',
chunkFilename: '[name]-[chunkhash].js',
publicPath: 'http://' + host + ':' + port + '/dist/'
},
module: {
loaders: [
{ test: /\.jsx?$/, exclude: /node_modules/, loaders: ['babel?' + JSON.stringify(babelLoaderQuery)]},
{ test: /\.json$/, loader: 'json-loader' },
{ test: /\.less$/, loader: 'style!css?modules&importLoaders=2&sourceMap&localIdentName=[local]___[hash:base64:5]!autoprefixer?browsers=last 2 version!less?outputStyle=expanded&sourceMap' },
{
test: /\.scss$/,
loader: 'style!css?sourceMap&localIdentName=[local]___[hash:base64:5]!autoprefixer?browsers=last 2 version!sass?outputStyle=expanded&sourceMap'
},
{ test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff" },
{ test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff" },
{ test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/octet-stream" },
{ test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file" },
{ test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=image/svg+xml" },
{ test: webpackIsomorphicToolsPlugin.regular_expression('images'), loader: 'url-loader?limit=10240' }
]
},
progress: true,
resolve: {
modulesDirectories: [
'src',
'node_modules'
],
extensions: ['', '.json', '.js', '.jsx']
},
plugins: [
// hot reload
new webpack.HotModuleReplacementPlugin(),
new webpack.IgnorePlugin(/webpack-stats\.json$/),
// TODO: where should I put this in array?
new CommonsChunkPlugin({ name: 'common' }),
new webpack.DefinePlugin({
__CLIENT__: true,
__SERVER__: false,
__DEVELOPMENT__: true,
__DEVTOOLS__: true, // <-------- DISABLE redux-devtools HERE
'process.env.API_URL': JSON.stringify(process.env.API_URL),
'process.env.PUBLIC_URL': JSON.stringify(process.env.PUBLIC_URL),
'process.env.STRIPE_PUBLISHABLE_KEY': JSON.stringify(process.env.STRIPE_PUBLISHABLE_KEY),
'process.env.INTERCOM_APP_ID': JSON.stringify(process.env.INTERCOM_APP_ID),
'process.env.GOOGLE_ANALYTICS_CODE': JSON.stringify(process.env.GOOGLE_ANALYTICS_CODE),
'process.env.FEATURE_MONITORING': JSON.stringify(process.env.FEATURE_MONITORING),
}),
webpackIsomorphicToolsPlugin.development()
],
}
UPDATE: I just noticed that HMR is working properly for some components (my header and footer for example) but not for others (content of About page). Can't figure out yet what it is that makes some components fail to update.
I had the exact same fail type: everything "looks" like it hot reloads (correct files showing up in console, compile succeeding), but the DOM simply wasn't updating. Full refresh required to update.
Fix for me was: Do not mix Stateless and 'normal' React Components in a single file.
Multiple components styles in a single file failed with HMR.
In short, keep Stateless components:
export default const MyCom = () => <div>🍦</div>
Separate (files) from class based components:
export default class NextCom extends React.Component {
render () { return <div>👻</div> }
}
Putting those two examples into a single file produced the HMR-working-but-failing-to-update-DOM issue you seem to be having too.

Resources