The exporters provide a extensive configuration system so the exporter packages can be built in such a way where modifying the code is not necessary in most cases. A good example of this is the CSS exporter which can be configured to form vastly different outputs without changing a line of code.
This page outlines the structure and customization possibilities of the export configuration, alongside how to use it from the perspective of the consumer of the exporter package.
Overview
The exporter package can define a set of configuration properties that can be accessed at the runtime to get their actual values. Such example of the property can be indexFileName: "index.css".
When declared, users can simply change this property and their output index file will be exported to a different filename, provided it was coded like this. The CSS exporter mentioned above declares the following interface:
export type ExporterConfiguration = {
/** When enabled, a disclaimer showing the fact that the file was generated automatically and should not be changed manually will appear in all style styles */
showGeneratedFileDisclaimer: boolean
/** When enabled, a disclaimer showing the fact that the file was generated automatically and should not be changed manually will appear in all style styles */
disclaimer: string
/** When enabled, file with all css style files imported will be generated */
generateIndexFile: boolean
/** When enabled, empty style files will be generated. Otherwise empty are omitted */
generateEmptyFiles: boolean
/** When enabled, token description will be shown as code comments for every exported token */
showDescriptions: boolean
/** When enabled, values will use references to other tokens where applicable */
useReferences: boolean
/** Style of exported token names */
tokenNameStyle: StringCase
/** Format of the exported colors */
colorFormat: ColorFormat
/** Max number of decimals in colors */
colorPrecision: number
/** Number of spaces used to indent every css variables */
indent: number
/** When set, will prefix each token of a specific type with provided identifier. Put empty string if not necessary */
tokenPrefixes: Record<TokenType, string>
/** Name of each file that will be generated. Tokens are grouped by the type and will land in each of those files */
styleFileNames: Record<TokenType, string>
/** Name of the index file that will be generated */
indexFileName: string
/** All files will be written to this directory (relative to export root set by the exporter / pipeline configuration / VSCode extension) */
baseStyleFilePath: string
/** Index file will be written to this directory (relative to export root set by the exporter / pipeline configuration / VSCode extension) */
baseIndexFilePath: string
}
As you can see, the number of options are vast and the output of the CSS exporter can be changed heavily because of it — but the consumer doesn't need to go deep into the code to actually do it.
There are three main parts to the configuration of the exporters:
- config.json: A structured definition of the configuration with default values.
- config.ts: A TypeScript type interface for the configuration.
- Special TypeScript Method: A mechanism to access and extend the configuration.
Additionally, consumers of the packages can create config.local.json which allows them to configure the exporter with their own values — those take precedent over the default values declared in config.json.
Configuration Structure
The config.json file provides a structured definition of the configuration. In order for the exporter to have access to the configuration property it must be declared there and each property must provide all the following attributes:
- key: A unique identifier for the configuration property.
- type: Defines the type of data expected for the configuration property.
- default: Specifies the default value for the configuration property.
- title: A brief, human-friendly name for the configuration property.
- description: A detailed explanation of the configuration property's purpose and effect.
There are multiple types of properties that you can create: string, boolean, enum, number, object and array.
Boolean
Represents a true or false value.
Example:
{
"key": "showDisclaimer",
"type": "boolean",
"default": true,
"title": "Show Disclaimer",
"description": "Indicates if a disclaimer should be shown."
}
String
Represents a sequence of characters.
Example:
{
"key": "disclaimerText",
"type": "string",
"default": "This is a default disclaimer.",
"title": "Disclaimer Text",
"description": "Text to display as the disclaimer."
}
Number
Represents numeric values.
Example:
{
"key": "maxAttempts",
"type": "number",
"default": 3,
"title": "Max Attempts",
"description": "Maximum number of retry attempts."
}
Enum
A predefined set of allowed values. The allowed values are defined in an options array.
Example:
{
"key": "colorFormat",
"type": "enum",
"options": ["rgb", "rgba", ...],
"default": "rgb",
"title": "Color Format",
"description": "Defines the format for color values."
}
Do note that if you want to bind the enum to actual TypeScript enum, the values in the definition must correspond to the values of the enum (not keys). You must declare the values of the enum, enums with dynamic values can't be used:
export enum ColorFormat {
rgb = "rgb"
rgba = "rgba"
....
}
This will make sure the enum lookup will always succeed.
Object
Represents a collection of key-value pairs. For more complex objects, the structure can be further defined using allowedKeys and allowedValues.
Example:
{
"key": "styleFileNames",
"type": "object",
"allowedKeys": {
"options": ["Color", "Typography", "Dimension"],
"type": "string"
},
"allowedValues": {
"type": "string"
},
"default": {
"Color": "colors.css",
"Typography": "typography.css",
"Dimension": "dimensions.css",
},
"title": "Style File Names",
"description": "Name of each file that will be generated."
}
Array
Represents an ordered list of values. The values can be whatever value can be serialized in JSON.
{
"key": "allowedExtensions",
"type": "array",
"default": [".css", ".scss", ".less"],
"title": "Allowed File Extensions",
"description": "List of file extensions that are permitted."
}
TypeScript interface
The config.ts file provides a type interface for the configuration. It outlines the expected shape and data types of the configuration properties. It should perfectly mirror what you have declared in config.json, so you don't have to worry about obscure mistakes happening when building the exporter. Here is an example of how such interface can look like:
export type ExporterConfiguration = {
showDisclaimer: boolean;
colorFormat: 'rgb' | 'rgba' | ... ;
...
}
Accessing configuration
Finally, after everything was configured, you can use the configuration in your code. To do so, use the following magic method of the Pulsar engine that resolves the configuration for you and makes it available, type-safe:
export const exportConfiguration = Pulsar.exportConfig<ExporterConfiguration>()
You should declare this property somewhere at the beginning of your exporter, so you can just access it in any code file / method you'll create. The method is generic and you must specialize it with your type interface, which will make it type-safe. Then, you can use it however you want, like this:
if (exportConfiguration.showDisclaimer) {
... do something based on the config value
}
Overriding the configuration
The main value of the configuration is to the consumers of the exporters — so they don't have to deep-dive into your code for any small change. To use this functionality, create a new file called config.local.json. This JSON file is much simpler than its config.json counterpart, as it only contains the overrides for the default values, like this:
{
"showDisclaimer": false,
"colorFormat": "rgba"
}
When the exporter runs with this file present, the resulting export configuration will be the default values and overrides from the local config file applied to them. The local file doesn't have to have all properties listed, instead, you can only provide those that should be different.