I’ve been playing with using Lerna to manage a monorepo for my single-spa project. It seems like a really good fit so far. One stumbling block I encountered was implementing a single-spa utility module (in this case a styleguide/component library).
A quick overview of the setup. First the lerna.json file:
2 | "packages": ["apps/*"], |
Each of the micro frontends (MFEs) is in a subdirectory under apps. The package.json for each has a start command with a different port:
1 | "start": "webpack serve --port 8501" |
The root package.json uses Lerna to start all the micro frontends in parallel:
1 | "start": "lerna run start --parallel" |
So far so good, and much easier than checking out several different projects and remembering to run start on all of them.
Typescript woes
I created a new single-spa utility module in the apps directory, using the single-spa CLI and selecting util-module
as the module type.
First I installed the util module as a dependency in the other micro frontends. The lerna add command “Adds package
to each applicable package. Applicable are packages that are not package
and are in scope”
1 | lerna add @myorg/my-util --ignore @myorg/root-config |
However, when I imported anything from the util-module into any of the MFEs, typescript was not happy:
TS2307: Cannot find module '@myorg/my-util' or its corresponding type declarations.
I read a few suggestions about needing to specify a “main” in package.json for the util-module (I don’t want to depend on the built module) and generating type declaration files (I think the ts-config-single-spa
config already does this). In the end the answer was to add a paths
element to the compilerOptions
in the tsconfig for each MFE
5 | "@myorg/my-util": ["../my-util/src/myorg-myutil"] |
This made my IDE happy!
Configuring as a shared dependency
Because this is a shared utility module, I don’t want to bundle it with every MFE. So I added it to webpack externals for each MFE:
1 | return merge(defaultConfig, { |
4 | "react-dom": "react-dom", |
5 | "@myorg/my-util": "@myorg/my-util" |
And added it to the import map in the root config. Note that there is no need to call registerApplication()
for the util-module because it won’t be loaded independently.
2 | <script type="systemjs-importmap"> |
5 | "@myorg/mfe1": "//localhost:8501/myorg-mfe1.js", |
6 | "@myorg/mfe2": "//localhost:8502/myorg-mfe2.js", |
7 | "@myorg/my-util": "//localhost:8503/myorg-myutil.js" |
And that’s it – all working in a development environment. The next step will be to look at how to hook up to Lerna’s version management abilities so I can test and deploy micro frontends independently.
Thank you! I can’t even describe how much time I’ve wasted trying to figure this out. Legendary.