Skip to content

Commit

Permalink
feat(tokens): add tailwind config output files for utility and helper…
Browse files Browse the repository at this point in the history
… tokensets (#3899)
  • Loading branch information
oliverschuerch authored Nov 7, 2024
1 parent c5bf1de commit 4a3a243
Show file tree
Hide file tree
Showing 12 changed files with 355 additions and 189 deletions.
54 changes: 0 additions & 54 deletions .github/workflows/build-tokens.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,57 +21,3 @@ jobs:

- name: Build tokens & dependencies
run: pnpm --filter design-system-tokens... build

- name: Create Summary
id: summary
uses: actions/github-script@v7
with:
script: |
const fs = require('fs')
const path = require('path')
const inputFileNames = fs.readdirSync('packages/tokens/tokensstudio-generated')
const inputFiles = inputFileNames.map(fileName => ({
type: path.extname(fileName).replace(/^\./, ''),
name: fileName,
content: fs.readFileSync(`packages/tokens/tokensstudio-generated/${fileName}`, 'utf8')
}))
const outputOrder = [
'index.scss',
'core.scss',
'scheme.scss',
'device.scss',
'channel.scss',
'theme.scss',
'components.scss',
]
const outputFileNames = fs.readdirSync('packages/tokens/dist')
const outputFiles = outputFileNames
.map(fileName => ({
type: path.extname(fileName).replace(/^\./, ''),
name: fileName,
content: fs.readFileSync(`packages/tokens/dist/${fileName}`, 'utf8')
}))
.map(({ type, name, content }) => {
if (type === 'scss') content = content.replaceAll('\n\n', '\n \n').replaceAll('$', '$').replaceAll(' ', '  ')
return { type, name, content }
})
.sort((a, b) => (outputOrder.includes(a.name) ? outputOrder.indexOf(a.name) : 1000) - (outputOrder.includes(b.name) ? outputOrder.indexOf(b.name) : 1000))
return `# Token Build
## Input
${inputFiles.map(({ type, name, content }) => `<details>
<summary><code>${name}</code></summary>
<pre lang="${type}">${content}</pre>
</details>`).join('\n')}
## Output
${outputFiles.map(({ type, name, content }) => `<details>
<summary><code>${name}</code></summary>
<pre lang="${type}">${content}</pre>
</details>`).join('\n')}
`
- name: Output Summary
run: echo -e ${{ steps.summary.outputs.result }} >> $GITHUB_STEP_SUMMARY
1 change: 1 addition & 0 deletions packages/tokens/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
# Outputs
dist/
_temp/
103 changes: 103 additions & 0 deletions packages/tokens/_build/configs/all.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { fileHeader } from 'style-dictionary/utils';
import { expandTypesMap } from '@tokens-studio/sd-transforms';
import StyleDictionary from '../style-dictionary.js';
import { getSetName, getSet, getTokenValue, registerConfigMethod } from '../methods.js';

/**
* Registers a config getter method to generate output files for all code relevant tokens in the tokens.json.
*/
registerConfigMethod((tokenSets, { sourcePath, buildPath }) => {
return Object.entries(tokenSets.output).map(([name, { type, layer, filePath, sets }]) => {
return {
meta: {
type,
layer,
filePath,
setNames: Object.keys(sets),
},
source: [`${sourcePath}_temp/output/${filePath}`],
include: [`${sourcePath}_temp/source/**/*.json`],
platforms: {
scss: {
transforms: ['name/kebab'],
buildPath,
expand: {
include: ['typography'],
typesMap: expandTypesMap,
},
files: [
{
destination: `_${name}.scss`.toLowerCase(),
format: 'swisspost/scss-format',
filter: 'swisspost/scss-filter',
options: {
outputReferences: true,
},
},
],
},
},
};
});
});

/**
* @function StyleDictionary.registerFilter()
* Defines a custom StyleDictionary filter.
*
* @param object {
* name: string,
* filter: (token: TransformedToken, options: Config) => boolean
* }
*
* swisspost/tokenset-filter:
* Used to filter only the tokens of the current tokenset (e.g. core, device-desktop, ...).
*/
StyleDictionary.registerFilter({
name: 'swisspost/scss-filter',
filter: (token, { meta }) => {
return token.filePath.includes(`/output/${meta.filePath}`);
},
});

/**
* @function StyleDictionary.registerFormat()
* Defines a custom StyleDictionary format to be used at specific places in the build process.
*
* @param object {
* name: string,
* format: (dictionary: Dictionary, file: File, options: Config & LocalOptions, platform: PlatformConfig) => string
* }
*
* swisspost/scss-format:
* Used to declare the format of the *.scss output files.
*/
StyleDictionary.registerFormat({
name: 'swisspost/scss-format',
format: async ({ dictionary, options, file }) => {
const { meta } = options;
const header = await fileHeader({ file, commentStyle: 'short' });

return (
header +
meta.setNames
.map(setName => {
const tokenSetName = getSetName(options, setName);
const tokenSet = getSet(options, dictionary, setName)
.map(token => {
const tokenValue = getTokenValue(options, token);

return meta.layer === 'core'
? ` --${token.name}: ${tokenValue};`
: ` ${token.name}: ${tokenValue},`;
})
.join('\n');

return meta.layer === 'core'
? `:root {\n${tokenSet}\n}\n`
: `$${tokenSetName}: (\n${tokenSet}\n);\n`;
})
.join('\n')
);
},
});
2 changes: 2 additions & 0 deletions packages/tokens/_build/configs/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import './all.js';
import './tailwind.js';
94 changes: 94 additions & 0 deletions packages/tokens/_build/configs/tailwind.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { fileHeader } from 'style-dictionary/utils';
import { TOKENSET_LAYERS, TOKENSET_PREFIX } from '../constants.js';
import StyleDictionary from '../style-dictionary.js';
import { registerConfigMethod, getTokenValue } from '../methods.js';
import { objectDeepmerge, objectTextoutput } from '../utils/index.js';

const TAILWIND_TOKENSET_NAMES = ['utilities', 'helpers'];

/**
* Registers a config getter method to generate output files for tailwind relevant tokens in the tokens.json.
*/
registerConfigMethod((tokenSets, { sourcePath, buildPath }) => {
return Object.entries(tokenSets.output)
.filter(
([name, { layer }]) =>
layer === TOKENSET_LAYERS.component && TAILWIND_TOKENSET_NAMES.includes(name),
)
.map(([name, { type, layer, filePath, sets }]) => {
return {
meta: {
type,
layer,
filePath,
setNames: Object.keys(sets),
},
source: [`${sourcePath}_temp/output/${filePath}`],
include: [`${sourcePath}_temp/source/**/*.json`],
platforms: {
tailwind: {
transforms: ['name/kebab'],
buildPath: `${buildPath}tailwind/`,
files: [
{
destination: `${name}.tailwind.js`,
format: 'swisspost/tailwind-format',
filter: 'swisspost/tailwind-filter',
options: {
outputReferences: true,
},
},
],
},
},
};
});
});

/**
* @function StyleDictionary.registerFilter()
* Defines a custom StyleDictionary filter.
*
* @param object {
* name: string,
* filter: (token: TransformedToken, options: Config) => boolean
* }
*
* swisspost/tailwind-filter:
* Used to filter only the component layer tokens defined in the tokensets with the names in TAILWIND_TOKENSET_NAMES.
*/
StyleDictionary.registerFilter({
name: 'swisspost/tailwind-filter',
filter: token => {
return token.filePath.includes('/output/');
},
});

/**
* @function StyleDictionary.registerFormat()
* Defines a custom StyleDictionary format to be used at specific places in the build process.
*
* @param object {
* name: string,
* format: (dictionary: Dictionary, file: File, options: Config & LocalOptions, platform: PlatformConfig) => string
* }
*
* swisspost/tailwind-format:
* Used to declare the format of the tailwind output files.
*/
StyleDictionary.registerFormat({
name: 'swisspost/tailwind-format',
format: async ({ dictionary, options, file }) => {
const header = await fileHeader({ file, commentStyle: 'short' });
const tailwindTokensObject = dictionary.allTokens.reduce((allTokens, token) => {
const tokenObj = token.path
.slice(token.path.indexOf(TOKENSET_PREFIX) + 1)
.reverse()
.reduce((res, p) => ({ [p]: res }), getTokenValue(options, token));

return objectDeepmerge(allTokens, tokenObj);
}, {});

return header + `export default {${objectTextoutput(tailwindTokensObject)}\n};\n`;
},
});
9 changes: 7 additions & 2 deletions packages/tokens/_build/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ import { resolve } from 'path';

export const SOURCE_PATH = resolve('./tokensstudio-generated/');
export const OUTPUT_PATH = resolve('./dist/');
export const FILE_HEADER =
'// Do not edit manually!\n// This file was generated on:\n// {date} by the @swisspost/design-system-tokens package build command\n\n';

export const FILE_HEADER = [
'Do not edit manually',
'This file was generated by the swisspost/design-system-tokens package',
new Date().toUTCString(),
];

export const EXPLICIT_COMPONENT_LAYER_GROUPNAMES = [
'elements',
Expand All @@ -19,3 +23,4 @@ export const TOKENSET_LAYERS = {
component: 'component',
};
export const TOKENSET_PREFIX = 'post';
export const CUSTOM_FORMAT_INDENT = ' ';
Loading

0 comments on commit 4a3a243

Please sign in to comment.