Petros Kyriakou
Posted on April 18, 2024
This might be a niche topic, but recently I had to link and develop a shared library that is not part of the same repo as the main app codebase. And there is no monorepo involved either.
On the web the resources to do this are scattered and don't cover hot reloading.
The problem
if you ever came across such a scenario, in such cases the usual workflow is develop the package in isolation, build and release. Then go to the app and update the version number so you can bring the changes in.
BUT, wouldn't it be cool to be able to develop the shared package side by side with live reloading and reflect the changes in the main app, make sure everything is working as expected and THEN release ?
In this article, we are gonna do just that.
Setup
The setup for the purposes of this tutorial is the following:
- Package boilerplate generated using tsdx
- Basic vite react app with typescript
- Using yarn
Shared package
Let's call it @awesome/package (do that by changing your package.json name).
The changes needed here are very few. The main thing to know is that Vite uses ESM and needs to know exactly where to look for the root file so lets help it
in package.json
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/shared.esm.js"
},
"require": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
},
So what we are saying above is the following.
If you are an ESM consumer (import) use the these types and this is the entry point.
If you are a commonJS consumer (require) use these types and this is the entry point.
Now vite knows what to look for, great!
Vite setup
in vite.config.ts
import {defineConfig, ViteDevServer} from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
optimizeDeps: {
exclude: ["@awesome/shared"]
},
})
By default, Vite likes to optimize deps but in our case since its an ESM lib we want to skip that.
Great, now vite knows about our package.
Yarn setup
Now we need to do two things.
Install the package as a local dependency.
In shared package
The first step is to make sure yarn knows where to look for the package.
yarn link
In main app
yarn add link:./../your-project // here adjust to be the relative path to your project. In case you use pnpm or npm you need to adjust this accordingly - most probably use file: in front.
If you already released and use the package with a specific version. e.g in package.json you already have the package as a dependency like so - "@awesome/shared": "0.0.1"
then do the following:
yarn link @awesome/shared
At this point, we have made sure vite is aware of the package, and our package manager, in this case yarn - has made the necessary linking.
Development
In shared package do a change that is meaningful and can be easily observed in the vite app.
Afterwards,
In shared package
yarn start // if you used tsdx to scaffold or else use the equivalent command with your setup
In vite app
yarn dev
You will notice that the shared package change you made is reflected in your vite app.
However, you will notice that making a second change to the package it is not being reflected to the vite app.
Thats because, Vite by design does not watch changes in node_modules
and because does caching of the dependencies under node_modules/.vite
directory.
The suggestion is to run the following command
yarn dev --force // underneath its vite dev --force
This is too manual for our liking, isn't it?
So, let's see how to fix that.
Adding a plugin to watch specific packages for changes
We need to add a plugin in order to watch for our packages, so here is the final vite.config.ts
import {defineConfig, ViteDevServer} from 'vite'
import react from '@vitejs/plugin-react'
// This plugin will watch node_modules for changes and trigger a reload.
// Works only for build --watch mode.
// https://github.com/vitejs/vite/issues/8619
export function pluginWatchNodeModules(modules: string[]) {
// Merge module into pipe separated string for RegExp() below.
const pattern = `/node_modules\\/(?!${modules.join('|')}).*/`;
return {
name: 'watch-node-modules',
configureServer: (server: ViteDevServer): void => {
server.watcher.options = {
...server.watcher.options,
ignored: [
new RegExp(pattern),
'**/.git/**',
]
}
}
}
}
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), pluginWatchNodeModules(['@awesome/shared'])],
optimizeDeps: {
exclude: ["@awesome/shared"]
},
})
Start the vite app again and do changes to the shared package. They should be reflected to vite app almost instantly.
Bonus
Now that you are able to develop and test your package, you need to unlink the local package and use the version you used when releasing the package with the changes
yarn unlink @awesome/shared
Now update package.json
(in vite app) with the latest release version and run yarn
to install it.
Conclusion
You have learned how you can easily make changes to a shared package and they are then reflected to your vite app. Now you can be sure the changes work before releasing the shared package.
I hope you found this useful and if you want to keep in touch you can always find me on Twitter.
Posted on April 18, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.