antd 源码解读 之 scripts中的 start

    xiaoxiao2022-06-24  192

    antd中的scripts 中的start 定义的脚本如下

    "start": "rimraf _site && mkdir _site && node ./scripts/generateColorLess.js && cross-env NODE_ENV=development bisheng start -c ./site/bisheng.config.js",

    可以按照他的执行顺序挨个来看

    rimraf _sitemkdir _sitenode ./scripts/generateColorLess.jscross-env NODE_ENV=development bisheng start -c ./site/bisheng.config.js"

    前两个没什么可说的清空然后创建_site目录

    node ./scripts/generateColorLess.js

    执行了scripts/generateColorLess.js这个脚本 代码如下

    const path = require('path'); const { generateTheme } = require('antd-theme-generator'); const options = { stylesDir: path.join(__dirname, '../site/theme/static'), antdStylesDir: path.join(__dirname, '../components'), varFile: path.join(__dirname, '../components/style/themes/default.less'), mainLessFile: path.join(__dirname, '../site/theme/static/index.less'), themeVariables: ['@primary-color'], outputFilePath: path.join(__dirname, '../_site/color.less'), }; generateTheme(options);

    关键的地方就是这个generateTheme 函数啥 其实翻开antd-theme-generator 这个包的介绍可以看到

    This script generates color specific styles/less file which you can use to change theme dynamically in browser

    他其实是为切换主题色服务的源码如下

    const fs = require("fs"); const path = require("path"); const glob = require("glob"); const postcss = require("postcss"); const less = require("less"); // 把多个less 文件合并为一个 const bundle = require("less-bundle-promise"); const hash = require("hash.js"); // 从npm impirt less const NpmImportPlugin = require('less-plugin-npm-import'); // const colorsOnly = require('postcss-colors-only'); const options = { withoutGrey: true, // set to true to remove rules that only have grey colors withoutMonochrome: true, // set to true to remove rules that only have grey, black, or white colors }; let hashCache = ""; let cssCache = ""; // 生成随机色 function randomColor() { return '#' + (Math.random() * 0xFFFFFF << 0).toString(16); } /* Recursively get the color code assigned to a variable e.g. @primary-color: #1890ff; @link-color: @primary-color; @link-color -> @primary-color -> #1890ff Which means @link-color: #1890ff */ function getColor(varName, mappings) { const color = mappings[varName]; if (color in mappings) { return getColor(color, mappings); } else { return color; } } /* Read following files and generate color variables and color codes mapping - Ant design color.less, themes/default.less - Your own variables.less It will generate map like this { '@primary-color': '#00375B', '@info-color': '#1890ff', '@success-color': '#52c41a', '@error-color': '#f5222d', '@normal-color': '#d9d9d9', '@primary-6': '#1890ff', '@heading-color': '#fa8c16', '@text-color': '#cccccc', .... } */ function generateColorMap(content) { return content .split("\n") .filter(line => line.startsWith("@") && line.indexOf(":") > -1) .reduce((prev, next) => { try { const matches = next.match( /(?=\S*['-])([@a-zA-Z0-9'-]+).*:[ ]{1,}(.*);/ ); if (!matches) { return prev; } let [, varName, color] = matches; if (color && color.startsWith("@")) { color = getColor(color, prev); if (!isValidColor(color)) return prev; prev[varName] = color; } else if (isValidColor(color)) { prev[varName] = color; } return prev; } catch (e) { console.log("e", e); return prev; } }, {}); } /* This plugin will remove all css rules except those are related to colors e.g. Input: .body { font-family: 'Lato'; background: #cccccc; color: #000; padding: 0; pargin: 0 } Output: .body { background: #cccccc; color: #000; } */ const reducePlugin = postcss.plugin("reducePlugin", () => { const cleanRule = rule => { // 清除掉.main-color .palatte- 开口的css 语句 if (rule.selector.startsWith(".main-color .palatte-")) { rule.remove(); return; } let removeRule = true; rule.walkDecls(decl => { if ( !decl.prop.includes("color") && !decl.prop.includes("background") && !decl.prop.includes("border") && !decl.prop.includes("box-shadow") ) { decl.remove(); } else { removeRule = false; } }); if (removeRule) { rule.remove(); } }; return css => { css.walkAtRules(atRule => { atRule.remove(); }); css.walkRules(cleanRule); //遍历容器的后代节点,为每个注释节点调用回调 css.walkComments(c => c.remove()); }; }); function getMatches(string, regex) { const matches = {}; let match; while ((match = regex.exec(string))) { if (match[2].startsWith("rgba") || match[2].startsWith("#")) { matches[`@${match[1]}`] = match[2]; } } return matches; } /* This function takes less input as string and compiles into css. */ // 编译less文件输出css function render(text, paths) { return less.render.call(less, text, { paths: paths, javascriptEnabled: true, plugins: [new NpmImportPlugin({ prefix: '~' })] }); } /* This funtion reads a less file and create an object with keys as variable names and values as variables respective values. e.g. //variabables.less @primary-color : #1890ff; @heading-color : #fa8c16; @text-color : #cccccc; to { '@primary-color' : '#1890ff', '@heading-color' : '#fa8c16', '@text-color' : '#cccccc' } */ // 转换 less文件中的less变量 function getLessVars(filtPath) { const sheet = fs.readFileSync(filtPath).toString(); const lessVars = {}; const matches = sheet.match(/@(.*:[^;]*)/g) || []; matches.forEach(variable => { const definition = variable.split(/:\s*/); const varName = definition[0].replace(/['"]+/g, "").trim(); lessVars[varName] = definition.splice(1).join(":"); }); return lessVars; } /* This function take primary color palette name and returns @primary-color dependent value .e.g Input: @primary-1 Output: color(~`colorPalette("@{primary-color}", ' 1 ')`) */ function getShade(varName) { let [, className, number] = varName.match(/(.*)-(\d)/); if (/primary-\d/.test(varName)) className = '@primary-color'; return 'color(~`colorPalette("@{' + className.replace('@', '') + '}", ' + number + ")`)"; } //验证字符串是否为颜色值 function isValidColor(color) { if (!color || color.match(/px/g)) return false; if (color.match(/colorPalette|fade/g)) return true; if (color.charAt(0) === "#") { color = color.substring(1); return ( [3, 4, 6, 8].indexOf(color.length) > -1 && !isNaN(parseInt(color, 16)) ); } return /^(rgb|hsl|hsv)a?\((\d+%?(deg|rad|grad|turn)?[,\s]+){2,3}[\s\/]*[\d\.]+%?\)$/i.test( color ); } function getCssModulesStyles(stylesDir, antdStylesDir) { const styles = glob.sync(path.join(stylesDir, './**/*.less')); return Promise.all( styles.map(p => less .render(fs.readFileSync(p).toString(), { paths: [ stylesDir, antdStylesDir, ], filename: path.resolve(p), javascriptEnabled: true, plugins: [new NpmImportPlugin({ prefix: '~' })], }) .catch(() => '\n') ) ) .then(csss => csss.map(c => c.css).join('\n')) .catch(err => { console.log('Error', err); return ''; }); } // 根据定义的主题变量生成对应的 less 文件 合并成一个文件输出 function generateTheme({ //包目录 antDir, //样式文件目录 antdStylesDir, // 输出样式的目录 stylesDir, // 主入口样式文件 mainLessFile, //自定义主题样式文件, varFile, // 写出样式文件的路径 outputFilePath, cssModules = false, themeVariables = ['@primary-color'] }) { return new Promise((resolve, reject) => { /* Ant Design Specific Files (Change according to your project structure) You can even use different less based css framework and create color.less for that - antDir - ant design instalation path - entry - Ant Design less main file / entry file - styles - Ant Design less styles for each component */ let antdPath; if (antdStylesDir) { antdPath = antdStylesDir; } else { antdPath = path.join(antDir, 'lib'); } // 项目入口文件 const entry = path.join(antdPath, './style/index.less'); // 所有less文件 const styles = glob.sync(path.join(antdPath, './*/style/index.less')); /* You own custom styles (Change according to your project structure) - stylesDir - styles directory containing all less files - mainLessFile - less main file which imports all other custom styles - varFile - variable file containing ant design specific and your own custom variables */ //自定义主题样式文件 varFile = varFile || path.join(antdPath, "./style/themes/default.less"); // 读取主文件 let content = fs.readFileSync(entry).toString(); content += "\n"; // 引入 所有样式文件 styles.forEach(style => { content += `@import "${style}";\n`; }); // 引入 所有样式文件 if (mainLessFile) { const customStyles = fs.readFileSync(mainLessFile).toString(); content += `\n${customStyles}`; } //如果文件内容没变的话 const hashCode = hash.sha256().update(content).digest('hex'); if(hashCode === hashCache){ resolve(cssCache); return; } hashCache = hashCode; let themeCompiledVars = {}; let themeVars = themeVariables || ["@primary-color"]; const lessPaths = [ path.join(antdPath, "./style"), stylesDir ]; //处理文件中颜色相关的变量 输出css return bundle({ src: varFile }) .then(colorsLess => { // 解析文件中的颜色变量 const mappings = Object.assign(generateColorMap(colorsLess),generateColorMap(mainLessFile)); return [ mappings, colorsLess ]; }) // 输出css .then(([ mappings, colorsLess]) => { let css = ""; themeVars = themeVars.filter(name => name in mappings); themeVars.forEach(varName => { const color = mappings[varName]; css = `.${varName.replace("@", "")} { color: ${color}; }\n ${css}`; }); themeVars.forEach(varName => { [1, 2, 3, 4, 5, 7].forEach(key => { let name = varName === '@primary-color' ? `@primary-${key}` : `${varName}-${key}`; css = `.${name.replace("@", "")} { color: ${getShade(name)}; }\n ${css}`; }); }); css = `${colorsLess}\n${css}`; return render(css, lessPaths).then(({ css }) => [ css, mappings, colorsLess ]); }) .then(([css, mappings, colorsLess]) => { css = css.replace(/(\/.*\/)/g, ""); const regex = /.(?=\S*['-])([.a-zA-Z0-9'-]+)\ {\n\ \ color:\ (.*);/g; themeCompiledVars = getMatches(css, regex); content = `${content}\n${colorsLess}`; return render(content, lessPaths).then(({ css }) => { return getCssModulesStyles(stylesDir, antdStylesDir).then(customCss => { return [ `${customCss}\n${css}`, mappings, colorsLess ]; }) }); }) .then(([css, mappings, colorsLess]) => { return postcss([reducePlugin]) // return postcss.use(colorsOnly(options)) .process(css, { parser: less.parser, from: entry }) .then(({ css }) => [css, mappings, colorsLess]); }) .then(([css, mappings, colorsLess]) => { Object.keys(themeCompiledVars).forEach(varName => { let color; if (/(.*)-(\d)/.test(varName)) { color = themeCompiledVars[varName]; varName = getShade(varName); } else { color = themeCompiledVars[varName]; } color = color.replace('(', '\\(').replace(')', '\\)'); css = css.replace(new RegExp(`${color}`, "g"), varName); }); css = `${colorsLess}\n${css}`; themeVars.reverse().forEach(varName => { css = css.replace(new RegExp(`${varName}(\ *):(.*);`, 'g'), ''); css = `${varName}: ${mappings[varName]};\n${css}\n`; }); css = css.replace(/\\9/g, ''); if (outputFilePath) { fs.writeFileSync(outputFilePath, css); console.log( `Theme generated successfully. OutputFile: ${outputFilePath}` ); } else { console.log(`Theme generated successfully`); } cssCache = css; return resolve(css); }) .catch(err => { console.log("Error", err); reject(err); }); }); } module.exports = { generateTheme, isValidColor, getLessVars, randomColor, renderLessContent: render };

    代码的逻辑是根据入参把所有样式文件打包到一个less文件中,这样做的目的就是为了实现换肤的功能 ,这个部分其实是用了less 提供的接口实现的 一个简单版本的演示如下

    定义html如下 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <link rel="stylesheet/less" type="text/css" href="./style.less" /> <script> window.less = { async: false, env: 'production' }; </script> <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/less.js/2.7.2/less.min.js"></script> </head> <body> <div class="box"> </div> <button id="cut">切换</button> <script> document.getElementById('cut').addEventListener('click',function(){ less.modifyVars({ '@base': '#5B83AD' }); }) </script> </body> </html> style.less 文件如下 @base:#00375B; .box{ width:100px; height:200px; display: block; background: @base; }

    需要注意的点就是link标签和script标签的顺序

    未完待续…


    最新回复(0)