I am building an Application that has a Node Backend that I am trying to bundle with Webpack.
At first I had one Webpack configuration that had target: node. I was unable to compile Websockets into the frontend bundle unless I changed it to target: web but that wasn't compiling my backend code changes. I would have to run tsc && webpack.
I am now to two configs to compile them separately. My current config is:
const path = require("path");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
var fs = require("fs");
var nodeModules = {};
fs.readdirSync("node_modules")
.filter(function(x) {
return [".bin"].indexOf(x) === -1;
})
.forEach(function(mod) {
nodeModules[mod] = "commonjs " + mod;
});
const common = {
module: {
rules: [
{
test: /\.tsx?$/,
use: "ts-loader",
exclude: /node_modules/
},
{
test: /\.html$/,
use: [{ loader: "html-loader" }]
}
]
},
resolve: {
extensions: [".tsx", ".ts", ".js"]
}
};
const frontend = {
entry: "./src/index.ts",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "build"),
publicPath: "/"
},
target: "web",
plugins: [
new CopyWebpackPlugin([
{
from: path.resolve(
"node_modules/#webcomponents/webcomponentsjs/webcomponents-bundle.js"
),
to: path.resolve(__dirname, "build/vendor")
},
{
from: path.resolve(
"node_modules/#webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"
),
to: path.resolve(__dirname, "build/vendor")
}
]),
new HtmlWebpackPlugin({
title: "Flop The World Poker",
template: "build/index.template.html"
})
]
};
const backend = {
entry: "./src/server.ts",
output: {
filename: "server.js",
path: path.resolve(__dirname, "build"),
publicPath: "/"
},
target: "node",
externals: nodeModules
};
module.exports = [
Object.assign({}, common, frontend),
Object.assign({}, common, backend)
];
Before I switch to two configs I was able to host my index.html that is in my output folder using:
import bodyParser from "body-parser";
import express from "express";
import { createServer } from "http";
import { listen } from "socket.io";
import { DeckController} from "./controllers";
const app: express.Application = express();
const port: number = ((process.env.PORT as any) as number) || 3000;
const server = createServer(app);
const io = listen(server);
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(express.static("static"));
app.use("/deck", DeckController);
app.use(express.static(__dirname, { extensions: ["html"] }));
server.listen(port, () => {
console.log(`Listening at http://localhost:${port}/`);
});
io.on("connection", socket => {
console.log("Client connected..");
socket.on("join", data => {
console.log(data);
});
});
I am now receiving cannot get /. I can set up a route like
router.get("/", (req: Request, res: Response) => {
res.send("Hello World");
});
and receive Hello World in the browser.
Can anyone help me to figure out what changed that now I cannot host my html file?
I fixed this with help from Robert in the comments. Here are my new files:
const path = require("path");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
var fs = require("fs");
var nodeModules = {};
fs.readdirSync("node_modules")
.filter(function(x) {
return [".bin"].indexOf(x) === -1;
})
.forEach(function(mod) {
nodeModules[mod] = "commonjs " + mod;
});
const common = {
module: {
rules: [
{
test: /\.tsx?$/,
use: "ts-loader",
exclude: /node_modules/
}
]
},
resolve: {
extensions: [".tsx", ".ts", ".js"]
}
};
const frontend = {
entry: "./src/index.ts",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "build"),
publicPath: "/"
},
target: "web",
plugins: [
new CopyWebpackPlugin([
{
from: path.resolve(
"node_modules/#webcomponents/webcomponentsjs/webcomponents-bundle.js"
),
to: path.resolve(__dirname, "build/vendor")
},
{
from: path.resolve(
"node_modules/#webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"
),
to: path.resolve(__dirname, "build/vendor")
}
]),
new HtmlWebpackPlugin({
title: "Flop The World Poker",
template: "build/index.template.html"
})
]
};
const backend = {
entry: "./src/server.ts",
output: {
filename: "server.js",
path: path.resolve(__dirname, "build"),
publicPath: "/"
},
target: "node",
externals: nodeModules
};
module.exports = [
Object.assign({}, common, frontend),
Object.assign({}, common, backend)
];
import bodyParser from "body-parser";
import express from "express";
import { createServer } from "http";
import { listen } from "socket.io";
import { DeckController } from "./controllers";
const app: express.Application = express();
const port: number = ((process.env.PORT as any) as number) || 3000;
const server = createServer(app);
const io = listen(server);
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(express.static("./build"));
app.use("/deck", DeckController);
app.use(express.static(__dirname, { extensions: ["html"] }));
server.listen(port, () => {
// tslint:disable-next-line:no-console
console.log(`Listening at http://localhost:${port}/`);
});
io.on("connection", socket => {
console.log("Client connected..");
socket.send("Testing Message");
socket.on("join", data => {
console.log(data);
});
});
Related
Recently I faced a following issue: I'm using expressJS framework for some backend stuff and webpack-dev-middleware for compiling assets during development, seems that dev-middleware is doing its stuff - compiling css and js, because I see some green messages on terminal, but I am constantly geting 404 error when I try to access my js or css file - /css/app.css - 404, /js/app.js - 404 etc. When I try to compile assets using webpack all works fine and I get compiled css and js files with no problems. There is github repo here
app.js
const createError = require('http-errors');
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const logger = require('morgan');
const argv = require('yargs').argv;
const app = express();
const devMiddleware = require('webpack-dev-middleware');
const webpack = require('webpack');
const webpackConfig = require('../../webpack.config');
const compiler = webpack(webpackConfig(null, { mode: 'development' }));
// view engine setup
app.set('views', path.join(__dirname, '../../views'));
app.set('view engine', 'pug');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.get('/', (req, res, next) => {
res.render('index', { title: "home" });
})
app.use(express.static(path.join(__dirname, '../../public')));
// catch 404 and forward to error handler
app.use(function (req, res, next) {
next(createError(404));
});
if (argv.mode !== 'production') {
app.use(devMiddleware(compiler, {
noInfo: true,
publicPath: webpackConfig(null, { mode: 'development' }).output.publicPath
}));
app.use(require("webpack-hot-middleware")(compiler));
}
// 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;
webpack.config.js
const Path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserJSPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');
const webpack = require('webpack');
module.exports = (env, argv) => {
return {
entry: {
app: [
"./src/client/app.js",
"./src/scss/style.scss",
'webpack-hot-middleware/client?path=http://localhost/__webpack_hmr&timeout=20000'
]
},
output: {
filename: 'js/[name].js',
path: Path.resolve(__dirname, 'public'),
publicPath: '/'
},
mode: argv.mode || 'development',
devtool: argv.mode !== 'production' ? 'source-map' : false,
optimization: {
minimizer: [
new TerserJSPlugin({ sourceMap: true }),
new OptimizeCSSAssetsPlugin({
cssProcessorOptions: {
map: argv.mode !== 'production' ? {
inline: false // set to false if you want CSS source maps
} : null
}
})],
},
plugins: [
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: 'css/[name].css',
chunkFilename: '[id].css',
}),
new CopyPlugin([
{ from: 'images/*' }
]),
// OccurrenceOrderPlugin is needed for webpack 1.x only
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.HotModuleReplacementPlugin(),
// Use NoErrorsPlugin for webpack 1.x
new webpack.NoEmitOnErrorsPlugin()
],
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: "babel-loader",
}
]
},
{
test: /\.s?css$/,
exclude: [Path.resolve("/node_modules/")],
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
// you can specify a publicPath here
// by default it uses publicPath in webpackOptions.output
publicPath: '../',
hmr: process.env.NODE_ENV === 'development',
},
}, {
loader: "css-loader",
options: {
sourceMap: true,
url: false,
}
}, {
loader: 'postcss-loader',
options: {
sourceMap: true
}
}, {
loader: "sass-loader",
options: {
sourceMap: true,
includePaths: [Path.resolve('src/scss')],
}
}
]
},
]
}
}
};
localhost:9400/ always prints my index.js file in the browser. All the urls after / are working as expected. I also want to know if this is server-side rendering or client-side since I am sending bundle.js as script file in index.html.
index.js:
/ import React from "react"
// import { renderToString } from "react-dom/server"
// import { StaticRouter, matchPath } from "react-router-dom"
// import serialize from "serialize-javascript"
// import App from '../shared/App'
// import routes from '../shared/routes'
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
process.env.PORT = process.env.PORT || 9400;
process.env.MONGO_HOST = process.env.MONGO_HOST || '192.168.1.81';
process.env.JWT_EXPIRE = process.env.JWT_EXPIRE || 200;
var _ = require('lodash');
var path = require('path');
var express = require('express');
var config = require('core-npm').config;
var db = require('core-npm').mongoose;
var app = express();
config.root = path.normalize(__dirname + '/..');
// expose the function to start the server instance
app.startServer = startServer;
app.serverShutdown = serverShutdown;
// Setup Express
require('core-npm').express(app);
var coreNPM = require('core-npm');
/////configure routes for frontend UI routing////////
// app.use('/api/users', coreNPM.userRoutes());
// Setup Routes
require('../../server/routes')(app);
// register the shutdown handler to close the database connection on interrupt signals
process
.on('SIGINT', serverShutdown)
.on('SIGTERM', serverShutdown);
/**
* Create an express http server and return it
* #api private
* #return
*/
function startServer() {
var server = require('http').createServer(app);
return server;
}
var coreNPM = require('core-npm');
// start sockets for this instance and start server
app.startServer().listen(app.get('port'), app.get('ip'), function serverStarted() {
console.log('OPRS App started server on ip %s on port %d, in %s mode',
app.get('ip'), app.get('port'), app.get('env'));
});
/**
* Shutdown handler
* Closes the database connection on iterrupt and exits the process
* #api private
*/
////enable script if mongodb connection establishment in environment
function serverShutdown() {
db.connection.close(function connectionClose() {
console.log('Database connection disconnected through app termination');
process.exit(0);
});
}
routes.js in the express server:
use strict';
console.log("routes executed");
var path = require('path');
var coreNPM = require('core-npm');
var middleware = coreNPM.middleware;
module.exports = function (app) {
// extend response with custom methods
app.use(middleware.extendResponse);
// default CUD middleware
app
.put(middleware.removeReservedSchemaKeywords)
.patch(middleware.removeReservedSchemaKeywords)
.delete(middleware.removeReservedSchemaKeywords)
.post(middleware.removeReservedSchemaKeywords);
// Insert routes below
app.use('/api/users', coreNPM.userRoutes());
app.use('/auth', coreNPM.auth);
// All undefined asset or api routes should return a 404
app.route('/:url(api|auth|components|app|bower_components|assets)/*')
.get(function invalidRoute(req, res) { return res.notFound(); });
// All other routes should redirect to the index.html
app.route('/*')
.get(function getIndexFile(req, res) {
console.log("app path from router ============================"+app.get('appPath'));
res.sendFile('/home/pavan/Downloads/rrssr-master/src/server/index.html');
});
// register the default error handler
app.use(middleware.defaultErrorHandler);
};
Another routes.js in my src:
import Home from './components/Home'
import SignIn from './components/SignIn';
const routes = [
{
path: '/home',
exact: true,
component: Home,
},
{
path: '/login',
exact: true,
component: SignIn
},
{
path: '/',
exact: true,
component: SignIn
}
]
export default routes
webpack.config.js:
var path = require('path')
var webpack = require('webpack')
var nodeExternals = require('webpack-node-externals')
var browserConfig = {
entry: './src/browser/index.js',
output: {
path: path.resolve(__dirname, 'public'),
filename: 'bundle.js',
publicPath: '/'
},
module: {
rules: [
{ test: /\.(js)$/, use: 'babel-loader' },
]
},
plugins: [
new webpack.DefinePlugin({
__isBrowser__: "true"
})
]
}
var serverConfig = {
entry: './src/server/index.js',
target: 'node',
externals: [nodeExternals()],
output: {
path: __dirname,
filename: 'server.js',
},
module: {
rules: [
{ test: /\.(js)$/, use: 'babel-loader' },
{
test : /\.css$/,
use : [
{
loader : 'style-loader'
}
]
},
{
test : /\.css$/,
use : [
{
loader : 'css-loader'
}
]
}
]
},
plugins: [
new webpack.DefinePlugin({
__isBrowser__: "false"
})
]
}
module.exports = [browserConfig, serverConfig]
And finally my index.html file
index.html:
<html>
<head>
<title>SSR with RR</title>
<script src="/bundle.js" defer></script>
</head>
<body>
<div>
<div id="app"></div>
</div>
</body>
</html>
i want to load images in public path. I use webpackDevMiddleware + express + react
i have webpack config:
const path = require('path');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const htmlWebpackPlugin = require('html-webpack-plugin');
const autoprefixer = require('autoprefixer');
const ExtraneousFileCleanupPlugin = require('webpack-extraneous-file-cleanup-plugin');
const VENDOR_LIBS = [
'lodash',
'react',
'react-dom',
'react-router-dom',
'babel-polyfill'
];
const config = {
entry: {
app: './public/js/app.js',
vendor: VENDOR_LIBS,
},
devtool: 'eval-source-map',
output: {
path: path.resolve(__dirname, 'public/build'),
filename: '[name].[hash].js',
publicPath: '/'
},
stats: {
colors: true
},
module: {
rules: [
{
use: 'babel-loader',
test: /\.jsx?$/,
exclude: /node_modules/
},
{
use: ['style-loader', 'css-loader'],
test: /\.css$/
},
{
test: /\.styl$/,
loader: ExtractTextPlugin.extract({ fallback: 'style-loader',
use: [
'css-loader?minimize!',
{
loader: 'postcss-loader',
options: {
plugins: function () {
return [autoprefixer()]
},
sourceMap: 'inline'
}
},
'stylus-loader'
]
})
},
{
test: /\.(png|woff|woff2|otf|eot|ttf|svg|jpg|jpeg)$/,
loader: 'url-loader?limit=100000'
}
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
names: ['vendor', 'manifest'],
}),
new ExtractTextPlugin("[name].[hash].min.css"),
new htmlWebpackPlugin({
template: 'index.html'
}),
new webpack.DefinePlugin({
__DEV__: JSON.stringify(JSON.parse(process.env.DEBUG || 'false'))
})
/*new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})*/
]
}
module.exports = config;
Also, app.js entry point.
const express = require('express');
const app = express();
const path = require('path');
const fs = require('fs');
const router = express.Router();
const history = require('connect-history-api-fallback');
if(process.env.NODE_ENV != 'production'){
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');
const webpackConfig = require('./webpack.config.js');
const compiler = webpack(webpackConfig)
app.use(history());
app.use(webpackDevMiddleware(compiler, {
publicPath: webpackConfig.output.publicPath,
stats: {colors: true}
}))
} else {
app.use("/", express.static(__dirname));
app.get('*', function(req, res) {
res.sendFile(path.resolve(__dirname, 'public/build', 'index.html'));
});
}
app.listen(3030, () => console.log('listen 3030'));
And some component Menu:
import React from 'react'
import { Link } from 'react-router-dom'
import Logo from '../../../img/logo.svg'
class Menu extends React.Component {
constructor(){
super()
}
render(){
return (
<Link to="/">
<img src='public/img/logo.svg'/>
</Link>
)
}
}
export default Menu;
At all component i want to use <img src="/public/img/***.***"> or <img src="/img/***.***">, but this not work. If i use import Logo from '../../../img/logo.svg' it is work. But i have many images loading from server and i need path for examle '/public/img' or some else path.
My Project structure:
public
bulid
js
img
styl
font
node_modules
webpack.config.js
app.js
****(other)***
Wepack 2 resolve alias does not work server side
I run wepback middleware with my express server. If the server is started when the import is relative, it'll work fine:
`import from '../../foobar/stuff'`
While the server is already running and the initial server side render is ready in memory; If we change the import in the component, to use the resolve property previously defined through webpack, it'll work:
`import from 'foobar/stuff'`
Meanwhile, if the server is stopped, and re-run with this last change, where the import uses the webpack resolve property defined in the configuration, this will fail (triggers the error not found)
File structure:
[root]
node_modules
webpack.config.js
|____ src
|____ src/js
|____ src/js/modules/
|____ src/js/modules/foobar/containers|components/**/*.js
|____ src/js/modules/lorem/containers|components/**/*.js
It seems that the express server does not know how to resolve the path that it's defined in the webpack 2 resolve property (even though I pass the webpackDevConfig file to the middleware).
Here's the server.dev.js:
import express from 'express'
import path from 'path'
import superagent from 'superagent'
import chalk from 'chalk'
import React from 'react'
import { renderToString } from 'react-dom/server'
import { StaticRouter } from 'react-router'
import configureStore from './src/js/root/store'
import { Provider } from 'react-redux'
import MyApp from './src/js/modules/app/containers/app'
import Routes from './src/js/root/routes'
const myAppChildRoutes = Routes[0].routes
const app = express()
const port = process.env.PORT ? process.env.PORT : 3000
var serverInstance = null
var dist = path.join(__dirname, 'dist/' + process.env.NODE_ENV)
var config = null
const webpack = require('webpack')
const webpackHotMiddleware = require('webpack-hot-middleware')
const webpackDevConfig = require('./webpack.dev.config')
const compiler = webpack(require('./webpack.dev.config'))
var webpackDevMiddleware = require('webpack-dev-middleware')
const webpackAssets = require('./webpack-assets.json')
config = require('./config')
/**
* Process error handling
*/
process.on('uncaughtException', (err) => {
throw err
})
process.on('SIGINT', () => {
serverInstance.close()
process.exit(0)
})
app.set('views', path.join(__dirname, 'src'))
app.set('view engine', 'ejs')
app.use(webpackDevMiddleware(compiler, {
noInfo: true,
publicPath: webpackDevConfig.output.publicPath,
stats: {
colors: true,
hash: false,
version: true,
timings: false,
assets: false,
chunks: false,
modules: false,
reasons: false,
children: false,
source: false,
errors: true,
errorDetails: true,
warnings: true,
publicPath: false
}
}))
app.use(webpackHotMiddleware(compiler, {
log: console.log
}))
/**
* The Cross origin resource sharing rules
*/
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE')
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type')
res.setHeader('Access-Control-Allow-Credentials', true)
next()
})
/**
* Health check
*/
app.use('/healthcheck', (req, res) => {
res.json({
'env': {
'NODE_ENV': process.env.NODE_ENV
}
})
res.end()
})
app.use('/api/test', (req, res) => {
superagent
.get('https://jsonip.com/')
.end((err, response) => {
if (err) {
console.log('api test err', err)
}
res.send(response.body)
})
})
app.use('/assets', express.static(dist))
app.get('*', (req, res) => {
// (wip) migration to react-router v4 temporary solution
// let matches
// if (typeof routes.props.children !== 'undefined' && Array.isArray(routes.props.children)) {
// matches = routes.props.children.find((v) => {
// return v.props.path === req.url
// })
// } else {
// matches = routes.props.children.props.path === req.url
// }
let matches = true
if (!matches) {
res.status(404).send('Not found')
} else {
const preloadedState = {'foobar': 1}
// Create a new Redux store instance
const store = configureStore(preloadedState)
// Render the component to a string
const myAppHtml = renderToString(<StaticRouter context={{}} location={req.url}>
<Provider store={store}>
<MyApp routes={myAppChildRoutes} />
</Provider>
</StaticRouter>)
// Grab the initial state from our Redux store
const finalState = store.getState()
res.render('index', {
app: myAppHtml,
state: JSON.stringify(finalState).replace(/</g, '\\x3c'),
bundle: webpackAssets.main.js,
build: config.build_name,
css: '/assets/css/main.min.css'
})
}
})
serverInstance = app.listen(port, (error) => {
if (error) {
console.log(error) // eslint-disable-line no-console
}
console.log(chalk.green('[' + config.build_name + '] listening on port ' + port + '!'))
})
Finally, the webpack dev config file is:
var path = require('path')
var webpack = require('webpack')
var AssetsPlugin = require('assets-webpack-plugin')
var assetsPluginInstance = new AssetsPlugin()
var ExtractTextPlugin = require('extract-text-webpack-plugin')
module.exports = {
context: path.resolve(__dirname, 'src'),
entry: [
'react-hot-loader/patch',
'webpack/hot/dev-server',
'webpack-hot-middleware/client',
'babel-polyfill',
'./js/index.js'
],
output: {
path: path.join(__dirname, '/dist/development'),
publicPath: '/assets/',
filename: 'js/bundle.js?[hash]'
},
devtool: 'inline-source-map',
devServer: {
hot: true,
// match the output path
contentBase: path.join(__dirname, '/dist/development'),
// match the output `publicPath`
publicPath: '/assets/'
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /(node_modules)/,
use: [{
loader: 'babel-loader'
}]
},
{
test: /\.scss$/,
use: ['css-hot-loader'].concat(ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader', 'sass-loader']
}))
},
{
test: /\.(ttf|eot|svg|woff(2)?)(\?[a-z0-9=&.]+)?$/,
use: [
'file-loader?name=[path][name].[ext]'
]
},
{
test: /\.(jpg|png|gif|svg)$/i,
use: [
'file-loader?name=[path][name].[ext]&emitFile=false'
]
}
]
},
plugins: [
new ExtractTextPlugin('css/[name].min.css?[hash]'),
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(),
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('development')
}
}),
assetsPluginInstance
],
resolve: {
alias: {
modules: path.resolve(__dirname, 'src/js/modules')
}
}
}
Also, tried to include the path to resolve automaticaly, without success:
module.paths.unshift(path.resolve(__dirname, 'src/js'))
The process.env.NODE_PATH is:
process.env.NODE_PATH Module {
id: '.',
exports: {},
parent: null,
filename: '/Users/johnColtrane/www/coolApp/server.js',
loaded: false,
children: [],
paths:
[ '/Users/johnColtrane/www/coolApp/src/js',
'/Users/johnColtrane/www/coolApp/node_modules',
'/Users/johnColtrane/www/node_modules',
'/Users/johnColtrane/node_modules',
'/Users/node_modules',
'/node_modules' ] }
NOTE: someone suggested babel-plugin-module-alias instead, because of some issues with the approach I want to take. I'll check it and if better then this approach, I'll post here.
I'm trying to run my React app locally using React, Node, Webpack 2. Whenever I hit a route that isn't / I get a 404. My goal is to be able to run my node server, have webpack-dev-server run, use browserHistory and back my webpack historyApiFallback work.
What currently does work:
If I just run webpack-dev-server and no node server then browserHistory works fine, no 404s.
If I run node with hashHistory it works fine, no 404s.
So that rules out that my routes aren't working. Here is some code:
server.js
const express = require('express');
const expressGraphQL = require('express-graphql');
const schema = require('./schema');
const app = express();
app.use('/graphql', expressGraphQL({
schema,
graphiql: true
}));
const webpackMiddleware = require('webpack-dev-middleware');
const webpack = require('webpack');
const webpackConfig = require('../webpack.config.js');
app.use(webpackMiddleware(webpack(webpackConfig)));
app.listen(process.env.PORT || 5000, () => console.log('Listening'));
webpack.config.js
const webpack = require('webpack');
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const VENDOR_LIBS = [
'axios', 'react', 'react-dom', 'react-router', 'react-apollo', 'prop-types'
];
module.exports = {
entry: {
bundle: './client/src/index.js',
vendor: VENDOR_LIBS
},
output: {
path: path.join(__dirname, 'dist'),
publicPath: '/',
filename: '[name].[chunkhash].js'
},
module: {
rules: [
{
use: 'babel-loader',
test: /\.js$/,
exclude: /node_modules/
},
{
test: /\.scss$/,
use: [{
loader: "style-loader"
}, {
loader: "css-loader"
}, {
loader: "sass-loader"
}]
},
{
test: /\.(jpe?g|png|gif|svg|)$/,
use: [
{
loader: 'url-loader',
options: {limit: 40000}
},
'image-webpack-loader'
]
}
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
names: ['vendor', 'manifest']
}),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
}),
new HtmlWebpackPlugin({
template: './client/src/index.html'
})
],
devServer: {
historyApiFallback: true
}
};
routes.js
import React from 'react';
import { Router, Route, IndexRoute, browserHistory } from 'react-router';
import App from './components/App';
import Portal from './components/portal/Portal';
const componentRoutes = {
component: App,
path: '/',
indexRoute: { component: Portal },
childRoutes: [
{
path: 'home',
getComponent(location, cb) {
System.import('./components/homepage/Home')
.then(module => cb(null, module.default));
}
}
]
};
const Routes = () => {
return <Router history={ browserHistory } routes={ componentRoutes } />
};
export default Routes;
Again, the goal is to be able to locally start up my node server, use browserHistory and not get 404s. I don't want to use hashHistory and I need to use my node server so I can use graphql. I also don't want to revert back to webpack v1. Though here is a link to where people got it working in v1:
historyApiFallback doesn't work in Webpack dev server
The historyApiFallback option is specifically for webpack-dev-server. If you're running your own server, even with webpack-dev-middleware, you need to configure it to send the index.html when a 404 occurs. Because you're using html-webpack-plugin the index.html you want to send does not exist on your file system but only in memory. To make it work you can access the output of the webpack compiler as shown in the comment of html-webpack-plugin #145.
server.js
const path = require('path');
const express = require('express');
const expressGraphQL = require('express-graphql');
const schema = require('./schema');
const app = express();
app.use('/graphql', expressGraphQL({
schema,
graphiql: true
}));
const webpackMiddleware = require('webpack-dev-middleware');
const webpack = require('webpack');
const webpackConfig = require('../webpack.config.js');
const compiler = webpack(webpackConfig);
app.use(webpackMiddleware(compiler));
// Fallback when no previous route was matched
app.use('*', (req, res, next) => {
const filename = path.resolve(compiler.outputPath, 'index.html');
compiler.outputFileSystem.readFile(filename, (err, result) => {
if (err) {
return next(err);
}
res.set('content-type','text/html');
res.send(result);
res.end();
});
});
app.listen(process.env.PORT || 5000, () => console.log('Listening'));