How to tell webpack dev server to serve index.html for any route - reactjs

React router allows react apps to handle /arbitrary/route. In order this to work, I need my server to send the React app on any matched route.
But webpack dev server doesn't handle arbitrary end points.
There is a solution here using additional express server.
How to allow for webpack-dev-server to allow entry points from react-router
But I don't want to fire up another express server to allow route matching. I just want to tell webpack dev server to match any url and send me my react app. please.

I found the easiest solution to include a small config:
devServer: {
port: 3000,
historyApiFallback: {
index: 'index.html'
}
}
I found this by visiting: PUSHSTATE WITH WEBPACK-DEV-SERVER.

historyApiFallback option on official documentation for webpack-dev-server explains clearly how you can achieve either by using
historyApiFallback: true
which simply falls back to index.html when the route is not found
or
// output.publicPath: '/foo-app/'
historyApiFallback: {
index: '/foo-app/'
}

Adding public path to config helps webpack to understand real root (/) even when you are on subroutes, eg. /article/uuid
So modify your webpack config and add following:
output: {
publicPath: "/"
}
devServer: {
historyApiFallback: true
}
Without publicPath resources might not be loaded properly, only index.html.
Tested on Webpack 4.6
Larger part of config (just to have better picture):
entry: "./main.js",
output: {
publicPath: "/",
path: path.join(__dirname, "public"),
filename: "bundle-[hash].js"
},
devServer: {
host: "domain.local",
https: true,
port: 123,
hot: true,
contentBase: "./public",
inline: true,
disableHostCheck: true,
historyApiFallback: true
}

Works for me like this
devServer: {
contentBase: "./src",
hot: true,
port: 3000,
historyApiFallback: true
},
Working on riot app

My situation was a little different, since I am using the angular CLI with webpack and the 'eject' option after running the ng eject command. I modified the ejected npm script for 'npm start' in the package.json to pass in the --history-api-fallback flag
"start": "webpack-dev-server --port=4200 --history-api-fallback"
"scripts": {
"ng": "ng",
"start": "webpack-dev-server --port=4200 --history-api-fallback",
"build": "webpack",
"test": "karma start ./karma.conf.js",
"lint": "ng lint",
"e2e": "protractor ./protractor.conf.js",
"prepree2e": "npm start",
"pree2e": "webdriver-manager update --standalone false --gecko false --quiet",
"startold": "webpack-dev-server --inline --progress --port 8080",
"testold": "karma start",
"buildold": "rimraf dist && webpack --config config/webpack.prod.js --progress --profile --bail"},

I agree with the majority of existing answers.
One key thing I wanted to mention is if you hit issues when manually reloading pages on deeper paths where it keeps the all but the last section of the path and tacks on the name of your js bundle file you probably need an extra setting (specifically the publicPath setting).
For example, if I have a path /foo/bar and my bundler file is called bundle.js. When I try to manually refresh the page I get a 404 saying /foo/bundle.js cannot be found. Interestingly if you try reloading from the path /foo you see no issues (this is because the fallback handles it).
Try using the below in conjunction with your existing webpack config to fix the issue. output.publicPath is the key piece!
output: {
filename: 'bundle.js',
publicPath: '/',
path: path.resolve(__dirname, 'public')
},
...
devServer: {
historyApiFallback: true
}

If you choose to use webpack-dev-server, you should not use it to serve your entire React app. You should use it to serve your bundle.js file as well as the static dependencies. In this case, you would have to start 2 servers, one for the Node.js entry points, that are actually going to process routes and serve the HTML, and another one for the bundle and static resources.
If you really want a single server, you have to stop using the webpack-dev-server and start using the webpack-dev-middleware within your app-server. It will process bundles "on the fly" (I think it supports caching and hot module replacements) and make sure your calls to bundle.js are always up to date.

For me I had dots "." in my path e.g. /orgs.csv so I had to put this in my webpack confg.
devServer: {
historyApiFallback: {
disableDotRule: true,
},
},

You can enable historyApiFallback to serve the index.html instead of an 404 error when no other resource has been found at this location.
let devServer = new WebpackDevServer(compiler, {
historyApiFallback: true,
});
If you want to serve different files for different URIs, you can add basic rewriting rules to this option. The index.html will still be served for other paths.
let devServer = new WebpackDevServer(compiler, {
historyApiFallback: {
rewrites: [
{ from: /^\/page1/, to: '/page1.html' },
{ from: /^\/page2/, to: '/page2.html' },
{ from: /^\/page3/, to: '/page3.html' },
]
},
});

I know this question is for webpack-dev-server, but for anyone who uses webpack-serve 2.0. with webpack 4.16.5; webpack-serve allows add-ons.You'll need to create serve.config.js:
const serve = require('webpack-serve');
const argv = {};
const config = require('./webpack.config.js');
const history = require('connect-history-api-fallback');
const convert = require('koa-connect');
serve(argv, { config }).then((result) => {
server.on('listening', ({ server, options }) => {
options.add: (app, middleware, options) => {
// HistoryApiFallback
const historyOptions = {
// ... configure options
};
app.use(convert(history(historyOptions)));
}
});
});
Reference
You will need to change the dev script from webpack-serve to node serve.config.js.

Related

Best way to integrate webpack builds with ASP.NET Core 3.0?

I'm upgrading my ASP.NET Core app to V3, and using Visual Studio 2019 for development / debugging. The process has been smooth except for this:
public void Configure(…..
app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
{
HotModuleReplacement = false,
ReactHotModuleReplacement = false
});
UseWebpackDevMiddleware is no more: https://github.com/aspnet/AspNetCore/issues/12890 .
I'm now looking to understand the best way to have VS run webpack every time I debug, ideally only on JS code that has changed. This was the value I was getting from UseWebpackDevMiddleware. My app is a React app, and it seems like there is some new replacement for this if your app was started from CreateReactApp, but mine was not. (I believe apps that stated from this but then separated are called "ejected.") Is it somehow possible for me to still take advantage of whatever that facility is, even though my app does not leverage CreateReactApp? Also, what is the role of CreateReactApp after it bootstraps your new React application? I imagined it would be only used for inflating template code at the first go.
What is the role of Microsoft.AspNetCore.SpaServices.Extensions in all of this?
I don't need hot module replacement; I don't need server side prerendering. I'm really just trying to understand how to get my JS to transparently build (via Webpack) as part of my debugging process. Is this something that I could hook into MSBuild for? I imagine others are going to face the same question as they upgrade.
Thanks for any suggestions.
So, I was using UseWebpackDevMiddleware for HMR for a much smoother dev process - In the end I reverted to using webpack-dev-server
Steps:
1) Add package to package.json: "webpack-dev-server": "3.8.2",
2) Add webpack.development.js
const merge = require('webpack-merge');
const common = require('./webpack.config.js');
const ExtractCssPlugin = require('mini-css-extract-plugin');
const webpackDevServerPort = 8083;
const proxyTarget = "http://localhost:8492";
module.exports = merge(common(), {
output: {
filename: "[name].js",
publicPath: '/dist/'
},
mode: 'development',
devtool: 'inline-source-map',
devServer: {
compress: true,
proxy: {
'*': {
target: proxyTarget
}
},
port: webpackDevServerPort
},
plugins: [
new ExtractCssPlugin({
filename: "[name].css",
chunkFilename: "[id].css"
})
]
});
Note that the proxy setting here will be used to proxy through to ASP.Net core for API calls
Modify launchSettings.json to point to webpack-dev-server:
"profiles": {
"VisualStudio: Connect to HotDev proxy": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "http://localhost:8083/",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:8492/"
}
}
(also I had some problem with configuring the right locations in webpack, and found this useful
Also, will need to start webpack-dev-server which can be done via a npm script:
"scripts": {
"build:hotdev": "webpack-dev-server --config webpack.development.js --hot --inline",
And then this is bootstrapped
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "build:hotdev");
}
});
(or you can install the npm task runner extension and:
"-vs-binding": {
"ProjectOpened": [
"build:hotdev"
]
}
Alternatively I realise you can proxy the other way using the following - this way any request to under "dist" will be pushed through to the proxy webpack-dev-server
app.UseSpa(spa =>
{
spa.Options.SourcePath = "dist";
if (env.IsDevelopment())
{
// Ensure that you start webpack-dev-server - run "build:hotdev" npm script
// Also if you install the npm task runner extension then the webpack-dev-server script will run when the solution loads
spa.UseProxyToSpaDevelopmentServer("http://localhost:8083");
}
});
And then you don't need to proxy though from that back any more and can just serve /dist/ content
- both hot and vendor-precompiled using as web.config.js like this:
module.exports = merge(common(), {
output: {
filename: "[name].js",
publicPath: '/dist/',
},
mode: 'development',
devtool: 'inline-source-map',
devServer: {
compress: true,
port: 8083,
contentBase: path.resolve(__dirname,"wwwroot"),
},
plugins: [
new ExtractCssPlugin({
filename: "[name].css",
chunkFilename: "[id].css"
})
]
});
In my opinion, Kram's answer should be marked as accepted as it really gives what's needed. I've spent some time setting up a .NET Core/React/Webpack project recently, and I could not get the spa.UseReactDevelopmentServer to work, but the spa.UseProxyToSpaDevelopmentServer works like a charm. Thanks Kram!
Here are my few gotchas they might help someone:
webpack.config.js (excerpt):
const path = require('path');
const webpack = require('webpack');
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'build.[hash].js',
},
devServer: {
contentBase: path.resolve(__dirname, 'public'),
publicPath: '/dist',
open: false,
hot: true
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
DevServer sets its root by publicPath property and completely ignores the output.path property in the root. So even though your output files (for prod) will go to dist folder, under webpack server they will be served under a root by default. If you want to preserve the same url, you have to set publicPath: '/dist'.
This means that your default page will be under http://localhost:8083/dist. I could not figure out a way to place assets under subfolder while keeping index in the root (other than hardcode the path for assets).
You need HotModuleReplacementPlugin in order to watch mode to work and also hot: true setting in the devServer configuration (or pass it as a parameter upon start).
If you (like me) copy some dev server configuration with open: true (default is false), you will finish up with two browsers opened upon application start as another one is opened by the launchSettings "launchBrowser": true. It was a bit of a surprise at the first moment.
Also, you will have to enable CORS for your project otherwise your backend calls will get blocked:
Startup.cs (excerpt):
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy(name: "developOrigins",
builder =>
{
builder.WithOrigins("http://localhost:8083");
});
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
app.UseCors("developOrigins");
}
If anything else will come to my mind I will update the answer, hope this helps to someone.
Webpack 5 update
webpack-dev-server command doesn't work anymore. Use:
"build:hotdev": webpack serve --config webpack.config.development.js
You might also need to add target: 'web' to your webpack.config.js in Webpack 5 to enable hot module reload to work:
module.exports = {
target: 'web'
}
Alternatively you might need to create a browserlist file. Hope this issue gets fixed soon.
You mention VS. My solution is good for Visual Studio, but not VS Code.
I use WebPack Task Runner: https://marketplace.visualstudio.com/items?itemName=MadsKristensen.WebPackTaskRunner
This adds webpack.config.js tasks into the "Task Runner Explorer" in Visual Studio, and you can then bind those tasks to events like "Before Build" or "After Build"
Extension is here : AspNetCore.SpaServices.Extensions
You could find examples here : https://github.com/RimuTec/AspNetCore.SpaServices.Extensions
With core 3.1 or 5.0
React with webpack
using :spaBuilder.UseWebpackDevelopmentServer(npmScriptName: "start");

Make webpack-dev-server to do hot reloading AND eliminate EMPTY response error in React

I have been trying to make React app development easier by using reloading of the app when I modify the files and I tried webpack-dev-server for that (Please, see my previous thread: Can't set up webpack-dev-server to start React app). I made hot reloading working but got an issue: timeouts started to occur on my requests and I see the empty response errors in console. There are threads that discuss the issue, e.g.: https://github.com/webpack/webpack-dev-server/issues/183
but so far I could not make it working. Setting --host 0.0.0.0 is not working, setting --port 3000 eliminates empty response error but hot reloading is gone... Below is my webpack relevant config:
devServer: {
index: '',
open: true,
proxy: {
context: () => true, // endpoints which you want to proxy
target: 'http://localhost:3000' // your main server address
}
},
entry: {
app: ['babel-polyfill', './views/index.js']
//vendor: ["react","react-dom"]
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './public')
},
devtool: "#eval-source-map",
...
I am starting the app by running npm run dev, here is a part of the package.json:
"scripts": {
"client": "webpack-dev-server --mode development --devtool inline-source-map --port 3000 --content-base public --hot",
"server": "node ./bin/www",
"dev": "concurrently \"npm run server\" \"npm run client\"",
},
So, above if we remove --port 3000 hot reloading on the front end part starts working, but then the timeout is happening. Also, upon modification of the server side code the app is not reloaded unfortunately and I am not sure how to add this feature too. I am using react-hot-loader:
import React from 'react';
import ControlledTabs from './ControlledTabs'
import { hot } from 'react-hot-loader/root'
class App extends React.Component {
render() {
return (
<ControlledTabs/>
);
}
}
module.exports = hot(App);
I think it should be related to the devServer configs most probably and how the webpack-dev-server is started. I just want to find a WORKING way of doing hot reloading instead of falling back into build - run cycle that is annoying and inefficient.
Also, it is really hard to say, what is going wrong, whether --port 3000 is really the issue. I noticed that the webpack-dev-server is somehow working in a very unpredictable way on my project meaning that after doing changes and launching the app I see one result, but then I restart webpack-dev-server and see a different result as if webpack-dev-server is doing something behind the scenes what it wants to and whenever it wants to without notifying me about that.
Update
I changed webpack.config.js to:
watch: true,
devServer: {
index: '',
open: true,
proxy: {
context: () => true, // endpoints which you want to proxy
target: 'http://localhost:3000' // your main server address
}
},
entry: {
app: ['babel-polyfill', './views/index.js']
//vendor: ["react","react-dom"]
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './public')
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
],
And removed react-hot-loader from the React entry point:
import React from 'react';
import ControlledTabs from './ControlledTabs'
class App extends React.Component {
render() {
return (
<ControlledTabs/>
);
}
}
module.exports = App;
Because otherwise it was giving me a syntax error in console, webpack could not start. After doing that if I modify any react file, whole webpage is reloaded it seems and the net::ERR_EMPTY_RESPONSE remains...
Add a watch to your webpack config.
watch: true
Also, you need to enable module replacement loading within the webpack dev server.
In short, if you see how this config is setup, this is a working example of hot reloading for a very basic react app. It uses ExpressJS as well.
https://github.com/chawk/bare_bones_react/blob/master/webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const express = require('express');
module.exports = {
entry: {
app: './src/index.js'
},
devServer: {
hot: true,
compress: true,
contentBase: path.join(__dirname, 'dist'),
open: 'Chrome',
before(app) {
app.use('/static', express.static(path.resolve(__dirname, 'dist')))
}
},
devtool: 'source-map',
output: {
filename: './js/[name].bundle.js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({
template: './server/index.html'
})
],
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: ['babel-loader']
}
]
},
resolve: {
extensions: [
'.js'
]
}
}

Webpack+SemanticUI+React: process is not defined

I have found numerous posts about the Webpack error:
Uncaught ReferenceError: process is not defined
most of which suggest adding a plugin to the webpack.config.js:
plugins: [
// ...
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV || 'development')
}
}),
// ...
]
however this does not seem to do the trick in my case.
To make things easy, I have created a repo with the bare minimum to setup SemanticUI-React with Webpack, which should be straightforward to navigate. My config in webpack.config.js is heavily inspired from this recent tutorial which seems to have a lot of positive comments.
To reproduce the error, just clone the repo on your machine (I use yarn, but this should work with npm too):
git clone https://github.com/sheljohn/minimal-semantic-react
cd minimal-semantic-react/
yarn install
yarn run serve
which opens at localhost:3000, and the error can be seen in the developer console.
As far as I understand, it seems that when React loads, it is looking to determine whether production or development mode is set, using the variable process.env.NODE_ENV, which is undefined in the browser.
This might be related to the target field in the Webpack config (set to web by default); but since React is loaded from CDN, prior to the bundle, I guess it doesn't know about what WebPack is doing, which makes me perplex as to why adding a plugin in the config would change anything...
Hence my question: is it possible to use semantic-ui-react by declaring the big libs (React, ReactDOM, semantic) as externals? Everything works fine if I bundle them, but the bundle ends up around 4MB, which is quite big.
Additional Details
Error as seen in Chrome (OSX High Sierra, v66.0.3359.181, dev console):
react.development.js:14 Uncaught ReferenceError: process is not defined
at react.development.js:14
(anonymous) # react.development.js:14
and code excerpt at line 14:
if (process.env.NODE_ENV !== "production") {
File webpack.config.js
const path = require("path");
const webpack = require("webpack");
const publicFolder = path.resolve(__dirname, "public");
module.exports = {
entry: "./src/index.jsx",
target: "web",
output: {
path: publicFolder,
filename: "bundle.js"
},
devServer: {
contentBase: publicFolder,
port: 3000
},
externals: {
'jquery': 'jQuery',
'lodash': '_',
'react': 'React',
'react-dom': 'ReactDOM',
'semantic-ui-react': 'semantic-ui-react'
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader'
}
}
]
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV || 'development')
}
}),
new webpack.HotModuleReplacementPlugin()
]
};
File .babelrc
{
"presets": ["env", "react"]
}
I think I finally solved this:
Mistake #1: I was using cjs versions of the React libs from cdnjs, when I should have been using umd instead. Although UMD style is ugly, it seems to work fine within browsers, whereas CommonJS uses require for example. See this post for a comparison of AMD / CommonJS / UMD.
Mistake #2: in webpack.config.js, the "name" for the external semantic-ui-react should be semanticUIReact (case sensitive). This is what is defined in the window global when the script is loaded from the CDN (e.g. like jQuery or React).
I updated the repository with these fixes, and you should be able to reproduce that working example on your machine. This repository contains the bare minimum needed to get SemanticUI, React and Webpack working together. This would have saved me a lot of time, so hopefully other people get to benefit from that!
Everything works fine if I bundle them, but the bundle ends up around 4MB, which is quite big.
It's because you bundle them in "development" mode. Try using "production" in your script instead, it will be much smaller.
"build": "webpack --mode production"
If you bundle everything in production, without specifying external, it will be better for a standalone app.

How can i suppress logs from webpack when using react hot reloading?

I am using this react redux starter kit https://github.com/coryhouse/pluralsight-redux-starter and i am getting the following outputs in my console
[HMR] connected
I tried to toggle the following fields in the web-pack config to true and false but it doesn't help
noInfo: true,
debug: false,
How can i turn these logs off?
Tried many solutions, the dev server option clientLogLevel: "none" had the closest meaning. But couldn't get it working.
Then I found a workaround:
Run your app with NODE_ENV=development; for that go to package.json and update scripts:
"scripts": {
"build": "webpack",
"start": "NODE_ENV=production webpack-dev-server --progress --inline --hot",
"dev": "NODE_ENV=development webpack-dev-server --progress --inline --hot"
}
modify webpack.config.js:
plugins: [
new webpack.DefinePlugin({
__DEVELOPMENT__: process.env.NODE_ENV !== 'production',
})
],
The DefinePlugin allows you to create global constants which can be
configured at compile time.
OR
Replace global.console.log with a custom console_log that filters out [HMR] and [WDS] logs. See this Github comment.
Add the below code to your development.js (a file which is conditionally appended to the entry in webpack.config.js based on the value process.env.NODE_ENV !== 'production')
(function(global) {
var console_log = global.console.log
global.console.log = function() {
if (!(
arguments.length == 1 &&
typeof arguments[0] === 'string' &&
arguments[0].match(/^\[(HMR|WDS)\]/)
)) {
console_log.apply(global.console,arguments)
}
}
})(window)
npm run dev
If you've used DefinePlugin, then we now have global constant __DEVELOPMENT__ === true on client side. So on every refresh you can do this:
if (__DEVELOPMENT__) {
console.clear();
}
On version >= 4.0.0 of Webpack You can keep the hot reload feature but limit the logs to errors under devServer like below:
devServer: {
hot: true,
client: {
overlay: false,
logging: 'error',
}
...
}
You can find more info about it on https://webpack.js.org/configuration/dev-server/#logging

webpack-dev-server react-router push state

I am trying to get react-router to work with webpack-dev-server but keep getting cannot Get /SOMEURL. This is because webpack-dev-server is looking for that specific file but cannot find it. I'd like webpack to resort to using react-router vs searching for the files itself.
How can I set that up?
My grunt:
'webpack-dev-server': {
options: {
hot: true,
port: 8000,
webpack: webpackDevConfig,
publicPath: '/assets/',
contentBase: './<%= pkg.src %>/'
},
start: {
keepAlive: true,
}
},
Use the --history-api-fallback option to webpack-dev-server. This uses connect-history-api-fallback to serve up index.html if the route doesn't match any other files. (This does mean you have to use index.html as the filename for your HTML page.)
For me, i had this in my index file:
<script src="./assets/app.js"></script>
rather than
<script src="/assets/app.js"></script>
So, it looks like the history fallback API was in competition with
my misconfiguration.

Resources