Webpack powered React router that supports virtual modules, virtual folders, and code splitting.
$ npm install --save-dev @openovate/webpack-react-router
$ npm install --save-dev history
Additionally you may want to make sure your react environment is properly setup. If your doing this from scratch, install the following modules as well.
$ npm i --save-dev @babel/core @babel/plugin-syntax-dynamic-import @babel/plugin-transform-react-jsx @babel/polyfill @babel/preset-env @babel/preset-react @babel/register babel-loader react react-dom webpack webpack-cli webpack-dev-server webpack-hot-middleware
Also make sure you add the following scripts in package.json
{
"scripts": {
"build": "webpack",
"start": "webpack-dev-server --mode development --no-inline"
}
}
Create a file called webpack.config.js
in your project root.
//#FILE: webpack.config.js
const path = require('path');
const ReactRouterPlugin = require('@openovate/webpack-react-router');
module.exports = {
mode: 'development',
entry: {
index: './client/index.js'
},
output: {
filename: '[name].bundle.js',
publicPath: '/',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: { presets: ['@babel/env'] }
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
},
devServer: {
contentBase: path.join(__dirname, 'dist'),
compress: false,
port: 3000,
hot: false
},
resolve: {
extensions: ['*', '.js', '.jsx']
},
plugins: [
new ReactRouterPlugin({
router: 'client/router.js',
routes: {
'/': './Hello.jsx',
'/about': './About.jsx'
}
})
]
};
You will also need to create a .babelrc
file in your project root.
{
"presets": ["@babel/env", "@babel/preset-react"],
"plugins": [
"@babel/plugin-transform-react-jsx",
"@babel/plugin-syntax-dynamic-import"
]
}
Create a file called dist/index.html
in your project root.
<!DOCTYPE html>
<html>
<head>
<meta charSet="utf-8" />
<title>Webpack React Router</title>
</head>
<body>
<div id="root"></div>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<script src="/index.bundle.js"></script>
</body>
</html>
Create a file called client/index.js
in your project root.
//#FILE: client/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import router from './router'
const { createBrowserHistory } = require('history');
ReactDOM.render(
router(createBrowserHistory()),
document.getElementById('root')
)
./router
is a virtual React component generated by this plugin. It requires
that you use the history module or
equivalent interface.
Create a component file called client/Hello.jsx
.
//#FILE: client/Hello.jsx
import React from 'react'
import Link from './Link'
export default class Hello extends React.Component {
render() {
return (
<div>
<h1>Hello World</h1>
<ul>
<li><Link to="/" history={this.props.history}>Home</Link></li>
<li><Link to="/about" history={this.props.history}>About Us</Link></li>
</ul>
</div>
)
}
}
The router will pass 2 properties to this.props
.
history
- which is the initial history was passed inclient/index.js
route
- which will have the information of the route matched
Create a component file called client/About.jsx
//#FILE: client/About.jsx
import React from 'react'
import Link from './Link'
export default class About extends React.Component {
render() {
return (
<div>
<h1>About World</h1>
<ul>
<li><Link to="/" history={this.props.history}>Home</Link></li>
<li><Link to="/about" history={this.props.history}>About Us</Link></li>
</ul>
</div>
)
}
}
Lastly, we need to create a component file called client/Link.jsx
//#FILE: client/Link.jsx
import React from 'react';
export default class Link extends React.Component {
constructor(props) {
super(props);
//quirk that is recommended by react. lame.
this.handle = this.handle.bind(this);
}
handle(event) {
const { to, history } = this.props
const props = this.props.props || {}
event.preventDefault();
history.push(to, props);
return false;
}
render() {
const { to, children } = this.props;
const props = { href: to, onClick: this.handle };
return React.createElement('a', props, children);
}
}
Run $ npm start
in terminal and open 127.0.0.1:3000
in your browser.
see test/env
in this repo for advance usage.
This plugin uses path-to-regexp which is used by react router and express
Needed a react router that considered the following.
- Un-opinionated file structure which Next.js and Razzle suffer from.
- Virtual Modules
- Virtual Folders
- Code Splitting
The following example underlies this topic.
const routes = {
'/': './screens/Hello.jsx',
'/about': './screens/About.jsx'
}
import(routes['/']).then(component => {
console.log(component.default)
})
Using dynamic import()
in webpack 4 enables code splitting. When using dynamic
pathing like the above, import(routes['/'])
simply won't work because webpack
could not predetermine the actual value when compiling.
For some reason, wrapping a JS template seems to work like the following.
import(`${routes['/']}`).then(component => {
console.log(component.default)
})
But for Virtually Defined Modules, this does not. Virtual Modules powers Virtual Folders. Virtual Folders allows folder resolution where webpack cannot find files at their specified location. With Virtual Folders, we could simply tell Webpack other places where it could be. This is ideal in the case where you may want to modularize your project using a particular file structure in an unopinionated way.
For the reasons above, a plugin that generates a virtual router was decided.