使用webpack和react-router进行延迟加载和代码分割,无法加载

时间:2016-01-21 13:54:30

标签: reactjs webpack react-router redux

我正在努力将react v0.14 + redux v3.0 + react-router v1.0代码库从客户端呈现移动到服务器端呈现,使用webpack v1.12进行捆绑和代码-split成块以按需加载路由和组件。

我跟随并将我的设置基于https://github.com/rackt/example-react-router-server-rendering-lazy-routes,因为我认为它提供了简单性和实用性。昨天整天我一直在努力转向服务器端渲染,但我遇到了一些我无法解决的问题,而且我不能完全确定它们是否是因为webpack没有正确设置,如果我在服务器/客户端或路由配置上对react-router做错了,或者我设置redux导致这些问题的错误

我遇到了以下问题:

  1. 我能够加载初始页面,一切正常,但没有其他路线加载并给我GET http://localhost:3000/profile 404 (Not Found)
  2. 索引/主页javascript有效,但所有资源(css)都呈现为text/javascript,因此除非内联样式,否则这些样式不会显示。
  3. webpack.config.js

    var fs = require('fs')
    var path = require('path')
    var webpack = require('webpack')
    
    module.exports = {
    
      devtool: 'source-map',
    
      entry: './client/client.jsx',
    
      output: {
        path: __dirname + '/__build__',
        filename: '[name].js',
        chunkFilename: '[id].chunk.js',
        publicPath: '/__build__/'
      },
    
      module: {
        loaders: [
          {
            test: /\.jsx?$/,
            exclude: /(node_modules|bower_components)/,
            loader: 'babel-loader'
          }
        ]
      },
    
      plugins: [
        new webpack.optimize.OccurenceOrderPlugin(),
        new webpack.optimize.DedupePlugin(),
        new webpack.optimize.UglifyJsPlugin({
          compressor: { warnings: false },
        }),
        new webpack.DefinePlugin({
          'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
        })
      ]
    
    }
    

    server.js

    import http from 'http';
    import React from 'react';
    import {renderToString} from 'react-dom/server';
    import { match, RoutingContext } from 'react-router';
    import {Provider} from 'react-redux';
    import configureStore from './../common/store/store.js';
    
    import fs from 'fs';
    import { createPage, write, writeError, writeNotFound, redirect } from './server-utils.js';
    import routes from './../common/routes/rootRoutes.js';
    
    const PORT = process.env.PORT || 3000;
    
    var store = configureStore();
    const initialState = store.getState();
    
    function renderApp(props, res) {
      var markup = renderToString(
        <Provider store={store}>
          <RoutingContext {...props}/>
        </Provider>
      );
      var html = createPage(markup, initialState);
      write(html, 'text/html', res);
    }
    
    http.createServer((req, res) => {
    
      if (req.url === '/favicon.ico') {
        write('haha', 'text/plain', res);
      }
    
      // serve JavaScript assets
      else if (/__build__/.test(req.url)) {
        fs.readFile(`.${req.url}`, (err, data) => {
          write(data, 'text/javascript', res);
        })
      }
    
      // handle all other urls with React Router
      else {
        match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {
          if (error)
            writeError('ERROR!', res);
          else if (redirectLocation)
            redirect(redirectLocation, res);
          else if (renderProps)
            renderApp(renderProps, res);
          else
            writeNotFound(res);
        });
      }
    
    }).listen(PORT)
    console.log(`listening on port ${PORT}`)
    

    服务器utils的

    与我在example-react-router-server-rendering-lazy-routes上面发布的回购信息相同,只是导航到回购中的/modules/utils/server-utils.js。唯一的区别是createPage功能:

    export function createPage(html, initialState) {
      return( `
      <!doctype html>
      <html>
        <head>
          <meta charset="utf-8"/>
          <meta name="viewport" content="width=device-width, initial-scale=1">
          <link rel="stylesheet" href="./../bower_components/Ionicons/css/ionicons.min.css">
          <link rel="stylesheet" href="./../dist/main.css">
          <title>Sell Your Soles</title>
        </head>
        <body>
          <div id="app">${html}</div>
          <script>window.__INITIAL_STATE__ = ${JSON.stringify(initialState)};</script>
          <script src="/__build__/main.js"></script>
        </body>
      </html>
      `);
    }
    

    rootRoute.js

    // polyfill webpack require.ensure
    if (typeof require.ensure !== 'function') require.ensure = (d, c) => c(require)
    
    import App from '../components/App.jsx'
    import Landing from '../components/Landing/Landing.jsx'
    
    export default {
      path: '/',
      component: App,
      getChildRoutes(location, cb) {
        require.ensure([], (require) => {
          cb(null, [
            require('./UserProfile/UserProfileRoute.js'),
            require('./UserHome/UserHomeRoute.js'),
            require('./SneakerPage/SneakerPageRoute.js'),
            require('./Reviews/ReviewsRoute.js'),
            require('./Listings/ListingsRoute.js'),
            require('./Events/EventsRoute.js')
          ])
        })
      },
      indexRoute: {
        component: Landing
      }
    }
    

    userProfileRoute.js

    import UserProfile from '../../components/UserProfile/UserProfile.jsx';
    
    export default {
      path: 'profile',
      component: UserProfile
    }
    

    client.js

    import React from 'react';
    import { match, Router } from 'react-router';
    import { render } from 'react-dom';
    import { createHistory } from 'history';
    import routes from './../common/routes/rootRoutes.js';
    import {Provider} from 'react-redux';
    import configureStore from './../common/store/store.js';
    
    
    const { pathname, search, hash } = window.location;
    const location = `${pathname}${search}${hash}`;
    
    const initialState = window.__INITIAL_STATE__;
    const store = configureStore(initialState);
    
    
    
    // calling `match` is simply for side effects of
    // loading route/component code for the initial location
    match({ routes, location }, () => {
      render(
        <Provider store={store}>
          <Router routes={routes} history={createHistory()} />
        </Provider>,
        document.getElementById('app')
      );
    });
    

2 个答案:

答案 0 :(得分:11)

我帮助你解决了不和谐问题,但我想我也会在这里发布答案。

如果您使用babel6(而不是babel5)并在组件中使用导出默认值,则需要将路由更新为以下内容:

getChildRoutes(location, cb) {
    require.ensure([], (require) => {
        cb(null, [
            require('./UserProfile/UserProfileRoute.js').default,
            require('./UserHome/UserHomeRoute.js').default,
            require('./SneakerPage/SneakerPageRoute.js').default,
            require('./Reviews/ReviewsRoute.js').default,
            require('./Listings/ListingsRoute.js').default,
            require('./Events/EventsRoute.js').default
        ])
    })
}

有关详细信息,请参阅此SO讨论:Babel 6 changes how it exports default

答案 1 :(得分:3)

如果您碰巧升级到Webpack 2(+树摇晃),您将使用System.import代替需求,这非常有用。

以下是:

&#13;
&#13;
import App from 'containers/App';
function errorLoading(err) {
  console.error('Dynamic page loading failed', err);
}
function loadRoute(cb) {
  return (module) => cb(null, module.default);
}
export default {
  component: App,
  childRoutes: [
    {
      path: '/',
      getComponent(location, cb) {
        System.import('pages/Home')
          .then(loadRoute(cb))
          .catch(errorLoading);
      }
    },
    {
      path: 'blog',
      getComponent(location, cb) {
        System.import('pages/Blog')
          .then(loadRoute(cb))
          .catch(errorLoading);
      }
    }
  ]
};
&#13;
&#13;
&#13;

您可以在此博客文章中获取整个指南:Automatic Code Splitting for React Router