Skip to content

Commit

Permalink
fix: platform preprocessors retrieve platform config
Browse files Browse the repository at this point in the history
Signed-off-by: Tobias Kuppens Groot <[email protected]>
  • Loading branch information
tkgroot authored and jorenbroekema committed Oct 21, 2024
1 parent f1f60f1 commit a67ed31
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 19 deletions.
5 changes: 5 additions & 0 deletions .changeset/gentle-lamps-add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'style-dictionary': patch
---

Pass PlatformConfig as options param to platform-applied preprocessors.
62 changes: 48 additions & 14 deletions __tests__/register/preprocessor.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ registerSuite({
});

describe('register/transformGroup', async () => {
let StyleDictionaryExtended;
let sdInstance;
beforeEach(async () => {
StyleDictionary.hooks.preprocessors = {};
StyleDictionaryExtended = new StyleDictionary({});
await StyleDictionaryExtended.hasInitialized;
sdInstance = new StyleDictionary({});
await sdInstance.hasInitialized;
});

it('should support registering preprocessor on StyleDictionary class', () => {
Expand All @@ -36,12 +36,12 @@ describe('register/transformGroup', async () => {
preprocessor: (dict) => dict,
});
expect(StyleDictionary.hooks.preprocessors['example-preprocessor']).to.not.be.undefined;
expect(StyleDictionaryExtended.hooks.preprocessors['example-preprocessor']).to.not.be.undefined;
expect(sdInstance.hooks.preprocessors['example-preprocessor']).to.not.be.undefined;
});

it('should throw if the preprocessor name is not a string', () => {
expect(() => {
StyleDictionaryExtended.registerPreprocessor({
sdInstance.registerPreprocessor({
name: true,
preprocessor: (dict) => dict,
});
Expand All @@ -50,7 +50,7 @@ describe('register/transformGroup', async () => {

it('should throw if the preprocessor is not a function', () => {
expect(() => {
StyleDictionaryExtended.registerPreprocessor({
sdInstance.registerPreprocessor({
name: 'example-preprocessor',
preprocessor: 'foo',
});
Expand All @@ -75,7 +75,7 @@ describe('register/transformGroup', async () => {
},
});

StyleDictionaryExtended = new StyleDictionary({
sdInstance = new StyleDictionary({
preprocessors: ['strip-descriptions'],
tokens: {
foo: {
Expand All @@ -86,8 +86,8 @@ describe('register/transformGroup', async () => {
description: 'My dictionary',
},
});
await StyleDictionaryExtended.hasInitialized;
expect(StyleDictionaryExtended.tokens).to.eql({
await sdInstance.hasInitialized;
expect(sdInstance.tokens).to.eql({
foo: {
value: '4px',
type: 'dimension',
Expand Down Expand Up @@ -120,7 +120,7 @@ describe('register/transformGroup', async () => {
},
});

StyleDictionaryExtended = new StyleDictionary({
sdInstance = new StyleDictionary({
preprocessors: ['strip-descriptions'],
tokens: {
foo: {
Expand All @@ -131,8 +131,8 @@ describe('register/transformGroup', async () => {
description: 'My dictionary',
},
});
await StyleDictionaryExtended.hasInitialized;
expect(StyleDictionaryExtended.tokens).to.eql({
await sdInstance.hasInitialized;
expect(sdInstance.tokens).to.eql({
foo: {
value: '4px',
type: 'dimension',
Expand All @@ -150,7 +150,7 @@ describe('register/transformGroup', async () => {
},
});

StyleDictionaryExtended = new StyleDictionary({
sdInstance = new StyleDictionary({
preprocessors: ['foo-processor'],
tokens: {
foo: {
Expand All @@ -160,7 +160,41 @@ describe('register/transformGroup', async () => {
},
},
});
await StyleDictionaryExtended.hasInitialized;
await sdInstance.hasInitialized;
expect(opts.usesDtcg).to.be.true;
});

it('should pass platform config options to preprocessor function as second argument for platform preprocessors', async () => {
let opts;
StyleDictionary.registerPreprocessor({
name: 'foo-processor',
preprocessor: async (dict, options) => {
opts = options;
return dict;
},
});

sdInstance = new StyleDictionary({
tokens: {
foo: {
$value: '4px',
$type: 'dimension',
$description: 'Foo description',
},
},
platforms: {
css: {
preprocessors: ['foo-processor'],
prefix: 'foo',
files: [
{
format: 'css/variables',
},
],
},
},
});
await sdInstance.formatAllPlatforms();
expect(opts.prefix).to.equal('foo');
});
});
8 changes: 6 additions & 2 deletions docs/src/content/docs/reference/Hooks/preprocessors.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ Starting in version 4.0, you can define custom preprocessors to process the dict
This is useful if you want to do more complex transformations on the dictionary as a whole, when all other ways are not powerful enough.

Preprocessors can be applied globally or per platform.
Applying them per platform means the tokens already have some more metadata on them such as "filePath" and "path",
and the options object resembles the `PlatformConfig` rather than the SD global config options.
It also allows you to preprocess on a per platform basis, while global means you won't have to repeat the same preprocessing because it will happen once on a global level, so essentially applies to all platforms.
See [lifecycle diagram](/info/architecture) for a visual diagram of the order of the lifecycle hooks.

:::caution
It should be clear that using this feature should be a last resort. Using custom parsers to parse per file or using transforms to do transformations on a per token basis,
Expand All @@ -21,7 +25,7 @@ That said, preprocessing the full dictionary gives ultimate flexibility when nee
A preprocessor is an object with two props:

- `name`: the name of the preprocessor
- `preprocessor` a callback function that receives the dictionary and SD options as parameters, and returns the processed dictionary
- `preprocessor` a callback function that receives the dictionary and SD options or platform config as parameters, and returns the processed dictionary

```javascript title="my-preprocessor.js"
const myPreprocessor = {
Expand Down Expand Up @@ -132,7 +136,7 @@ StyleDictionary.registerPreprocessor({

## Default preprocessors

There are two default preprocessors that are always applied and run before other custom preprocessors do:
There are two default preprocessors that are always applied and run after other custom preprocessors do:

- [`typeDtcgDelegate`](/reference/utils/dtcg#typedtcgdelegate), for DTCG tokens, make sure the `$type` is either already present or gets inherited from the closest ancestor that has it defined, so that the `$type` is always available on the token level, for ease of use
- [`expandObjectTokens`](/reference/config#expand), a private preprocessor that will expand object-value (composite) tokens when user config has this enabled.
1 change: 1 addition & 0 deletions lib/StyleDictionary.js
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,7 @@ export default class StyleDictionary extends Register {
platformProcessedTokens,
platformConfig.preprocessors,
this.hooks.preprocessors,
platformConfig,
);
if (this.shouldRunExpansion(platformConfig.expand)) {
platformProcessedTokens = expandTokens(platformProcessedTokens, this.options, platformConfig);
Expand Down
3 changes: 2 additions & 1 deletion lib/utils/preprocess.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
/**
* @typedef {import('../../types/DesignToken.ts').PreprocessedTokens} PreprocessedTokens
* @typedef {import('../../types/Config.ts').Config} Config
* @typedef {import('../../types/Config.ts').PlatformConfig} PlatformConfig
* @typedef {import('../../types/Preprocessor.ts').Preprocessor} Preprocessor
*/

Expand All @@ -24,7 +25,7 @@
* @param {PreprocessedTokens} tokens
* @param {string[]} [appliedPreprocessors]
* @param {Record<string, Preprocessor['preprocessor']>} [preprocessorObj]
* @param {Config} [options]
* @param {Config|PlatformConfig} [options]
* @returns {Promise<PreprocessedTokens>}
*/
export async function preprocess(
Expand Down
4 changes: 2 additions & 2 deletions types/Preprocessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@
*/

import type { PreprocessedTokens } from './DesignToken.ts';
import type { Config } from './Config.ts';
import type { Config, PlatformConfig } from './Config.ts';

export type Preprocessor = {
name: string;
preprocessor: (
dictionary: PreprocessedTokens,
options: Config,
options: Config | PlatformConfig,
) => PreprocessedTokens | Promise<PreprocessedTokens>;
};

0 comments on commit a67ed31

Please sign in to comment.