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:
{
"packages": ["apps/*"],
"npmClient": "yarn",
"useWorkspaces": true,
"version": "0.1.0"
}
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:
"start": "webpack serve --port 8501"
The root package.json uses Lerna to start all the micro frontends in parallel:
"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”
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
"compilerOptions": {
"jsx": "react",
"baseUrl": ".",
"paths": {
"@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:
return merge(defaultConfig, {
externals: {
"react": "react",
"react-dom": "react-dom",
"@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.
<% if (isLocal) { %>
<script type="systemjs-importmap">
{
"imports": {
"@myorg/mfe1": "//localhost:8501/myorg-mfe1.js",
"@myorg/mfe2": "//localhost:8502/myorg-mfe2.js",
"@myorg/my-util": "//localhost:8503/myorg-myutil.js"
}
}
</script>
<% } %>
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.