Use NPM Packages
Use NPM packages as the building blocks of your application. Having separate packages is a little bit more of work but:
- Force us to have clear interfaces
- Having packages that can run independently makes them easier to test
- Easier to refactor because of the clear dependencies
Each package is a building block. Each of them works independently of the rest. Each of them can be tested in an isolated environment.
- Simple blocks:
@grajal/ui-text
,@grajal/ui-image
, - Composition blocks:
@grajal/ui-loginform
, - Functionality blocks:
@grajal/ui-loginprovider
, - Utilities blocks:
@grajal/ui-themes
,@grajal/ui-utils
, - Whole apps:
@grajal/app-picturerating
,
We have TONS of packages. Is there a way to manage them? Monorepos to the rescue.
Lerna
Lerna is a tool that optimizes the workflow around managing multi-package repositories with git and npm.
npm install -g lerna
Lerna is a tool to manage one Git repo with multiple NPM packages. The alternative, using one repository per NPM package…
- Harder to document and maintain: Difficult to know after a few years what packages are in use and which ones are deprecated
- Easier to refactor because you can commit across packages
- Tons of places where to open merge requests, issues etc.
I created a repository that contains scoped @grajal/[package-name]
packages.
In lerna, the packages are stored on
the packages/
folder. This is how it looks:
1 | ── packages |
And each package is an independent NPM module.
1 | ❯ cat Loginform/package.json |
The packages are written with js ECMA stage-2 and they are transpiled on a build process. The packages can be tested independently with jest+ex
1 | Test Suites: 8 failed, 7 passed, 15 total |
Lerna has three different steps:
lerna bootstrap
. For each package, installs external dependencies and links sub-packages together. That means that the scoped packages@grajal
present in the repo will be symlinked on thenode_modules/@grajal/
instead of installed. Ex. import(‘@grajal/ui-text’) -> import(‘../../ui-text’)- lerna run [command]. Runs npm run [command] on each package. Big lerna users like Babel, Jest, React etc dont really use this command because it is faster to run global build scripts than spawning 30 build scripts in parallel. Example: On the packages I created
lerna run build
will runnpm run build
on all packages, transpiling the code and exporting it ondist/
lerna run test
Will runnpm run test
on all packages
lerna publish
. Publishes all packages to npm
NPM server
The lerna managed NPM packages need to be published somewhere so they can be consumed by the applications. On my tests I installed a verdaccio server as a private local NPM repository. In a real environment we will publish our lerna packages directly to the company NPM package repository. By default verdaccio runs on port 4873.
Change .npmrc to search for packages also on our local server
You will need to create a user
1 | npm adduser --registry http://localhost:4873 |
You will end up with something like
1 | > cat ~/.npmrc |
A new application with monorepo
Create a new application with react-create-app.
Add a couple of building blocks:
@grajal/ui-loginform
This one allows the user to login and creates a localStorage key with the sessionID@grajal/app-picturerating
This is a React version of BB picture rating.
The app will display the loginform if the user is not logged in, and will display the picturerating app if the user is logged in.
Caveats
- Initial setup is horrible
- I was not able to make NPM resolve dependencies of the demo app.
yarn
was able to install the packages without trouble