原文:Zack Jackson翻译:疯狂的技术宅javascript
https://indepth.dev/webpack-5...html
未经容许严禁转前端
从没有哪种在独立的应用程序之间共享代码的可伸缩解决方案可以如此便捷,并且在成规模时几乎是不可能的作到的。咱们所拥有的最接近的东西是 externals 或 DLLPlugin,不过这形成了对外部文件的集中式依赖。共享代码很麻烦,各个应用程序并非真正独立的,而且一般只能共享有限数量的依赖项。此外,在单独捆绑的应用程序之间共享实际的功能代码或组件是不可行的、无效的而且是无益的。
对于那些想要更通俗版本的人,Jack Herrington 录了一个视频!java
油管视频:https://youtu.be/D3XYAx30CNcnode
咱们须要一个可扩展的解决方案来共享 node 模块和功能与应用程序代码。它须要在运行时发生,以便具备适应性和动态性。 Externals
并不能有效或灵活地完成工做;Import maps 没法解决规模问题。我并非要单独下载代码并共享依赖项,而是须要一个业务编配层,该层可以在运行时动态地共享模块,并有后备功能。react
Module Federation 是我发明并原型化的一种 JavaScript 体系结构。而后,在个人联合创始人和 Webpack 创始人的帮助下— —它变成了 Webpack 5 核心中最使人兴奋的功能之一(里面有一些很棒的东西,新的 API 确实功能强大且简洁)。webpack
我很自豪地向你介绍,JavaScript 应用架构中期待已久的飞跃。咱们对开源社区的贡献:Module Federation
模块联合(Module Federation) 容许 JavaScript 应用动态地从另外一个应用中加载代码,而后在过程当中共享依赖项。若是使用模块联合的应用程序不具备联合代码所需的依赖项,则 Webpack 将从该联合的生成源中下载缺乏的依赖项。git
能够共享代码,可是每种状况都存在后备方案。联合代码始终能够加载其依赖关系,但在下载更多有效负载以前将尝试使用使用者的依赖关系。这意味着像单片 Webpack 构建同样,更少的代码重复和依赖关系共享。虽然我发明了这个系统,但它是 Marais Rossouw 和我(Zack Jackson)共同编写的,并获得了 Tobias Koppers 的大量指导和帮助。这些工程师在重写和稳定 Webpack 5 核心中的模块联合部分发挥了关键做用。感谢他们一直以来的合做与支持。程序员
请注意,该系统的设计宗旨是使每一个彻底独立的构建或应用均可以位于本身的存储库中,能够独立部署,并可以做为本身的独立 SPA 运行。
这些应用都是双向主机(bi-directional hosts)。 首先加载的任何应用都将会成为主机**。当你修改路由并在应用程序中移动时,它将会以和动态导入相同的方式加载联合模块。可是若是你要刷新页面,则首先在该负载上启动的任何应用程序都将会成为主机。github
假设网站的每一个页面都是独立部署和编译的。我须要这种 micro-frontend 样式的体系结构,可是咱们不但愿在修改路由时从新加载页面。我还但愿在它们之间动态共享代码和服务以使其高效,就好像它是一个大型的 Webpack 构建并进行了代码拆分同样。
登录主页应用程序将使 “主页” 页面成为“主机”。若是浏览到 “about” 页面,则主机(主页 spa)其实是从另外一个独立的应用程序( about 页面 spa)动态导入模块,它不会加载主入口点和整个应用程序:仅仅几千字节的代码。若是我在 “about” 页面上并刷新浏览器,“about” 页面会成为“主机”,而再次浏览回到主页将是 “about” 页面 “主机” 的一种状况,即从 “远程” 页面(即主页)中获取运行时的一部分。
全部应用程序都是远程和主机,被调用者以及系统中任何其余联合模块的使用者。
你能够在 GitHub 上阅读更多有关技术方面的信息:
https://github.com/webpack/we...
让咱们从三个独立的应用程序开始。
配置:
我将使用 App 1 中的应用容器 App
。其余应用程序将会使用它。为此我将其 App
公开为 AppContainer
。
App 1 还将使用来自另外两个联合应用的组件。为此,我指定了remotes
配置项:
const HtmlWebpackPlugin = require("html-webpack-plugin"); const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); module.exports = { // other webpack configs... plugins: [ new ModuleFederationPlugin({ name: "app_one_remote", remotes: { app_two: "app_two_remote", app_three: "app_three_remote" }, exposes: { 'AppContainer':'./src/App' }, shared: ["react", "react-dom","react-router-dom"] }), new HtmlWebpackPlugin({ template: "./public/index.html", chunks: ["main"] }) ] }
设置构建流程:
在我应用程序的开头加载了 app_one_remote.js
。这样能够把你链接到其余 Webpack 运行时,并在运行时预配业务编配层。这是专门设计的 Webpack 运行时和入口点。 它不是普通的应用程序入口点,只有几个 KB 。
<head> <script src="http://localhost:3002/app_one_remote.js"></script> <script src="http://localhost:3003/app_two_remote.js"></script> </head> <body> <div id="root"></div> </body>
从远程主机使用代码
App1 的页面使用了来自App 2 的对话框组件。
const Dialog = React.lazy(() => import("app_two_remote/Dialog")); const Page1 = () => { return ( <div> <h1>Page 1</h1> <React.Suspense fallback="Loading Material UI Dialog..."> <Dialog /> </React.Suspense> </div> ); } export default Page1;
路由看起来很标准:
import { Route, Switch } from "react-router-dom"; import Page1 from "./pages/page1"; import Page2 from "./pages/page2"; import React from "react"; const Routes = () => ( <Switch> <Route path="/page1"> <Page1 /> </Route> <Route path="/page2"> <Page2 /> </Route> </Switch> ); export default Routes;
配置:
App 2 将公开对话框,使 App 1 可以使用它。App 2 也会使用 App 1 的 App
,所以咱们指定 app_one
为远端-展现双向主机:
const HtmlWebpackPlugin = require("html-webpack-plugin"); const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); module.exports = { plugins: [ new ModuleFederationPlugin({ name: "app_two_remote", filename: "remoteEntry.js", exposes: { Dialog: "./src/Dialog" }, remotes: { app_one: "app_one_remote", }, shared: ["react", "react-dom","react-router-dom"] }), new HtmlWebpackPlugin({ template: "./public/index.html", chunks: ["main"] }) ] };
使用:
根 App 以下所示:
import React from "react"; import Routes from './Routes' const AppContainer = React.lazy(() => import("app_one_remote/AppContainer")); const App = () => { return ( <div> <React.Suspense fallback="Loading App Container from Host"> <AppContainer routes={Routes}/> </React.Suspense> </div> ); } export default App;
使用 Dialog 的默认页面以下所示:
import React from 'react' import {ThemeProvider} from "@material-ui/core"; import {theme} from "./theme"; import Dialog from "./Dialog"; function MainPage() { return ( <ThemeProvider theme={theme}> <div> <h1>Material UI App</h1> <Dialog /> </div> </ThemeProvider> ); } export default MainPage
不出所料,App 3 看上去相似。可是它不会使用 App 1 中的App
,它能够做为独立的自运行组件(没有导航或侧边栏)工做。因此它不指定任何 remote:
new ModuleFederationPlugin({ name: "app_three_remote", library: { type: "var", name: "app_three_remote" }, filename: "remoteEntry.js", exposes: { Button: "./src/Button" }, shared: ["react", "react-dom"] }),
请密切注意浏览器中 network 标签。该代码将在三个不一样的服务器之间进行联合:三个不一样的 bundle。一般状况下,除非你用了 SSR 或渐进式加载,不然不要联合整个应用程序容器。可是这个概念很是强大。
查看推文中的视频:https://twitter.com/ScriptedA...
几乎没有依赖项重复。经过 shared
选项 —— 远程将依赖于主机依赖关系,若是主机没有依赖关系,则 remote 将下载本身的依赖关系。没有代码重复,可是内置冗余。
手动将供应商或其余模块添加到 shared
并不理想。能够用自定义编写的函数或补充性的 Webpack 插件轻松地将其自动化。咱们确实打算发布 AutomaticModuleFederationPlugin
并从 Webpack 核心外部对其进行维护。既然咱们已经在 Webpack 中内置了一流的代码联合支持,那么扩展其功能就变得微不足道了。
如今有一个大问题 —— SSR 能够胜任这项工做吗?
咱们将其设计为通用的。模块联合可在任何环境中使用。在服务器端渲染联合代码是彻底可能的。只需让服务器构建使用 commonjs 库目标便可。有多种实现联合 SSR 的方法:S3流、ESI、自动执行 npm 发布以使用服务器变体。我计划用公共共享文件卷或异步 S3 流在整个文件系统中流式传输文件,使服务器可以像在浏览器中同样请求联合代码,并用 fs
而不是 http
来加载联合代码。
module.exports = { plugins: [ new ModuleFederationPlugin({ name: "container", library: { type: "commonjs-module" }, filename: "container.js", remotes: { containerB: "../1-container-full/container.js" }, shared: ["react"] }) ] };
“模块联合也能够与 target:"node"
一块儿使用。做为代替指向其余微前端的 URL,在这里用指向其余微前端的文件路径。这样你可使用相同的代码库和不一样的 webpack 配置进行 SSR,以构建 node.js。对于 node.js 中的 Module Federation,相同的属性仍然适用:e.g. 单独构建,单独部署” —— Tobias Koppers
联合须要 Webpack 5 —— Next 还没有正式支持。可是,我确实设法 fork 并升级了 Next.js 以使其与 Webpack 5 兼容!这项工做仍在进行中。一些开发模式的中间件须要完成。生产模式目前能够工做,一些其余加载器仍须要从新测试。
我但愿有机会分享更多有关这项技术的信息。若是你想使用 Module Federation 或 Federated 体系结构,咱们很想听听你对当前体系结构的经验和改进。咱们也但愿有机会在播客、聚会或公司中谈论它。经过 Twitter 与我联系https://twitter.com/ScriptedA...
你也能够成为个人共同创做者。请关注咱们,并获取有关模块联合、FOSA(独立应用程序联盟)体系结构以及咱们正在建立的其余工具的最新更新,这些工具被用于联合应用程序
社区对此反应热烈!个人共同创做者以及我本身的时间都花费在编写到 Webpack 5 中。咱们但愿最终完成其他功能并编写一些文档的同时,一些代码示例会对你有所帮助:https://twitter.com/codervandal
Webpack 5 and Module Federation - A Microfrontend Revolution
因为有足够的带宽,咱们将会建立 SSR 示例和更全面的演示。若是有人想构建可用做演示的东西,咱们将很乐意接受将请求并 pull 到 webpack-external-import
中。