当前位置: 代码迷 >> 综合 >> antd 源码解读 之 scripts中的 start
  详细解决方案

antd 源码解读 之 scripts中的 start

热度:50   发布时间:2023-11-21 05:09:49.0

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

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

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

  1. rimraf _site
  2. mkdir _site
  3. node ./scripts/generateColorLess.js
  4. cross-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 colorswithoutMonochrome: 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 -> #1890ffWhich 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.lessIt 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 colorse.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-1Output: 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];//处理文件中颜色相关的变量 输出cssreturn 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 提供的接口实现的
一个简单版本的演示如下

  1. 定义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>
  1. style.less 文件如下
@base:#00375B;
.box{
    width:100px;height:200px;display: block;background: @base;
}

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

未完待续…

  相关解决方案