使用 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
属性的<script>
,等待它加载,然后让组件完成它的工作。这行得通好的,但是从可维护性的角度来看确实很糟糕(如果需要两个或更多库,那么我需要复制笨拙的手册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
组件几乎相同,但承诺
我强烈建议检查一下。
【讨论】:
【参考方案3】:这个问题有一些案例需要详细分析。
让我们开始吧。
观察
您应该将react-router
的使用从组件切换到PlainRoute
对象,这样在进行代码拆分时会给您更大的灵活性,并且还可以跳过<AsyncComponent>
组件的创建
我很确定你的路由中会有更多的嵌套组件,那么如果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
组件中的 getComponent
或 getComponents
属性来传递您专门针对该路由所需的模块。
【讨论】:
我认为您在使用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 进行延迟加载和代码拆分不加载
无法使 react-hot-loader 和 webpack-dev-server 与 react-router 一起工作