Micro-front ends in Webpack 5

栏目: IT技术 · 发布时间: 3年前

内容简介:The motivation for Module Federation is developing one or more applications with multiple teams.Applications are splitted into smaller application "parts". These could be frontend components like a "Header" or "Sidebar" component, but also logical componen

Micro-front ends in Webpack 5

The motivation for Module Federation is developing one or more applications with multiple teams.

Applications are splitted into smaller application "parts". These could be frontend components like a "Header" or "Sidebar" component, but also logical components like "Data Fetching Logic" or other business logic.

Each part can be developed by a separate team.

Applications or parts of them share other parts or libraries.

Micro-front ends in Webpack 5

Micro-front ends in Webpack 5

Let's looks at the existing options you have when using webpack...

Micro-front ends in Webpack 5

Micro-front ends in Webpack 5

Micro-front ends in Webpack 5

Micro-front ends in Webpack 5

Micro-front ends in Webpack 5

Micro-front ends in Webpack 5

which brings us to...

Micro-front ends in Webpack 5

Micro-front ends in Webpack 5

With Module Federation each part would be a separate build. These builds are compiled as "Containers".

Containers can be referenced by applications or other containers.

In such a relationship the container is the "remote" and the consumer of the container is the "host". The "remote" can expose modules to the "host". The "host" can use such modules. They are called "remote modules".

By using separate builds we getting a good build performance for the whole system.

Micro-front ends in Webpack 5

This is an overview over Module Federation.

The two aspects of Module Federation are shown here: exposed modules and shared modules.

The container exposes modules in an async way. You would ask the container to load/download the modules you want to use before using them from the container. Async exposing allows the build to put each exposed module in a separate file, together with its dependencies. This way only used modules have to be loaded, but the container can still bundle modules together. Additionally usualy chunking techniques from webpack apply here (like vendor splitting or creating a file for common dependencies between exposed modules). This allows to keep requests and total download low, which leads to a good web performance.

The consumer of the container need to be able to handle async loading of exposed modules. Here webpack will do some trickery, which we will see later.

The other aspect, shared modules, is also show here. Every party, containers and applications, is able to put shared modules into the share scope together with version information. And they are also able to consume shared modules from the share scope together with a version requirement check. The share scope will deduplication shared modules in a way that provides each party with the highest available version of a shared modules that within the version requirement.

Shared modules are also provided and consumed in an async way. So providing shared modules has no download cost. Only used/consumed shared modules are downloaded.

Micro-front ends in Webpack 5

Let's take a look at an example...

Micro-front ends in Webpack 5

Here a single build application is shown.

A "HomePage" (from team A) uses a "Dropdown" component (from team B). A "Login" link on the "HomePage" loads the "LoginModal" (from team A) on demand, which uses a "Button" component (from team B).

"react" is used by nearly all modules from both teams.

Let's apply Module Federation to this application...

Micro-front ends in Webpack 5

From team Bs point of view. Team B only cares about their components.

Micro-front ends in Webpack 5

Team B want to build a "Container" and therefore flags some of their modules. "Button" and "Dropdown" are flagged as "Exposed" as their should be usable by other teams.

"react" is flagged as "Shared", so that it can potentially be shared with other teams.

Micro-front ends in Webpack 5

Now webpack kicks in...

webpack will automatically generate a "Container entry" for the container. This generated module will contain references to all exposes and shared modules and how to load them.

Each exposed modules is put into a separate file together with dependencies.

Each shared module is also put into a separate file.

When loading "Button" from the container it would only load the button chunk and the react chunk. When loading "Dropdown" it would only load the dropdown chunk and the react chunk.

When loading "Dropdown", but another party have provided another react version (potentically higher) it would load the dropdown chunk and the chunk for the provided react version of the other party (Acutally it delegates the loading operation to the other party and they will load the chunk, if it hasn't already been loaded yet).

Micro-front ends in Webpack 5

Here is how team A would consume the container of team B.

Module from team B are not included directly. Instead only "Remote module" stubs are used. They reference the container at runtime and will load the module from the container instead (at runtime).

The tricky part is the async loading. A normal import is sync and can't wait until the module is downloaded. Some webpack magic will hoist all async remote module loading up to the next async boundary (like an import() ). At this point remote modules will be loaded by the container in parallel to the download of the normal modules in this chunk.

An example is the "Login" link, which opens the "LoginModal". When clicking on the link the code for "LoginModal" and the code for "Button" will download in parallel.

Similar thing happens for shared modules.

Micro-front ends in Webpack 5

There is a ModuleFederationPlugin to use Module Federation. Different properties are used to setup the different aspects.

To create a container the exposes property is the important one. Here all modules are specified that should be accessible for a consumer of the container. One can give them a public name, which is the name that the consumer has to use, and point it the a module in the own code base (internal request). Any module is supported, it might be javascript, typescript, CSS, WebAssembly, assets, anything webpack can process in your code base.

The consume other containers the remotes property is the goto property. It's a object will also containers that should be usable in the current build. The key is a module scope at which the containers exposed modules should be accessible in the own code base. Any request starting with this key will create a remote module that will be loaded at runtime. The value is the location of the container. By default script externals are used as container locations. Here one would specify an URL of a script file and a global. This script will be loaded at runtime and the container is accessed from the global.

To share modules on any side the shared property should be used. For simple cases a list of module specifiers can be provided which flag these modules (when used in the code base) as shared module. They will be provided in the current installed version and consumed in a version range specified in the package.json of the consuming package.

All properties also support advanced configuration options. One notable advanced option is the singleton: true option for shared modules. It makes sure that only a single instance of the module is created at runtime. This is needed for some libraries like e. g. react which dislike being instantiated multiple times in the same application. An invalid version range will only lead to a warning at runtime in this case.

More advanced options allow to override or disable automatically inferred values like version , requiredVersion or filename , allow to use different ways of libraries and externals e. g. for usage in Node.js or opt-into stricter version checking (which lead to errors instead of warnings when version range is invalid).

Micro-front ends in Webpack 5

Micro-front ends in Webpack 5

Now we have build all these separate container. It's time to put them together. This is were the fun starts...

We will look into two different approaches: Everygreen and Managed.

Micro-front ends in Webpack 5

Micro-front ends in Webpack 5

Bonus content: Read more about using the Managed approach for client and server-side

Micro-front ends in Webpack 5

One way to use the Managed approach on client-side is a clever way to use server-relative URLs together with a simple API which redirects requests based on the locked version.

Each application is placed on a different (sub)domain. Examples: app.example.com and admin.examples . Additional nesting could be added for multiple stages for an application. Examples: myapp.example.com for the production version, staging.myapp.example.com for the staging version.

All containers are referenced by a server-relative URL pointing to an API available on all domains: /_remotes/<name> . Depending on the application that will resolve to an absolute URL like https://staging.myapp.example.com/_remotes/analytics . The API is now able to extract referencing application and target container from this request. It will lookup the container in the lockfile of the application and redirect request to the final URL of the container, e. g. 302 Location: https://static.example.com/analytics/3fe867c.js .

The redirect is a little bit in-perfect as it requires 2 roundtrips to the server to load the container. On second visit the container itself is probably cached, but as the redirect can't be cached (to allow changing the lockfile anytime), it will still take 1 roundtrip.

The /_remotes/ API could directly respond with the container javascript. This would reduce the initial loading to 1 roundtrip, but the second visit case will still use 1 roundtrip as we can't cache the API correctly.

So while keeping the API solution as fallback we add an additional layer. We generate the HTML with some server logic and inject script tags to the final container URLs directly. Module Federation in webpack is able to pick them up when data-webpack="<name>" is set on the script tags. (It's also able to pick up the exposed globals directly, but data-webpack is needed when all script tags are async and application script may execute before the container script. In this case the loading logic will find the data-webpack script tag and attach event handlers to it.)

This results in a single roundtrip on the initial pageload and 0 roundtrips for the second visit.

Technically it's also possible to inline the container script directly into HTML as they are pretty small (a few kB). This would lead to 0 roundtips for the initial pageload too. But this solution is a bit problematic to scale with many containers as it will blow up the size of the HTML and prevent caching of the containers.

To use the everygreen approach instead always redirect to the latest version of a container.

One way to use the Managed approach on server-side it the use npm/yarn package.json for the compiled dist files as well.

For this approach you need a private package registry where you can push your compiled dist files to.

To deploy a container, create a package out of your webpack output files and a package.json. In this example we use @containers/ a prefix for compiled container packages. The analytics container package could look like this:

{
    "name": "@containers/analytics",
    "version": "1.2.3",
    "main": "./container.js"
}

In the ModuleFederationPlugin containers would be created like this:

new ModuleFederationPlugin({
    name: "analytics",
    library: "commonjs-module",
    exposes: {
        "./something": "./something"
    }
})

To create an application that uses the container you would do the same (maybe with a different prefix like @apps/ ) and also specify the container package as dependency:

{
    "name": "@apps/myapp",
    "version": "3.4.5",
    "dependencies": {
        "@containers/analytics": "*"
    }
}

In the ModuleFederationPlugin containers would be references like this:

new ModuleFederationPlugin({
    name: "myapp",
    remoteType: "commonjs-module",
    remotes: {
        analytics: "@container/analytics"
    }
})

Now you can run npm install / yarn as part of your server bootstrap. This will download the containers into node_modules from your private registry. The Module Federation runtime will load them via require("@container/analytics") . All code will come together in the same node.js process.

To use the Managed approach keep a package-lock.json / yarn.lock file together with your dist package.json. You could commit this file as part of your application repo. You could use multiple lockfiles e. g. for staging and production environments.

To update to the latest version of the container usually npm/yarn utilities can be used, or the lockfile could be deleted and regenerated.

To use the everygreen approach instead only keep a package.json and no lockfile. Mixed approach might require some additional tooling.

Note that package.jsons for containers might also have dependencies if they reference other containers.

Micro-front ends in Webpack 5

Micro-front ends in Webpack 5

Support for sharing prefixed module requests ( lodash-es/ ) has been adding now.

Micro-front ends in Webpack 5

Documentation: https://webpack.js.org/concepts/module-federation/

Talk by Manfred Steyer (1h): https://youtu.be/-ThsrA8hRLc

More videos in playlist by Zack Jackson: https://www.youtube.com/playlist?list=PLWSiF9YHHK-DqsFHGYbeAMwbd9xcZbEWJ

webpack core example: https://github.com/webpack/webpack/tree/master/examples/module-federation

More examples by Zack Jackson: https://github.com/module-federation/module-federation-examples

github: https://github.com/sokra

twitter: https://twitter.com/wSokra

Micro-front ends in Webpack 5

Bonus content: A large chart comparing all different approaches shown here.

You see that there is no clear winning approach here. Module Federation is not perfect at all categories, it's more a good trade-off in all categories.

Micro-front ends in Webpack 5

Bonus content: A chart showing the interals of the ModuleFederationPlugin .

Each property delegated to an lower level plugin, which technically can also be used on their own. One notable mention is the SharePlugin which could also be used to share modules in a different system.


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

破茧成蝶:用户体验设计师的成长之路

破茧成蝶:用户体验设计师的成长之路

刘津、李月 / 人民邮电出版社 / 2014-7 / 69.00

市面上已经有很多专业的用户体验书籍,但解决用户体验设计师在职场中遇到的众多现实问题的图书并不多见。本书从用户体验设计师的角度出发,系统地介绍了其职业生涯中的学习方法、思维方式、工作流程等,覆盖了用户体验设计基础知识、设计师的角色和职业困惑、工作流程、需求分析、设计规划和设计标准、项目跟进和成果检验、设计师职业修养以及需要具备的意识等,力图帮助设计师解决在项目中遇到的一些常见问题,找到自己的职业成长......一起来看看 《破茧成蝶:用户体验设计师的成长之路》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具