使用 React Router 和 Webpack 2 如何仅在某些路由上需要外部库?

Posted

技术标签:

【中文标题】使用 React Router 和 Webpack 2 如何仅在某些路由上需要外部库?【英文标题】:Using React Router and Webpack 2 how to require external libraries only on certain routes? 【发布时间】: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 的编辑——该死的我的拼写(即我称之为手的烤箱手套) 【参考方案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

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

by route config by component

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

https://webpack.js.org/guides/lazy-load-react/ https://gist.github.com/acdlite/a68433004f9d6b4cbc83b5cc3990c194

在准备该存储库时,我发现@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 上的相关问题:

https://github.com/webpack/webpack/issues/150 https://github.com/webpack/webpack/issues/118 https://github.com/webpack/webpack/issues/462 https://github.com/webpack/webpack/issues/240 https://github.com/webpack/webpack/issues/274 https://github.com/webpack-contrib/script-loader/issues/7

今天我在媒体上发现了 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,不过谢谢

以上是关于使用 React Router 和 Webpack 2 如何仅在某些路由上需要外部库?的主要内容,如果未能解决你的问题,请参考以下文章

使用 Django、webpack、reactjs、react-router 解耦前端和后端

使用 Webpack 2 和 React Router 进行 CSS 代码拆分

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

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

webpack 配置react脚手架:路由配置

无法使 react-hot-loader 和 webpack-dev-server 与 react-router 一起工作