Skip to content

Commit

Permalink
Fix HMR (#865)
Browse files Browse the repository at this point in the history
* works on ReduxApp and ReduxSharedStoreApp
* On ReduxSharedStoreApp, only one instance of the component shown twice updates.
  • Loading branch information
Judahmeek authored and justin808 committed Jun 8, 2017
1 parent 79a5e4c commit b933ed8
Show file tree
Hide file tree
Showing 7 changed files with 841 additions and 650 deletions.
5 changes: 4 additions & 1 deletion spec/dummy/client/.babelrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
{
"presets": ["es2015", "stage-2", "react"]
"presets": [["es2015", {"modules": false}], "stage-2", "react"],
"plugins": [
"react-hot-loader/babel"
]
}
37 changes: 28 additions & 9 deletions spec/dummy/client/app/startup/ClientReduxApp.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
import React from 'react';
import { combineReducers, applyMiddleware, createStore } from 'redux';
import { Provider } from 'react-redux';
import middleware from 'redux-thunk';
import thunkMiddleware from 'redux-thunk';
import { AppContainer } from "react-hot-loader";
import { render } from "react-dom";

import reducers from '../reducers/reducersIndex';
import composeInitialState from '../store/composeInitialState';
Expand All @@ -18,19 +20,36 @@ import HelloWorldContainer from '../components/HelloWorldContainer';
* React will see that the state is the same and not do anything.
*
*/
export default (props, railsContext) => {
export default (props, railsContext, domNodeId) => {
const combinedReducer = combineReducers(reducers);
const combinedProps = composeInitialState(props, railsContext);

// This is where we'll put in the middleware for the async function. Placeholder.
// store will have helloWorldData as a top level property
const store = applyMiddleware(middleware)(createStore)(combinedReducer, combinedProps);
const store = createStore(combinedReducer, combinedProps, applyMiddleware(thunkMiddleware));

// Provider uses the this.props.children, so we're not typical React syntax.
// renderApp is a function required for hot reloading. see
// https://github.com/retroalgic/react-on-rails-hot-minimal/blob/master/client/src/entry.js

// Provider uses this.props.children, so we're not typical React syntax.
// This allows redux to add additional props to the HelloWorldContainer.
return (
<Provider store={store}>
<HelloWorldContainer />
</Provider>
);
const renderApp = (Komponent) => {
const element = (
<AppContainer>
<Provider store={store}>
<Komponent />
</Provider>
</AppContainer>
)
render(element, document.getElementById(domNodeId));
}

renderApp(HelloWorldContainer);

if (module.hot) {
module.hot.accept(['../reducers/reducersIndex', '../components/HelloWorldContainer'], () => {
store.replaceReducer(combineReducers(reducers));
renderApp(HelloWorldContainer);
})
}
};
32 changes: 26 additions & 6 deletions spec/dummy/client/app/startup/ClientReduxSharedStoreApp.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import React from 'react';
import { Provider } from 'react-redux';
import ReactOnRails from 'react-on-rails';
import { AppContainer } from "react-hot-loader";
import { render } from "react-dom";

import HelloWorldContainer from '../components/HelloWorldContainer';

Expand All @@ -12,13 +14,31 @@ import HelloWorldContainer from '../components/HelloWorldContainer';
* This is used for the client rendering hook after the page html is rendered.
* React will see that the state is the same and not do anything.
*/
export default () => {
export default (props, railsContext, domNodeId) => {
// This is where we get the existing store.
const store = ReactOnRails.getStore('SharedReduxStore');

return (
<Provider store={store}>
<HelloWorldContainer />
</Provider>
);
// renderApp is a function required for hot reloading. see
// https://github.com/retroalgic/react-on-rails-hot-minimal/blob/master/client/src/entry.js

// Provider uses this.props.children, so we're not typical React syntax.
// This allows redux to add additional props to the HelloWorldContainer.
const renderApp = (Komponent) => {
const element = (
<AppContainer>
<Provider store={store}>
<Komponent />
</Provider>
</AppContainer>
)
render(element, document.getElementById(domNodeId));
}

renderApp(HelloWorldContainer);

if (module.hot) {
module.hot.accept(['../components/HelloWorldContainer'], () => {
renderApp(HelloWorldContainer);
})
}
};
4 changes: 2 additions & 2 deletions spec/dummy/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
"react": "^15.5.4",
"react-dom": "^15.5.4",
"react-helmet": "^5.0.3",
"react-on-rails": "^8.0.1",
"react-hot-loader": "^3.0.0-beta.6",
"react-on-rails": "8.0.2",
"react-proptypes": "^0.0.1",
"react-redux": "^5.0.4",
"react-router": "3.0.5",
Expand All @@ -59,7 +60,6 @@
"eslint-plugin-react": "^6.10.3",
"fbjs": "^0.8.12",
"jsdom": "^9.12.0",
"react-transform-hmr": "^1.0.4",
"tape": "^4.6.3",
"webpack-dev-server": "^2.4.2"
},
Expand Down
11 changes: 8 additions & 3 deletions spec/dummy/client/server-rails-hot.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
// 2. Make sure you have a hot-assets target in your client/package.json
// 3. Start up `foreman start -f Procfile.hot` to start both Rails and the hot reload server.

import webpack from 'webpack';
import WebpackDevServer from 'webpack-dev-server';
const webpack = require('webpack');
const WebpackDevServer = require('webpack-dev-server');
const { resolve } = require('path');
import webpackConfig from './webpack.client.rails.hot.config';
const webpackConfig = require('./webpack.client.rails.hot.config');

const webpackConfigLoader = require('react-on-rails/webpackConfigLoader');
const configPath = resolve('..', 'config');
Expand All @@ -26,6 +26,11 @@ const compiler = webpack(webpackConfig);

const devServer = new WebpackDevServer(compiler, {
contentBase: hotReloadingUrl,
headers: {
'Access-Control-Allow-Origin': '*',
},
disableHostCheck: true,
clientLogLevel: 'info',
hot: true,
inline: true,
historyApiFallback: true,
Expand Down
34 changes: 17 additions & 17 deletions spec/dummy/client/webpack.client.rails.hot.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
// cd client && babel-node server-rails-hot.js
// Note that Foreman (Procfile.dev) has also been configured to take care of this.

// require() is used rather than import because hot reloading with webpack
// requires webpack to transform modules from ES6 to ES5 instead of babel
// and webpack can not transform its own config files.
const { resolve } = require('path');
const webpack = require('webpack');
const merge = require('webpack-merge');
Expand All @@ -11,12 +14,19 @@ const webpackConfigLoader = require('react-on-rails/webpackConfigLoader');
const configPath = resolve('..', 'config');
const { hotReloadingUrl, webpackOutputPath } = webpackConfigLoader(configPath);

module.exports = merge(config, {
// entry is prepended because 'react-hot-loader/patch' must be the very first entry
// for hot reloading to work.
module.exports = merge.strategy(
{
entry: 'prepend'
}
)(config, {

devtool: 'eval-source-map',

entry: {
'app-bundle': [
'react-hot-loader/patch',
`webpack-dev-server/client?${hotReloadingUrl}`,
'webpack/hot/only-dev-server'
],
Expand All @@ -25,6 +35,7 @@ module.exports = merge(config, {
output: {
filename: '[name].js',
path: webpackOutputPath,
publicPath: `${hotReloadingUrl}/`,
},

module: {
Expand All @@ -33,22 +44,6 @@ module.exports = merge(config, {
test: /\.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
plugins: [
[
'react-transform',
{
transforms: [
{
transform: 'react-transform-hmr',
imports: ['react'],
locals: ['module'],
},
],
},
],
],
},
},
{
test: /\.css$/,
Expand Down Expand Up @@ -111,8 +106,13 @@ module.exports = merge(config, {
],
},

// webpack.NamedModulesPlugin() is an optional module that is great for HMR debugging
// since it transform module IDs (112, 698, etc...) into their respective paths,
// but it can conflict with other libraries that expect global references. When in doubt, throw it out.

plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
],
});
Expand Down
Loading

0 comments on commit b933ed8

Please sign in to comment.