chikara-chan / react-isomorphic-boilerplate

🌟 An universal React isomorphic boilerplate for building server-side render web app.

Home Page:https://chikara-chan.github.io/react-isomorphic-boilerplate/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Redux无法热更新

wd2010 opened this issue · comments

看了作者的例子受益匪浅,我刚入门webpack,做练习的过程中我也自己用router4+react-loadable仿写了一个demo,但在reducer更改初始值时无法热更新,这是我的demo地址:https://github.com/wd2010/webpack-demo,请大神帮忙看看!

app.js(entry文件)

import React from 'react';
import {hydrate} from 'react-dom';
import configureStore from './store/configureStore';
import createHistory from 'history/createBrowserHistory'
import createApp from './store/createApp';
import Loadable from 'react-loadable';

const initialState = window.__INITIAL_STATE__;
let store=configureStore(initialState)
const history=createHistory()

const render=()=>{
  let application=createApp({store,history});
  hydrate(application,document.getElementById('root'))
}

window.main = () => {
  Loadable.preloadReady().then(() => {
    render()
  });
};

configureStore.js

import {createStore, applyMiddleware,compose} from "redux";
import thunkMiddleware from "redux-thunk";
import createHistory from 'history/createMemoryHistory';
import {  routerReducer, routerMiddleware } from 'react-router-redux'
import {composeWithDevTools} from 'redux-devtools-extension'
import rootReducer from './reducers/index';
const routerReducers=routerMiddleware(createHistory());//路由
const middleware=[thunkMiddleware,routerReducers];

let configureStore=(initialState)=>{
  let store=createStore(rootReducer,initialState,composeWithDevTools(applyMiddleware(...middleware)));
  //热加载配置
if(module.hot) {
    console.log(module.hot.status())
    module.hot.accept('./reducers/index.js', () => {
        store.replaceReducer(rootReducer)
      console.log('=',store.getState())
    });
  }
  return store;
}

export default configureStore;

webpack.config.js

const  ExtractTextWebpackPlugin = require("extract-text-webpack-plugin"),
  HtmlWebpackPlugin=require('html-webpack-plugin'),
  ProgressBarPlugin = require('progress-bar-webpack-plugin');
const path=require('path');
const webpack=require('webpack');
import { ReactLoadablePlugin } from 'react-loadable/webpack';
const DIR_NAME=path.join(__dirname,'../');

const config={
  context: DIR_NAME,
  entry:{
    bundle: ['./client/app.js','webpack-hot-middleware/client?path=/__webpack_hmr'],
    vendor: ['react','react-dom','redux','react-redux'],
  },
  output:{
    path:  path.resolve(DIR_NAME,'./dist/client'),
    filename: '[name].js',
    chunkFilename: 'chunk.[name].js',
    publicPath:'/'
  },

  resolve: {extensions: ['.js', '.jsx' ,'.json', '.scss']},
  module:{
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader:'babel-loader',
          options:{
            presets: ['env', 'react', 'stage-0','react-hmre'],
            plugins: ['transform-runtime', 'add-module-exports','syntax-dynamic-import','react-loadable/babel'],
            cacheDirectory: true,
          }
        }
      },{
        test: /\.html$/,
        exclude:/node_modules/,
        loader: 'html-loader'
      }
    ],
  },
  plugins:[
    new webpack.optimize.OccurrenceOrderPlugin(),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoEmitOnErrorsPlugin(),
    new ProgressBarPlugin({summary: false}),
    new webpack.optimize.CommonsChunkPlugin({
      names:['vendor','manifest'],
      filename:'[name].js',
    }),
    new HtmlWebpackPlugin({
      filename: '../views/dev/index.html',
      template: './views/tpl/index.html',
      chunks: ['vendor','manifest', 'bundle'],
    }),
    new ReactLoadablePlugin({
      filename: './views/dev/react-loadable.json',

    }),
  ],

}


module.exports=config

clientRouter.js

import React from 'react';
import fs from 'fs';
import path from 'path';
import {renderToString} from 'react-dom/server';
import Loadable from 'react-loadable';
import { getBundles } from 'react-loadable/webpack';
import stats from '../../views/dev/react-loadable.json';
import {routesConfig} from '../../client/routes';
import configureStore from '../../client/store/configureStore';
import createHistory from 'history/createMemoryHistory'
import createApp from '../../client/store/createApp';
import {matchPath} from 'react-router-dom';
import {matchRoutes} from 'react-router-config';
import Helmet from 'react-helmet';

const store=configureStore();


const prepHTML=(data,{html,head,body,scripts,styles,state})=>{
  data=data.replace('<html',`<html ${html}`);
  data=data.replace('</head>',`${head} \n ${styles}</head>`);
  data=data.replace('<div id="root"></div>',`<div id="root">${body}</div>`);
  data=data.replace('<body>',`<body> \n <script>window.__INITIAL_STATE__ =${JSON.stringify(state)}</script>`);
  data=data.replace('</body>',`${scripts}</body>`);
  return data;
}

const markup=({ctx})=>{
  let modules = [];
  const history=createHistory({initialEntries:[ctx.req.url]})
  let state=store.getState();
  let appString= renderToString(
    <Loadable.Capture report={moduleName => modules.push(moduleName)}>
      {createApp({store,history})}
    </Loadable.Capture>
  )

  let bundles = getBundles(stats, modules);
  let scriptfiles = bundles.filter(bundle => bundle.file.endsWith('.js'));
  let stylefiles = bundles.filter(bundle => bundle.file.endsWith('.css'));

  let scripts=scriptfiles.map(script=>`<script src="/${script.file}"></script>`).join('\n');
  let styles=stylefiles.map(style=>`<link href="/${style.file}" rel="stylesheet"/>`).join('\n');

  let filePath=path.join(__dirname,'../../views/dev/index.html')
  let htmlData=fs.readFileSync(filePath,'utf-8');

  const helmet=Helmet.renderStatic();

  let renderedHtml=prepHTML(htmlData,{
    html:helmet.htmlAttributes.toString(),
    head:helmet.title.toString()+helmet.meta.toString()+helmet.link.toString(),
    body:appString,
    scripts:scripts,
    styles:styles,
    state
  })

  return renderedHtml
}

const getMatch=(routesArray, url)=>{
  return routesArray.some(router=>matchPath(url,{
    path: router.props.path,
    exact: router.props.exact,
  }))
}

const clientRouter=async(ctx,next)=>{
  let isMatch=getMatch(routesConfig,ctx.req.url);

  if(isMatch){
    let renderedHtml=markup({ctx});
    ctx.body=renderedHtml
  }else{
    await next()
  }
}

export default clientRouter;

更改reducer初始值时,console.log输出值

[HMR] connected
    bundle.js:6781 [HMR] bundle rebuilding
    bundle.js:6789 [HMR] bundle rebuilt in 180ms
    bundle.js:7715 [HMR] Checking for updates on the server...
    bundle.js:7788 [HMR] Updated modules:
    bundle.js:7790 [HMR]  - ./client/store/reducers/home.js
    bundle.js:7790 [HMR]  - ./client/store/reducers/index.js
    bundle.js:7795 [HMR] App is up to date.
    bundle.js:6781 [HMR] bundle rebuilding
    bundle.js:6789 [HMR] bundle rebuilt in 195ms
    bundle.js:7715 [HMR] Checking for updates on the server...
    bundle.js:7788 [HMR] Updated modules:
    bundle.js:7790 [HMR]  - ./client/store/reducers/home.js
    bundle.js:7790 [HMR]  - ./client/store/reducers/index.js
    bundle.js:7795 [HMR] App is up to date.
    bundle.js:6781 [HMR] bundle rebuilding
    bundle.js:6789 [HMR] bundle rebuilt in 175ms
    bundle.js:7715 [HMR] Checking for updates on the server...
    bundle.js:7788 [HMR] Updated modules:
    bundle.js:7790 [HMR]  - ./client/store/reducers/home.js
    bundle.js:7790 [HMR]  - ./client/store/reducers/index.js
    bundle.js:7795 [HMR] App is up to date.
commented

I also encountered the same problem when using dva.