A new build setup for Volto
What if I told you that a project-less build in Volto is possible? What if we unified the development experience while working on a project or an standalone add-on? What if the project artifact is no longer necessary, and the burden of maintaining the boilerplate, along with utilities configuration, etc is gone? How about if updating Volto have become changing the version number in one file? or that this build will continue working independently of future changes that may happen in how Volto is being built?
Show me, or it didn't happen
I've been toying around with the idea of a project-less setup since we moved Volto core to a monorepo. I'm still impressed how well pnpm
workspaces works and fits into the JavaScript ecosystem.
The Volto Team have this dream since long time ago that at some point we would be able to be able to get rid of the "Volto project" abstraction, and use Volto directly to run any kind of project. We put lots of efforts during the last years to be able to move all the code and configuration from the project level into add-ons. The project became expendable by moving all the project setup and configuration out of it.
project setup === add-on setup
A project setup, as we know it, it consists in a boilerplate that uses Volto as a library, plus a (policy) add-on. An isolated add-on setup needs a project around to work. We came to the realisation that both scenarios are the same if we remove the project artifact.
So what if we come up with a setup that uses directly vanilla Volto in all the possible ways, instead of having a man in the middle (project)?
The main idea is being able to make everything work, dev server, production build, utilities, tests, etc just using plain vanilla Volto plus an add-on (or add-ons).
To accomplish this, we will make extensive use of pnpm workspaces, of course, like we do in Volto core monorepo. We also had to make small changes, fix some bugs, and add some features here and there.
mrs-developer
If we need vanilla Volto, the best way to check it out it is via mrs-developer
. We could do it using a plain git
command, but it's convenient to keep all our required checkouts in one place using mrs.developer.json
. We do not need it for keeping any jsconfig.json
or tsconfig.json
anymore, because that's pnpm
job now.
Originally mrs-developer
output all the checkouts to the same folder. We extended it by adding a new feature that allows having specific optional outputs per repository. So we will configure it to checkout Volto like this:
So when we run mrs-developer
, we get Volto checked out in the core
folder, and pin it to use a specific tag (or branch, if required).
pnpm workspaces
After this, we need to make pnpm
know about the code in the setup. We use pnpm-workspace.yaml
file with this configuration:
We declare all Volto core packages as workspaces. Then we declare another possible source of workspaces in the packages
folder.
package.json
In order to round up the trick, we should make the top package.json
a full blown pnpm
monorepo. Let's take a look at it:
The key are the dependencies: @plone/volto
and @plone/registry
, then our add-on (either if it's an standalone add-on or the policy add-on of our project) or add-ons for our setup. All of them, using their versions inside the pnpm
monorepo ("workspace: *"
)
Then a lonely devDependency: mrs-developer >= 2.2.0
The last thing are the scripts. Please notice that they are pointing to Volto, so Volto itself is the one running these processes, under Volto core environment itself. This is done thanks to the pnpm --filter
feature.
How about tooling?
Dealing with tooling is one of the hardest things due to the different nature of how they behave, and the assumptions that every tool does. So we have to make happy every single one of them. In some cases it is easy, in others are a bit more difficult. Also, tools behave different if you run them through the command line or used via IDEs extensions.
Remember that we have the requirement that this setup should use the dependencies and configuration from Volto itself to reduce maintenance burden.
Tools mainly make the assumption that they are available in the root repository (as they expect it to be hoisted in a flat node_modules
environment). Using pnpm
workspaces this is not always true, since it uses a symlinked node_modules
structure, every dependency is installed by the package that it's requiring it.
Fortunately, pnpm
has yet another feature: public-hoist-pattern[]
This allows you to "lift up" dependencies from the different workspaces to the main root one. This is key for tooling and specially IDEs which expect them to be in the root repository. So we will hoist all the tooling dependencies up to the top level. This is done by specifying them in .npmrc
file.
By doing this, IDEs have access to the tooling packages and the linters IDE extensions will be happy. Command line tools will also find the right dependencies in the root.
volto.config.js
There's one missing link to cover, which is how we tell Volto which add-ons should it load?
Luckily, we have in Volto a way to specify the add-ons and the theme programmatically, using volto.config.js
file:
And thanks to a recent addition to @plone/registry
we can provide this list via and environment variable:
Makefile
We worked in some convenience Makefile
commands to make life easier to developers.
The most interesting one is make install
which will bootstrap the environment the first time. This is required because there's a chicken-egg problem that we should solve when we still haven't installed the environment and mrs-developer
is not around yet.
make install will bootstrap the environment, being the first command that should be run. Every time you change mrs.developer.json
you must also run it as well.
Conclusion
After this, we have closed the circle, and we have a full blown setup that covers all the bases. Dev server, production build, linters, i18n, unit testing, Cypress, Storybook, releases.
We have the same layout and developer experience for developing a standalone add-on or a full blown project setup.
In addition, we can also use mrs-developer
to add more external add-ons to the build, or even improve Volto at the same time that we develop our project (a long time missed feature).
We have reduced the boilerplate fingerprint to a buch of files, the vast majority being placeholders that would not require to be updated over time, because we are using the Volto configuration directly. If something changes in the future, it will happen in Volto, not in our setup.
A single place to specify the Volto version that we want to use. No more duplicated dependencies or devDependencies.
There's already a generator that you can try if you want to test drive it in your upcoming add-on or project. Feedback is welcome! The plans is that it will become the default if the results are as expected. You can try it by running:
You can take a look at some of the first add-ons that are using it already: