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

时间:2017-02-13 15:03:53

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

目前正在工作

使用Webpack 2和React Router v4我已经能够设置工作代码分割。有一个中间<AsyncComponent>解析了promise并返回组件(在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配置中标记lib外部。这(正确地)将库标记为外部库,并且在require的解析序列期间不会尝试将其捆绑。但是期望手动引入库。
  • 我玩过使用伪脚本加载器的想法(当遇到该路由时,手动创建一个带有<script>属性的src,等待它加载,然后让组件执行它这个工作正常,但是从可维护性(如果需要两个或更多库,我需要复制笨拙的手动script加载)的观点来看真的很糟糕,并且似乎对Webpack&#34有效;方式&#34;。

配置的相关部分

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(),
    ],
};

4 个答案:

答案 0 :(得分:3)

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

存储库包含使用动态import()webpack2 + react-router v4 +已实施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模块或以某种方式使用AMD模块与webpack的信息。相反,我发现它不起作用

  

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

by @jhns see on github

  

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

by @sokra see on github

  

如果不可能,你不应该要求()它,而是通过脚本加载程序如script.js加载它。

by @jhns see on github

Github上的相关问题:

今天我在媒体上找到了关于他的项目an article by James Kalereact-loadable,它与LazilyLoad组件几乎完全相同,但承诺

  • 它通过动态require()
  • 支持服务器端呈现
  • 在需要时急切地预加载组件

我强烈建议您查看。

答案 1 :(得分:0)

如果您使用的是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);
     }
   },
 ]
};

您还可以使用getComponent组件中的getComponentsRouter道具传递您专门针对该路线所需的模块。

答案 2 :(得分:0)

在这个问题中有一些案例应该详细讨论。

让我们开始。

观察

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

建议

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

这是一项尚未实施的建议,但我试图让您就问题采取正确的方向

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

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 
};

答案 3 :(得分:0)

您可能需要查看:

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

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

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

此包用于刷新确定为请求所需的Webpack块:

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

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