The Plone Volto Monorepo

Not long ago, I’ve pushed for an essential change in the Volto repository. I want to explain why we did this and what benefits it brings.

I’ve started to write this post and since it was getting ridiculously long, I’ll decided to use a QA format intead. So here we go.

What?

We transformed the Volto repository in a full fledged JavaScript-based (and managed) monorepo.

This involved mainly two things: Instead of yarn, we use now pnpm as a package manager and the reorganization of the code in "workspaces".

The move to pnpm was in the bucket list since forever. The fact that it handles workspaces flawlessly and much better than yarn and npm helped to make the decission. It uses a non-flat node_modules directory, and uses hard links to save disk space, and this approach makes much more sense to me than the npm and yarn approaches using a flat structure with hoisting if required.

Don't worry because pnpm command line is a superset of yarn's one and a few more useful ones. It's a breeze to make the switch. The only thing that you'll struggle is to type it quickly (hint: I created an alias).

I cetainly like one pnpm feature, which is to issue workspace commands with the --filter command line argument.

The organization of the code changed also a bit, in order to fit the monorepo requirements. All packages now live under the packages folder, including Volto itself. There is a new folder apps that contains applications with example usage of Volto or it's satellite packages.

The rest is untouched, including the Makefile convenience commands that proxy the needed commands to the right place.

Why?

Initially, Volto core was self-contained in the same package and namespace: @plone/volto. Following the Plone core lesson learned, we avoided prematurely splitting the code, and preferred to have all the core under the same umbrella.

Then, the add-on story came and it opened the door to split things. Again, we decided willingly not to and maintain the core cohesive. There was simply no reason to do it.

At some point, it was necessary to have a shared i18n story extracted from core, so add-ons could use it in isolation from Volto. So @plone/scripts was born. It is an independently released package used by Volto core and by Volto add-ons alike.

Eventually, Slate made into the core, technically was better to add it with the shape it had originally, so it was added as an special add-on, and the “core” add-ons concept was born. They were packages that were used by core, but never released. Webpack magic and the add-on registry hacks into it were in place to “fake” them, and the related package resolution.

We could have gone to the monorepo solution at that point, but it seemed overwhelmingly complex to make the move only because one core add-on. So we didn’t.

It was like this, until it stopped to make sense. Following the idea of using Volto technologies outside Volto, it made sense to start splitting features into their own packages. @plone/registry along with other experimental packages like @plone/types, @plone/components and @plone/client were born. Developing them on its own was a nightmare, so the idea of finally moving on and adopt a monorepo was born.

So, we removed the hacks, and moved using a monorepo setup so we could benefit of it and develop all these other packages at the same time as Volto.

How?

It's quite amazing how it all started to fall in the right place with a monorepo setup. You can declare dependencies between your monorepo packages in a way that you can specify a version or simply "take what's in there, right now" fashion. This is done by using:

So, the monorepo from that moment on, will take care of resolving your package using the reference to the current contents of the workspace that contains @plone/registry. This is all pnpm workspaces magic.

In fact, you can even drop a symlink to any add-on in there that you have in your machine, then add it as a dependency as shown above in Volto, and register it as an add-on, using the addons key in package.json.

That easy.

Where?

The monorepo enabled also the addition of proof of concepts that demo the usage of Plone in other frameworks. You can find them in the apps folder. These act also as monorepo workspaces and they use packages from the monorepo.

Right now, PoCs of NextJS and Remix are in there, using both the experimental @plone/client package. They also are playground of the upcoming @plone/components and other experimental packages.