• 环境变量配置


    环境变量配置

    srm-front/config/compile{Profile}Env.js

    环境变量

    // srm-front/config/compileBuildEnv.js
    module.exports = {
      // 自定义的登陆,登出地址, 没有配置 会根据配置的网关地址 和 oauth 服务生成
      // LOGIN_URL: '',
      // LOGOUT_URL: '',
      // 项目的访问基地址
      BASE_PATH: '/',
      // PUBLIC_URL: '/app',
      // 项目是 OP/SAAS
      PLATFORM_VERSION: 'BUILD_PLATFORM_VERSION',
      // websocket 地址
      WEBSOCKET_HOST: 'BUILD_WEBSOCKET_HOST',
      // 工作流地址
      BPM_HOST: 'BUILD_BPM_HOST',
      // 工作流你编辑器地址
      WFP_EDITOR: 'BUILD_API_HOST',
      // oauth 访问 客户端id
      CLIENT_ID: 'BUILD_CLIENT_ID',
      // 网关地址
      API_HOST: 'BUILD_API_HOST',
      // 在 build 时 不生成 source-map
      GENERATE_SOURCEMAP: false,
      // 服务合并的环境变量
      routeMap: JSON.stringify({
        "/hpfm": "/hpfm",
        "/iam": "/iam",
        "/hdtt": "/hdtt",
        "/hmsg": "/hmsg",
        "/hptl": "/hptl",
        "/hwfl": "/hwfl",
        "/hdtw": "/hdtw",
        "/hsdr": "/hsdr",
        "/hsgp": "/hsgp",
        "/hitf": "/hitf",
        "/hfle": "/hfle",
        "/oauth": "/oauth",
        "/hagd": "/hagd",
        "/himp": "/himp",
        "/hrpt": "/hrpt",
        "/hcnf": "/hcnf",
        "/hwfp": "/hwfp",
        "/hnlp": "/hnlp",
        "/hpay": "/hpay",
        "/hmnt": "/hmnt"
      }),
    };
    

    分类

    编译环境变量

    在编译代码时需要用到的环境变量

    BASE_PATH, PUBLIC_URL 等

    运行环境变量

    在项目中使用的环境变量, 需要在config中配置

    BASE_PATH, API_HOST 等

    项目中使用到的 可通过环境变量的配置的变量 全部统一通过 ApiConfig 生成

    ApiConfig

    编写

    配置说明

    配置是固定字符串

    高级配置

    1. route: init 返回的值 可以 被 routeMap 这个环境变量中对应值覆盖
    2. noChange: 不能在运行中被changeRoute更改
    3. deps&get: 该配置依赖于其他配置 deps, 生成方法是 get

    config/apiConfig.js 文件的编写

    固定配置

      const config ={
        VERSION: 'v1',
      };
    

    后端服务路由

      const config ={
        HZERO_HXXX: {
          init: () => {
             return "'/hxxx'";
          },
          route: true,
        },
      };
    

    不可在运行时更改

      const config = {
        CLIENT_ID: {
          init: () => {
            return '`${process.env.CLIENT_ID}`';
          },
          noChange: true,
        },
      };
    

    依赖其他变量

      const config = {
        AUTH_LOGOUT_URL: {
          deps: ['HZERO_HXXX'],
          get: (HZERO_HXXX) => {
            return `${HZERO_HXXX}/logout`;
          },
        },
      };
    

    实际文件

    主模块webpack配置srm-front/packages/spfm-front

    srm-front/packages/spfm-front/config/alias.js

    module.exports = {
      '@': path.resolve(__dirname, '../src'),
      _config: path.resolve(__dirname, '../src/config.js'),
    };
    

    其他模块主工程

    module.exports = {
      '@': path.resolve(__dirname, '../src'),
      _config: 'spfm-front/lib',
    };
    

    config/apiConfig.js 编写文件

    /**
     * 配置 utils/config 下的常量
     */
    
    /**
     * deps 和 get 必须同时出现
     * get 和 init 是互斥的
     * 如果有 route, 那么必须有init
     *
     * @typedef {Object|string} CONFIG
     * @property {?Function} init - 返回返回字符串包裹的字符串模板; 参照 CLIENT_ID
     * @property {?boolean} noChange - 是否能改变 false/undefined => 能改变, true => 不能改变; 参照 CLIENT_ID
     * @property {?boolean} route - 是否能被环境变量routeMap改变 false/undefined => 可以改变, true => 不能改变; 参照 HZERO_PLATFORM
     * @property {?string[]} deps - 依赖的CONFIG, 当 依赖的CONFIG 改变时 会同时改变; 参照 AUTH_SELF_URL 的配置
     * @property {?Function} get - 依赖改变生成自己的方法; (...deps: string[]) => string; 参照 AUTH_SELF_URL 的配置
     */
    
    /**
     * @type {{[configId]: CONFIG}}
     */
    const config = {
     VERSION: 'v1',
     HZERO_HXXX: {
       init: () => {
          return "'/hxxx'";
       },
       route: true,
     },
     CLIENT_ID: {
      init: () => {
       return '`${process.env.CLIENT_ID}`';
      },
      noChange: true,
     },
     AUTH_LOGOUT_URL: {
       deps: ['HZERO_HXXX'],
       get: (HZERO_HXXX) => {
         return `${HZERO_HXXX}/logout`;
       },
     },
    };
    
    const prefix = `/**
     * 不要直接修改这个文件, 请修改 config/apiConfig 文件
     * Config - 全局统一配置
     * @date: 2018-6-20
     * @author: niujiaqing <njq.niu@hand-china.com>
     * @version: 0.0.1
     * @copyright Copyright (c) 2018, Hand
     */
    `;
    
    const suffix = '';
    
    module.exports = {
      config,
      prefix,
      suffix,
    };
    
    

    生成方法文件 scripts/genConfig.js

    /**
     * TODO: 已经可以直接通过 changeRoute('/iam', '/iam-xxxxx') 来在运行时改变配置了, 需要将 .route 相关的代码注释取消 和注释之下的代码, 需要注意单引号问题, 已知问题, 不能有重复的 配置名
     * 通过 ../config/apiConfig 生成 utils/config 文件
     * @author WY <yang.wang06@hand-china.com>
     * @date 2019-05-22
     * @copyright ® HAND
     */
    const {config, prefix, suffix} = require('../config/apiConfig');
    
    const helpFuncMap = {};
    const initLang = [];
    const changeRouteSwitch = new Map(); // 存储 case 代码
    const configChangeFunc = [];
    const exportConfig = [];
    // 不需要从 'HZERO_PLATFORM' -> '/hpfm' 的映射
    // '/hpfm' -> 'HZERO_PLATFORM' 的映射
    // const routeMapVar = {};
    
    Object.entries(config).forEach(([key, value]) => {
    
      // #region exportConfig
      exportConfig.push(key);
      // #endregion
    
      // #region initConfig
      let varType = 'let';
      if (value.noChange) {
        varType = 'const';
      }
    
      //  init  lang
      if (value.init) {
        if (value.route) {
          // 路由 不能直接被环境变量更改, 所有 route: true 的 init 是直接返回可用的变量
          const routePath = value.init();
          // if (routeMapVar[routePath]) {
          //   throw new Error(formatString('route define has two config %12s, %12s has same value %8s', routeMapVar[routePath], key, routePath));
          // } else {
          //   routeMapVar[routePath] = key;
          // }
          initLang.push(formatString('%8s %16s = %s;', varType, key, ` routeMap[${routePath}] || ${routePath}`));
        } else {
          // not route
          initLang.push(formatString('%8s %16s = %s;', varType, key, value.init()));
        }
    
      } else if (value.get) {
        //  get  config  return  body;
        // TODO: 初始化时也调用方法
        // const retBody = value.get.toString().match(/return ([\S\s]+);/);
        // initLang.push(`${varType} ${key} = ${(retBody && retBody[1] || '')};`);
        initLang.push(formatString('%8s %16s = %s;', varType, key, `change${key}(${value.deps.join(', ')})`));
      } else {
        initLang.push(formatString('%8s %16s = %s;', varType, key, `'${value}'`));
      }
      // #endregion
    
      if (value.noChange) {
        // noChange 不能在运行时更改
        return;
      }
    
      // #region help  func
      // const helpKey = value.route ? value.init() : key;
      const helpKey = key;
      const curHelp = helpFuncMap[helpKey] || {changeConfig: [], depBy: []};
      helpFuncMap[helpKey] = curHelp;
      if (value.deps) {
        value.deps.forEach((depKey) => {
          // const depHelpKey = (config[depKey] && config[depKey].route) ? config[depKey].init() : depKey;
          const depHelpKey = depKey;
          const depHelp = helpFuncMap[depHelpKey] || {changeConfig: [], depBy: []};
          helpFuncMap[depHelpKey] = depHelp;
          depHelp.changeConfig.push(helpKey);
          curHelp.depBy.push(helpKey);
        });
      }
      curHelp.changeConfig.push(helpKey);
      // #endregion
    
      // #region change  route  func
      // const caseKey = value.route ? value.init() : key;
      const caseKey = key;
      const curCase = changeRouteSwitch.get(caseKey) || [];
      changeRouteSwitch.set(caseKey, curCase);
      if (value.deps) {
        //  if  have  deps,  must  have  get
        value.deps.forEach((depKey, depIndex) => {
          const depCase = changeRouteSwitch.get(depKey) || [];
          changeRouteSwitch.set(depKey, depCase);
          const newDep = [...value.deps];
          newDep[depIndex] = 'value';
          depCase.push(`${key} = change${key}(${newDep.join(', ')});`);
        });
        const changeFuncLines = value.get.toString().split('\n');
        changeFuncLines[0] = `function change${key} (${value.deps.join(', ')}) {`;
        const lastLineNo = changeFuncLines.length - 1;
        configChangeFunc.push(
          changeFuncLines.map((line, lineNo) => {
            if (lineNo === 0 || lineNo === lastLineNo) {
              return line.trim();
            } else {
              return `  ${line.trim()}`;
            }
          }).join('\n'));
      }
      // every config can change it self
      curCase.push(`${key} = value;`);
      // #endregion
    
    });
    
    function renderChangeRouteSwitch() {
      const strs = [
        'window.changeRoute = function changeRoute(key, value)  {',
        '  if(key && value)  {',
        '    switch(key)  {',
      ];
      for ([key, value] of changeRouteSwitch.entries()) {
        if (value.noChange) {
          return;
        }
        const curCase = (value || []);
        strs.push(`      case '${key}':`,
          curCase.map(str => {
            return `        ${str}`;
          }).join('\n'),
          '        break;',
        );
      }
      strs.push('      default:');
      strs.push('        console.error(`${key} is not exists`);');
      strs.push('        helpMethod();');
      strs.push('        break;');
      strs.push('',
        '    }',
        '  } else {',
        '    helpMethod(key);',
        '  }',
        '};',
      );
      return strs.join('\n');
    }
    
    function renderHelpMethod() {
      const strs = [];
      strs.push(`const helpMethodAssist = ${JSON.stringify(helpFuncMap)};`,
        'function helpMethod(key) {',
        '  if(key && helpMethodAssist[key]) {',
        `     console.error(\`\${key} 会更改: [\${helpMethodAssist[key].changeConfig.join(', ')}], 被级连更改: [\${helpMethodAssist[key].depBy.join(', ')}]\`)`,
        '  } else {',
        `    console.error('使用 changeRoute() 查看可以更改的参数');`,
        `    console.error('使用 changeRoute("参数") 查看具体改变');`,
        `    console.error('使用 changeRoute("参数", "参数值") 更改参数');`,
        `    console.error(\`可以更改的配置: [\${Object.keys(helpMethodAssist).join(', ')}]\`);`,
        '  }',
        '}');
      return strs.join('\n');
    }
    
    function renderExportsConfig() {
      return ['export {', exportConfig.join(',\n  '), '};'].join('\n');
    }
    
    const configData = [
      '/* eslint-disable no-shadow,camelcase,import/no-mutable-exports,no-console */',
      '// TODO: 自动生成的 src/utils/config 禁用了部分 eslint, 请查看 scripts/genConfig.js',
      prefix,
      'const routeMap = JSON.parse(process.env.routeMap || \'{}\');',
      // `const routeMapVar = ${JSON.stringify(routeMapVar)};`,
      '// #region initConfig',
      initLang.join('\n'),
      '// #endregion',
      '',
      '// #region changeConfig Funcs',
      configChangeFunc.join('\n'),
      '// #endregion',
      '',
      '// #region changeRoute',
      renderChangeRouteSwitch(),
      '// #endregion',
      '',
      '// #region helpMethod',
      renderHelpMethod(),
      '// #endregion',
      '',
      '// #regioin exportsConfig',
      renderExportsConfig(),
      '// #endregion',
      suffix,
    ];
    
    require('fs').writeFileSync(
      require('path').resolve(__dirname, '../src/utils/config.js'),
      configData.join('\n'),
    );
    
    
    function formatString(format, ...args) {
      let argIndex = 0;
      return format.replace(/%(\d*)s/g, (testStr, match/* , start, all */) => {
        const padNum = +match;
        let ret;
        if (!padNum) {
          ret = args[argIndex]
        } else {
          ret = ("" + args[argIndex]).padEnd(padNum, ' ');
        }
        argIndex++;
        return ret;
      });
    }
    

    生成的 src/utils/config.js 文件

    /* eslint-disable no-shadow,camelcase,import/no-mutable-exports,no-console */
    // TODO: 自动生成的 src/utils/config 禁用了部分 eslint, 请查看 scripts/genConfig.js
    /**
     * 不要直接修改这个文件, 请修改 config/apiConfig 文件
     * Config - 全局统一配置
     * @date: 2018-6-20
     * @author: niujiaqing <njq.niu@hand-china.com>
     * @version: 0.0.1
     * @copyright Copyright (c) 2018, Hand
     */
    
    const routeMap = JSON.parse(process.env.routeMap || '{}');
    // #region initConfig
    let VERSION = 'v1';
    let HZERO_HXXX = routeMap['/hxxx'] || '/hxxx';
    const CLIENT_ID = `${process.env.CLIENT_ID}`;
    let AUTH_LOGOUT_URL = changeAUTH_LOGOUT_URL(HZERO_HXXX);
    // #endregion
    
    // #region changeConfig Funcs
    function changeAUTH_LOGOUT_URL(HZERO_HXXX) {
      return `${HZERO_HXXX}/logout`;
    }
    // #endregion
    
    // #region changeRoute
    window.srmChangeRoute = function changeRoute(key, value) {
      if (key && value) {
        switch (key) {
          case 'VERSION':
            VERSION = value;
            break;
          case 'HZERO_HXXX':
            HZERO_HXXX = value;
            AUTH_LOGOUT_URL = changeAUTH_LOGOUT_URL(value);
            break;
          case 'AUTH_LOGOUT_URL':
            AUTH_LOGOUT_URL = value;
            break;
          default:
            console.error(`${key} is not exists`);
            helpMethod();
            break;
        }
      } else {
        helpMethod(key);
      }
    };
    // #endregion
    
    // #region helpMethod
    const helpMethodAssist = {
      VERSION: { changeConfig: ['VERSION'], depBy: [] },
      HZERO_HXXX: { changeConfig: ['HZERO_HXXX', 'AUTH_LOGOUT_URL'], depBy: [] },
      AUTH_LOGOUT_URL: { changeConfig: ['AUTH_LOGOUT_URL'], depBy: ['AUTH_LOGOUT_URL'] },
    };
    function helpMethod(key) {
      if (key && helpMethodAssist[key]) {
        console.error(
          `${key} 会更改: [${helpMethodAssist[key].changeConfig.join(
            ', '
          )}], 被级连更改: [${helpMethodAssist[key].depBy.join(', ')}]`
        );
      } else {
        console.error('使用 changeRoute() 查看可以更改的参数');
        console.error('使用 changeRoute("参数") 查看具体改变');
        console.error('使用 changeRoute("参数", "参数值") 更改参数');
        console.error(`可以更改的配置: [${Object.keys(helpMethodAssist).join(', ')}]`);
      }
    }
    // #endregion
    
    // #regioin exportsConfig
    export { VERSION, HZERO_HXXX, CLIENT_ID, AUTH_LOGOUT_URL };
    // #endregion