A non-destructive-map-merge function for Sass
I work as a Front End Engineer for some of the largest digital publications in Hungary. Our team is responsible for the codes of several sites, some of which share a huge amount of code.
Not only the code, but the general styles are similar also. They share some user interface components as well as colors and sizes. Most of these are stored in variables — Sass lists to be exact.
We use Sass because it helps us maintain our CSS and create links between logically related codes snippets.
When setting up these relations, we decided that we are going to separate global and local scopes. We put variables that are mostly shared between publications in the global scope and extend or possibly override them in our local scope.
Keep in mind, I am not talking about actual, programming language scope here. What we did was create query functions, like get-size for example, that returns a value based on it’s arguments from a Sass list named $sizes. $sizes is created by merging $local-sizes and $global-sizes.
This is the point where we ran into some trouble. Remember, $local-sizes stores the sizes that are prevalent to the actual site only, while $global-sizes stores the shared values.
The problem was, we used multi-dimensional Sass lists.
Consider the simple example above. If we used the built-in Sass function map-merge to create $colors, we would get this:
The map-merge function simply adds the keys of the second argument to the first and in case the same key exists in the first argument, it overrides it, whatever it’s type or value is.
Our goal was to extend the first argument by adding the x-dark tone of the neutral color to the already existing neutral tones.
In other words, we wanted to merge a Sass map with another Sass map — all inside a Sass map.
While this sounds quite easy, there is one catch though: some of our variables are two-dimensional, some are three or four. We wanted to avoid manually writing merges for every variable type we have and wanted to create a one-size-fits-all kind of solution.
Exit map-merge; enter recursive functions
When we are talking about repeating a task n times, it usually takes us to either a for/while loop or a recursive function. This time, it is the latter.
Recursion in computer science is a method where the solution to a problem depends on solutions to smaller instances of the same problem…
When it comes to a function, that basically means that a function is going to call itself. Given that we want to merge lists inside of a list, this approach seems handy.
The entire function with examples can be found on CodePen, but I’ll also paste it here and go through each step.
We iterate over the child map, which is going to be the local lists in our case. We need to separate two main cases: when we want a standard map-merge and when we don’t.
On line 4, we check three things.
- Does the parent list have a key of the same name?
- Does the parent list’s key with the same name have the same type?
- Are they both maps?
We need to do a standard map-merge if the parent list doesn’t have the key with the same name, or it does, but they are of a different type or they are of the same type, but not maps.
The reason for this is extensibility. We wanted to be able to define anything on a global scope and override it locally.
In case we have two maps with matching key names, on line 8, we recursively call this same function we are defining, but this time, on the values themselves.
This solution allows us to do complex mergers, such as:
I hope you find this useful, let me know what you think, get in touch in case you have any questions or a better solution, approach to the problem!