React-router with BrowserRouter / browserHistory doesn't work on refresh - node.js

I have the following webpack config file:
var webpack = require('webpack');
var path = require('path');
var BUILD_DIR = path.resolve(__dirname, 'src/client/public');
var APP_DIR = path.resolve(__dirname, 'src/client/app');
var config = {
entry: [
APP_DIR + '/config/routes.jsx',
'webpack/hot/dev-server',
'webpack-dev-server/client?http://localhost:8080'
],
output: {
publicPath: 'http://localhost:8080/src/client/public/'
},
module : {
loaders : [
{
test: /\.jsx?$/,
loader: 'babel-loader',
include: APP_DIR,
exclude: /node_modules/,
query: {
presets: ['es2015']
}
},
{
test: /\.scss$/,
loaders: [ 'style', 'css', 'sass' ]
},
{
test: /\.json$/,
loader: "json-loader"
}
]
}
};
module.exports = config;
all I am trying to do is run my app on localhost, however when I hit: "http://localhost:8080/src/client/home" (as per my routes.jsx and after running webpack-dev-server)
import React from 'react';
import { Route, Router, browserHistory } from 'react-router';
import ReactDOM from 'react-dom';
import Wrapper from './../components/wrapper.jsx';
import Home from './../components/home.jsx';
import Projects from './../components/projects.jsx';
import SingleProject from './../components/projectContent/singleProject.jsx';
import About from './../components/aboutUs.jsx'
ReactDOM.render((
<Router history={browserHistory} >
<Route path="/" component={Wrapper} >
<Route path="home" component={Home} />
<Route path="projects" component={Projects} />
<Route path="projects/:id" component={SingleProject} />
<Route path="about" component={About} />
</Route>
</Router>
), document.getElementById('app'));
I get
"Cannot GET /src/client/home".

First thing you have mentioned in your routes as the home component to have path /home. So you need to visit http://localhost:8080/home. Also if you try to access this url directly, it will give you this error since you are using browserHistory. If you want you can use hashHistory or HashRouter in react-router v4, in which case you will need to visit http://localhost:8080/#/home. If you want to continue using browserHistory or BrowserRouter as in react-router v4, then you will need to add historyApiFallback: true in you webpack
var webpack = require('webpack');
var path = require('path');
var BUILD_DIR = path.resolve(__dirname, 'src/client/public');
var APP_DIR = path.resolve(__dirname, 'src/client/app');
var config = {
entry: [
APP_DIR + '/config/routes.jsx',
'webpack/hot/dev-server',
'webpack-dev-server/client?http://localhost:8080'
],
output: {
publicPath: 'http://localhost:8080/src/client/public/'
},
devServer: {
historyApiFallback: true
},
module : {
loaders : [
{
test: /\.jsx?$/,
loader: 'babel-loader',
include: APP_DIR,
exclude: /node_modules/,
query: {
presets: ['es2015']
}
},
{
test: /\.scss$/,
loaders: [ 'style', 'css', 'sass' ]
},
{
test: /\.json$/,
loader: "json-loader"
}
]
}
};
module.exports = config;

You need to add this in your webpack settings:
devServer: {
historyApiFallback: true,
},
And start your server like this:
webpack-dev-server --config webpack.config.js
Because you want React-Route to handle the route instead of your server. So no matter what the url is it should goes to index.html.

Related

Bug with building SSR for reactjs-website

I have some react-app and trying to enable server-side rendering.
I make the server/index.js and describe the server logic (using express)
import path from 'path';
import fs from 'fs';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import express from 'express';
import App from 'src/App';
const PORT = process.env.PORT || 3006;
const app = express();
app.get('/', (req, res) => {
const app = ReactDOMServer.renderToString(<App />);
const indexFile = path.resolve('./build/index.html'); <-- it's result of `npm run build`
fs.readFile(indexFile, 'utf8', (err, data) => {
if (err) {
console.error('Something went wrong:', err);
return res.status(500).send('Oops, better luck next time!');
}
return res.send(
data.replace('<div id="root"></div>', `<div id="root">${app}</div>`)
);
});
});
app.use(express.static('./build'));
app.listen(PORT, () => {
console.log(`Server is listening on port ${PORT}`);
});
Then I use webpack for convert jsx to js
const path = require('path');
const nodeExternals = require('webpack-node-externals');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
entry: './server/index.js',
target: 'node',
externals: [nodeExternals()],
devtool: false,
output: {
path: path.resolve('build'),
filename: 'server.js',
asyncChunks: true,
clean: true
},
module: {
rules: [
{
test: /\.tsx?$/,
use: [
{
loader: 'babel-loader',
options: { presets: ['solid'] },
},
{
loader: 'ts-loader',
options: {
compilerOptions: { noEmit: false },
}
}],
exclude: /node_modules/,
},
{
test: /\.s[ac]ss$/i,
use: [
MiniCssExtractPlugin.loader, // Creates `style` nodes from JS strings
"css-loader", // Translates CSS into CommonJS
"sass-loader", // Compiles Sass to CSS
],
},
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
{
test: /\.js$/,
use: 'babel-loader'
}
]
},
plugins: [
new MiniCssExtractPlugin()
],
resolve: {
roots: [
'node_modules'
],
alias: {
src: path.resolve(__dirname, 'src')
},
extensions: ['.tsx', '.ts', '.js'],
},
};
Then I try to start server node build/server.js but got some error
/* harmony import */ var solid_js_web__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! solid-js/web */ "./node_modules/solid-js/web/dist/server.js");
...
const _tmpl$ = /*#__PURE__*/(0,solid_js_web__WEBPACK_IMPORTED_MODULE_0__.template)(`<li></li>`, 2),
TypeError: (0 , solid_js_web__WEBPACK_IMPORTED_MODULE_0__.template) is not a function
I can't understand how it happened and what's wrong? How can I make it work?
It was my mistake in webpack config
{
loader: 'babel-loader',
options: {
presets: ['#babel/env', '#babel/preset-react'], <-- correct
// presets: ['solid'], <-- incorrect
},
},

Webpack doesn't render sass correctly when not exporting file

I am trying to configure Webpack 4 to use Sass for a MERN project. When I use mini-css-extract-plugin and export a CSS file and then link it in the index.html file it works. However, when I try to bundle the css with the bundle.js file, it will not render the css and the odd thing is, it does not render just some parts of it. I tried following the official bootstrap webpack installation, but it did not work as intended.
Here are the files:
webpack.config.js:
const path = require('path');
// const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: './client/src/index.js',
mode: 'development',
module: {
rules: [
{
test: /\.s?css$/,
exclude: /node_modules/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
options: {
localIdentName: '[name]__[local]__[hash:base64:5]',
modules: true,
importLoaders: 1,
sourceMap: true,
},
},
{
loader: 'sass-loader'
},
],
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: ['#babel/preset-env']
}
},
{
test: /\.jsx$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: ['#babel/preset-env']
}
}
]
},
output: {
path: path.resolve(__dirname, 'client/public'),
filename: 'bundle.js'
}
}
./client/src/index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import 'bootstrap';
import '../sass/style.scss'
import App from './components/App';
ReactDOM.hydrate(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
)
I bealive that webpack 4 support MiniCssExtractPlugin , the other loaders that are you trying to use dont work correctly .
Check de Docs and try it :
https://webpack.js.org/plugins/mini-css-extract-plugin/

ReactDOMServer renderToStaticMarkup on Node/Express server

I'm trying to create a server rendered react app, the only part I'm stuck on is importing my components to my express server and getting the static markdown to send back to the user. Essentially what I have right now is this:
Express server:
const Report = require('../public/source/components/index.js').default;
....
router.get('/*', function(req, res, next) {
var reportHTML = ReactDOMServer.renderToStaticMarkup(react.createElement(Report)))
res.render('index', { title: 'Report' });
});
When I hit that route, I get the following error:
Warning: React.createElement: type is invalid -- expected a string
(for built-in components) or a class/function (for composite components)
but got: object. You likely forgot to export your component from the file
it's defined in. Check the render method of `ReportApp`.
in ReportApp
The contents of my index.js file, note that I stripped out a lot of the complexity involving graphql and setting the initial state, which is why this isn't a functional component.
import React, { Component } from 'react';
import Header from './header/Header';
import PageOneLayout from './pageOneLayout/PageOneLayout';
import styles from './main.scss';
const hexBackground = require('./assets/hex_background.png');
export default class ReportApp extends Component {
render() {
return (
<div className={styles.contentArea}>
<img src={`/build/${hexBackground}`} alt={'hexagonal background'} className={styles.hexBackground}/>
<Header client={"client name"} />
<div className={styles.horizontalLine}></div>
<PageOneLayout chartData={this.state} />
</div>
)
}
}
Any pointers in the right direction would be appreciated!
EDIT:
here's my webpack:
/* eslint-disable no-console */
/* eslint-disable import/no-extraneous-dependencies */
import autoprefixer from 'autoprefixer';
import nodemon from 'nodemon';
import ExtractTextPlugin from 'extract-text-webpack-plugin';
nodemon({
script: './bin/www',
ext: 'js json',
ignore: ['public/'],
});
nodemon.on('start', () => {
console.log('App has started');
}).on('quit', () => {
console.log('App has quit');
}).on('restart', files => console.log('App restarted due to: ', files));
export default {
watch: true,
entry: './public/source/main.js',
output: { path: `${__dirname}/public/build/`, filename: 'main.js' },
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel',
query: {
presets: ['react', 'es2015', 'stage-1'],
plugins: ['transform-decorators-legacy'],
cacheDirectory: true
}
},
// {
// test: /\.jsx?$/,
// exclude: /node_modules/,
// loader: 'eslint',
// },
{
test: /\.s?css$/,
loader: ExtractTextPlugin.extract('style-loader', 'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!postcss-loader!sass-loader?outputStyle=expanded&sourceMap')
},
{ test: /\.jpe?g$|\.gif$|\.png$|\.svg$|\.woff$|\.ttf$|\.wav$|\.mp3$/, loader: "file", output: {path: `${__dirname}/public/build/`, filename: 'logo.svg'}},
],
},
// eslint: {
// configFile: './public/.eslintrc',
// },
resolve: {
modulesDirectories: ['node_modules', 'public/source'],
extensions: ['', '.js', '.jsx'],
},
postcss: [
autoprefixer,
],
plugins: [
new ExtractTextPlugin('main.css', { allChunks: true }),
],
};
There are few things to consider:
Are you doing any code transpiling at server side?
How are you building your component bundle(show the config, I assume webpack)?
Make sure the bundle component exposes the component.
The extra createElement shouldn't be needed in this case ReactDOMServer.renderToStaticMarkup(react.createElement(Report))).

WebPack and Express server not working together

I'm having a hard time finding resources that explain how to connect webpack to a express server app. I'm wanting to use webpack for babel to use es6 when writing react and use its hot-module and cheap-module-source-map. But, webpack runs it's own express server and that currently conflicts with my express app. I want my express app to dictate the port and routes but still get the benefits of using webpack.
Any ideas?
The express app looks something like this:
var express = require('express'),
Sequelize = require('sequelize'),
/*
set up sequelize ...
app.route ...
*/
app.listen(port), function () {
console.log('Express server listening on port ' + port
});
You don't need the webpack-dev-server to use Webpack for Babel to use ES2015 when writing React and use its hot-module and cheap-module-source-map.
Webpack configuration for React app in development env:
module.exports = {
entry: {
app: [
'react-hot-loader/patch',
'webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000',
'app/index.js,
],
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
],
})
.babelrc looks like this:
{
"presets": ["react", "es2015", "stage-0"],
"env": {
"development": {
"plugins": ["react-hot-loader/babel"]
}
}
}
app/index.js:
import { AppContainer} from 'react-hot-loader'
...
<AppContainer>
<App />
</AppContainer>
...
if (module.hot) {
module.hot.accept('./routes', () => {
// Hot reloading
})
}
server/index.js:
import webpack from 'webpack'
import webpackDevMiddleware from 'webpack-dev-middleware'
import webpackHotMiddleware from 'webpack-hot-middleware'
import webpackConfig from './webpack.dev.config'
const compiler = webpack(webpackConfig)
app.use(webpackDevMiddleware(compiler, {
noInfo: true,
publicPath: webpackConfig.output.publicPath,
}))
app.use(webpackHotMiddleware(compiler, {
path: '/__webpack_hmr',
heartbeat: 10000,
}))
I am not sure if it's allowed to refer to my own repo here, but please check my Github repo here to see how I have integrated React, Express, Webpack, HMR and Babel.
What I ended up doing was I used 2 different configurations, 1 for packing the server stuff together using webpack, and 1 for packing all the browser stuff together and also run webpack dev server for hot reloading.
Server webpack config aka webpack.node.config.js now looks like this:
var webpack = require('webpack');
var path = require('path');
var fs = require('fs');
var nodeModules = {};
// note the path.resolve(__dirname, ...) part
// without it, eslint-import-resolver-webpack fails
// since eslint might be invoked with different cwd
fs.readdirSync(path.resolve(__dirname, 'node_modules'))
.filter(x => ['.bin'].indexOf(x) === -1)
.forEach(mod => { nodeModules[mod] = `commonjs ${mod}`; });
// es5 style alternative
// fs.readdirSync(path.resolve(__dirname, 'node_modules'))
// .filter(function(x) {
// return ['.bin'].indexOf(x) === -1;
// })
// .forEach(function(mod) {
// nodeModules[mod] = 'commonjs ' + mod;
// });
module.exports =
{
// The configuration for the server-side rendering
name: 'server',
target: 'node',
entry: './app/server/serverEntryPrototype.js',
output: {
path: './bin/',
publicPath: 'bin/',
filename: 'serverEntryPoint.js'
},
externals: nodeModules,
module: {
loaders: [
{ test: /\.js$/,
loaders: [
// 'imports?document=this',
// 'react-hot',
'babel-loader'
//,'jsx-loader'
]
},
{ test: /\.json$/, loader: 'json-loader' },
]
},
plugins: [
// new webpack.NormalModuleReplacementPlugin("^(react-bootstrap-modal)$", "^(react)$")
// new webpack.IgnorePlugin(new RegExp("^(react-bootstrap-modal)$"))
// new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
]
};
Browser webpack config aka webpack.browser.config.js now looks like this:
var webpack = require('webpack');
var path = require('path');
var buildPath = path.resolve(__dirname, 'assets');
var fs = require('fs');
var commonLoaders = [
{ test: /\.js$/,
loaders: [
'react-hot',
'babel-loader'
//,'jsx-loader'
]
}
];
module.exports =
{
// Makes sure errors in console map to the correct file
// and line number
name: 'browser',
devtool: 'eval',
entry: [
//'./bin/www.js',
'./app/index.js',
'webpack/hot/dev-server',
'webpack-dev-server/client?http://localhost:8081' // WebpackDevServer host and port
],
output: {
path: buildPath,
filename: '[name].js',
// Everything related to Webpack should go through a build path,
// localhost:3000/build. That makes proxying easier to handle
publicPath: 'http://localhost:8081/assets/'
},
extensions: [
'',
'.jsx', '.js',
'.json',
'.html',
'.css', '.styl', '.scss', '.sass'
],
module: {
loaders: [
// Compile es6 to js.
{
test: /app\/.*\.jsx?$/,
loaders: [
'react-hot',
'babel-loader'
]
},
///app\/.*\.json$/
{ test: /\.json$/, loader: 'json-loader' },
// Styles
{ test: /\.css$/, loader: 'style-loader!css-loader' },
{ test: /\.s(a|c)ss$/, loader: 'style!css?localIdentName=[path][name]---[local]---[hash:base64:5]!postcss!sass' },
// Fonts
{ test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: 'url-loader?limit=10000&minetype=application/font-woff' },
{ test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: 'file-loader' }
//{ test: /\.png$/, loader: 'url-loader?limit=100000' },
//{ test: /\.jpg$/, loader: 'file-loader' }
],
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin()
]
},
postcss: [
require('autoprefixer-core')
],
devtool: 'source-map'
}
;

babel having trouble with import statement

I have a project I'm doing with node in ES6 which was using babel-node to run. Now I'm trying to implement babel in a more production manner and have tried two attempts.
Webpack babel-loader with following configuration:
module.exports = {
entry: './src/cloud/main.js',
devtool: 'source-map',
output: {
path: './src/static/build',
filename: 'bundle.js'
},
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loaders: [
'babel-loader?presets[]=es2015',
],
},
{
test: /\.css$/,
loaders: [
'style-loader',
'css-loader',
],
},
{
test: /\.html$/,
loaders: [
'raw-loader',
],
},
],
},
}
It started complaining about the import statement in main.js and to silence it I used ?presets[]=es2015 which I found in a similar question. Then the problem arrived in which it filtered to the import statements that went to node_modules with the following message:
ERROR in ./~/socket.io/lib/index.js
Module not found: Error: Cannot resolve module 'fs' in
My other approach was with the register hook like this:
require('babel-core/register')({
ignore: /node_modules/,
});
require('./main.js');
but it threw this message:
import express from 'express';
^^^^^^
SyntaxError: Unexpected reserved word
//main.js - simplified
import express from 'express'
const app = express()
const server = app.listen(port, () => {
console.log(`Listening at http://${server.address().address === '::' ? 'localhost' : server.address().address}:${server.address().port}`)
})
I don't think you need to exclude the node_modules in your loader config. However, you might want to let webpack know what to resolve. Try adding something like this:
resolve: {
root: path.join(__dirname),
fallback: path.join(__dirname, 'node_modules'),
modulesDirectories: ['node_modules'],
extensions: ['', '.json', '.js', '.jsx', '.scss', '.png', '.jpg', '.jpeg', '.gif']
},
The modulesDirectories key should keep webpack from running down every single require / import in your working directory.
Also, adding target to the top of your config should resolve issues with builtins like fs
target: 'node'
Ok I figured it out thanks to other answers and 4m1r' answer. I post the example code.
var path = require('path');
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;
});
module.exports = {
name: 'server',
target: 'node',
context: path.join(__dirname, 'src', 'cloud'),
entry: {
server: './main.js'
},
output: {
path: path.join(__dirname),
filename: '[name].js'
},
externals: nodeModules,
module: {
loaders: [
{test: /\.js$/, exclude: /node_modules/, loaders: ['babel-loader?presets[]=es2015']}
]
},
resolve: {
root: path.join(__dirname),
fallback: path.join(__dirname, 'node_modules'),
modulesDirectories: ['node_modules'],
}
};
What was really important too was the externals key which prevented it fro leaking to the node_modules through requires and specifying for some reason ?presets[]=2015 in the babel-loader. I'm accepting 4m1r because it was what finally fixed the code.

Resources