├── .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 | }; --------------------------------------------------------------------------------