I am trying to setup webpack dev server using webpackDevMiddleware, webpackHotMiddleware with express generator and react. I got everything working, but there's a huge delay in the reload.
I will get this message every time in the browser
'GET http://localhost:8080/__webpack_hmr
net::ERR_INCOMPLETE_CHUNKED_ENCODING 200 (OK)'
But 5 to 10 seconds later the browser will reload. In the terminal, these messages are showing
GET /7310e23232f92e879547.hot-update.json 404 6.282 ms - 1573
GET / 304 1.071 ms - -
GET /__webpack_hmr 200 1.767 ms - -
GET /stylesheets/style.css 304 1.306 ms - -
GET /app-bundle.js 200 5.337 ms - 2960039
I think the express server has a delay or stopping from getting the hot-update.json.
I have tried time out and keepAliveTimeout the bin/www file
server.listen(port, () => {
server.timeout = 0
server.keepAliveTimeout = 0
});
package.json
{
"name": "react-webpack-hmr",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www",
"dev": "nodemon ./bin/www --inspect --watch app.js --watch webpack.config.js --watch src",
"build": "webpack --config=webpack.config.js",
"clean": "rimraf public/dist"
},
"dependencies": {
"babel-loader": "^8.0.4",
"cookie-parser": "~1.4.3",
"css-loader": "^2.1.0",
"debug": "~2.6.9",
"ejs": "~2.5.7",
"ejs-loader": "^0.3.1",
"express": "~4.16.0",
"extract-loader": "^3.1.0",
"file-loader": "^3.0.1",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^3.2.0",
"http-errors": "~1.6.2",
"morgan": "~1.9.0",
"react": "^16.7.0",
"react-dom": "^16.7.0",
"react-hot-loader": "^4.6.3",
"style-loader": "^0.23.1"
},
"devDependencies": {
"#babel/preset-react": "^7.0.0",
"#babel/runtime": "^7.2.0",
"#babel/generator": "^7.2.2",
"#babel/polyfill": "^7.2.5",
"babel-plugin-async-to-promises": "^1.0.5",
"#babel/core": "^7.2.2",
"#babel/plugin-transform-runtime": "^7.2.0",
"#babel/preset-env": "^7.2.3",
"webpack": "^4.28.3",
"webpack-cli": "^3.1.2",
"webpack-dev-middleware": "^3.4.0",
"webpack-dev-server": "^3.1.14",
"webpack-hot-middleware": "^2.24.3"
}
}
webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
module.exports = {
entry: {
app: [
'webpack-hot-middleware/client?reload=true',
// 'webpack/hot/only-dev-server',
// 'react-hot-loader/patch',
"#babel/runtime/regenerator",
"./src/app.js"
]
},
mode: 'development',
output: {
filename: "[name]-bundle.js",
path: path.join(__dirname, 'public/dist'),
publicPath: "/"
},
devtool: "cheap-eval-source-map",
devServer: {
contentBase: "dist",
overlay: true,
hot: true
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader'
}
]
},
{
test: /\.html$/,
use: [
{
loader: "html-loader"
}
]
},
]
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(),
new HtmlWebpackPlugin({
template: './views/index.ejs'
})
]
}
app.js
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
const webpack = require("webpack");
const config = require("./webpack.config");
const compiler = webpack(config);
const webpackDevMiddleware = require('webpack-dev-middleware')(compiler, config.devServer);
const webpackHotMiddleware = require('webpack-hot-middleware')(compiler, config.devServer);
var app = express();
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(webpackDevMiddleware);
app.use(webpackHotMiddleware);
//app.use(express.static(path.join(__dirname, 'public')));
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', indexRouter);
app.use('/users', usersRouter);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
React side app.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App.js';
import { AppContainer } from 'react-hot-loader';
function render(Component) {
ReactDOM.render(
<AppContainer>
<Component />
</AppContainer>,
document.getElementById('app')
)
}
render(App);
if (module.hot) {
module.hot.accept('./components/App', () => {
const newApp = require('./components/App').default
render(newApp);
})
}
I expect the browser will reload after is finished compiling the new code without a delay everytime I save my files.
So I fixed it was my package.json
I was watching the react files which it shouldn't
I removed the old code this is the new one
"dev": "nodemon --inspect --watch webpack.config.js --watch app.js",
TLDR; In order to have hot module reload working with Nodemon you need to exclude the client code from watch.
Webpack uses __webpack_hmr to receive events about changes in code. If you edit a file then save it, Nodemon restarts and in this time Webpack HMR loses connection to the event stream, resulting in a miss for getting updated code. This is the reason why you need to exclude client side code from the watch list of Nodemon. Basically client side code refresh is 'managed' by Webpack dev server.
Usually I have a nodemon.json file in my root to let Nodemon know what to watch:
{
"watch": [
"server.js",
"src/server",
"config"
]
}
Related
I am trying to deploy my React app to heroku 22 stack, I have completed the code part of it and local machine it works fine. I have a Reactjs FrontEnd and Nodejs Backend. First After completing the project I moved my Reactjs frontend into the Nodejs Backend and run the npm run build command on the front end My folder structure looks like this:
but When I push my code to heroku using CLI
git push heroku master:main
I keep getting cannot get / or no such file or directory /app/vrestaurant/build/index.html
here is my package.json in backend
{
"name": "nodejs-mongodb-mongoose",
"version": "1.0.0",
"engines": {
"node": "17.x",
"npm": "8.x"
},
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node app.js"
},
"author": "Vishal",
"license": "ISC",
"dependencies": {
"body-parser": "^1.19.1",
"connect-mongo": "^4.6.0",
"connect-mongodb-session": "^3.1.1",
"cors": "^2.8.5",
"crypto": "^1.0.1",
"debug": "^4.3.4",
"dotenv": "^16.0.1",
"express": "^4.17.2",
"express-session": "^1.17.2",
"mini-css-extract-plugin": "^2.4.6",
"mongoose": "^6.1.7",
"nodemon": "^2.0.19",
"passport": "^0.5.2",
"passport-local": "^1.0.0",
"passport-local-mongoose": "^7.0.0",
"razorpay": "^2.8.1",
"shortid": "^2.2.16"
}
}
my app.js file on backend
// imports
require('dotenv').config();
const express = require('express')
const session = require("express-session")
const bodyParser = require('body-parser')
const cors = require('cors')
const zomatoRoutes = require('./Routes/zomato')
const paymentRoutes = require('./Routes/payments')
const mongoose = require('mongoose')
const passport = require("passport")
const LocalStrategy = require("passport-local")
const passportLocalMongoose = require("passport-local-mongoose")
const MongoStore = require('connect-mongo');
const MongoClient = require("mongodb").MongoClient;
const User = require('./Models/user')
// create express server
var app = express()
// add middleware before routes
app.use(bodyParser.json())
app.use(cors())
//connect to mongoDB
const uri = 'mongodb+srv://vishal_torne_22:*****#db-hernode.zu6btrs.mongodb.net/<mydbname>?retryWrites=true&w=majority';
console.log(uri, "this is the uri")
const options = {
useNewUrlParser: true,
useUnifiedTopology: true,
dbName:'zomato1'
}
mongoose.connect(uri, options).then(() => {
console.log('mongoose connected')
}).catch(e => console.log(e))
mongoose.connection.on('connected', () => {
console.log('mongoose is connected')
})
app.use(session({
secret: "this is unambigous secret",
resave: false,
saveUninitialized: false,
cookie: { maxAge: 24*60*60*1000 },
store : MongoStore.create({
mongoUrl: uri,
ttl: 14 * 24 * 60 * 60
})
}));
app.use(passport.initialize());
app.use(passport.session());
// middleware routes
app.use('/zomato', zomatoRoutes)
app.use('/payment', paymentRoutes)
// heroku configuration
if(process.env.NODE_ENV=="production"){
app.use(express.static('vrestaurant/build'))
const path = require('path')
app.get("*", (req, res) => {
res.sendFile(path.resolve(__dirname, "vrestaurant/build/index.html"))
})
}
//listen to a port
app.listen( process.env.PORT ||5252 , () => {
console.log("express app is up and running on port 5252");
console.log(process.env.PORT,'this is the env port')
})
things I have tried :
I have added heroku post build but gives error ' Missing Build script'
I added build webpack in package.json but it gives me error
also added the https://github.com/mars/create-react-app-buildpack.git build pack using heroku website settings, It gives me error :
Downloading Buildpack: https://github.com/mars/create-react-app-inner-buildpack.git#v9.0.0
remote: =====> Detected Framework: React.js (create-react-app)
remote: Writing `static.json` to support create-react-app
remote: Enabling runtime environment variables
remote: =====> Downloading Buildpack: https://github.com/heroku/heroku-buildpack-static.git#21c1f5175186b70cf247384fd0bf922504b419be
remote: =====> Detected Framework: Static HTML
remote: Stack heroku-22 is not supported!
remote: ! Push rejected, failed to compile React.js (create-react-app) multi app.
can anybody help me out with this please? Thank you.
Your buildpack should be Node.Js and not react.
Also try to print the output of
path.resolve(__dirname, "vrestaurant/build/index.html") to verify if it is resolving to correct path for index.html file in build folder.
Provide full path in setting the static folder
app.use(express.static(path.resolve(__dirname, "vrestaurant/build/index.html")))
Actually it might be better for you to use os.path.join() because it can cause on error on different systems:
you can use:
const path = require('path');
app.get('*', (req, res) => {
res.sendFile(path.resolve(__dirname, 'vrestaurant', 'build', 'index.html'));
});
I am trying to create a webservice using Express, which will be executable from the localhost as well as an AWS Lambda using Claudia.
I want to separate the app configuration and the app.listen function.
My app.ts file looks like this:
import express = require('express');
const dotenv = require('dotenv');
class App {
public app: express.Application = express();
constructor() {
dotenv.config();
// parse application/json request body to JSON Objects
this.app.use(express.json());
// parse x-ww-form-urlencoded request body to JSON Objects
this.app.use(express.urlencoded({ extended: true }));
this.app.get('/', (req, res) => {
res.send('API server is up and running');
});
}
}
module.exports = new App().app
Then, I am requiring the app in the local.ts file
const app = require('./app');
app.listen(process.env.PORT,
() => console.log(`Application is running on port ${process.env.PORT}`)
);
My package.json looks like this
{
"name": "nodejs-express-lambda",
"version": "1.0.0",
"description": "",
"main": "app.ts",
"scripts": {
"run": "npx ts-node local.ts",
"build": "tsc"
},
"author": "",
"license": "ISC",
"dependencies": {
"#types/dotenv": "^8.2.0",
"dotenv": "^8.2.0",
"express": "^4.17.1"
},
"devDependencies": {
"#types/express": "^4.17.6",
"#types/node": "^14.0.1",
"claudia": "^5.12.0",
"ts-node": "^8.10.1",
"tslint": "^6.1.2",
"typescript": "^3.9.2"
}
}
tsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"target": "ES6"
},
"exclude": [
"node_modules",
]
}
Finally, running the npm run-script run will return
app.listen is not a function
Application is running on port 3000
I tried import app = require('./app') and it didn't work either. I am a bit lost in all those exports and imports, anybody can help ?
Running the app.listen from app.ts works fine.
I am not sure if this will help, but you may want to try this:
import express from 'express'
import dotenv from 'dotenv' // or just import 'dotenv/config'
class App {
public app: express.Application = express();
constructor() {
dotenv.config();
// parse application/json request body to JSON Objects
this.app.use(express.json());
// parse x-ww-form-urlencoded request body to JSON Objects
this.app.use(express.urlencoded({ extended: true }));
this.app.get('/', (req, res) => {
res.send('API server is up and running');
});
}
}
const app = new App().app
export default app
and in second file
import app from './app'
I am trying to enable webpack HMR in my express app. It's NOT an SPA app. For the view side, I am using EJS and Vue. I don't have the advantage of vue-cli here so I have to manually configure the vue-loader for the SFCs(.vue files) in the webpack. Also worth mentioning, my workflow is very typical: I have my main client-side resources (scss, js, vue etc) in resources dir. and I wish to bundle them inside my public dir.
My webpack.config.js:
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const webpack = require('webpack');
module.exports = {
mode: 'development',
entry: [
'./resources/css/app.scss',
'./resources/js/app.js',
'webpack-hot-middleware/client'
],
output: {
path: path.resolve(__dirname, 'public/js'),
publicPath: '/',
filename: 'app.js',
hotUpdateChunkFilename: "../.hot/[id].[hash].hot-update.js",
hotUpdateMainFilename: "../.hot/[hash].hot-update.json"
},
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
hmr: process.env.NODE_ENV === 'development'
}
},
'css-loader',
'sass-loader'
],
},
{
test: /\.vue$/,
loader: 'vue-loader'
}
]
},
plugins: [
new VueLoaderPlugin(),
new MiniCssExtractPlugin({
filename: '../css/app.css'
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin()
]
};
My app/index.js file:
import express from 'express';
import routes from './routes';
import path from 'path';
import webpack from 'webpack';
import devMiddleware from 'webpack-dev-middleware';
import hotMiddleware from 'webpack-hot-middleware';
const config = require('../webpack.config');
const compiler = webpack(config);
const app = express();
app.use(express.static('public'));
app.use(devMiddleware(compiler, {
noInfo: true,
publicPath: config.output.publicPath
}));
app.use(hotMiddleware(compiler));
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, '../resources/views'))
routes(app);
app.listen(4000);
export default app;
The scripts section of my package.json file:
"scripts": {
"start": "nodemon app --exec babel-node -e js",
"watch": "./node_modules/.bin/webpack --mode=development --watch",
"build": "./node_modules/.bin/webpack --mode=production"
}
I am using nodemon to restart server to pick up the changes of server-side code. In one tab I keep npm run start open and in other tab npm run watch.
In my console, I see that HMR connected:
It only picks up the change first time only, and throws some warning like this:
Ignored an update to unaccepted module ./resources/css/app.scss -> 0
And doesn't pick up the subsequent changes. How can I fix this?
Reproduction Repo:
https://bitbucket.org/tanmayd/express-test
Since it's not a SPA and you want to use EJS that will require server side rendering. It's not that easy in your case, first you will need to overwrite the render method and after that you need to add those files generated by webpack.
Based on your repo from your description, https://bitbucket.org/tanmayd/express-test, you were on the right track, but you combined development and production settings in your webpack config.
Since I can't push on your repo, I will list below the files that suffered changes or those that are new.
1. Scripts & Packages
"scripts": {
"start": "cross-env NODE_ENV=development nodemon app --exec babel-node -e js",
"watch": "./node_modules/.bin/webpack --mode=development --watch",
"build": "cross-env NODE_ENV=production ./node_modules/.bin/webpack --mode=production",
"dev": "concurrently --kill-others \"npm run watch\" \"npm run start\"",
"production": "cross-env NODE_ENV=production babel-node ./app/server.js"
},
I installed cross-env(because i'm on windows), cheerio(a nodejs jquery kind of version --- it's not that bad), style-loader (which is a must in development while using webpack).
The scripts:
start - start the development server
build - generate production files
production - start the server using the files generated from "build"
2. webpack.config.js - changed
style-loader was added in the mix so webpack will deliver your css from the bundle (see ./resources/js/app.js - line 1). MiniCssExtractPlugin is meant to be used when you want to extract the styles to a separate file, that is in production.
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const webpack = require('webpack');
// Plugins
let webpackPlugins = [
new VueLoaderPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
];
// Entry points
let webpackEntryPoints = [
'./resources/js/app.js',
];
if (process.env.NODE_ENV === 'production') {
webpackPlugins = [
new VueLoaderPlugin()
];
// MiniCssExtractPlugin should be used in production
webpackPlugins.push(
new MiniCssExtractPlugin({
filename: '../css/app.css',
allChunks: true
})
)
}else{
// Development
webpackEntryPoints.push('./resources/css/app.scss');
webpackEntryPoints.push('webpack-hot-middleware/client');
}
module.exports = {
mode: process.env.NODE_ENV === 'development' ? 'development' : 'production',
entry: webpackEntryPoints,
devServer: {
hot: true
},
output: {
path: path.resolve(__dirname, 'public/js'),
filename: 'app.js',
publicPath: '/'
},
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
// use style-loader in development
(process.env.NODE_ENV === 'development' ? 'style-loader' : MiniCssExtractPlugin.loader),
'css-loader',
'sass-loader',
],
},
{
test: /\.vue$/,
loader: 'vue-loader'
}
]
},
plugins: webpackPlugins
};
3. ./resources/js/app.js - changed
The styles are now added on the first line import "../css/app.scss";
4. ./app/middlewares.js - new
Here you will find 2 middlewares, overwriteRenderer and webpackAssets.
overwriteRenderer, must be the first middleware before your routes, it's used in both development and production, in development it will suppress the ending of the request after render and will populate the response(res.body) with the rendered string of your file. In production your views will act as layouts, therefore the files that were generated will be added in head(link) and body(script).
webpackAssets will be used only in development, must be the last middleware, this will add to the res.body the files generated in memory by webpack(app.css & app.js). It's a custom version of the example found here webpack-dev-server-ssr
const cheerio = require('cheerio');
let startupID = new Date().getTime();
exports.overwriteRenderer = function (req, res, next) {
var originalRender = res.render;
res.render = function (view, options, fn) {
originalRender.call(this, view, options, function (err, str) {
if (err) return fn(err, null); // Return the original callback passed on error
if (process.env.NODE_ENV === 'development') {
// Force webpack in insert scripts/styles only on text/html
// Prevent webpack injection on XHR requests
// You can tweak this as you see fit
if (!req.xhr) {
// We need to set this header now because we don't use the original "fn" from above which was setting the headers for us.
res.setHeader('Content-Type', 'text/html');
}
res.body = str; // save the rendered string into res.body, this will be used later to inject the scripts/styles from webpack
next();
} else {
const $ = cheerio.load(str.toString());
if (!req.xhr) {
const baseUrl = req.protocol + '://' + req.headers['host'] + "/";
// We need to set this header now because we don't use the original "fn" from above which was setting the headers for us.
res.setHeader('Content-Type', 'text/html');
$("head").append(`<link rel="stylesheet" href="${baseUrl}css/app.css?${startupID}" />`)
$("body").append(`<script type="text/javascript" src="${baseUrl}js/app.js?${startupID}"></script>`)
}
res.send($.html());
}
});
};
next();
};
exports.webpackAssets = function (req, res) {
let body = (res.body || '').toString();
let h = res.getHeaders();
/**
* Inject scripts only when Content-Type is text/html
*/
if (
body.trim().length &&
h['content-type'] === 'text/html'
) {
const webpackJson = typeof res.locals.webpackStats.toJson().assetsByChunkName === "undefined" ?
res.locals.webpackStats.toJson().children :
[res.locals.webpackStats.toJson()];
webpackJson.forEach(item => {
const assetsByChunkName = item.assetsByChunkName;
const baseUrl = req.protocol + '://' + req.headers['host'] + "/";
const $ = require('cheerio').load(body.toString());
Object.values(assetsByChunkName).forEach(chunk => {
if (typeof chunk === 'string') {
chunk = [chunk];
}
if (typeof chunk === 'object' && chunk.length) {
chunk.forEach(item => {
console.log('File generated by webpack ->', item);
if (item.endsWith('js')) {
$("body").append(`<script type="text/javascript" src="${baseUrl}${item}"></script>`)
}
});
}
body = $.html();
});
});
}
res.end(body.toString());
}
5. ./app/index.js - changed
This file is meant for development. Here i added the middlewares from 4 and added the serverSideRender: true option to devMiddleware so webpack will serve us those assets that are used in 4
import express from 'express';
import routes from './routes';
import path from 'path';
import devMiddleware from 'webpack-dev-middleware';
import hotMiddleware from 'webpack-hot-middleware';
import webpack from 'webpack';
const {webpackAssets, overwriteRenderer} = require('./middlewares');
const config = require('../webpack.config');
const compiler = webpack(config);
const app = express();
app.use(express.static('public'));
app.use(devMiddleware(compiler, {
publicPath: config.output.publicPath,
serverSideRender: true // enable serverSideRender, https://github.com/webpack/webpack-dev-middleware
}));
app.use(hotMiddleware(compiler));
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, '../resources/views'));
// This new renderer must be loaded before your routes.
app.use(overwriteRenderer); // Local render
routes(app);
// This is a custom version for server-side rendering from here https://github.com/webpack/webpack-dev-middleware
app.use(webpackAssets);
app.listen(4000, '0.0.0.0', function () {
console.log(`Server up on port ${this.address().port}`)
console.log(`Environment: ${process.env.NODE_ENV}`);
});
export default app;
6. ./app/server.js - new
This is the production version. It's mostly a cleanup version of 5, all development tools were removed, and only overwriteRenderer remained.
import express from 'express';
import routes from './routes';
import path from 'path';
const {overwriteRenderer} = require('./middlewares');
const app = express();
app.use(express.static('public'));
app.use(overwriteRenderer); // Live render
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, '../resources/views'));
routes(app);
app.listen(5000, '0.0.0.0', function() {
if( process.env.NODE_ENV === 'development'){
console.error(`Incorrect environment, "production" expected`);
}
console.log(`Server up on port ${this.address().port}`);
console.log(`Environment: ${process.env.NODE_ENV}`);
});
I had faced similar issue some time ago and was able to solve by combination of xdotool and exec in node. It might help you as well.
Here is the summary:
Have a bash script to reload the browser. The script uses xdotool to get the Chrome window and reload (Script can be used for firefox and other browser also).
Relevant SO question:
How to reload Google Chrome tab from terminal?
In the main file (app/index.js), using exec, run the script (inside app.listen callback). When any changes are made, nodemon will reload causing the script to execute and reloading the browser.
Bash script: reload.sh
BID=$(xdotool search --onlyvisible --class Chrome)
xdotool windowfocus $BID key ctrl+r
app/index.js
...
const exec = require('child_process').exec;
app.listen(4000, () => {
exec('sh script/reload.sh',
(error, stdout, stderr) => {
console.log(stdout);
console.log(stderr);
if (error !== null) {
console.log(`exec error: ${error}`);
}
}
);
});
export default app;
Hope it helps. Revert for any doubts.
Actually, your reproduction has some issues on the declaration which they're not related to your current issue but please observe them:
Do not push the build files to the git server, just send source files.
Set a cleaner on the webpack to clean the public folder on the production build.
Rename the folders and files to a name that just exactly they do.
Install nodemon on your project in the dev dependencies.
And your problem, I changed many things on your reproduction structure and if you have no time to read this answer post just see this repo and get what you want.
Change the app/index.js to the below:
import express from 'express';
import routes from './routes';
import hotServerMiddleware from 'webpack-hot-server-middleware';
import devMiddleware from 'webpack-dev-middleware';
import hotMiddleware from 'webpack-hot-middleware';
import webpack from 'webpack';
const config = require('../webpack.config');
const compiler = webpack(config);
const app = express();
app.use(devMiddleware(compiler, {
watchOptions: {
poll: 100,
ignored: /node_modules/,
},
headers: { 'Access-Control-Allow-Origin': '*' },
hot: true,
quiet: true,
noInfo: true,
writeToDisk: true,
stats: 'minimal',
serverSideRender: true,
publicPath: '/public/'
}));
app.use(hotMiddleware(compiler.compilers.find(compiler => compiler.name === 'client')));
app.use(hotServerMiddleware(compiler));
const PORT = process.env.PORT || 4000;
routes(app);
app.listen(PORT, error => {
if (error) {
return console.error(error);
} else {
console.log(`Development Express server running at http://localhost:${PORT}`);
}
});
export default app;
Install webpack-hot-server-middleware, nodemon and vue-server-renderer in the project and change the start script to have package.json like below:
{
"name": "express-test",
"version": "1.0.0",
"main": "index.js",
"author": "Tanmay Mishu (tanmaymishu#gmail.com)",
"license": "MIT",
"scripts": {
"start": "NODE_ENV=development nodemon app --exec babel-node -e ./app/index.js",
"watch": "./node_modules/.bin/webpack --mode=development --watch",
"build": "./node_modules/.bin/webpack --mode=production",
"dev": "concurrently --kill-others \"npm run watch\" \"npm run start\""
},
"dependencies": {
"body-parser": "^1.19.0",
"csurf": "^1.11.0",
"dotenv": "^8.2.0",
"ejs": "^3.0.1",
"errorhandler": "^1.5.1",
"express": "^4.17.1",
"express-validator": "^6.3.1",
"global": "^4.4.0",
"mongodb": "^3.5.2",
"mongoose": "^5.8.10",
"multer": "^1.4.2",
"node-sass-middleware": "^0.11.0",
"nodemon": "^2.0.2",
"vue": "^2.6.11",
"vue-server-renderer": "^2.6.11"
},
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-preset-env": "^1.7.0",
"babel-preset-stage-0": "^6.24.1",
"concurrently": "^5.1.0",
"css-loader": "^3.4.2",
"mini-css-extract-plugin": "^0.9.0",
"node-sass": "^4.13.1",
"nodemon": "^2.0.2",
"sass-loader": "^8.0.2",
"vue-loader": "^15.8.3",
"vue-style-loader": "^4.1.2",
"vue-template-compiler": "^2.6.11",
"webpack": "^4.41.5",
"webpack-cli": "^3.3.10",
"webpack-dev-middleware": "^3.7.2",
"webpack-hot-middleware": "^2.25.0",
"webpack-hot-server-middleware": "^0.6.0"
}
}
Change the entire your webpack configuration file to below:
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const webpack = require('webpack');
module.exports = [
{
name: 'client',
target: 'web',
mode: 'development',
entry: [
'webpack-hot-middleware/client?reload=true',
'./resources/js/app.js',
],
devServer: {
hot: true
},
output: {
path: path.resolve(__dirname, 'public'),
filename: 'client.js',
publicPath: '/',
},
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
hmr: process.env.NODE_ENV === 'development'
}
},
'css-loader',
'sass-loader'
],
},
{
test: /\.vue$/,
loader: 'vue-loader'
}
]
},
plugins: [
new VueLoaderPlugin(),
new MiniCssExtractPlugin({
filename: 'app.css'
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
]
},
{
name: 'server',
target: 'node',
mode: 'development',
entry: [
'./resources/js/appServer.js',
],
devServer: {
hot: true
},
output: {
path: path.resolve(__dirname, 'public'),
filename: 'server.js',
publicPath: '/',
libraryTarget: 'commonjs2',
},
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
hmr: process.env.NODE_ENV === 'development'
}
},
'css-loader',
'sass-loader'
],
},
{
test: /\.vue$/,
loader: 'vue-loader'
}
]
},
plugins: [
new VueLoaderPlugin(),
new MiniCssExtractPlugin({
filename: 'app.css'
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
]
}
];
Add a file that name is htmlRenderer.js inside the resources folder:
export default html => `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Tanmay Mishu</title>
<link rel="stylesheet" href="/app.css">
</head>
<body>
<div id="app">${html}</div>
<script src="/client.js"></script>
</body>
</html>`;
Add a new file that name is appServer.js and its codes should be like following:
import Vue from 'vue';
import App from './components/App.vue';
import htmlRenderer from "../htmlRenderer";
const renderer = require('vue-server-renderer').createRenderer()
export default function serverRenderer({clientStats, serverStats}) {
Vue.config.devtools = true;
return (req, res, next) => {
const app = new Vue({
render: h => h(App),
});
renderer.renderToString(app, (err, html) => {
if (err) {
res.status(500).end('Internal Server Error')
return
}
res.end(htmlRenderer(html))
})
};
}
Now, just run yarn start and enjoy server-side rendering alongside hot reload.
I'm trying to deploy my reactjs (first) app on heroku, but i'm getting some trouble to get it online. I have no builds error, but when i try to launch my app i'm getting the "Cannot get /" error.
So on the Heroku logs i'm not getting any errors but only the heroku[routers] info with a 404 satus.
I did verify that my dist folder is not in .gitignore, try to add heroku logger (without success), add some code to package.json, server.json and webpack.config.js (without success).
my package.json (only important part) :
"engines": {
"node": "9.7.0",
"npm": "5.6.0"
},
"dependencies": {
"#material-ui/core": "^3.9.3",
"axios": "^0.17.1",
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-preset-env": "^1.6.1",
"babel-preset-react": "^6.24.1",
"css-loader": "^0.28.9",
"dotenv": "^5.0.0",
"express": "^4.16.2",
"extract-text-webpack-plugin": "^3.0.2",
"faker": "^4.1.0",
"heroku": "^7.24.4",
"html-webpack-plugin": "^2.30.1",
"logger": "0.0.1",
"material-ui": "^0.20.2",
"react": "^16.2.0",
"react-dom": "^16.2.0",
"react-draggable": "^3.3.0",
"react-img-zoom": "^0.1.0",
"react-magnifier": "^3.0.1",
"react-router-dom": "^5.0.0",
"react-tap-event-plugin": "^3.0.2",
"reactjs-popup": "^1.4.1",
"style-loader": "^0.19.1",
"twilio": "^3.28.0",
"twilio-video": "^1.15.2",
"webpack": "^3.12.0",
"webpack-dev-middleware": "^2.0.4",
"webpack-hot-middleware": "^2.21.0"
},
"scripts": {
"build": "./node_modules/.bin/webpack",
"start": "node server",
"start:prod": "npm run build && node server.js"
},
my webpack.config.js :
const HtmlWebpackPlugin = require("html-webpack-plugin");
const webpack = require("webpack");
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const path = require("path");
require("dotenv").config();
var configFunc = function(){
var config = {
devtool: "source-map",
entry: [
__dirname + "/app/app.js"
],
output: {
path: __dirname + "/dist",
filename: "bundle.js",
publicPath: "/"
},
module: {
rules: [
{
test: /\.js$/,
use: "babel-loader",
exclude: [/node_modules/]
},
{
test: /\.(sass|scss|css)$/,
use: [
{
loader: "style-loader" // creates style nodes from JS strings
},
{
loader: "css-loader" // translates CSS into CommonJS
}
]
}
]
},
plugins: [
new HtmlWebpackPlugin({
hash: true,
template: path.join(__dirname , "/app/index.html"),
inject: "body"
}),
new webpack.BannerPlugin("React Twilio"),
new ExtractTextPlugin("[name]-[hash].css")
]};
if(process.env.NODE_ENV === "PROD") {
config.plugins.push(new webpack.optimize.UglifyJsPlugin());
config.plugins.push(new webpack.optimize.CommonsChunkPlugin({
name: "commons",
filename: "commons.js"
}));
}
if(process.env.NODE_ENV === "DEV") {
config.entry.push('webpack-hot-middleware/client?reload=true');
config.plugins.push(new webpack.HotModuleReplacementPlugin());
}
return config;
}();
module.exports = configFunc;
my server.js :
require("dotenv").config();
var path = require("path");
var express = require("express");
var webpack = require("webpack");
var faker = require("faker");
var AccessToken = require("twilio").jwt.AccessToken;
var VideoGrant = AccessToken.VideoGrant;
var app = express();
if(process.env.NODE_ENV === "DEV") { // Configuration for development environment
var webpackDevMiddleware = require("webpack-dev-middleware");
var webpackHotMiddleware = require("webpack-hot-middleware");
var webpackConfig = require("./webpack.config.js");
const webpackCompiler = webpack(webpackConfig);
app.use(webpackDevMiddleware(webpackCompiler, {
hot: true
}));
app.use(webpackHotMiddleware(webpackCompiler));
app.use(express.static(path.join(__dirname, "app")));
} else if(process.env.NODE_ENV === "PROD") { // Configuration for production environment
app.use(express.static(path.join(__dirname, "dist")));
app.get('*', (req, res) => {
res.sendFile(path.resolve(__dirname, 'dist', 'index.html'));
});
}
app.use(function(req, res, next){
console.log("Request from: ", req.url);
next();
})
// Endpoint to generate access token
app.get("/token", function(request, response) {
var identity = faker.name.findName();
// Create an access token which we will sign and return to the client,
// containing the grant we just created
var token = new AccessToken(
process.env.TWILIO_ACCOUNT_SID,
process.env.TWILIO_API_KEY,
process.env.TWILIO_API_SECRET
);
// Assign the generated identity to the token
token.identity = identity;
const grant = new VideoGrant();
// Grant token access to the Video API features
token.addGrant(grant);
// Serialize the token to a JWT string and include it in a JSON
response
response.send({
identity: identity,
token: token.toJwt()
});
});
var port = process.env.PORT || 3000;
app.listen(port, function() {
console.log("Express server listening on *:" + port);
});
and my Procfile :
web: npm run start:prod
You can find my app at this link : https://salty-dawn-74805.herokuapp.com/
For information my app is inspired by this tutorial : https://www.twilio.com/blog/2018/03/video-chat-react.html
You can clone this repository to reproduce : https://github.com/kimobrian/TwilioReact.git
The only error message i get is this (and obviously my website is working well on localhost):
heroku[router]: at=info method=GET path="/" host=salty-dawn-74805.herokuapp.com request_id= fwd= dyno=web.1 connect=1ms service=20ms status=404 bytes=383 protocol=https
Hope someone can help me, i'm new to reactjs and it's the first time i'm deploying a reactjs app.
Thank you very much,
Solution found for others who maybe will have the same error !
It was really stupide : heroku node_env is "production" and i was verifying node_env equals "prod". :'(
I have an Angular app that is talking to a REST service.
When I run the Angular app local with the CLI, correctly proxies all /api requests to the REST service. When I try to build the app and run through a server.js (so that I can deploy the app to Heroku) I lose the proxy routing.
The REST service is deployed on Heroku and runs fine.
I run the Angular with:
ng serve
My proxy.conf.json
{
"/api": {
"target": "https://my-app.herokuapp.com",
"secure": true,
"changeOrigin": true
}
}
I created a server.js as described in this article so that I can deploy onto Heroku.
// server.js
const express = require('express');
const app = express();
const path = require('path');
// If an incoming request uses
// a protocol other than HTTPS,
// redirect that request to the
// same url but with HTTPS
const forceSSL = function () {
return function (req, res, next) {
if (req.headers['x-forwarded-proto'] !== 'https') {
return res.redirect(
['https://', req.get('Host'), req.url].join('')
);
}
next();
}
}
// Instruct the app
// to use the forceSSL
// middleware
app.use(forceSSL());
// Run the app by serving the static files
// in the dist directory
app.use(express.static(__dirname + '/dist'));
// For all GET requests, send back index.html
// so that PathLocationStrategy can be used
app.get('/*', function (req, res) {
res.sendFile(path.join(__dirname + '/dist/index.html'));
});
// Start the app by listening on the default
// Heroku port
app.listen(process.env.PORT || 4200);
I also set up a post install build in my package.json scripts:
{
"name": "catalog-manager-client",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "node server.js",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e",
"postinstall": "ng build --aot"
},
"private": true,
"dependencies": {
"#angular/animations": "^6.0.3",
"#angular/cdk": "^6.2.1",
"#angular/cli": "~6.0.8",
"#angular/language-service": "^6.0.3",
"#angular/common": "^6.0.3",
"#angular/compiler": "^6.0.3",
"#angular/compiler-cli": "^6.0.3",
"#angular/core": "^6.0.3",
"#angular/flex-layout": "^6.0.0-beta.16",
"#angular/forms": "^6.0.5",
"#angular/http": "^6.0.3",
"#angular/material": "^6.2.1",
"#angular/platform-browser": "^6.0.3",
"#angular/platform-browser-dynamic": "^6.0.3",
"#angular/router": "^6.0.3",
"#swimlane/ngx-charts": "^8.0.2",
"#swimlane/ngx-datatable": "^13.0.1",
"core-js": "^2.5.4",
"express": "^4.16.4",
"hammerjs": "^2.0.8",
"jquery": "^3.3.1",
"moment": "^2.22.2",
"ngx-perfect-scrollbar": "^6.2.0",
"ngx-quill": "^3.2.0",
"rxjs": "^6.0.0",
"rxjs-compat": "^6.2.1",
"rxjs-tslint": "^0.1.4",
"zone.js": "^0.8.26"
},
"devDependencies": {
"#angular-devkit/build-angular": "~0.6.8",
"typescript": "~2.7.2",
"#types/jasmine": "~2.8.6",
"#types/jasminewd2": "~2.0.3",
"#types/node": "~8.9.4",
"codelyzer": "~4.2.1",
"jasmine-core": "~2.99.1",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~1.7.1",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "~2.0.0",
"karma-jasmine": "~1.1.1",
"karma-jasmine-html-reporter": "^0.2.2",
"protractor": "~5.3.0",
"ts-node": "~5.0.1",
"tslint": "~5.9.1"
},
"engines": {
"node": "9.11.2",
"npm": "6.5.0"
}
}
I am an Angular novice so I could be making a fundamental mistake, but how do I modify the server.js to use the proxy.conf.json settings?
The explanation falls into the yes, you're making a fundamental mistake category, but I've seen enough similar questions that I thought an explanation might just help the next dev.
The Angular CLI is running a full http server. The Angular UI is fully compiled and the CLI is serving it as static content from the /dist directory.
The proxy.conf.json settings are for the Server run by the Angular CLI, it has nothing to do with your compiled code.
When you move from a local environment to something like Heroku you need a server to take the place of the Angular CLI. This is where all the examples of node.js and express come in. The simple server.js file they walk you through is enough to set up a basic static content server. And that's fine, because your Angular code is static content!
But if you need routing to a dynamic backend server via a proxy.conf.json, well, your simple static server doesn't know anything about that.
In my case, my backend server runs on Koa, so I added static routing to the Angular code.
const router = require('koa-router')();
const body = require('koa-body')({ text: false });
const send = require('koa-send');
const fs = require('fs');
/**
* Code about server routes ommited
*/
async function main(ctx, next) {
//All dynamic routes start with "/api"
if (/\/api\//.test(ctx.path)) {
try {
await next();
}
catch (error) {
if (error instanceof ApplicationError) {
logger.error(error, { data: error.data, stack: error.stack });
ctx.status = error.code;
} else {
ctx.status = 500;
logger.error(error.message, { stack: error.stack });
}
}
return;
} else {
//Not a dynamic route, serve static content
if ((ctx.path != "/") && (fs.existsSync('dist' + ctx.path))) {
await send(ctx, 'dist' + ctx.path);
} else {
await send(ctx, 'dist/index.html');
}
}
}
module.exports = app => {
app.use(main);
app.use(router.routes());
};
NOTE - this isn't a performant solution for any kind of high workload, but if you've got a very small project that doesn't justify spending resources setting up something more scalable, this will work until you get bigger.
Any One looking for Implementation of angular application using proxy api on heroku you can use WebpackDev Server and http-proxy-middleware in server.js
npm install http-proxy-middleware
npm install webpack webpack-dev-server
webpack.config.js
const path = require('path');
module.exports = {
entry:'./src/index.js',//no implemenation needed by default webpack verification
mode: 'development',
devServer: {
historyApiFallback: true,// handle 404 cannot get error after refreshing url
https: true,//secure the server
compress: true,//invalid header multiple url proxy
client: {
webSocketURL: 'ws://0.0.0.0:8080/ws',// handle Invalid header error in heroku port 8080 maps in server.js
},
static: {
directory: path.join(__dirname, '/dist/<app-name>'),
},
proxy: {
/** Same as proxy.conf.json or proxy.conf.js */
' /api1/*': {
target: 'https://<other-heroku-deployed-url>',
changeOrigin:true,
secure:false,
pathRewrite: {
'^/api1':'https://<other-heroku-deployed-url>/api1' },
},
' /api2/*': {
target: 'https://<other-heroku-deployed-url>',
changeOrigin:true,
secure:false,
pathRewrite: {
'^/api2':'https://<other-heroku-deployed-url>/api2' },
}
},
},
};
server.js
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const app = express();
const Webpack = require('webpack');
const WebpackDevServer = require('webpack-dev-server');
/** this is custom js to help proxy in server.js*/
const webpackConfig = require('./webpack.config.js');
const compiler = Webpack(webpackConfig);
const devServerOptions = { ...webpackConfig.devServer, open: true };
const server = new WebpackDevServer(devServerOptions, compiler);
const runServer = async () => {
console.log('Starting server...');
await server.start();
};
runServer();
/** If you have error creating proxy <app-url> to localhost
* Heroku internally redirect the Server port 8080 .
* For that reason we need to open listener port(I used 3000 here) redirect
through http-proxy-middleware*/
app.use("/*", createProxyMiddleware(
{ target: "https://localhost:8080",
ws: true ,
changeOrigin: true,
secure:false,
router: {
'dev.localhost:3000': 'https://localhost:8080',
},}))
app.listen(process.env.PORT || 3000)
npm start or node server.js