现代Web开发可提供丰富的用户体验,涵盖了用户流和交互的范围。建立,维护,部署和交付这些体验需要大规模的开发团队和复杂的部署系统。
Web应用程序的当前状态
用于现代Web应用程序的最常见模式是单页应用程序(SPA)。SPA的核心原理是构建交付给用户的单个Web应用程序。SPA通过根据用户交互或数据更改来重写页面内容来工作。SPA通常将包含用于处理页面导航和深层链接的路由器,并且可以由多个组件(例如购物篮或产品列表)组成。
典型的SPA应用流程遵循标准步骤:
用户访问Web应用程序
浏览器请求JavaScript和CSS
JavaScript应用程序启动,并将初始内容添加到浏览器文档中
用户与应用程序进行交互-例如单击导航链接或将产品添加到购物篮
该应用程序重写了部分浏览器文档以反映更改
在大多数情况下,使用JavaScript框架可实现上述目的。React,Vue或Angular等框架具有可帮助构建SPA的模式和最佳实践。作为示例,React是一个非常直观的框架,使用JSX来根据用户和数据更改呈现内容。让我们看下面的基本示例:
//App.js import React from "react"; import "./styles.css"; const App = () => { return ( <div className="App"> <h1>Hello I'm a SPA.</h1> </div> ); } export default App;
这是我们的基本应用。它呈现了一个简单的视图:
import React from "react"; import ReactDOM from "react-dom"; import App from "./App"; const rootElement = document.getElementById("root"); ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, rootElement );
接下来,我们通过将React应用程序呈现到浏览器DOM中来启动应用程序。这只是SPA的基础。从这里,我们可以添加更多功能,例如路由和共享组件。
SPA是现代开发的主要内容,但并不完美。SPA有很多缺点。
其中之一是搜索引擎优化的损失,因为只有在用户在浏览器中查看应用程序后,才会呈现该应用程序。Google的网络爬虫将尝试呈现页面,但无法完全呈现应用程序,并且您将失去攀登搜索排名所需的许多关键字。
框架复杂性是另一个缺点。如前所述,有许多框架可以提供SPA体验,并允许您构建可靠的SPA,但是每个框架都针对不同的需求,因此很难知道采用哪种框架。
浏览器性能也可能是一个问题。因为SPA执行用户交互的所有呈现和处理,所以它可能具有连锁效应,具体取决于用户的配置。并非所有用户都会在现代浏览器上通过高速连接运行您的应用程序。为了保持流畅的用户体验,需要减小捆绑包的大小并尽可能减少客户端上的处理。
以上所有这些导致最终的问题,即规模。试图构建一个可以满足用户所有需求的复杂应用程序需要多个开发人员。使用SPA可能会导致许多人使用相同的代码来尝试进行更改并引起冲突。
那么所有这些问题的解决方案是什么?微型前端!
什么是微型前端?
微型前端是一种架构模式,用于构建可扩展的Web应用程序,该应用程序随您的开发团队一起增长,并允许您扩展用户交互。我们可以说这是我们SPA的简化版本,从而将其与现有SPA关联起来。对于用户而言,此版本仍然看起来像SPA,但在幕后,它会根据用户的流动态加载应用程序的某些部分。
为了进一步说明这一点,让我们以比萨店应用程序为例。核心功能包括选择披萨,然后将其添加到您的购物篮中并签出。以下是该应用程序的SPA版本的模型。
通过考虑可以拆分的应用程序的不同部分,让我们将其变成一个微型前端。我们可以按照分解创建应用程序所需的组件时的方式来考虑。
所有微型前端都从宿主容器开始。这是将所有部分结合在一起的主要应用程序。这将是访问应用程序时发送给用户的主要JavaScript文件。然后,我们转到实际的微型前端-产品列表和购物篮前端。这些可以在本地与主要主机分离,并作为微型前端交付。
让我们更深入地研究“与主机分离的本地”。当我们想到传统的SPA时,在大多数情况下,您将构建一个JavaScript文件并将其发送给用户。使用微型前端,我们仅将主机代码发送给用户,并且根据用户流,我们进行网络调用以获取应用程序其余部分的其他代码。该代码可以与启动主机存储在不同的服务器上,并且可以随时更新。这将导致更多的生产开发团队。
如何建立一个微型前端?
有多种构建微型前端的方法。对于此示例,我们将使用webpack。Webpack 5发布了模块联合作为核心功能。这使您可以将远程Webpack构建导入到您的应用程序中,从而为微型前端提供易于构建和维护的模式。
完整的webpack微型前端应用程序可以在这里[https://github.com/sitepoint-editors/webpack-federation-example]找到。
Home Container
首先,我们需要创建一个将成为应用程序宿主的容器。这可以是应用程序的非常基本的框架,也可以是在用户与产品进行交互之前具有菜单组件和一些基本UI的容器。使用webpack,我们可以导入ModuleFederation
插件并配置容器和任何微型前端:
// packages/home/webpack.config.js const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); module.exports = { ... plugins: [ new ModuleFederationPlugin({ name: "home", library: { type: "var", name: "home" }, filename: "remoteEntry.js", remotes: { "mf-products": "products", "mf-basket": "basket", }, exposes: {}, shared: require("./package.json").dependencies, }), new HtmlWebPackPlugin({ template: "./src/index.html", }), ], };
注意:您可以查看webpack.config.js
文件在GitHub上这里[https://github.com/sitepoint-editors/webpack-federation-example/blob/master/packages/home/webpack.config.js]。
在这里,我们将模块命名为“ home”,因为这是容纳所有前端的容器。然后,我们提供库详细信息,因为容器也可以是微型前端,因此我们声明有关它的详细信息,例如其类型,在这种情况下为var
。类型定义了它是哪种webpack模块类型。var
声明该模块是ES2015兼容模块。
然后,我们将产品和购物篮模块设置为遥控器。这些将稍后在导入和使用组件时使用。将模块导入应用程序时,将使用我们提供的模块名称(“ mf-products”和“ mf-basket”)。
配置模块后,可以将脚本标签添加到主index.html
目录的主文件中,该文件将指向托管的模块。在我们的例子中,它们都在localhost上运行,但是在生产环境中,它可以在Web服务器或Amazon S3存储桶上。
<!-- packages/home/src/index.html --> <script src="http://localhost:8081/remoteEntry.js"></script> //product list <script src="http://localhost:8082/remoteEntry.js"></script> //basket
注意:您可以查看index.html
文件在GitHub上这里[https://github.com/sitepoint-editors/webpack-federation-example/blob/master/packages/home/src/index.html]。
家用容器的最后一部分是导入和使用模块。在我们的示例中,模块是React组件,因此我们可以使用React.lazy导入它们,并像对待任何react组件一样使用它们。
通过使用React.lazy
我们可以导入组件,但是仅在呈现组件时才获取基础代码。这意味着即使用户不使用它们,我们也可以导入它们,并在有条件后有条件地渲染它们。让我们看一下如何使用组件:
// packages/home/src/src/App.jsx const Products = React.lazy(() => import("mf-nav/Products")); const Basket = React.lazy(() => import("mf-basket/Basket"));
注意:您可以查看App.jsx
文件在GitHub上这里[https://github.com/sitepoint-editors/webpack-federation-example/blob/master/packages/home/src/App.jsx#L4]。
与标准组件用法的主要区别在于React.lazy。这是一个内置的React函数,用于处理代码的异步加载。正如我们过去在使用React.lazy
代码时获取代码一样,我们需要将组件包装在Suspense组件中。这有两件事:触发组件代码的获取,并呈现正在加载的组件。除了Suspense组件和fallback组件,我们可以像其他任何React组件一样使用我们的微型前端模块。
产品[Product and Basket]
配置家用容器后,我们需要设置产品和购物篮模块。这些遵循与家用容器类似的模式。首先,我们需要导入webpackModuleFederation
插件,就像在home容器的webpack配置中所做的那样。然后我们配置模块设置:
// packages/basket/webpack.config.js const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); module.exports = { ... plugins: [ new ModuleFederationPlugin({ name: 'basket', library: { type: 'var', name: 'basket' }, filename: 'remoteEntry.js', exposes: { './Basket': './src/Basket' }, shared: require('./package.json').dependencies }) ], };
我们为模块提供一个名称,该名称将是产品或购物篮以及库的详细信息,然后是fileName
-在这种情况下为远程输入。这是webpack的标准,但可以是您想要的任何东西-例如产品代码名称或模块名称。这将是webpack生成的文件,并将托管给宿主容器以供参考。使用fileName remoteEntry,模块的完整URL将为http://myserver.com/remoteEntry.js
。然后,我们定义暴露选项。这定义了模块导出的内容。在我们的例子中,这只是购物篮或产品文件,这是我们的组件。但是,这可能是多个组件或不同的资源。
最后,回到家庭容器中,这就是使用这些组件的方式:
// packages/home/src/src/App.jsx <div className="app-content"> <section> <React.Suspense fallback={<div>....loading product list</div>}> <ProductList onBuyItem={onBuyItem} /> </React.Suspense> </section> <section> { selected.length > 0 && <React.Suspense fallback={<div>....loading basket</div>}> <Basket items={selected} onClear={() => setSelected([])} /> </React.Suspense> } </section> </div>
注意:您可以查看Product and Basket usage
文件在GitHub上这里[https://github.com/sitepoint-editors/webpack-federation-example/blob/master/packages/home/src/App.jsx#L23]。
依存关系[Dependencies]
我们还没有谈论依赖性。如果您从上述代码示例中注意到,则每个webpack模块配置都有一个共享的配置选项。这告诉webpack在微型前端之间应该共享哪些Node模块。这对于减少最终应用程序上的重复非常有用。例如,如果basket和home容器都使用样式化的组件,则我们不想加载两个版本的样式化组件。
您可以通过两种方式配置共享选项。第一种方法是作为您要共享的已知共享节点模块的列表。另一个选项是从其自己的程序包JSON文件中提供模块依赖项列表。这将共享所有依赖关系,并且在运行时webpack将确定所需的依赖项。例如,当导入购物篮时,webpack将能够检查其需求以及是否已共享其依赖项。如果购物篮使用Lodash,但家不使用,则它将从购物篮模块获取Lodash依赖项。如果房屋已经有Lodash,则不会加载。
缺点
所有这些听起来都很棒-太好了,难以置信。在某些情况下,这是完美的解决方案。在其他情况下,这可能会导致超出其价值的开销。尽管微型前端模式可以使团队更好地合作,并在应用程序的各个部分上快速前进,而不会因繁琐的部署管道,混乱的Git合并和代码审查而减慢速度,但仍有一些缺点:
复制依赖逻辑。如依赖性部分所述,webpack可以为我们处理共享的Node模块。但是,当一个团队使用Lodash来实现其功能逻辑而另一个团队使用Ramda时会发生什么呢?现在,我们提供了两个功能编程库,以实现相同的结果。
设计,部署和测试的复杂性。现在,我们的应用程序可以动态加载内容,因此很难完整了解整个应用程序。确保跟踪所有微型前端本身就是一项任务。部署可能会变得更加危险,因为您不确定100%在运行时将什么内容加载到应用程序中。这导致更难的测试。每个前端都可以单独进行测试,但是需要获得完整的,真实世界的用户测试,以确保该应用程序适合最终用户。
标准。现在,应用程序被分解成较小的部分,很难使所有开发人员都遵循相同的标准。一些团队可能比其他团队进步更多,或者提高或降低代码质量。将所有人保持在同一页面上对于提供高质量的用户体验很重要。
成熟:微前端不是一个新概念,并且在使用iframe和自定义框架之前已经实现。但是,webpack直到最近才将其作为webpack 5的一部分引入。对于webpack捆绑,它仍然是新事物,并且有很多工作可以建立标准并使用这种模式发现错误。要使它成为一种强大的,可立即投入生产的模式,还有很多工作要做,可以由使用webpack的团队轻松使用。
结论
因此,我们学习了如何使用webpack模块联合来构建React应用程序以及如何在微前端之间共享依赖项。这种构建应用程序的模式非常适合团队将应用程序分解为较小的部分,从而与传统的SPA应用程序(部署和发布过程较慢)相比,可以更快地增长和升级。显然,这并不是可以应用于所有用例的灵丹妙药,但是在构建下一个应用程序时需要考虑这一点。由于一切仍然很新,我建议您尽早采用微型前端以进入地面,因为从微型前端模式转换为标准SPA比其他方法更容易。
网友评论文明上网理性发言 已有0人参与
发表评论: