Skip to content

Commit

Permalink
feat(tokens): add tooling for EDS theming
Browse files Browse the repository at this point in the history
[sc-225326]:
- handle initializing token override file from base
- handle configuration for local paths to use
- handle comparison to base in package for checksum
- handle updates to themes when base has changed
  • Loading branch information
booc0mtaco committed Jul 21, 2023
1 parent 1e1a03f commit 772c0a2
Show file tree
Hide file tree
Showing 9 changed files with 3,438 additions and 64 deletions.
23 changes: 23 additions & 0 deletions bin/_util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module.exports = {
/**
* Fetch the EDS config from the project using the lilconfig hierarchy.
* This can be from package.json, or from various separate non-YAML files.
*
* @see https://github.com/antonk52/lilconfig#usage
* @returns nullable config object returned from lilconfig
*/
getConfig: async function () {
const { lilconfig } = require('lilconfig');

// read in the config from config file, package json "eds", etc.
const { config } = await lilconfig('eds').search();

// If no config exists, exit.
if (!config) {
// TODO: point to documentation (linked either to the repo or confluence)
console.error('Please add EDS config to your project before continuing.');
}

return config;
},
};
106 changes: 106 additions & 0 deletions bin/eds-apply-theme.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#!/usr/bin/env node
(async function () {
/**
* Documentation:
* - Token change log published when EDS changed, and errors link to changelog
*
* Possible Usages:
* - Checks for reference to the token to link to change log entry(ies)
* - Integrate with husky so that PRs always include updated theme values (pre-push)
*
* TODO:
* - remove the config, and generated files from this PR before merging
* - update the local app-theme.json with any new values from the base theme
*
* Questions:
* - should this preserve the tier 1 tokens used by tier 2 and 3 files?
* - (in wireframe kit, the only updates happen to colors/fonts)
* - (in along, colors/fonts/border-* & drop shadows need overriding)
* - should we use token references in the output instead of the hex codes?
* - should we remove the listing of the tier-1 values in the theme base file?
* - should we use the wireframe theme as the values used in the theme base?
* - should all the grade-based tokens be filtered out (they might not apply to point solutions)?
*/
const StyleDictionary = require('style-dictionary');
const path = require('path');
const fs = require('fs');
const { getConfig } = require('./_util');

// Compare local to base theme file for differences. flag and quit when theme has
// undefined tokens being overridden
let packageRootPath;
try {
packageRootPath =
path.dirname(require.resolve('@chanzuckerberg/eds')) + '/tokens/';
} catch (e) {
console.error('EDS package not installed. Using local path...');
packageRootPath =
path.dirname(require.main.path) + '/src/tokens-dist/json/';
}

const config = await getConfig();

// read and parse JSON files on disk
const localTheme = JSON.parse(
fs.readFileSync(`${config.json}app-theme.json`, 'utf8'),
);
const baseTheme = JSON.parse(
fs.readFileSync(`${packageRootPath}theme-base.json`, 'utf8'),
);

// Keys in theme must be a strict subset of those in base
try {
isStrictSubset(baseTheme, localTheme);
} catch (error) {
// TODO: if theme has things not in base, error showing where the conflict
console.error('Theme error:', error.message);
return;
}

StyleDictionary.registerFileHeader({
name: 'cssOverrideHeader',
fileHeader: (defaultMessage) => [
...defaultMessage,
'To update, edit app-theme.json, then run `npx eds-update-theme`',
],
});

const EDSStyleDictionary = StyleDictionary.extend({
source: [config.json + 'app-theme.json'],
platforms: {
css: {
transforms: [...StyleDictionary.transformGroup.css, 'name/cti/kebab'],
buildPath: config.css,
files: [
{
format: 'css/variables',
destination: 'app-theme.css',
options: {
fileHeader: 'cssOverrideHeader',
},
filter: function (token) {
// don't allow theming on legacy tokens
return token.attributes.category !== 'legacy';
},
},
],
},
},
});
EDSStyleDictionary.buildAllPlatforms();

function isStrictSubset(base, theme, path = []) {
for (const name in theme) {
if (typeof theme[name] === 'object') {
if (base[name] === undefined) {
throw new Error(
`Local themeable value does not exist in base theme: ${path.join(
'.',
)}.${name}"`,
);
}
isStrictSubset(base[name], theme[name], path.concat(name));
}
}
}
})();
36 changes: 36 additions & 0 deletions bin/eds-init.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/usr/bin/env node
(async function () {
const fs = require('fs');
const path = require('path');
const { getConfig } = require('./_util');

let packageRootPath;
try {
packageRootPath =
path.dirname(require.resolve('@chanzuckerberg/eds')) + '/tokens/';
} catch (e) {
console.error('EDS package not installed. Using local path...');
packageRootPath = path.dirname(require.main.path) + '/src/tokens-dist/';
}

// read in the config from config file, package json "eds", etc.
const config = await getConfig();

// take the packaged var file and place a copy in the project's 'json' directory
if (config) {
try {
fs.copyFileSync(
packageRootPath + 'json/theme-base.json',
`${config.json}app-theme.json`,
fs.constants.COPYFILE_EXCL,
);
} catch (error) {
console.error('The local theme file already exists. Exiting.');
return;
}

console.log(
'File copy completed! Please use `npx eds-update-theme` to generate theme override CSS.',
);
}
})();
15 changes: 14 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,14 @@
"url": "https://github.com/chanzuckerberg/edu-design-system/issues"
},
"files": [
"/bin",
"/lib",
"tailwind.config.*"
],
"bin": {
"eds-apply-theme": "bin/eds-apply-theme.js",
"eds-init": "bin/eds-init.js"
},
"scripts": {
"build": "yarn build:clean && yarn build:tokens && yarn build:declarations && yarn build:js && yarn copy-fonts-to-lib",
"build:clean": "rm -rf lib/",
Expand Down Expand Up @@ -91,12 +96,16 @@
"@tippyjs/react": "^4.2.6",
"@types/lodash": "^4.14.195",
"clsx": "^1.2.1",
"lilconfig": "^2.0.6",
"lodash": "^4.17.21",
"react-beautiful-dnd": "^13.1.1",
"react-children-by-type": "^1.1.0",
"react-focus-lock": "^2.9.5",
"react-popper": "^2.3.0",
"react-portal": "^4.2.2"
"react-portal": "^4.2.2",
"react-uid": "^2.3.2",
"style-dictionary": "^3.7.0",
"svg4everybody": "^2.1.9"
},
"devDependencies": {
"@chanzuckerberg/axe-storybook-testing": "^6.3.1",
Expand Down Expand Up @@ -183,5 +192,9 @@
"stylelint --fix"
]
},
"eds": {
"json": "src/components/",
"css": "src/components/"
},
"packageManager": "yarn@3.6.1"
}
Loading

0 comments on commit 772c0a2

Please sign in to comment.