【问题标题】:Using React Router and Webpack 2 how to require external libraries only on certain routes?使用 React Router 和 Webpack 2 如何仅在某些路由上需要外部库?
【发布时间】:2017-07-01 14:55:19
【问题描述】:

目前工作中

使用 Webpack 2 和 React Router v4,我已经能够设置工作代码拆分。有一个中间 <AsyncComponent> 解决了承诺并返回组件(在 github 问题上找到的模式)。

下面的一组示例路线:

<Switch>
    <Route 
        path="me" 
        render={(props) => 
            <AsyncComponent promise={ require.ensure([], (require) => require('./modules/Profile'), 'profile') } props={props} />
        } 
    />
    <Route 
        path="credit-card" 
        render={(props) => 
            <AsyncComponent promise={ require.ensure([], (require) => require('./modules/CreditCard'), 'credit-card') } props={props} />
        } 
    />
</Switch>

目标

我想进一步扩展它,并且对于某些路线,加载其他库。在上面的示例中,我想在信用卡路由上获取 StripeJS (https://js.stripe.com/v2/) 库。

我想强调的是,我可以将 Stripe 直接加载到页脚中,并且一切正常。有多个库,我使用 Stripe 作为一个易于理解的示例。

已尝试

以下尝试收效甚微:

  • 在 webpack 配置中标记外部库。这(正确地)将库标记为外部库,并且不会在 require 的解析序列期间尝试捆绑它。然而,期望是手动引入库。
  • 我玩过使用伪脚本加载器的想法(当到达该路径时,手动创建一个带有src 属性的&lt;script&gt;,等待它加载,然后让组件完成它的工作。这行得通好的,但是从可维护性的角度来看确实很糟糕(如果需要两个或更多库,那么我需要复制笨拙的手册script load)并且似乎与 webpack“方式”背道而驰。

配置的相关部分

const core = [
    'lodash',
    'react',
    'react-dom',
    'axios',
    'react-router-dom',
];

const config = {
    context: path.resolve(__dirname, './ts_build'),
    node: {
        fs: "empty"
    },
    entry: {
        app: './app.js',
        core: core,
    },
    output: {
        filename: '[name].js',
        chunkFilename: '[name].[id].chunk.js',
        path: path.resolve(__dirname, './../../public'),
        publicPath: 'http://example.org/',
    },
    resolve: {
        modules: [
            path.resolve('./src'),
        ],
    },
    plugins: [
        new webpack.optimize.CommonsChunkPlugin({
            names: ['core'], 
            minChunks: Infinity,
        }),
        new webpack.NamedModulesPlugin(),
    ],
};

【问题讨论】:

  • 感谢@Ben Everard 的编辑——该死的我的拼写(即我称之为手的烤箱手套)

标签: javascript reactjs webpack ecmascript-6 react-router


【解决方案1】:

你可能想看看:

https://github.com/faceyspacey/webpack-flush-chunks

https://github.com/faceyspacey/require-universal-module

后者是一个通用包,旨在创建同时需要同步和异步需要其他模块的模块。

这个包是你用来刷新请求所需要的 Webpack 块的:

https://github.com/faceyspacey/webpack-flush-chunks

它不是特定于 React Router,这可能意味着它更简单,需要更少的解决方法。

【讨论】:

    【解决方案2】:

    https://github.com/hinok/webpack2-react-router-code-splitting

    存储库包含webpack2 + react-router v4 + 使用动态import() 实现code splitting 并使用loadjs 仅按需加载外部库一次。例如,它会为特定路线加载 Stripe.js

    在存储库中,您可以找到两种进行代码拆分的方法

    它基于官方 Webpack 文档和 Andrew Clark 的要点

    在准备该存储库时,我发现@Chris 只想为托管在外部 CDN 上的某些路由加载 Stripe.js。从那时起,我一直在考虑使用 AMD 模块并避免泄漏全局变量的最佳方法,但这有点棘手,因为每个 AMD 模块可能有不同的包装器,说包装器我的意思是:

    (function (root, factory) {
        if (typeof define === 'function' && define.amd) {
            define(['b'], function (b) {
                return (root.amdWebGlobal = factory(b));
            });
        } else {
            root.amdWebGlobal = factory(root.b);
        }
    }(this, function (b) {
        return {};
    }));
    

    我可以说UMD 包装器是一种标准,但有时人们更喜欢不那么自以为是的包装器,或者他们只是不关心其他环境。我之所以提到它,是因为在 git 历史记录中,您可以找到 commit called Amdify,这更加证明了如何模拟 AMD env 的概念。最后我决定删除它,因为它远非理想,而且它没有涵盖所有用法和边缘情况。

    我找不到任何有关集成外部 AMD 模块或以某种方式与 webpack 一起使用 AMD 模块的信息。相反,我发现它根本行不通

    @mc-zone 你应该能够使用 script.js 加载 AMD 模块。但这与 webpack 无关,它只是起作用,因为 AMD 就是为此而设计的。因此,您将无法在这些 AMD 模块中使用 webpack 功能。 webpack 需要对构建时间进行静态分析。

    @jhns see on github

    webpack 不处理脚本加载。为此使用单独的库。一世。 e. https://github.com/ded/script.js/

    @sokra see on github

    如果不可能,你不应该 require() 它,而是通过像 script.js 这样的脚本加载器来加载它。

    @jhns see on github

    Github 上的相关问题:


    今天我在媒体上发现了 an article by James Kale 关于他的项目 react-loadable 的内容与 LazilyLoad 组件几乎相同,但承诺

    • 它通过动态 require() 支持服务器端渲染
    • 它会在需要时热切地预加载组件

    我强烈建议检查一下。

    【讨论】:

      【解决方案3】:

      这个问题有一些案例需要详细分析。

      让我们开始吧。

      观察

      • 您应该将react-router的使用从组件切换到PlainRoute对象,这样在进行代码拆分时会给您更大的灵活性,并且还可以跳过&lt;AsyncComponent&gt;组件的创建
      • 我很确定你的路由中会有更多的嵌套组件,那么如果credit-card 路由中的嵌套组件需要一个库怎么办?

      建议

      • 使用您的路由组件作为入口点,因此它不会成为单个组件中的大型实现
      • 鉴于上述建议,您可以在其中添加一个包含库依赖项的索引,并在该路由中使用它们(在您的嵌套组件中)
      • 您不应在代码的任何部分导入这些库,否则此功能会中断
      • 加载这些库后,将它们注入到窗口对象中。并且您的嵌套组件将能够全局访问您的库

      这是一个尚未实施的建议,但我正试图就您的问题将您引向正确的方向

      最后,这是我建议的方法:

      import App from 'components/App';
      
      function errorLoading(err) {
        console.error('Dynamic page loading failed', err);
      }
      
      function loadRoute(cb) {
        return (module) => cb(null, module.default);
      }
      
      function injectLibraries(libraries) {
        Object.keys(libraries).forEach(key => {
          window[key] = libraries[key];
        });
      }
      
      export default {
        component: App,
        childRoutes: [
          {
            path: '/(index.html)',
            name: 'home',
            getComponent(location, cb) {
              const importModules = Promise.all([
                import('components/HomePage/'),
                import('components/HomePage/libraries'),
              ]);
      
              const renderRoute = loadRoute(cb);
      
              importModules.then(([component, libraries]) => {
                injectLibraries(libraries);
                renderRoute(component);
              });
      
              importModules.catch(errorLoading);
            },
          },
          {
            path: '/credit-card',
            name: 'credit-card',
            getComponent(nextState, cb) {
              const importModules = Promise.all([
                import('components/CreditCardPage/'),
                import('components/CreditCardPage/libraries'),
              ]);
      
              const renderRoute = loadRoute(cb);
      
              importModules.then(([component, libraries]) => {
                injectLibraries(libraries);
                renderRoute(component);
              });
      
              importModules.catch(errorLoading);
            },
          },
        ],
      };
      

      您的库文件应如下所示:

      import stripe from 'stripe';
      
      export default {
        stripe 
      };
      

      【讨论】:

      • 感谢您花时间回答。 RR4 不再支持普通路由。 System.import 在 webpack2 中也被弃用了。否则,答案在某种程度上是正确的——但是像 Stripe 这样的库不能按照你在最终库文件中提到的方式导入,因为它们位于远程(基本上就像 cdn)
      • @Chris 我准备了一个存储库来回答您的问题,我将在今天晚上下班回家时发布长答案。敬请期待 - RR4 + Webpack2 + 没有 System.import() 或 require.ensure 的代码拆分 ;)
      • @hinok 期待!确保将其作为单独的答案包含在内,以便您获得正确的信用。
      【解决方案4】:

      如果你使用 Webpack 2,你在 React Router 配置文件中使用 import()

      export default {
       component: App,
       childRoutes: [
         {
           path: '/',
           getComponent(location, cb) {
             import('external-library-here')
             .then(function(){
               return System.import('pages/Home');
             })
            .then(loadRoute(cb)).catch(errorLoading);
           }
         },
         {
           path: 'blog',
           getComponent(location, cb) {
             import('pages/Blog').then(loadRoute(cb)).catch(errorLoading);
           }
         },
         {
           path: 'about',
           getComponent(location, cb) {
             import('pages/About').then(loadRoute(cb)).catch(errorLoading);
           }
         },
       ]
      };
      

      您还可以使用 Router 组件中的 getComponentgetComponents 属性来传递您专门针对该路由所需的模块。

      【讨论】:

      • 我认为您在使用 System.import 方面很正确 - 我正在从 WebpackV1 项目迁移,所以 require.ensure 可能应该被换掉。这么说 - 我的理解是System.import == require.ensure,上面的答案反映了我上面使用的确保模式。我的问题更多是关于进一步扩展它 - 我如何在某些路由上需要(或导入)特定的外部库。例如,使用您的答案,可能是System.import('stripe.js').then(System.import('blog').then(loadRoute(cb))
      • 抱歉回复晚了 - 没有收到评论。我相信 - 是的 - 你必须做链接,因为 System.import 返回一个承诺(就像你刚才说的那样)。为了让下一个阅读本文的人更清楚,我更新了答案。
      • 快速提问 - loadRoute 来自哪里?我了解cb,但还没有看到 loadRoute 记录在案?
      • 事实上这对于原始问题不起作用 - 它使用 RR2 语法并且 System.import 不接受外部网址。 Webpack 2 也已弃用 System.import,不过谢谢
      猜你喜欢
      • 2019-01-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-02-04
      • 1970-01-01
      • 1970-01-01
      • 2016-11-27
      相关资源
      最近更新 更多