使用 webpack 和 react-router 进行延迟加载和代码拆分不加载

Posted

技术标签:

【中文标题】使用 webpack 和 react-router 进行延迟加载和代码拆分不加载【英文标题】:Using webpack and react-router for lazyloading and code-splitting not loading 【发布时间】:2016-04-27 19:32:45 【问题描述】:

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

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

我遇到以下问题:

    我可以加载初始页面,一切正常,但没有其他路由加载,给了我GET http://localhost:3000/profile 404 (Not Found) 索引/主页 javascript 有效,但所有资产 (css) 都呈现为 text/javascript,因此样式不会显示,除非它们是内联的。

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`)

服务器工具

与我在example-react-router-server-rendering-lazy-routes 上面发布的repo 相同,只需导航到repo 中的/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')
  );
);

【问题讨论】:

【参考方案1】:

我在不和谐方面帮助了你,但我想我也会在这里发布答案。

如果您使用 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

【讨论】:

如果你不想在每次需要时都写.default,你也可以添加add-module-exports babel-plugin。【参考方案2】:

如果你碰巧升级到 Webpack 2(+ tree shaking),你会使用 System.import 而不是 requires,这非常有用。

方法如下:

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);
      
    
  ]
;

您可以在这篇博文中获得完整指南:Automatic Code Splitting for React Router

【讨论】:

你会知道 webpack 2 什么时候发布吗?还是仍处于测试阶段? @kennetpostigo 目前仍处于测试阶段。团队正在为完成发布付出巨大努力,但我无法透露时间表 你好。我想做服务器渲染?当我尝试使用System.import 实现此功能时,我收到System.import is not a function 错误 您的服务器端不知道如何解析 System.import。目前最简单的方法是在服务器端使用 react.ensure。 System.import() 现已弃用

以上是关于使用 webpack 和 react-router 进行延迟加载和代码拆分不加载的主要内容,如果未能解决你的问题,请参考以下文章

使用 webpack 和 react-router 进行延迟加载和代码拆分不加载

使用 Webpack 和 React-router bundle.js Not Found

React-Router 和 webpack-dev-server 重新加载

webpack-dev-server 使用 react-router 启用 browserhistory 采坑记

webpack-dev-server 使用 react-router 启用 browserhistory 采坑记

webpack-dev-server react-router 推送状态