[CLOSED] FC-0054 - Composable Micro-frontends (Piral Discovery)
Funded Project ID | FC-0054 |
|
---|---|---|
Provider | Joy Engineering (@David Joy) |
|
Axim Contact(s) | @Sarina Canelake |
|
Expected Completion Date | May 2024 |
|
Status | Complete |
|
Additional Project Details |
| |
GitHub |
|
|
- 1 Overview
- 2 Discovery Areas
- 2.1 Frontend Shell Application *
- 2.2 Shared dependencies *
- 2.3 Central Data Store *
- 2.3.1 Inter-MFE Eventing *
- 2.4 Lazy Loading / Bundle Splitting *
- 2.5 User Experience Impact *
- 2.6 Plugin Framework
- 2.7 Paragon Design Tokens
- 2.8 Configuration
- 2.9 Iterative Rollout
- 2.10 Build Backwards Compatibility
- 2.11 Paragon Lifecycle
- 2.12 DevEx Support
- 2.13 MFE Sandboxing
- 2.14 Child MFE config via unshared dependency
- 2.15 Glossary
- 2.16 Routing
- 2.17 Nesting Dynamic Modules
- 2.18 Static Assets in Guest Modules
- 2.19 PUBLIC_PATH must be specified
- 2.20 Browser caching of remoteEntry.js
- 2.21 Behavior for long-lived chunks after a new deployment
- 2.22 Header and Footer loading
Overview
This SOW began with some initial discovery on Piral and how it might fit into Open edX. Since beginning that discovery, we’ve decided to pivot toward a more narrowly focused effort to enable Webpack Module Federation for Open edX micro-frontends. We intend to repurpose OEP-65 to document that decision.
This top-level document describes a number of considerations in the effort to use module federation in Open edX MFEs with links to other documents where appropriate. Not all of these are going to be explored during the first SOW, but we can consider this an inventory of what we’ll need to work through for a reference implementation.
Discovery Areas
* Starred Areas are being investigated in this SOW.
Frontend Shell Application *
What does our frontend “shell” application look like, and how does it interact with the child MFEs?
Shared dependencies *
How do we upgrade dependencies safely, predictably, and publicly?
Can we create Github actions/workflows to help us keep things in sync?
What dependencies should be shared?
Central Data Store *
What types of central data might exist?
i18n message ingestion
Global state
https://openedx.atlassian.net/wiki/spaces/COMM/pages/4140302338
Inter-MFE Eventing *
Broke this out from ‘central data store’ because it came up during that discovery.
What are the use cases for eventing? Do we have any?
Lazy Loading / Bundle Splitting *
User Experience Impact *
Plugin Framework
How does usage of module federation fit in with the frontend-plugin-framework?
Add “Module Components” back in, use module federation to load them.
Module components likely take the place of build-time “direct” components in many cases.
Paragon Design Tokens
Is there any overlap between this work and the Paragon Design Tokens effort?
Configuration
How do we configure module federation?
MFE config API (“Feed Service”)
env.config.js
Do we do this at runtime? At build time? Both?
Iterative Rollout
How do we add module federation to MFEs iteratively so that they can coexist with MFEs we haven’t yet updated?
Alternate frontend-build webpack config
Build Backwards Compatibility
How do we add module federation to MFEs in such a way that they can continue to be used in “standalone mode” while we adopt the new architecture?
Paragon Lifecycle
Runbook for what happens when there’s a breaking change?
Best practices to reduce breaking changes
Treeshaking/incremental loading options
DevEx Support
How does this change the tutor MFE setup?
What other considerations exist here?
MFE Sandboxing
Error Boundaries
Module Fallback
What happens if a shared dependency is an incompatible version?
What if that dependency is a “singleton”, meaning only one of them can be loaded (i.e., React)?
Child MFE config via unshared dependency
How can a child MFE get to its own config document if we share frontend-platform with the shell?
Glossary
What do we call the parent and child so we have common terminology.
Routing
Does React Router play nice with this setup? Can an MFE include routes and they’ll just work?
How do we assign routes to dynamically loaded pages?
Nesting Dynamic Modules
Can a dynamically loaded module load its own dynamically loaded module?
Static Assets in Guest Modules
Do we need to use getPublicPath in our module federation config to allow guest modules to load their own static assets properly?
PUBLIC_PATH must be specified
Determine if all MFEs must specify a PUBLIC_PATH for module federation at runtime to work. Without PUBLIC_PATH the shell tries to load modules from its own domain. Just need to understand the lay of the land here.
Browser caching of remoteEntry.js
The remoteEntry.js file is created by webpack without a hash on it. This means that browsers may cache it beyond when they should, or we need to find another way of cache busting it.
Answer: cache control headers or add a timestamp to the URL. Straight from the author’s mouth: https://github.com/module-federation/module-federation-examples/issues/491
Suggestion: I think we’ll go with the latter, since that’s dead simple.
Behavior for long-lived chunks after a new deployment
If someone loads the page (and manifest/remoteEntry.js) for an MFE and then someone does a deployment, in certain setups the previous versions of the files may be removed and no longer available. Think an S3 bucket that gets cleaned out each time there’s a deployment. If this happens, users viewing a previous version of the page will get 404 errors when trying to load chunks that no longer exist.
Answer: There are a few recommendations floating around online here.
Don’t delete old chunks on deploy
Have a file you can check to understand if your user is on the current version - presumably you poll this file before loading new chunks, or regularly? If it’s out of date, force a refresh.
When a chunk fails to load, force a refresh under the assumption that it’s because the user was on an outdated version of the app.
Suggestion: I think we could likely build in a mechanism to do the third fairly easily.
Header and Footer loading
We need to determine how we efficiently load headers and footers, particularly when MFEs request different ones.
Module federation has no issues allowing a host to load a module from itself. Step one, I think, is to split out the header and footer into runtime modules and configuring the shell to load them by default from itself. This also means if an MFE wants to use a different header, we don’t waste bandwidth downloading headers to a client that we don’t intend to use.