designToken.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. const fs = require('fs');
  2. const path = require('path');
  3. const lodash = require('lodash');
  4. const { mergeWith } = require("lodash");
  5. const isComment = codeLine => lodash.startsWith(codeLine, '//') || lodash.startsWith(codeLine, '/*');
  6. const getTokenCategory = codeLine => {
  7. const categorySet = new Set(['color', 'width', 'height', 'spacing', 'radius', 'font', 'motion', "animation", "transition"]);
  8. const firstWord = lodash.get(codeLine.match(/\$([\w\W]+?)[-_]/), 1, { toLowerCase: () => null }).toLowerCase();
  9. if (firstWord) {
  10. return categorySet.has(firstWord) ? firstWord : 'other';
  11. } else {
  12. return 'other';
  13. }
  14. };
  15. const codeLineSplit = codeLine => {
  16. const [key, value, comment] = codeLine.split(/:|\/\/|\/\*/).map(code => code.trim()).filter(code => code);
  17. let category = getTokenCategory(codeLine);
  18. if (category==='transition'){
  19. category = "animation";
  20. }
  21. return { key, value: lodash.trimEnd(value, ';'), comment:comment && comment.replace("ignore-semi-css-trans", ""), category: category, raw: codeLine };
  22. };
  23. const getGlobalDesignToken = () => {
  24. const globalScssContentArray = fs.readFileSync(path.join(__dirname, '../packages/semi-theme-default/scss/global.scss'), { encoding: 'utf-8' }).split('\n');
  25. const paletteScssContentArray = fs.readFileSync(path.join(__dirname, '../packages/semi-theme-default/scss/_palette.scss'), { encoding: 'utf-8' }).split('\n');
  26. const animationScssContentArray = fs.readFileSync(path.join(__dirname, '../packages/semi-theme-default/scss/animation.scss'), { encoding: 'utf-8' }).split('\n');
  27. const normalContentArray = fs.readFileSync(path.join(__dirname, '../packages/semi-theme-default/scss/variables.scss'), { encoding: 'utf-8' }).split('\n');
  28. const getLightAndDarkScss = scssFileContentArray => {
  29. const contentArray = scssFileContentArray.map(codeLine => codeLine.trim())
  30. .filter(codeLine => codeLine && !isComment(codeLine))
  31. .filter(codeLine => !codeLine.startsWith('}'))
  32. .filter(codeLine => !codeLine.startsWith('@'));
  33. // {key,value,category,raw};
  34. const rawData = {
  35. light: [],
  36. dark: []
  37. };
  38. let currentMode = 'light';
  39. for (let i in contentArray) {
  40. i = Number(i);
  41. const codeLine = contentArray[i];
  42. if (/body/.test(codeLine)) {
  43. if (/semi-always-dark/.test(codeLine)) {
  44. currentMode = 'dark';
  45. }
  46. continue;
  47. }
  48. rawData[currentMode].push(codeLineSplit(codeLine));
  49. }
  50. return rawData;
  51. };
  52. let globalScssContent = getLightAndDarkScss(globalScssContentArray);
  53. let paletteContent = getLightAndDarkScss(paletteScssContentArray);
  54. const mergeCommentLightToDark = content => {
  55. const map = new Map();
  56. content.light.forEach(token => {
  57. const { key } = token;
  58. map.set(key, { light: token });
  59. });
  60. content.dark.forEach(token => {
  61. const { key } = token;
  62. const data = map.get(key);
  63. if (!data) {
  64. console.warn(`${key} in dark but not in light`);
  65. return;
  66. }
  67. data.dark = token;
  68. });
  69. Array.from(map.values()).forEach(({ light, dark }) => {
  70. if (!dark.comment) {
  71. dark.comment = light.comment;
  72. }
  73. });
  74. return content;
  75. };
  76. globalScssContent = mergeCommentLightToDark(globalScssContent);
  77. paletteContent = mergeCommentLightToDark(paletteContent);
  78. const normalContent = normalContentArray.map(codeLine => codeLine.trim())
  79. .filter(codeLine => codeLine && !isComment(codeLine))
  80. .map(codeLine => codeLineSplit(codeLine));
  81. const animationContent = animationScssContentArray.map(codeLine => codeLine.trim())
  82. .filter(codeLine => codeLine && !isComment(codeLine))
  83. .filter(codeLine => !codeLine.startsWith('body'))
  84. .filter(codeLine => !codeLine.startsWith('}'))
  85. .filter(codeLine => !codeLine.startsWith('@'))
  86. .map(codeLine=>codeLineSplit(codeLine)).map(token=>{
  87. token.category="animation";
  88. return token;
  89. });
  90. return { global: globalScssContent, palette: paletteContent, normal: normalContent, animation:animationContent };
  91. };
  92. // 官网组件 design token 注入
  93. async function main() {
  94. const componentVariablesMap = {};
  95. const animationVariablesMap = {};
  96. const semiUIDir = path.join(__dirname, '../packages/semi-foundation');
  97. fs.readdirSync(semiUIDir).map(dirname => {
  98. const variableSCSSPath = path.join(semiUIDir, dirname, 'variables.scss');
  99. if (fs.existsSync(variableSCSSPath)) {
  100. const raw = fs.readFileSync(variableSCSSPath, { encoding: 'utf-8' });
  101. const scssCodeLineList = raw.split('\n').filter(codeLine => codeLine && !isComment(codeLine));
  102. componentVariablesMap[dirname.toLowerCase()] = scssCodeLineList.map(codeLine => codeLineSplit(codeLine));
  103. }
  104. const animationSCSSPath = path.join(semiUIDir, dirname, 'animation.scss');
  105. if (fs.existsSync(animationSCSSPath)){
  106. const raw = fs.readFileSync(animationSCSSPath, { encoding: 'utf-8' });
  107. const scssCodeLineList = raw.split('\n').filter(codeLine => codeLine && !isComment(codeLine));
  108. animationVariablesMap[dirname.toLowerCase()] = scssCodeLineList.map(codeLine => codeLineSplit(codeLine));
  109. }
  110. });
  111. mergeWith(componentVariablesMap, animationVariablesMap, (objValue, srcValue)=>{
  112. if (Array.isArray(objValue)) {
  113. return objValue.concat(srcValue);
  114. }
  115. });
  116. componentVariablesMap.global = getGlobalDesignToken();
  117. const [_, __, savePath] = process.argv;
  118. fs.writeFileSync(savePath || './designToken.json', JSON.stringify(componentVariablesMap));
  119. }
  120. main();