├── .babelrc
├── .gitignore
├── README.md
├── dist
└── index.html
├── package.json
├── src
├── RootApp.jsx
├── components
│ ├── Page1.jsx
│ └── Page2.jsx
├── index.jsx
└── store.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["es2015", {"modules": false}],
4 | // webpack understands the native import syntax, and uses it for tree shaking
5 |
6 | "stage-2",
7 | // Specifies what level of language features to activate.
8 | // Stage 2 is "draft", 4 is finished, 0 is strawman.
9 | // See https://tc39.github.io/process-document/
10 |
11 | "react"
12 | // Transpile React components to JavaScript
13 | ],
14 | "plugins": [
15 | "react-hot-loader/babel"
16 | // Enables React code to work with HMR.
17 | ]
18 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | project.lock.json
3 | node_modules/
4 | npm-debug.log
5 | debug.log
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Webpack2 + HMR + ReactHotLoader3 + ReactRouter4 + Redux
2 | An absolute bare minimum implementation of HMR + ReactHotLoader in Webpack2 in combination with ReactRouter4 and Redux
3 |
4 | # Build/Run
5 | ```javascript
6 | npm install
7 | npm run dev
8 | ```
9 | # Test
10 | 1. Navigate to http://localhost:8080
11 | 2. Enter a value into the input field
12 | 3. Update the source in `./components/Page1`
13 | 4. Witness the glory; the local state is also retained (input value)
14 | 5. Click the `page2` link
15 | 6. Click the `Increment` button
16 | 7. Open `ReduxDevTools` and view the history
17 |
18 | # TODO
19 | - Integrate `react-router-redux` when ReactRouter4 is compatible
20 |
--------------------------------------------------------------------------------
/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "dev": "webpack-dev-server --config webpack.config.js --progress --colors"
4 | },
5 | "dependencies": {
6 | "react": "~15.4.2",
7 | "react-dom": "~15.4.2",
8 | "redux": "~3.6.0",
9 | "react-router-dom": "~4.0.0",
10 | "react-redux": "~5.0.2"
11 | },
12 | "devDependencies": {
13 | "webpack": "~2.2.1",
14 | "babel-core": "~6.23.1",
15 | "babel-loader": "~6.3.0",
16 | "babel-preset-es2015": "~6.22.0",
17 | "babel-preset-react": "~6.23.0",
18 | "babel-preset-stage-2": "~6.22.0",
19 | "react-hot-loader": "3.0.0-beta.6",
20 | "webpack-dev-server": "~2.3.0"
21 | },
22 | "engines": {
23 | "node": "6.9.1"
24 | }
25 | }
--------------------------------------------------------------------------------
/src/RootApp.jsx:
--------------------------------------------------------------------------------
1 | import { BrowserRouter, Link, Route, Redirect } from 'react-router-dom'
2 | import React, { Component } from 'react';
3 | import Page1 from './components/Page1'
4 | import Page2 from './components/Page2'
5 |
6 | export default class extends Component {
7 | render() {
8 | return
9 |
10 | App
11 | page1
12 | page2
13 | } />
14 |
15 |
16 |
17 |
18 | }
19 | }
--------------------------------------------------------------------------------
/src/components/Page1.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | export default class extends Component {
4 | constructor(){
5 | super();
6 |
7 | this.state = {
8 | value: 1
9 | }
10 | }
11 |
12 | changed = event => {
13 | this.setState({
14 | value: event.target.valueAsNumber
15 | });
16 | }
17 |
18 | render() {
19 | return
20 | Page 1
21 |
22 |
23 | }
24 | }
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/components/Page2.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 |
4 | function mapStateToProps(state) {
5 | return { state: state }
6 | }
7 |
8 | function mapDispatchToProps(dispatch) {
9 | return {
10 | increment: amount => dispatch({ type: 'INCREMENT' }),
11 | }
12 | }
13 |
14 |
15 | class Page2 extends Component {
16 | constructor() {
17 | super();
18 | }
19 |
20 | render() {
21 | return
22 | Page 2!
23 |
24 |
25 | }
26 | }
27 |
28 | export default connect(mapStateToProps, mapDispatchToProps)(Page2)
--------------------------------------------------------------------------------
/src/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import RootApp from './RootApp';
4 | import { Provider } from 'react-redux';
5 | import store from './store';
6 | import { AppContainer } from 'react-hot-loader';
7 |
8 | const render = (Component) => {
9 | ReactDOM.render(
10 |
11 |
12 |
13 | ,
14 | document.getElementById('root')
15 | );
16 | };
17 |
18 | render(RootApp);
19 |
20 | module.hot.accept('./RootApp', () => {
21 | render(RootApp)
22 | });
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/store.js:
--------------------------------------------------------------------------------
1 | import { createStore } from 'redux';
2 |
3 | const reducer = (state = 0, action) => {
4 | switch (action.type) {
5 | case 'INCREMENT':
6 | return state + 1;
7 | }
8 |
9 | return state;
10 | }
11 |
12 | export default createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const { resolve } = require('path');
2 | const webpack = require('webpack');
3 |
4 | module.exports = {
5 | entry: [
6 | 'react-hot-loader/patch',
7 | // activate HMR for React
8 |
9 | 'webpack-dev-server/client?http://localhost:8080',
10 | // bundle the client for webpack-dev-server
11 | // and connect to the provided endpoint
12 |
13 | 'webpack/hot/only-dev-server',
14 | // bundle the client for hot reloading
15 | // only- means to only hot reload for successful updates
16 |
17 |
18 | './index.jsx'
19 | // the entry point of our app
20 | ],
21 | resolve: {
22 | extensions: ['.js', '.jsx'],
23 | },
24 | output: {
25 | filename: 'bundle.js',
26 | // the output bundle
27 |
28 | path: resolve(__dirname, 'dist'),
29 |
30 | publicPath: '/'
31 | // necessary for HMR to know where to load the hot update chunks
32 | },
33 |
34 | context: resolve(__dirname, 'src'),
35 |
36 | devtool: 'inline-source-map',
37 |
38 | devServer: {
39 | hot: true,
40 | // enable HMR on the server
41 |
42 | contentBase: resolve(__dirname, 'dist'),
43 | // match the output path
44 |
45 | publicPath: '/',
46 | // match the output `publicPath`
47 |
48 | //fallback to root for other urls
49 | historyApiFallback: true
50 | },
51 |
52 | module: {
53 | rules: [{
54 | test: /\.(js|jsx)$/,
55 | use: [
56 | 'babel-loader'
57 | ],
58 | exclude: /node_modules/
59 | }],
60 | },
61 |
62 | plugins: [
63 | new webpack.HotModuleReplacementPlugin(),
64 | // enable HMR globally
65 |
66 | new webpack.NamedModulesPlugin(),
67 | // prints more readable module names in the browser console on HMR updates
68 | ],
69 | };
--------------------------------------------------------------------------------