Pulsar and Blueprints

Blueprints are the meat of the exporter. You can think about them as templates, similar to Handlebars or Moustache. However, compared to those two templating engines, Pulsar templates are oriented and specifically tailored to help you with code generation and communication with the design system.

In fact, in order to even enable something like this, we wrote our own programming language that you will be using, that shares the syntax with handlebars - but has capabilities more similar to actual programming language. If you ever used any templating language, you will immediately know how to use it. And if not, it is dead easy to start writing it.

Of course, if you want to resort to something you already know and love, Pulsar has an in-built extension model so you can use modern javascript code as well.

But as you will soon learn, there is no need to do things harder than they have to be :)

Pulsar

Pulsar is code-generation-oriented programming/templating language that allows you to communicate with your design systems, get specific data out of it in a unified fashion, and export them in any form or shape you want. Pulsar and Javascript power all exporter packages you can find in the Exporter Store.

Let's see how one such template could look like:

Blueprint: colors.pr
import 'package:flutter/material.dart';

class SDMColors {
  const SDMColors();


{[ const resolvedTokens = @dsm.resolveTokens(@dsm.allColorTokens())  /]}
{[ for token in resolvedTokens ]}
  final {[ inject "export_token_name" context token.source /]} = {[ inject "export_color" context token /]};
{[/]}

}

The template is taken from Supernova's Flutter exporter and takes care of the generation of Flutter color definitions. For example, the output of this blueprint can look like this:

Result: colors.dart
import 'package:flutter/material.dart';

class SDMColors {
  const SDMColors();

  final ctaButtonColor = const Color(0xff4589ff);
  final ctaTextColor = const Color(0xffffffff);
  final contentTextHi = const Color(0xff555353);
  final contentTextMid = const Color(0xff697077);
}

Pulsar combines powerful computing functionality and all you would expect from a normal programming language (such as loops, conditions, and so on) but also combines it with the ability to immediately put the result of your computation to good use - outputting it to generated code files.

Dynamic code templates

Wondering how did we get the color definitions here? This is because the Pulsar core functions you use when developing the blueprint know about your design system selection from either the VSCode, or the CI when you are running automatic code delivery through hooks.

For example, compare the following:

In the example, we are using the previously shown blueprint to generate colors from design system "Magic Wand" from "General Magic" workspace. However, if we switch to the different design system:

You can see that the output is completely different! The exporter package and debugging runtime always target a specific combination of workspace / design system / version. If you publish your exporter to Exporter Store, then the completely same code we just executed twice with different targets can be used by others to run against their design system as well.

Static vs. Dynamic Content

Blueprints define both static and dynamic content - static content will always be the same, while dynamic content depends on the data coming from design systems. Consider the previous blueprint to generate dart color definitions and dissect it:

import 'package:flutter/material.dart';

class SDMColors {
  const SDMColors();


{[ const resolvedTokens = @dsm.resolveTokens(@dsm.allColorTokens())  /]}
{[ for token in resolvedTokens ]}
  final {[ inject "export_token_name" context token.source /]} = {[ inject "export_color" context token /]};
{[/]}

}

First, we declare the static part that will always be output into a generated file:

import 'package:flutter/material.dart';

class SDMColors {
  const SDMColors();

You can immediately tell it is a static part of the generated output because it is not contained within dynamic execution tags, {{ }} and {[ /]}. Anything that is not contained is always considered output.

Next, we ask the design system to give us all data we are interested in - in this case, we ask for a specific set of design tokens, tokenized colors, and define a new property to store this data into:

{[ const resolvedTokens = @dsm.resolveTokens(@dsm.allColorTokens())  /]}

There are dozens of functions you can use to ask for the design system data. If you want to learn all about what is available, see the description of our universal data model

Finally, we iterate each token we have obtained and generate a color definition for each:

{[ for token in resolvedTokens ]}
  final {[ inject "export_token_name" context token.source /]} = {[ inject "export_color" context token /]};
{[/]}

You are probably wondering how did we obtain the color definition from the token if there is no code that does this? Blueprints can be used inside each other, so we have defined a separate blueprint called export_color to provide this shared functionality (color conversion in case of the Flutter exporter is used on multiple places - for shadows, gradients, and so on):

{[ const colorValue = value.substring(1, 8) /]}
{[ const alpha = colorValue.substring(6, 2) /]}
{[ const red = colorValue.substring(0, 2) /]}
{[ const green = colorValue.substring(2, 2) /]}
{[ const blue = colorValue.substring(4, 2) /]}
const Color(0x{{alpha}}{{red}}{{green}}{{blue}})

Because the color is defined as a RGBA colorValue but Flutter expects a different format (ARGB), we are just splitting it up and reformatting it so it fits the needs of the platform better. This blueprint can be then used everywhere we need it.

Combined all together, tokens or any other elements of the design system can be represented in any form or shape, limited just by your imagination.

Keen to learn everything about the language syntax and options? Refer to our Pulsar Language reference documentation to understand it in detail.

Using javascript

You could argue that converting the values using the blueprint is a bit cumbersome. For this reason, it is also possible to enhance the language capabilities with javascript code, bringing together a powerful templating language with the most-used computing and scripting language for an uber-powerful combination.

Last updated