-
Notifications
You must be signed in to change notification settings - Fork 0
Synergy Values
Most issues with front-end development arise due to inconsistencies in various areas, leading to unpredictable and difficult to debug situations, leading to codebases which become unmanageable and unmaintainable. This guide aims to bring-to-light these areas so you and your team can establish the ground rules, as well suggest sensible recommendations for each area.
This guide makes no assumptions about your stack or the technology you use - because it is irrelevant. At the end of the day, technologies and frameworks are just tools to help get the job done, but unless your team is consistent where it matters, no technology or framework will save your codebase, so it's important that you and your team are on the same page from the get-go.
Synergy is a philosophy created to help lead to the creation of scalable UI products. It's core value is to isolate and reduce a UI into logical and meaningful domains to allow for a completely modular structure. One-Nexus provides tools to apply the Synergy philosophy to your website or webapp, but since Synergy is just a philosophy, you can can adhere to its values whilst using any tools or technologies.
Synergy values include:
- Separation of concerns
- Project architecture
- Naming convention
- Code quality
- Developer Experience (DX)
- Documentation
- Synergy contract
Understanding how and when concerns should be separated is probably the most important aspect when developing software of any kind. Without a sensible separation of concerns, your whole project will never be able to stand on its own two feet. When concerns become separated, it's not always by design, and it can be difficult to un-separate them again when it doesn't work out, so it's important to acknowledge your top concerns before starting anything. When deciding to separate concerns, it can be difficult to choose criteria on which to separate them. Some of the concerns when dealing with website/webapp code might be:
- file types/technology (
.js
,.scss
,.json
,.jsx
etc...) - environment (e.g. server/client side)
- logic
- presentation
- accessibility
- origin (first/second/third-party software)
- importance
- distributing/sharing
- complexity
And some goals when dealing with website/webapp code might be to create a product that is:
- maintainable
- scalable
- performant
- practical
- DRY
The nature of the goals and concerns means they are all intertwined into one confusing mess, so deciding how to cut and slice this mess is naturally a confusing process. With these concerns and goals considered, it should guide you into thinking about how to separate the concerns of your website/webapp. Decide how much weight each concern should hold, and compare your options.
It's likely you've already made a crucial separation of concerns, and that is separating your front-end code from your back-end code. This separation of concern is probably the most common example for websites/webapps, to the extent that many projects even have separate repositories for their front and back end code. Think about the reasons why this separation makes sense. At the highest level, what is the biggest concern? The environment? Since this separation is fairly well ingrained within the industry (though it's being challenged), the only assumption this guide makes is that your front-end code is isolated from your back-end code.
For the sake of argument, our hypothetical example project will be its own repository with implied src
and dist
directories, and we will be focusing on the src
directory.
Within the front-end realm, traditionally the next most significant relationship to be concerned about has been that of logic and presentation.
- Logic
- Presentation (styles)
This relationship has traditionally been the highest-level front-end concern due to technological limitations; front-end development environments using things like node.js are relatively recent; before that it was just text-editors and basing your source-code architecture on the structure websites would interpret it, separating your files by technology (html
, js
, css
, images
etc..). Naturally as the way we develop evolves, as should (and has) our architecture. Today, however, we aren't ruled by technologies; we are ruled by concepts, giving rise to more modular and reusable approaches.
So the question we are trying to answer here is essentially "what top level directories should exist in the front-end's src
directory?". Our biggest concern as UI developers is ultimately the user-interface - which is the DOM we create. A DOM is split up into sections using HTML (styled with CSS and interacted with JavaScript). Since we aren't thinking in technologies, but rather thinking in concepts, we can translate "HTML sections" (and their encompassing styles and interactions) into "UI components", and since we are thinking in a modular way, we can adapt this into "UI modules".
src/
|--modules/
Each module would contain the relevant front-end assets required to be able to render as expected, whatever technologies they may be. Now that we understand how and why we should choose to separate our concerns, we can apply these concepts to create the rest of our project architecture.
Perhaps the next most important aspect is the way you and your team organize the various files and folders that compose your project's front-end (which should largely be the result of how you separate your concerns). Given that the concerns on which to base the architecture will be weighted differently depending on various factors that will be unique to you and your project, there is no single holy-grail approach, but if we can agree what the goals for our architecture should be, it may be easy to agree whether or not a given architecture is solid, and it may be possible to agree on a generic, flexible architecture that would suit the average project as a starting point.
A project architecture should be:
An ideal architecture will be intuitively navigable - you should be able to tell by looking at the architecture where to find something without resorting to searching for specific chunks of code. A good separation of concerns would likely lead to an intuitively navigable architecture.
Your architecture should allow for enough flexibility so that you can add and remove features with as little impact as possible, and be modular enough so that code can be reused without being repeated, and without having any external impact.
Taking a look at the architecture of our example project so far, we've got modules
as the only top-level concern:
src/
|--modules/
At this point, the consideration is whether or not we can contain all of our front-end code within separate modules within this modules
directory. The likely answer is "no", but for now let's roll with an answer of "yes".
Looking inside the modules
directory, how to group your modules (or components), is where it starts to get quite subjective, so let's look at what our goal should be. We want to be able to create user-interfaces with our modules, with as much reusability and as little effort as possible. The goal could be extended to include keeping the number of modules down to a minimum so long as they're kept DRY. Having a lot of modules might seem helpful and practical, but if we can drop an existing module whilst satisfying the rest of our goals then we should do it.
With this in mind, the ideal result would be a collection of generic reusable modules that could be adapted and extended to satisfy any design/UI requirement. The reality would however most likely result in requiring at least several non-generic specific modules that may only ever be used once, but let's still roll with the ideal scenario, as it means we don't have to make an assumption about how to group our modules (which as previously mentioned is quite subjective). But at the highest level, you could perhaps consider having generic and non-generic modules, and separating them as such.
Here's a list of some common UI modules:
Accordion | Alert-Bar | Billboard |
Blockquote | Breadcrumb | Button |
Card | Carousel | Container |
Dropdown | Footer | Form |
Header | Image | List |
Logo | Modal | Navigation |
Overlay | Preloader | Progress-Bar |
Table | Tabs | Tooltip |
Scroll-Top | Search | Side-Nav |
For our hypothetical example project we will assume that this is the entire list of modules for our project, and that we can create our entire UI using only these modules.
So far we haven't had to create anything along side our modules
directory, but we need a place include these modules we've just created. You might choose to even have a separate repository for your UI components, isolated from any application data, but even still you would likely want somewhere to create static prototypes of your views, leaving us with:
src/
|--modules/
|--views/
There's an article which refers to what we're calling views
as screens
. What you call this may or may not depend on the technologies you use, but moving forward we will also go with screens
:
src/
|--modules/
|--screens/
In an ideal world this might be the end of it, but in the real world we will likely want to have some tools and utilities to either facilitate or speed-up development, as these are big concerns of ours, and there is no question in whether or not we should create a new high-level folder if it means speeding up and facilitating development. Tools/utilities might include polyfills, helper functions and libraries etc (again without making any assumptions about the technologies used).
src/
|--modules/
|--screens/
|--tools/
The final thing that most projects will likely require is the ability to share theming values like colors and typography etc, since repeating these in your modules wouldn't be DRY and would lead to time-consuming updates. Again depending on your technologies and other factors you may have a single configuration file and may not need a new directory, but for our example we will assume we may potentially have more than one theme:
src/
|--modules/
|--screens/
|--themes/
|--tools/
Once you strip away the technologies that once dictated your architecture and re-build it from the ground up using more sensible and flexible concerns, we are left with what feels like a comfortable starting point that could be used for most projects' UI. Any changes beyond this will largely be influenced by the technologies you end up using as well as many other factors, but by sticking to this general guide you should always be in a decent position. If you're having to change your goals because of the technologies you're using, maybe you're using the wrong technology. Choose the technologies that allow you to build what you need in a way that suits your goals.
A crucial aspect to a maintainable front-end code-base is the naming convention you use for your UI modules/components. If nothing else, having a solid convention that your entire team adheres to is likely enough to significantly impact your team's productivity, as it will shape how your team think about the UI, naturally pushing it to a more modular state.
If we're assuming our website/webapp is made-up of of screens, which are made up of modules, what are modules made up of? At this point various realms and contexts in the industry are polluted with similar words, so without taking these into account and going purely off whatever word seems to have the most suitable definition, modules are made up of components. For example, you may have an accordion
module that is composed of panel
components, which in turn may be composed of title
and content
components. So in theory, with the correct structure and naming convention, any HTML element in the DOM would either be a module or a component of a module (or a component of a component).
Represented as XML
, an Accordion module might look like:
<Accordion>
<Panel>
<Title />
<Content />
</Panel>
<Panel>
<Title />
<Content />
</Panel>
</Accordion>
In Synergy terms this would be represented as:
<Module>
<Component>
<Component />
<Component />
</Component>
<Component>
<Component />
<Component />
</Component>
</Component>
This convention ensures your UI is structured into ergonomic modules and components that can optionally have modifiers to highlight difference in visual state. In essence, each page would be a collection of modules, and each module would be a collection of components.
The Synergy naming convention is more or less identical to BEM (in terms of theory/philosophy; not in practice), just with different defaults and keywords - so for brevity and lack of wanting to re-invent the wheel, it is recommended to read more about BEM to understand the motivations behind and benefits of the convention. The main difference is that in Synergy, what is referred to as a
Module
is referred to as aBlock
in BEM, and what's referred to as aComponent
in Synergy is referred to anElement
in BEM.
The naming convention used by Synergy and Synergy projects for DOM elements follows the following structure:
[module][component-glue][component][modifier-glue][modifier]
Which in BEM would be represented as:
[block][element-glue][element][modifier-glue][modifier]
This convention allows for syntactic flexibility whilst adhering to a standard philosophy.
<div class="button_wrapper-large">...</div>
module-name: 'button'
component-glue: '_'
component-name: 'wrapper'
modifier-glue: '-'
modifier-name: 'large'
There is no substitute for consistency, so getting your team to agree upon a single convention is more important than the convention you ultimately choose - it's difficult to strictly follow an established convention and yield bad results (but not impossible). Other popular naming conventions include:
There are many things that can and should be done to ensure good code quality in a project, such as unit-testing, code-reviews and using something like Git Hooks to ensure that only code that meets a certain quality can be committed to your repository.
Having a code styleguide is important as it ensure that the code you write is consistent, hence more predictable and maintainable. When the same codebase contains multiple conventions and standards it starts to become easy to lose control. Whatever languages and technologies you're using, there will likely already be established styleguides for them, or you may decide a custom one is more appropriate. As long as everyone is following the same styleguide, that's the most important thing.
- One of the most popular JavaScript styleguides is the Airbnb JavaScript Style Guide
- Airbnb also have a CSS/Sass styleguide
- Airbnb also have a JSX Styleguide
Developer Experience (DX) is the concept of applying UX principles in the context of API development. Developers are users at the end of the day, users of APIs. A really basic example of good DX vs bad DX would be allowing the user of your code to pass a date/time/duration in various formats, with your code working under the hood to serialize it, instead of forcing the user to enter it in a specific format. Another example of good DX is providing helpful errors and warnings when your code doesn't work as expected, so the user of your code doesn't have to waste time debugging the issue.
Quite simply, DX is about making the developer do as little work as possible to achieve their goal without compromising on the outcome. This is why the concept of API-driven-development (or ADD) is appealing as it ensures your APIs are written first, highlighting any issues with usability that may have otherwise gone un-noticed and required a code-refactor.
Documentation is vastly underrated - well documented code can potentially save weeks of time, but it's difficult to do well. Documentation should always be straightforward and easy to understand, providing as much context as needed. Not everyone using your code will have the same knowledge as you, so try to avoid making too many assumptions about what the user will know. For example, don't simply say something like "run build app
to build the app" as the only piece of information - mention where the command needs to be run from etc, and instead of saying "install SomeProgram", provide steps on how to install the program, and provide version numbers and anything else of relevance. If a user gets stuck because something doesn't make sense, the documentation is useless to them. A documented module is arguably more valuable than a tested module.
Key things to be documented include:
- Setup/installation
- Running/testing
- UI components
- Dependencies
- Integration
The Synergy Contract is a Synergy convention which dictates that all websites adhering to Synergy will expose Synergy
to the Window
object making it available as a global variable, meaning window.Synergy
will never be undefined
. This can just be an empty object, but the various One-Nexus tools that use Synergy all share the same configuration by looking to this object, to get things like the namespace component/modifier glue.
In essence, the Synergy Contract is a defined list of what should be expected to exist globally in any given environment. In practice the contract would mean that you never have to do things like if (typeof Synergy.config === 'function')
if the contract states that Synergy.config
is a function that exists on the Window
object, otherwise your application would error with something like Uncaught ReferenceError: Synergy is not defined
. The agreement allows for separate tools to talk to each other and share configuration at runtime. To satisfy the contract, it can be as simple as adding this before importing any code:
window.Synergy = window.Synergy || {};
Another useful convention that is part of the Synergy Contract is setting a SYNERGY
property on process.env
when building applications using Node.js.
For example this can be done in Webpack by adding to the plugins
section:
new webpack.DefinePlugin({
'process.env': {
SYNERGY: true
}
})
This can have many uses; one thing it has proven useful for is when developing One-Nexus tools where certain dependencies that exist in Synergy would otherwise be required by the user (who may not be using Synergy); so when building the tools with Node.js there is a check for process.env.SYNERGY
- if it exists then the dependency is not included with the tool when building (as it already exists in the build).
let extendedConfig;
if (process.env.SYNERGY) {
// In Synergy environments `Synergy.config` is an alias for
// the `deep-extend` package, so we don't `require` it
extendedConfig = Synergy.config(defaults, custom);
}
else {
// Otherwise we don't expect `Synergy.config` to exist, so we
// `require` the package to include it in the bundle
extendedConfig = require('deep-extend')(defaults, custom);
}
The Synergy Contract is an open contract that should be defined on a per-project basis (i.e it doesn't dictate that all projects must expose the deep-extend
package to Synergy.config
, that is just part of the contract for the Synergy package itself).