Micro Frontends: After one year with Single-SPA
Samim Pezeshki
Posted on January 23, 2022
Why did we choose micro frontend architecture?
We have a codebase that was originally written using AngularJS. After some years and when finally the news about AngularJS end of life came around, we started migrating to Angular (actually hybrid Angular/AngularJS). Finally, two years ago we successfully migrated to Angular (You can read the details in another post) having high hopes that by migrating to it we can leverage a new ecosystem. But after some months it became evident that Angular and AngularJS are so different that we need to rewrite everything, which is not very pleasant. Also, the React ecosystem and talent pool seemed much more vibrant so investing again in Angular for new upcoming features seemed like a non-optimal long-term solution. Over the years there were more experienced React developers in our teams so developing features in React would be much faster than having them in Angular.
So we were looking for options to be able to keep our current Angular app while being able to add new upcoming features and sections using React or other frameworks. After some search, we found out that micro frontends were the solution we were looking for! Using Single-SPA one can have multiple frameworks, Angular and React, running side by side. Single-SPA is composed of so-called apps each being a SystemJS or ES module. Each app can use a different framework and technology and it only needs to mount itself somewhere on the page. Apps are mounted and unmounted based on the page route. All of this happens client-side. As a side note, I was thinking if we had known about micro frontends, maybe we would never have migrated to hybrid Angular and would have chosen Single-SPA from the beginning.
Micro frontends are created for various purposes. Mainly it is discussed as a solution for keeping release cycles, deployments, and decisions in each team independent of others, like microservices but for frontend. In our case, we settled on micro frontends to be able to extend the lifetime of a legacy codebase by being able to take advantage of newer frameworks alongside old ones.
We also assessed some other micro frontend frameworks and solutions, even using iframes and server-side routing, but finally, we decided to go with SignleSPA as it is less opinionated, simple and the best fit for our current codebase. The website is fully static (Angular/AngularJS) and is served from a CDN, so using server-side routing was out of options.
Benefits
The main benefit was improving the developer experience. Each Single-SPA app is developed separately, so when a developer starts to work on a React app (Single-SPA app) he/she does not need to install all the dependencies for other apps, like Angular, or to know how other apps are configured. Also because each app is small the development cycle of local builds, hot-reloads, and tests are much shorter in time. Developers can build features (Single-SPA apps) truly independently and separately. So now we could use all the experiences of our React developers in our legacy website.
Each app in single-SPA is bundled separately. Using different apps for different features results in multiple small chunks, instead of a big fat bundle. Splitting the bundle can also be done by configuring Webpack without Single-SPA, but here we got it for free.
Apart from smaller chunks and bundles we got lazy loading too. Some features are not used frequently, so their bundle can be loaded separately in the background after the initial load.
As new feature apps are developed using React, even after migration to a whole new framework like NextJS in the future, those parts can be re-used without the need to rewrite everything from scratch.
Issues
One issue I had was that I could not generate source maps for Angular when it was built as a SystemJS module. I did not dig deep into the issue as it did not have a great impact on the project. But it is was nice to have source maps.
Another issue was the integration between the apps. We used local storage, global events, and shared modules for this and they all worked pretty well. But deciding on the best option was sometimes challenging.
Also as the whole concept is new, it took some time for the new developers to learn how to get on track, although this was negligible and even sometimes exciting to learn about new trends.
Code structure and deployment pipelines
All Single-SPA apps are put into a single repository. Each app has its own package.json
file and is developed and built separately. There is also the root app which contains the main router responsible for mounting and unmounting other apps.
├── apps
│ ├── root
│ │ ├── node_modules
│ │ ├── package.json
│ │ └── src
│ │ └── index.html
│ ├── feature-one (Angular)
│ │ ├── node_modules
│ │ └── package.json
│ └── feature-two (React)
│ ├── node_modules
│ └── package.json
└── scripts
├── build.sh
├── deploy.sh
└── start.sh
During deployment, there is a shell script that installs and builds each app and assembles them by copying the built files into a final build directory. Then it uses AWS Cloudformation to create a static website on S3, CloudFront, and Route53.
export ROOT_PATH=$PWD
export VERSION=4.0-$(git log -1 --pretty="%h")${BUILD_NUMBER}-$(date --iso)
for d in ./apps/*; do
if [ -d "$d" ]; then
echo " * Installing dependencies for $d"
echo
cd $d
npm install
npm run build
mv dist $ROOT_PATH/dist/$d
cd -
fi
done
As a single deployment pipeline and repository is used for all the apps, we are not gaining from one of the main benefits of using micro frontends architecture which is independent release cycles for each app. But by putting everything in a single repository we could achieve what we were looking for without dealing with the complexity of managing multiple repositories and deciding on how to update import maps (solutions like import-map-deployer).
Development Experience
There are two ways to start developing. One is using the single-spa-inspector browser extension. This way the developer opens the fully-deployed live website (not localhost:3000 or any local address) and overrides the import maps to make the live website connect to the Single-SPA app running locally. This way the developer only runs the one feature app he/she is working on while running it inside the live deployed website. It frees the developer from running the whole website locally and even has the side benefit of seeing and developing the feature app in the context of the deployed website connected to the live database. This way of development was personally very unique and new to me, it was amazing.
Another approach is to start all Single-SPA apps locally. This approach is sometimes needed for debugging the integration between the apps. The below script is used to run all apps:
SCRIPT_ENV="${1:-dev}"
PORT=3000
echo "⚜ Starting ${SCRIPT_ENV}..."
echo
echo ⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
echo "📡 Listening on https://localhost:${PORT}"
echo ⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽
echo
echo "⚠ Ignore if the below single-spa apps report about their ports! root app is served on port ${PORT}."
echo
npx concurrently --names "ROOT,FEATURE1,FEATURE2" \
-c "#E0E0E0,#26C6DA,#FFA726" \
"cd apps/root && env PORT=${PORT} npm run start:${SCRIPT_ENV}" \
"cd apps/feature-one && env PORT=$(expr ${PORT} + 1) npm run start:${SCRIPT_ENV}" \
"cd apps/feature-two && env PORT=$(expr ${PORT} + 2) npm run start:${SCRIPT_ENV}"
Road ahead
Adopting micro frontend architecture (Single-SPA) enabled us to further keep our legacy website while utilizing more trendy technologies to deliver new features. Otherwise, we had to rewrite the whole website or stick to what we had. Now that new features are delivered on time and we are on schedule plans can be made to rewrite the whole website without a rush.
With new trends, frameworks, and ideas popping up in the web development space every day, like server-side rendering, statically generated dynamic content, edge serverless workers, etc., I am not sure if we would again choose Single-SPA for a project creating from scratch. But for our use case, the micro frontend architecture served us well. If you have any framework or architecture in mind to suggest for our next project, please share, I would appreciate it.
Posted on January 23, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.