Most of today’s frameworks offer users great flexibility by providing a component based architecture. This is supposed to divide a project into reusable, small and simple to understand chunks of code, like a lego set.
Unfortunately, the reality is not as wonderful as the "hello world" example leads us to believe. The components are well defined and all folders make perfect sense. But as the project grows, everything starts to get a bit more messy.
There are too many components, and its structure starts to actually become a burden.
In this blogpost, I am going to share my experience, that is the result of many mistakes made over the course of my career. This article is meant to help you avoid my wrongdoing by providing useful tips and resources that will support you in creating a well structured code base.
We are going to use VueJs as a framework of choice, but most of the topics discussed make sense and can be adapted to different frameworks.
Disclaimer
I will try to support most of my decision with reference, but at the end of the day most of them are personal, and you are more than free to adapt them to what may work for your personal project.
The website idea
Every great product starts with a simple idea. Our article is going to follow the same approach.
We are taking a simple sketch, and use it to build our application. The article is going to cover the following topics:
Design a folder structure that will support the growth of our app
Define what makes a good component
The sketch
The sketch may not look extremely pretty, but it is going to help us in encountering our first dilemma. How do we split it in different components? What is the best approach to do so? And finally, which one is the first step that need to be taken?
Many developers, including a younger me, would have simply answered by defining the following components:
Header
Footer
Sidebar
Main Content
It makes perfect sense, splitting the page with a sharp knife until I define my components. This approach, may not actually be wrong, but in my opinion, it is not where we are supposed to start.
Splitting the design
When I first started to use vue started pack provided by its CLI, I was so excited to get into the framework, that I made no notice of the actual structure of the project, and therefore missed the opportunity to structure things differently to support my app.
The actual design proposed are great to set your project up quickly, and there is nothing wrong in their structure. Unfortunately, they are built to be used by everyone, but as we all know, every project is unique, and specific modification may be required.
There is no right and wrong in defining your components, but I usually like to align my ideas with the way ITCSS (inverted triangle CSS) is structured. Learning it for styling purposes, has helped me in understanding the core of CSS "specificity". Using the same approach on our components structure, should create a good, strong and reusable foundation for every of our projects.
If you have never used or heard of ITCSS before, it is a style architecture that divides your styles in different layers. The layer are formed using three main properties: Reach, Specificificity and explicitness. So for example, on the first layer you will have structure defined (eg. body width), as you start to go down the pyramid, you will start to make the component more specific and reduce the reach they will have on the DOM (eg, style a simple button with no class), as we continue further you may start to create different iterations of the button to support your branding ( eg. button.cta-1, button.cta2).
Using the above methodology, allows me to "re-use" the top most layers of my style across different project, by just apply small modifications.
Today we are going to use the idea of ITCSS and coin a new term ITCS - Inverted Triangle Component Structure. The methodology that we are going to introduce, may not share the same fundamental applied to CSS, but it will be nevertheless useful for our purposes.
Creating a bridge between CSS and JS, will also support us in implementing ITCSS within our app, while being able to take full advantage of a component based architecture ( .vue files)
A common mistake in building big SPA, is in the definition of components, and the methodology in which this are separated and organized within the app. As with ITCSS, we are going to separate our components taking into consideration three aspects: Reach, Logic, complexity.
Reach: This is intended as the amount of impact the Component has on the main UI. A layout component has a great reach, while a specific button may have very little.
Logic: This is intended as business logic. The higher you are within the axis, the less business logic your components are going to include. In an ideal world the first few layers of the pyramid should be sharable within project, as they do not have "project" specific logic.
Complexity: This term is usually refeered to Dumb Vs Smart. Components are expected to gain more complexity as we move down the pyramid.
The layers
It is important to note that this article is going to cover mainly aspects regarding a component, and it will not cover information such as assets, mixins, entry file or other file available within your app.
Now that our Pyramid is set, it is time to fill those slots and start to introduce some structure to our web application. If we take into consideration the three main pillars of our methodology, our first and topmost layer is going to be occupied by Layout type components.
Layout
The layout component is known in VueJs as a named slot, and can be replicated in React with the use of higher order component. This layer will include components that expose a high level of reach, are simple to implement, and are free of business logic.
<template>
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</template>
Some website could end up being built on just one single layout, but this does not mean that the layout layer should not be implemented. The benefits of implementing it are the following:
All style within this component, can be compared to the Generic layer in ITCSS
Allow us to easily Change layout and/or style in the future
Can be Unit tested easily
It provides a scaffolding for future layouts
If you want to learn more about this approach, please read the following medium article titled Anyway, here’s how to create a multiple layout system with Vue and Vue-router
Views
Now that our layers have been fully defined, it is time to introduce another layer for our ITCS structure: Views.
This layer will be home of all previously defined layouts. Usually these components are also referred as Pages, because they will resemble the pages of our website.
<layout-main>
<template v-slot:header>
<s-logo ></s-logo>
<s-navbar></s-navbar>
<s-social ></s-social>
</template>
<template v-slot:default>
<s-sidebar ></s-sidebar>
<s-main></s-main>
<s-advertisement></s-advertisement>
</template>
<template v-slot:footer>
<s-social></s-social>
<s-links></s-links>
</template>
</layout-main>
Sections
When we illustrated our views layer, we populated our named slot with a set of components. These are going to occupy our next Pyramid layer - Sections.
Sections are going to have a limited reach compared to our previous layers, but are still quite simple in their design. All Section components should have scarce or absent Business logic.
Sections, allows us to be able to easily build multiple views. For example we could create a new layer, and simple removing the sidebar from this page, or adding a slideshow (<s-slideshow>
). This component main responsibility is to make use the next two layers of the pyramid, Presentational and SmartComponents.
These two components are dependent with each other, and their position within the pyramid, is not strictly defined.
I personally prefer the Presentational components first, for the following reasons:
They help me to build them without linking to the logical use of it (it makes them more reusable)
They are quicker to develop, and can help to build a quick lifecycle ( Product owner is able to comment earlier on the look of the finished product).
Presentational Components / Dumb Component
This layer is home of Presentational Components. The name derives from the fact that the main responsibility of this component is to define its UI and definition within the DOM, and it should not include any business or state logic. These are referred to in the official VueJs style-guide documentation as Base Components.
A base component, can either be developed in house, or it could derive from a third party UI library. We are going to analyse both aspects below.
In house component
As defined above, a Presentational component main purpose is to define the DOM element rendered by the framework. Further to the UI aspect, this layer will also be responsible in creating methods and events handlers, that will later be used to provide an extra level of functionality to our to it. A BaseButton for example would have all the required HTML, a prop for its name ( or a slot) and a defined method for a click event. The method will just be defined in the HTML part of the .vue template and will not have any corresponding JS ( or if it will have it, it will just be a placeholder).
<template>
<button @click="onClick($event)">{{name}}</button>
</template>
<script>
export default {
name: "pButton",
props: {
name: String
},
methods: {
onClick: function(event) {
alert(this);
}
}
};
</script>
<style scoped>
button {
background-color: red;
color: White;
padding: 10px 15px;
border-radius: 5px 5px;
border: none;
box-shadow: 2px 1px lightgray;
}
</style>
I usually like to make these components as simple as possible, and then create more handlers and property just if really necessary (remember that this component will just define the handlers in the UI not the implementation of it)
Third party library component
In case in which you are planning to use a third party UI library, the presentation layer will include individual imports of the components used. This is a very useful abstraction methodology, that makes you loosely coupled to the third party library, allowing you to change it in the future, without the need to go and change other components.
Import { Checkbox } from 'specificUiLibrary';
Export default Checkbox
Smart Components
Finally it is time to define the last and most complex layer of our pyramid. Being at the bottom of the pyramid, this layer will exhibit low level of reach, include high level of Business logic and could at times be complex.
A smart component, is usually going to extend a presentational component, by providing its existing UI with a relevant set of business logic and functionality required for your specific project.
In the example below, we are going to make use of the presentational component above, by adding specific business logic to it.
<script>
import pButton from "./HelloWorld";
import { submitForm } from "BusinessLogic";
export default {
extends: pButton,
props: {
formValid: Boolean
},
methods: {
onClick: function(event) {
if (this.formValid) {
submitForm(event);
}
}
}
};
</script>
There may be a need for multiple layers of presentational/smart component, and this should be achieved with a good naming/folder structure. It is beneficial to read the official vue documentation referred below for more information regarding this topic.
It is usually good practice to keep the actual component as small as possible, and export code that does not require to be embedded within our template in its own file ( in our example BusinessLogic). Doing so not only will keep the component small, but it will also support us to simply apply unit tests to both components and Logic.
Summary
The topics covered in this article are very subjective, and what I have described should be used to define your own implementation, and small modification should be made to fulfil your needs.
It is too common to see people trying to build up a structure a week before the release date, and using this approach, is going to support you in building a stable structure to support the growth of your application from the start.
The main advantage in taking this kind of approach are:
Easy to understand: due to the clear distinctions of the layers
Easy to unit test
Easy to open source
Defined abstraction level throughout the app
Support "junior" developers, by allowing them to focus on small units
I hope you will find the ITCS methodology good for you, and that it will serve and support your development, as it does for me.
References
https://www.creativebloq.com/web-design/manage-large-css-projects-itcss-101517528
https://vuejs.org/v2/style-guide/
https://nuxtjs.org/guide/directory-structure/
https://itnext.io/how-to-structure-a-vue-js-project-29e4ddc1aeeb
This article was written by Simone Cuomo who is a Senior Software Engineer at This Dot.
You can follow them on Twitter at @zelig880.
Need JavaScript consulting, mentoring, or training help? Check out our list of services at This Dot Labs.
Top comments (1)
great article