最近在学习一些微前端的知识,转载一片我的以为比较好的。javascript
微前端能够理解为,一个很大的前端应用,将此应用总体分解,分解成多个项目,每一个项目交给一个团队,各团队能够独立开发、测试、部署和维护,其实用什么方法都没有明确规定,最终的目的是要达到代码隔离和团队自治便可。html
咱们用一个很是传统的方式开始,将多个模板渲染到服务器上的HTML里。咱们有一个index.html,其中包含全部常见的页面元素,而后使用 include 来引入其余模板:前端
<html lang="en" dir="ltr"> <head> <meta charset="utf-8"> <title>Feed me</title> </head> <body> <h1>🍽 Feed me</h1> <!--# include file="$PAGE.html" --> </body> </html>
而后配置 nginxjava
server { listen 8080; server_name localhost; root /usr/share/nginx/html; index index.html; ssi on; # 将 / 重定向到 /browse rewrite ^/$ http://localhost:8080/browse redirect; # 根据路径访问 html location /browse { set $PAGE 'browse'; } location /order { set $PAGE 'order'; } location /profile { set $PAGE 'profile' } # 全部其余路径都渲染 /index.html error_page 404 /index.html; }
这是至关标准的服务器端应用。咱们之因此能够称其为微前端,是由于咱们让每一个页面独立,可由一个独立的团队交付。node
为了得到更大的独立性,能够有一个单独的服务器负责渲染和服务每一个微型前端,其中一个服务器位于前端,向其余服务器发出请求。经过缓存,能够把延迟降到最低。react
有人会用到的一种方法是将每一个微前端发布为一个 node 包,并让容器应用程序将全部微前端应用做为依赖项。好比这个 package.json:webpack
{ "name": "@feed-me/container", "version": "1.0.0", "description": "A food delivery web app", "dependencies": { "@feed-me/browse-restaurants": "^1.2.3", "@feed-me/order-food": "^4.5.6", "@feed-me/user-profile": "^7.8.9" } }
乍看彷佛没什么问题,这种作法会产生一个可部署的包,咱们能够轻松管理依赖项。
可是,这种方法意味着咱们必须从新编译并发布每一个微前端应用,才能发布咱们对某个应用做出的更改。咱们强烈不建议使用这种微前端方案。nginx
iframe 是集成的最简单方式之一。本质上来讲,iframe 里的页面是彻底独立的,能够轻松构建。并且 iframe 还提供了不少的隔离机制。git
<html> <head> <title>Feed me!</title> </head> <body> <h1>Welcome to Feed me!</h1> <iframe id="micro-frontend-container"></iframe> <script type="text/javascript"> const microFrontendsByRoute = { '/': 'https://browse.example.com/index.html', '/order-food': 'https://order.example.com/index.html', '/user-profile': 'https://profile.example.com/index.html', }; const iframe = document.getElementById('micro-frontend-container'); iframe.src = microFrontendsByRoute[window.location.pathname]; </script> </body> </html>
iframe 并非一项新技术,因此上面代码也许看起来并不那么使人兴奋。
可是,若是咱们从新审视先前列出的微前端的主要优点,只要咱们谨慎地划分微应用和组建团队的方式,iframe便很适合。
咱们常常看到不少人不肯意选择iframe。由于 iframe有点使人讨厌,但 iframe 实际上仍是有它的优势的。上面提到的容易隔离确实会使iframe不够灵活。它会使路由、历史记录和深层连接变得更加复杂,而且很难作成响应式页面。github
这种方式多是最灵活的一种,也是被采用频率最高的一种方法。每一个微前端都对应一个 <script> 标签,而且在加载时导出一个全局变量。而后,容器应用程序肯定应该安装哪些微应用,并调用相关函数以告知微应用什么时候以及在何处进行渲染。
<html> <head> <title>Feed me!</title> </head> <body> <h1>Welcome to Feed me!</h1> <!-- 这些脚本不会立刻渲染应用 --> <!-- 而是分别暴露全局变量 --> <script src="https://browse.example.com/bundle.js"></script> <script src="https://order.example.com/bundle.js"></script> <script src="https://profile.example.com/bundle.js"></script> <div id="micro-frontend-root"></div> <script type="text/javascript"> // 这些全局函数是上面脚本暴露的 const microFrontendsByRoute = { '/': window.renderBrowseRestaurants, '/order-food': window.renderOrderFood, '/user-profile': window.renderUserProfile, }; const renderFunction = microFrontendsByRoute[window.location.pathname]; // 渲染第一个微应用 renderFunction('micro-frontend-root'); </script> </body> </html>
上面是一个很基本的例子,演示了 JS 集成的大致思路。
与 package 集成不一样,咱们能够用不一样的bundle.js独立部署每一个应用。
与 iframe 集成不一样的是,咱们具备彻底的灵活性,你能够用 JS 控制何时下载每一个应用,以及渲染应用时额外传参数。
这种方法的灵活性和独立性使其成为最经常使用的方案。当咱们展现完整的示例时,会有更详细的探讨。
这是前一种方法的变体,每一个微应用对应一个 HTML 自定义元素,供容器实例化,而不是提供全局函数。
<html> <head> <title>Feed me!</title> </head> <body> <h1>Welcome to Feed me!</h1> <!-- 这些脚本不会立刻渲染应用 --> <!-- 而是分别提供自定义标签 --> <script src="https://browse.example.com/bundle.js"></script> <script src="https://order.example.com/bundle.js"></script> <script src="https://profile.example.com/bundle.js"></script> <div id="micro-frontend-root"></div> <script type="text/javascript"> // 这些标签名是上面代码定义的 const webComponentsByRoute = { '/': 'micro-frontend-browse-restaurants', '/order-food': 'micro-frontend-order-food', '/user-profile': 'micro-frontend-user-profile', }; const webComponentType = webComponentsByRoute[window.location.pathname]; // 渲染第一个微应用(自定义标签) const root = document.getElementById('micro-frontend-root'); const webComponent = document.createElement(webComponentType); root.appendChild(webComponent); </script> </body> </html>
主要区别在于使用 Web Component 代替全局变量。若是你喜欢 Web Component 规范,那么这是一个不错的选择。若是你但愿在容器应用程序和微应用之间定义本身的接口,那么你可能更喜欢前面的示例。
CSS 没有模块系统、命名空间和封装。就算有,也一般缺少浏览器支持。在微前端环境中,这些问题会更严重。
例如,若是一个团队的微前端的样式表为 h2 { color: black; },而另外一个团队的则为 h2 { color: blue; },而这两个选择器都附加在同一页面上,就会冲突!
这不是一个新问题,但因为这些选择器是由不一样的团队在不一样的时间编写的,而且代码可能分散在不一样的库中,所以更难避免。
多年来,有许多方法可让 CSS 变得更易于管理。有些人选择使用严格的命名约定,例如 BEM,以确保选择器的范围是足够小的。其余一些人则使用预处理器,例如 SASS,其选择器嵌套能够用做命名空间。一种较新的方法是经过 CSS 模块 或各类 CSS-in-JS 库,以编程的方式写 CSS。某些开发者还会使用 shadow DOM 来隔离样式。
只要你选择一种能确保开发人员的样式互不影响的方案便可。
上面咱们提到,视觉一致性很重要,一种解决方法是应用间共享可重用的 UI 组件库。
提及来容易,作起来难。建立这样一个库的主要好处是减小工做量。此外,你的组件库能够充当样式指南,做为开发人员和设计师之间进行协做的重要桥梁。
第一个容易出错的点,就是过早地建立了太多组件。好比你试图建立一个囊括全部常见 UI 组件的组件库。可是,经验告诉咱们,在实际使用组件以前,咱们很难猜想组件的 API 应该是什么样的,强行作组件会致使早期的混乱。所以,咱们宁愿让团队根据需求建立本身的组件,即便这最初会致使某些重复。
让 API 天然出现,一旦组件的 API 变得显而易见,就能够将重复的代码整合到共享库中。
与任何共享内部库同样,库的全部权和治理权很难分配。一种人认为,全部开发成员都拥有库的全部权,实际上这意味着没有人拥有库的全部权。若是没有明确的约定或技术远见,共享组件库很快就会成为不一致代码的大杂烩。若是取另外一个极端,即彻底集中式的开发共享库,后果就是建立组件的人与使用这些组件的人之间将存在很大的脱节。
咱们见过的最好的合做方式是,任何人均可觉得库贡献代码,可是有一个 托管者(一我的或一个团队)负责确保这些代码的质量、一致性和有效性。
维护共享库的人须要技术很强,同时沟通能力也要很强。
关于微前端的最多见问题之一是如何让应用彼此通讯。咱们建议应该尽量少地通讯,由于这一般会引入没必要要的耦合。
不过跨应用通讯的需求仍是存在的。
若是你使用的是 Redux,那么一般你会为整个应用建立一个全局状态。但若是每一个微应用是独立的,那么每一个微应用就都应该有本身的 Redux 和全局状态。
不管选择哪一种方法,咱们都但愿咱们的微应用经过消息或事件进行通讯,并避免任何共享状态,以免耦合。
你还应该考虑如何自动验证集成没有中断。功能测试是解法之一,可是因为实现和维护成本,咱们倾向于只作一部分功能测试。或者,你能够实施消费者驱动接口,让每一个微应用指定它对其余微应用的要求,这样你就不用实际将它们所有集成在一块儿并在浏览器中测试。
若是咱们有独立的团队独立处理前端应用,那么后端开发又是怎样的呢?
咱们坚信全栈团队的价值,从界面代码一直到后台 API 开发,再到数据库和网站架构。
咱们推荐的模式是 Backends For Frontends 模式,其中每一个前端应用程序都有一个相应的后端,后端的目的仅仅是为了知足该前端的需求。BFF模式起初的粒度多是每一个前端平台(PC页面、手机页面等)对应一个后端应用,但最终会变为每一个微应用对应一个后端应用。
这里要说明一下,一个后端应用可能有独立业务逻辑和数据库的,也可能只是下游服务的聚合器。 若是微前端应用只有一个与之通讯的API,而且该API至关稳定,那么为它单独构建一个后台可能根本没有太大价值。指导原则是:构建微前端应用的团队没必要等待其余团队为其构建什么事物。
所以,若是一个微前端用到的新功能须要后端接口的变动,那么这一前一后两个地方就应该交给一个团队来开发。
另外一个常见的问题是,如何作身份验证和鉴权?
显然用户只须要进行一次身份验证,所以身份验证应该放在容器应用里。容器可能具备某种登陆形式,经过该登陆形式咱们能够得到某种令牌。该令牌将归容器全部,并能够在初始化时注入到每一个微前端中。最后,微前端能够将令牌发送到服务器,而后服务器进行验证。
虽然咱们但愿每一个团队和微应用尽量独立,可是有些事情仍是会共享的。
上面提过共享组件库,可是对于这个小型应用而言,组件库会显得过大。所以,咱们有一个小 的公共内容库,其中包括图像、JSON数据和CSS,这些内容被全部其余微应用共享。
还有一个重要的东西须要共享:依赖库。重复的依赖项是微前端的一个常见缺点。即便在应用程序之间共享这些依赖也很是困难,咱们来讨论如何实现依赖库的共享。
第一步是选择要共享的依赖项。对咱们已编译代码的分析代表,大约50%的代码是由 react 和 react-dom 贡献。这两个库是咱们最核心的依赖项,所以若是把这两个库单独提取出来做为共享库,会很是有效。最后,它们是很是稳定和成熟的库,升级也很慎重,因此升级工做应该不会太困难。
至于如何提取,咱们须要作的就是在 webpack 配置中将库标记为外部库(externals):
module.exports = (config, env) => { config.externals = { react: 'React', 'react-dom': 'ReactDOM' } return config; };
而后,用 script 向每一个index.html 文件添加几个标签,以从共享内容服务器中获取这两个库:
<body> <div id="root"></div> <script src="%REACT_APP_CONTENT_HOST%/react.prod-16.8.6.min.js"></script> <script src="%REACT_APP_CONTENT_HOST%/react-dom.prod-16.8.6.min.js"></script> </body>
做者:方应杭
连接:https://juejin.im/post/5d8adb... 来源:掘金