Two ways of using Vue together with Silverstripe
Speed and flexibility for a smaller project or robust and testable for a larger project? Vue and Silverstripe together can accomplish both.
We recently released an e-commerce website we have been working on for a few months. During development we faced some interesting challenges in the front end so in this post I will share our experience of using VueJS together with Silverstripe.
Other than providing an interface for content editors, Silverstripe also stands out with its powerful features for developers which makes the integration between adding CMS pages and piping the data to a template very easy. This is highly valued in our team because it speeds up development significantly without compromising the quality of the outcome. So when we started this project we did not want to miss out on those features in favor of an SPA solution with a headless CMS, for instance. Nonetheless, the project still required many of the features of a modern front-end web framework in order to provide the desired user experience, therefore, we needed a solution where we could use a technology like React or Vue.js together with Silverstripe.
Since Vue allows for multiple instances on the same website and components and instances essentially are the same thing, we can create instances and mount them to our already defined Silverstripe templates. That way we can easily add a front-end framework on top of the already defined Silverstripe template. This suited our case perfectly because we kept all the power of Silverstripe but still got features from Vue such as reactivity, all with the same effort as including jQuery in a project.
In a typical Vue Single Page Application you would define one root instance followed by multiple nested components to compose a larger app. In our case we can create multiple Vue instances on already defined Silverstripe templates throughout the app to make some components reactive.
Using Vue in this way is fine, particularly when you need to add a few small, independent Vue components into a project that uses another templating language or gradually want to migrate an existing project to use Vue exclusively. There are some issues with using the DOM as a template but if you are aware of them this can be a good solution for the type of problem described above, and we have done it on several other projects in the past.
Problems with this approach
As the project got larger I started to feel some of the drawbacks of this pattern. First of all, even with only a few Vue instances, it became hard to get a clear picture of the dependencies between them and to follow method calls when they were manipulating each other directly from anywhere in the project.
Vue instances calling methods on each other can quickly become messy and hard to follow.
The main problem I ran into was when I started thinking about unit testing the instances. Because of the way the instances are mounted to an external DOM element and does not contain the template itself, you have to supply the markup every time you want to render a component in a test. This gets increasingly time consuming as the number of components grow. The most straightforward way to unit test Vue components is to define them as single file components where the template part is included in the component and thus used by the test renderer. This is also the common way of defining components when building SPA:s with Vue. Note that single file components requires an extra build step to compile, whereas otherwise Vue does not require any build tooling at all.
The fact that the components were hard to test and therefore left untested also made it more difficult to control the size and complexity by breaking things up into smaller units. I ended up with one large Vue instance used on multiple pages that handled most of the main logic on the front end with many dependencies that was hard to refactor or add features to. This led me to thinking about another way to structure this project.
Simply turning this project into an SPA was, as mentioned, not an option because of the importance of Silverstripe templates. Ultimately I defined the challenge as retaining testability and readability among the Vue components while still being able to get the full power of Silverstripe.
One way of improving the code was to start to break out parts of the large unmaintainable Vue instance and make them into small reusable components and add unit tests to them. This felt like a good first step which would allow me to easily cover many parts of the app with tests.
There were a few things with this that needed to be solved, however. In order to easily test Vue components they need to be defined as a single page component in a file with a .vue extension. These files are not compiled by the Silverstripe templating engine but handled by a build tool which means that any HTML that gets moved into one cannot contain Silverstripe variables or any other feature of the templating language. Another issue was that even if I was able to extract parts of the code into its own component, most of the logic that decided how to render these components still ended up in the untested main instance.
CMS data and other Silverstripe variables can easily be passed into Vue components via props which takes care of the first problem. It also makes it easy to mock CMS data in the tests by simply passing in any props when rendering the component. The downside with this is that if you have multiple components nested and the last one depends on some data that comes from a Silverstripe variable you have to pass it down through the whole tree of components, and with more than two or three components this can get quite annoying. It is however a common problem in both Vue and React that is not exclusive to Silverstripe variables and can be solved with some kind of state management library like Vuex.
In order to get includes to work as expected we can use slots in Vue. It allows us to define empty blocks in our components which can then be populated when used in the app. The purpose of this feature is mainly to be able to compose different components together but we can use it to define the parts of a component that requires the Silverstripe templating engine in the .ss files and have the rest of the code inside the component itself. This allows us to easier reuse our HTML because we can bring it along when using the component in different places but one possible downside is that when we start to define our markup in two different ways, in the single file Vue components and in the Silverstripe templates that could make the codebase less readable. You could argue that it makes sense to keep the Silverstripe code in one place and the Vue stuff in another instead of mixing.
How we used to mix Vue templating with the Silverstripe templating in the first approach mentioned in the beginning.
Separating the code makes it a bit longer but when we want to render my-component’ in a test we only have to pass in mocks of the header as a Vue slot and title variable as props.
As for the final issue that a lot of the logic still ended up in the initial untested instance I solved by doing as recommended and defining one Root instance that renders only single file components. This removes the simplicity of adding an instance anywhere in the Silverstripe template since I now have to bother with single file components, props and slots whenever I need a new Vue component, but it allowed me to move that logic into a single file component as well which made the unit tests a lot easier to write.
This is not a revolutionary new way of using Vue but is shows the versatility of the library and the reasons why we like to use it in our projects. We can think about how we structure our projects and how we combine the tools we use in order to get the most out of them and hopefully end up with more efficient development.