Webpack: Cannot get index.html with express server middleware - node.js

I followed the official getting started instructions of webpack to setup a litte test project and config file. As they recommend, I use an express server with webpack middleware.
The setup
server.js:
const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const app = express();
const config = require('./webpack.config.js');
const compiler = webpack(config);
// Tell express to use the webpack-dev-middleware and use the webpack.config.js
// configuration file as a base.
app.use(
webpackDevMiddleware(compiler, {
publicPath: config.output.publicPath,
})
);
// Serve the files on port 3000.
app.listen(3000, function () {
console.log('Example app listening on port 3000!\n');
});
My directory folder looks like this:
- dist/
- assets/
- css/
- bundle.index.html
- bundle.about.html
- index.html
- about.html
- src/
- fonts/
- images/
- sass/
- templates/
- about.html
- index.html
- ts/
- abstracts/
- utils/
- pages/
- about.ts
- index.ts
An this is my webpack.config (removed unnecessary items for this purpose).
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
module.exports = {
mode: mode,
entry: {
index: './src/ts/pages/index.ts',
about: './src/ts/pages/about.ts',
},
output: {
filename: 'bundle.[name].js',
path: path.resolve(__dirname, './dist'),
clean: true,
publicPath: '.',
assetModuleFilename: 'assets/[name][ext][query]',
},
devServer: {
port: 3000,
sockPort: 3000,
contentBase: path.resolve(__dirname, 'dist'),
},
plugins: [
new HtmlWebpackPlugin({
title: 'Index',
filename: 'index.html',
template: 'dist/index.html',
chunks: ['index'],
}),
new HtmlWebpackPlugin({
title: 'about',
filename: 'about.html',
template: 'dist/about.html',
chunks: ['about'],
}),
],
module: {
rules: [
{
test: /\.html$/i,
loader: "html-loader",
},
// Support von versch. Grafiktypen
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
generator: {
filename: './assets/images/[name][ext][query]'
}
},
// Support von Fonts
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
generator: {
filename: './assets/fonts/[name][ext][query]'
}
},
],
},
resolve: {
extensions: ['.ts'],
alias: {
Style: path.resolve(__dirname, './src/sass'),
Fonts: path.resolve(__dirname, './src/fonts'),
Images: path.resolve(__dirname, './src/images'),
}
}
};
The problem
Running localhost:3000 in my browser, express returns Cannot get /.
Running localhost:3000 in my browser, express returns Cannot get / index.html.
Running localhost:3000/dist/index.html in my browser, express returns Cannot get dist/index.html.
So simply said, I cannot accees anything. Why? My config says that the
dist directory should be used as root directory.
devServer: {
port: 3000,
sockPort: 3000,
contentBase: path.resolve(__dirname, 'dist'),
},

Below values for the publicPath option should work:
publicPath: 'auto'
publicPath: '/'
publicPath: ''
Complete example:
webpack.config.js:
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
mode: "development",
entry: {
index: "./src/ts/pages/index.ts",
about: "./src/ts/pages/about.ts",
},
output: {
filename: "bundle.[name].js",
path: path.resolve(__dirname, "./dist"),
clean: true,
publicPath: "",
},
plugins: [
new HtmlWebpackPlugin({
title: "Index",
filename: "index.html",
template: "src/templates/index.html",
chunks: ["index"],
}),
new HtmlWebpackPlugin({
title: "about",
filename: "about.html",
template: "src/templates/about.html",
chunks: ["about"],
}),
]
};
src/templates/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
index
</body>
</html>
src/ts/pages/index.ts:
console.log("index");
server.js:
const express = require("express");
const webpack = require("webpack");
const webpackDevMiddleware = require("webpack-dev-middleware");
const app = express();
const config = require("./webpack.config.js");
const compiler = webpack(config);
app.use(
webpackDevMiddleware(compiler, {
publicPath: config.output.publicPath,
})
);
app.listen(3000, function () {
console.log("Example app listening on port 3000!\n");
});
Result:
Package versions:
"devDependencies": {
"html-webpack-plugin": "^5.3.2",
"webpack": "^5.51.1",
"webpack-cli": "^4.8.0",
"webpack-dev-server": "^4.0.0"
},
"dependencies": {
"express": "^4.17.1",
"webpack-dev-middleware": "^5.0.0"
}

Related

Cannot GET /path using ReactJS, webpack, express

I've tried almost every solution but I can't find any solution for this problem. I'm using express to render my ReactJS code, build with webpack. I can open pages without any issues until I'm being redirected from home page. But when I tried entering the URL on browser or refresh the page, I cannot see the page. Instead I see this error:
Cannot GET /path
I have also tried adding historyApiFallback: true to my webpack.config.js but no luck there.
Below are my scripts from package.json
{
...
"scripts": {
"build": "webpack --config webpack.config.js",
"dev": "webpack-dev-server --mode development --open",
"start": "npm run build && node server.js"
},
...
}
And this is my webpack.config.js:
const HtmlWebPackPlugin = require("html-webpack-plugin");
const path = require('path');
const htmlPlugin = new HtmlWebPackPlugin({
template: "./src/index.html",
filename: "./index.html"
});
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'main.js',
publicPath: '/',
},
devServer: {
historyApiFallback: true,
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
},
{
test: /\.css$/,
use: ["style-loader", "css-loader"]
},
]
},
plugins: [htmlPlugin],
resolve: {
extensions: ['*', '.js', '.jsx']
},
};
And the server.js file:
const path = require('path');
const express = require('express');
const PORT = process.env.PORT || 3000;
const app = express();
app.use(express.static(path.join(__dirname, 'dist')));
app.get('/', function(request, response) {
response.sendFile(__dirname + '/dist/index.html');
});
app.listen(PORT, error => (
error
? console.error(error)
: console.info(`Listening on port ${PORT}. Visit http://localhost:${PORT}/ in your browser.`)
));
Important NOTE
When I run server with npm run dev, there is no issues. I can enter URL manually and refresh the page.
But when I run server with npm run start, I am facing the issue I described above.
app.get('/', ... ); only sends back the index.html on the / path, you need to return it on every path
app.get('*', function(request, response) {
response.sendFile(__dirname + '/dist/index.html');
});

How to set up webpack-hot-middleware in an express 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.

Webpack main.js error "Unexpected token <" when served from express server

I'm trying to serve my react app from an express server with a catch all route so that users can still access react routes when they refresh the page or manually type in a url. However, when I try to serve the app entry point, /dist/index.html, from my express server I get Uncaught SyntaxError: Unexpected token <
When I look at the main.js file in the sources tab of the console, this is what is see:
<!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>React E-Store</title>
</head>
<body>
<div id="root">
</div>
</body>
<script src="/dist/main.js"></script>
</html>
This is what my server.js file looks like:
const express = require('express')
const path = require('path')
const port = process.env.PORT || 8080
const app = express()
// Handles all routes so you do not get a not found error
app.get('/*', function (request, response){
response.sendFile(path.resolve(__dirname, "dist", "index.html"))
})
app.listen(port)
console.log("server started on port " + port)
and here is my webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlWebpackPluginConfig = new HtmlWebpackPlugin({
template: './index.html',
filename: 'index.html',
inject: 'body'
})
module.exports = {
entry: './src/app.js',
output: {
path: path.resolve('dist'),
publicPath: '/dist/',
filename: '[name].js'
},
module: {
loaders: [
{ test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/ },
{ test: /\.jsx$/, loader: 'babel-loader', exclude: /node_modules/ },
{
test: /\.(png|jp(e*)g|svg)$/,
loader: 'url-loader',
options: {
limit: 8000, // Convert images < 8kb to base64 strings
name: 'images/[hash]-[name].[ext]'
}
},
{
test: /\.sass$/,
use: [{
loader: "style-loader" // creates style nodes from JS strings
}, {
loader: "css-loader" // translates CSS into CommonJS
}, {
loader: "sass-loader" // compiles Sass to CSS
}]}
]
},
plugins: [HtmlWebpackPluginConfig]
}
I appreciate any help

Webpack-dev-server doesn't find files to serve

I just copy-pasted webpack-dev-server + express settings from webpack docs, but it doesn't work because the server can't find files to serve. What is wrong with this setup?
server.js
const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const app = express();
const config = require('../webpack.config.js');
const compiler = webpack(config);
app.use(webpackDevMiddleware(compiler, {
publicPath: config.output.publicPath
}));
/*app.use('/',express.static('public')) it works when used insted of webpack dev middleware */
// Serve the files on port 3000.
app.listen(3000, function () {
console.log('Example app listening on port 3000!\n');
});
webpack.config.js
const path = require('path');
const webpack = require('webpack');
const scssRules = {
test:/\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader']
};
const jsRules = {
test: /\.js$/,
use: [
{loader: 'babel-loader'}
]
};
module.exports = {
entry: './js/App.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'public'),
publicPath: '/'
},
module: {
rules: [
jsRules,
scssRules
]
},
devServer: {
contentBase: './public'
},
plugins:[
new webpack.HotModuleReplacementPlugin()
],
devtool: 'inline-source-map'
}
File structure:
What i see:
I also came across this problem.
Turns out webpack-dev-middleware doesn't output any files, instead serves from memory. While the express server is expecting physical files.
Here, this plugin might help: https://github.com/gajus/write-file-webpack-plugin

How to use webpack-dev-middleware with feathers / express?

I'm trying to get a feathersjs app started with a reactjs frontend. Using the webpack-dev-middleware and webpack-hot-middleware, I should be able to simply extend the feathers app with all this webpack stuff during development. The only problem is always end up getting a feathers 404 page whenever I fetch the js file from webpack.
Currrently, here's my directory structure:
/feathers/public/index.html
/feathers/src/app.js
/react/src/index.js
/react/webpack.config.js
/react/develop.js
/feathers/src/app.js is is default feathers app, serves static files from the public folder.
.use('/', serveStatic( app.get('public') ))
In /react/develop.js, I'm requiring the feathers app and extending it with the webpack middlewares.
const app = require('../feathers/src/app');
const config = require('./webpack.config');
const path = require('path');
const webpack = require('webpack');
var compiler = webpack(config);
app.use(require('webpack-dev-middleware')(compiler, {
publicPath: '/',
stats: {colors: true},
}));
app.use(require('webpack-hot-middleware')(compiler));
const port = app.get('port');
const server = app.listen(port);
server.on('listening', () =>
console.log(`Feathers application started on ${app.get('host')}:${port}`)
);
Sadly this isn't working at all. For reference, here's my /react/webpack.config.js
var webpack = require("webpack")
module.exports = {
devtool: 'source-map',
entry: [
'webpack-hot-middleware/client',
'src/index.js'
],
output: {
path: '/',
filename: "bundle.js",
},
module: {
loaders: [
{ test: /\.js$/, loader: "babel", exclude: /node_modules/, query: { presets: ['es2015', 'react', 'stage-0'] } },
{ test: /\.(svg|png|jpe?g|gif|ttf|woff2?|eot)$/, loader: 'url?limit=8182' },
]
},
resolve: {
root: [
__dirname,
__dirname + '/src',
]
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin(),
]
}
And /feathers/public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>React App</title>
</head>
<body>
<div id="root"></div>
<script src="bundle.js"></script>
</body>
</html>
I've tried messing around with the publicPath stuff but no luck. Any ideas how to get this working? I've spend a solid 2 hours on this and got no where. Here's a link to the repo I'm working with for more context.
I see from your repository that you got this to work by including the webpack dev/hot middlewares in the proper place, in feathers/src/middleware/index.js where they will be used before Feathers' notFound middleware returns the 404. Middleware order matters!
Exporting a function for this purpose like you did in react/middleware.js is a clean solution to this problem, because it isolates the concern of setting up the webpack middleware from the backend itself (all the webpack stuff stays in the frontend).
Hope this helps anyone else!

Resources