Watch for Changes and Automatically Rebuild Projects in an NPM Workspace Monorepo
By now we're accustomed to have a HMR experience when developing. When we change a file, the underlying dev server watches for the changes and automatically re-compiles. In a monorepo this is more tricky. Consider you have your dev server running on APP
with a dependency tree that looks like APP -> LIB-A -> LIB-B -> LIB-C
. You'd have to watch all of these dependencies of APP
s.t. if you change any of them it'd automatically recompile it. And it should work even as you change that dependency tree while developing.
Nx has such command built-in:
nx watch --projects=app1 --includeDependentProjects -- echo \$NX_PROJECT_NAME
Let's have a look how this works in an NPM workspace monorepo.
Transcript
We have here our react this react Vita application remix app application which both depend on these and this shared UI package Now in development mode what you would want to do is something like I'm running here an axe Let me have a look here. What actual serving is for that one is dev, nxdev react-vite, or the according npm workspace command and this now launches the dev server on 5.1.7.3 so if I navigate to that I see my application running. Now this part in here is actually my shared UI component. So in an usual development scenario, what you would go is say, oh, I actually want this uppercase and I want this with a colon. All right, and so I save this.
And I would expect in a normal development workflow that Vite picks this up. But there's actually no way because Vite depends on the buildR effect of that package. So it depends on that dist output folder which didn't change because we didn't rebuild that specific package. So what I would have to do is here go ahead and say nx build sharedui and we would now build that package. Now we can see the v server, the hmr server here updates it and if I chime back here into my web view I see the count is being updated on the component.
But that's obviously a very tedious workflow. So what you want to have is really a watching functionality. And so if we go to the NX.dev docs, there is something that is called workspace watching. There is an NX watch command where you can either watch all of the projects and then run a specific command and it will give you the name of the actual project and then you can just basically construct here your build command for instance or your test command or whatever you're trying to achieve. Now especially further down we see something that's more interesting for us where We just want to watch some project, in our case, for instance, the React Vite project, and then include all its dependent projects as well as a watching phase automatically, so we don't have to specify all of them manually, but we can have one command that then holds even as our monorepro structure changes, and then we want to actually just run the build for it.
So let me copy this over and try this out here. So if I do something like this here and I want to watch my React Meet app And so this now starts watching and now we can see if I go here to the button and I change some of the things in the components, you will see that the actual project name here gets echoed out. And based on this we can now actually construct our command. So we can say nx watch and we want to watch the React Vite as our starting project, include dependent projects and then we now want to run the actual command. So I want to run something like nx run nx colon build.
So I want to run the build of that project. So again now if I here change this, I save, we can see now runs NX run shared UI build. And so with that, we have now an automatic watching going based on all of the dependencies of my React Vite project here. Let's just make one optimization because clearly what also happens right now is if I go in here and I change something in my React Vita app it is also being built because we are watching that project as well and that's probably something I want to avoid. I'm already running the Vite dev server on main project, so I don't really want to rebuild that because that's already been taken care by Vite.
So what I can do here, but I can work around this by using something like runMany-t build. And So I want to run build for the projects which is here in this one here. So my shared UI in this case or any other potential dependencies I might have. But I want to exclude and that's why I'm using the run menu because that has an exclude command. ReactV.
So in this case now I'm watching still the dependencies so if I go here I can change this and it would rebuild here my shared UI project. Now it has been cached so it doesn't really do anything. That's another benefit of having caching active. But even if I change my React V project here and I save it doesn't do anything. So it runs clearly because it watches it but it's being excluded so it doesn't run effectively the build.
So now it's up to us how we want to actually use this in our setup. So I could see for instance that we go into ReactV and define here some tasks, some scripts that allow us to do that type of watching. So I could see how we have here maybe a watch dev command or watch deps command or trial dependencies right and so I can use that here Let me actually just make sure we escape this properly. And then I could see how we have potentially a dev watch command, which would now run both this and this in parallel. Now we can do that by installing something like npm run all package.
So once I have this installed I now have something like run p for running parallel where I can give it basically here the def and the watch deps command. So copilot already helped me there. And so with this now I should be able to do something like nx and now I want to use that dev watch for the react-vite project that we have in our workspace and it will now run the watching as well as the dev server for Vite so if I go here I refresh here count is zero so let me actually go now to our component and say count is and then you see here there's one updates and here's updates as well because the build has happened in the background for our dependencies and so we get back that kind of like interactive development flow where we don't have to think about what to build just because these are different projects here in my mon repo.