Javascript 使用System.import或require.Sure响应代码拆分和服务器端呈现

Javascript 使用System.import或require.Sure响应代码拆分和服务器端呈现,javascript,reactjs,webpack,isomorphic-javascript,Javascript,Reactjs,Webpack,Isomorphic Javascript,我正在调查React应用程序中使用的代码拆分 我似乎找不到一种方法来为服务器端渲染引入代码拆分(和导入),这将干净地传递到客户端 仅供参考:我知道使用React路由器有一种方法可以做到这一点,但我认为这是一个更普遍的问题,不是每个人都想使用它。另外,我觉得代码拆分并不一定就是路由的同义词 下面是一个非常基本的类示例,该类将加载和呈现拆分代码包的内容SplitComponent 如果服务器端呈现的路由包含此组件,则componentWillMount将确保在调用render之前,代码与requir

我正在调查React应用程序中使用的代码拆分

我似乎找不到一种方法来为服务器端渲染引入代码拆分(和导入),这将干净地传递到客户端

仅供参考:我知道使用React路由器有一种方法可以做到这一点,但我认为这是一个更普遍的问题,不是每个人都想使用它。另外,我觉得代码拆分并不一定就是路由的同义词

下面是一个非常基本的类示例,该类将加载和呈现拆分代码包的内容
SplitComponent

如果服务器端呈现的路由包含此组件,则
componentWillMount
将确保在调用
render
之前,代码与
require
同步加载。它检查是否是服务器端,这样就不会执行此客户端操作

//Lazy.jsx
import React from 'react';
import { connect } from 'react-redux';
import { splitComponent, splitComponentSync } from './split';

const canUseDOM = !!(
  (typeof window !== 'undefined' &&
  window.document && window.document.createElement)
);

class Lazy extends React.Component {

  constructor() {
    super();
    this.state = {
      module: null
    };
  }

  componentWillMount() {

    // On server side only, synchronously load
    const { dispatch } = this.props;

    if (!canUseDOM) {

      // Also, register this bundle with the current component state as on
      // the server there is only a single render and thus the redux state
      // available through mapStateToProps is not up-to-date because it was
      // requested before the above dispatch.
      this.setState({
        module: splitComponentSync(dispatch)
      });

    }
  }

  componentDidMount() {
    const { dispatch, modules } = this.props;

    if (!modules.hasOwnProperty('./SplitComponent')) {
      splitComponent(dispatch);
    }
  }

  render() {
    const { module } = this.state;
    const { modules } = this.props;

    // On server side, rely on everything being loaded
    if (!canUseDOM && module) {
      return React.createElement(module);

    // On client side, use the redux store
    } else if (modules.hasOwnProperty('./SplitComponent') && modules['./SplitComponent']) {
      return React.createElement(modules['./SplitComponent']);
    }

    return null;
  }
}


function mapStateToProps(state) {

  const modules = state.modules;

  return {
    modules
  };
}

export default connect(mapStateToProps)(Lazy);
然后,对于客户端,
componentDidMount
将使用
System.import
异步加载
SplitComponent

这样做的结果是服务器端呈现正确的页面,客户端将显示该页面,但随后
组件didmount
将立即导致客户端加载
拆分组件
,在此期间,它将不显示任何内容(但短暂地取决于加载时间)。最后,
SplitComponent
将加载并呈现。但是,当它被删除,然后又被添加时,可能会出现闪烁。这削弱了在服务器上进行渲染的优势

有没有更好的方法来处理这个问题

import React from 'react';

const canUseDOM = !!(
  (typeof window !== 'undefined' &&
  window.document && window.document.createElement)
);

class Lazy extends React.Component {
  constructor() {
    super();
    this.state = {
      module: null
    };
  }

  componentWillMount() {
    if (!canUseDOM) {
      const m = require('./SplitComponent');
      this.setState({
        module: m.default
      });
    }
  }

  componentDidMount() {
    if (!this.state.module) {
      System.import('./SplitComponent').then(m => {
        this.setState({
          module: m.default
        });
      });
    }
  }

  render() {
    const { module } = this.state;
    console.log('Rendering Lazy', module);
    if (module) {
      return React.createElement(module);
    }

    return null;
  }
}

export default Lazy;

这似乎是一个棘手的问题,但我有一个解决方案,似乎是工作。这并不理想,我非常希望看到其他选择

基本思想是,一个React组件可以触发另一个组件的
导入
,以便于代码拆分。这相当简单,但是扩展它以支持服务器端渲染增加了很多复杂性

规则:

  • 导入必须在服务器端同步,因为只有一个渲染
  • 服务器端必须能够通知客户端服务器呈现的任何视图都需要哪些捆绑包
  • 然后,客户机必须在React开始呈现之前加载服务器通知它的任何捆绑包
  • 然后,客户机可以从这一点开始继续普通的代码拆分实践。bundle是异步加载的,加载后,会对重新渲染器作出反应,将其包含在渲染中
  • 下面是负责为
    SplitComponent
    管理代码拆分的
    Lazy
    类。它使用了
    split.js

    在服务器端呈现
    Lazy
    时,将运行
    componentWillMount
    ,并检查它是否实际上是服务器端。如果是,则会导致同步加载
    SplitComponent
    。加载的模块默认值存储在
    Lazy
    组件的状态中,以便可以立即呈现。它还向Redux发送一个操作,以注册正在呈现的视图需要此捆绑包的事实

    服务器端将成功呈现应用程序,redux存储将包含这样一个事实:客户端需要包含
    /SplitComponent
    的捆绑包

    //Lazy.jsx
    import React from 'react';
    import { connect } from 'react-redux';
    import { splitComponent, splitComponentSync } from './split';
    
    const canUseDOM = !!(
      (typeof window !== 'undefined' &&
      window.document && window.document.createElement)
    );
    
    class Lazy extends React.Component {
    
      constructor() {
        super();
        this.state = {
          module: null
        };
      }
    
      componentWillMount() {
    
        // On server side only, synchronously load
        const { dispatch } = this.props;
    
        if (!canUseDOM) {
    
          // Also, register this bundle with the current component state as on
          // the server there is only a single render and thus the redux state
          // available through mapStateToProps is not up-to-date because it was
          // requested before the above dispatch.
          this.setState({
            module: splitComponentSync(dispatch)
          });
    
        }
      }
    
      componentDidMount() {
        const { dispatch, modules } = this.props;
    
        if (!modules.hasOwnProperty('./SplitComponent')) {
          splitComponent(dispatch);
        }
      }
    
      render() {
        const { module } = this.state;
        const { modules } = this.props;
    
        // On server side, rely on everything being loaded
        if (!canUseDOM && module) {
          return React.createElement(module);
    
        // On client side, use the redux store
        } else if (modules.hasOwnProperty('./SplitComponent') && modules['./SplitComponent']) {
          return React.createElement(modules['./SplitComponent']);
        }
    
        return null;
      }
    }
    
    
    function mapStateToProps(state) {
    
      const modules = state.modules;
    
      return {
        modules
      };
    }
    
    export default connect(mapStateToProps)(Lazy);
    
    客户端按照从服务器渲染合并redux存储的常规过程进行初始化

    一旦发生这种情况,就必须确保在开始渲染之前导入所有必需的包。我们检查redux存储
    模块
    ,看看需要什么。我在这里用一个简单的if语句查找它们。对于所需的每个bundle,都是异步加载的,它的默认模块存储在redux存储中,并返回一个承诺。一旦所有这些承诺都得到解决,那么React将被允许兑现

    //configureStore.js (Excerpt)
    let ps;
    if (initialState && initialState.hasOwnProperty('modules')) {
      ps = Object.keys(initialState.modules).map(m => {
        if (m === './SplitComponent') {
          return splitComponent(store.dispatch);
        }
      });
    }
    
    // My configureStore.js returns a Promise and React only renders once it has resolved
    return Promise.all(ps).then(() => store);
    
    今后,无论何时使用
    Lazy
    +
    SplitComponent
    ,都不需要加载代码,因为它已经存在于redux存储中


    在初始应用程序不包括
    Lazy
    +
    SplitComponent
    的情况下,当React呈现
    Lazy
    时,
    componentDidMount
    将启动异步操作导入
    /SplitComponent
    ,并向redux注册。与任何redux操作一样,此状态更改将导致
    惰性组件尝试重新加载,并且由于
    拆分组件现在已加载并注册,因此它可以重新加载和注册。

    如果您正在寻找一种可大大减少解决方案中涉及的样板文件数量的方法,我建议签出“react async component”()

    github页面中的说明:

    创建异步解析的组件,支持服务器端呈现和代码拆分

    这个库是代码拆分组件的一个演变。与代码拆分组件不同,该库不要求您使用Webpack或Babel。相反,它为您提供了一个纯Javascript/React API,该API经过适当调整,使其在一般情况下对延迟加载的组件非常有用,并支持现代代码拆分API(例如import()、System.import、require.sure)

    我也遇到了同样的问题(客户端渲染上的半秒闪烁),而您的解决方案是我找到的唯一一个解决方案,但从那时起,我发现了这个库,它对我来说非常有吸引力

    它的工作原理是将组件的状态存储在服务器渲染模板中的
    窗口
    对象上,在异步客户端渲染完成之前使用该模板

    文档也很好,总是很好

    在服务器和clie上使用少量样板文件包装渲染方法后
    //configureStore.js (Excerpt)
    let ps;
    if (initialState && initialState.hasOwnProperty('modules')) {
      ps = Object.keys(initialState.modules).map(m => {
        if (m === './SplitComponent') {
          return splitComponent(store.dispatch);
        }
      });
    }
    
    // My configureStore.js returns a Promise and React only renders once it has resolved
    return Promise.all(ps).then(() => store);
    
    import React from 'react';
    import { createAsyncComponent } from 'react-async-component';
    
    const AsyncComponent = createAsyncComponent({
      resolve: () => System.import('./components/MyComponent')
    });
    
    <AsyncComponent myProp={1} />