首页
学习
活动
专区
圈层
工具
发布
50 篇文章
1
【架构师(第一篇)】整体需求分析和架构设计
2
【架构师(第二篇)】脚手架架构设计和框架搭建
3
【架构师(第三篇)】脚手架开发之掌握Lerna操作流程
4
【架构师(第四篇)】脚手架开发之Lerna源码分析
5
【架构师(第五篇)】脚手架之import-local执行流程及简历设计
6
【架构师(第六篇)】脚手架之需求分析和架构设计
7
【架构师(第七篇)】脚手架之准备阶段编写
8
【架构师(第八篇)】脚手架之 commander 框架使用方法
9
【架构师(第九篇)】如何让 Node 环境支持 ES Module
10
【架构师(第十篇)】脚手架之注册命令及架构优化
11
【架构师(第十一篇)】脚手架之命令注册和执行过程开发
12
【架构师(第十二篇)】脚手架之命令行交互工具 inquirer.js 使用方法
13
【架构师(第十三篇)】脚手架之创建项目准备阶段开发
14
【架构师(第十四篇)】脚手架之 egg.js 和 mongodb 的使用
15
【架构师(第十五篇)】脚手架之创建项目模板开发
16
【架构师(第十六篇)】脚手架之创建项目模板的下载与更新
17
【架构师(第十七篇)】脚手架之 ejs 和 glob 的使用
18
【架构师(第十八篇)】脚手架之项目模板的安装
19
【架构师(第十九篇)】脚手架之组件库模板开发
20
【架构师(第二十篇)】脚手架之自定义模板及第一阶段总结
21
【架构师(第二十一篇)】编辑器开发之需求分析和架构设计
22
【架构师(第二十二篇)】编辑器开发之项目整体搭建
23
【架构师(第二十三篇)】编辑器开发之画布区域组件的渲染
24
【架构师(第二十四篇)】编辑器开发之添加模版到画布
25
【架构师(第二十五篇)】编辑器开发之属性编辑区域表单渲染
26
【架构师(第二十六篇)】编辑器开发之属性编辑同步渲染
27
【架构师(第二十七篇)】前端单元测试框架 Jest 基础知识入门
28
【架构师(第二十八篇)】 测试工具 Vue-Test-Utils 基础语法
29
【架构师(第二十九篇)】Vue-Test-Utils 触发事件和异步请求
30
【架构师(第三十篇)】Vue-Test-Utils 全局组件和第三方库 vuex | vue-router
31
【架构师(第三十一篇)】前端测试之 TDD 的开发方式
32
【架构师(第三十二篇)】 通用上传组件开发及测试用例
33
【架构师(第三十三篇)】 Vue 中的实例及本地图片预览
34
【架构师(第三十四篇)】 业务组件库开发之 vue3 的插件系统
35
【架构师(第三十五篇)】 业务组件库开发之使用 Rollup 进行打包
36
【架构师(第三十六篇)】 业务组件库开发之发布到 NPM
37
【架构师(第三十七篇)】 服务端开发之后端框架与数据库技术选型
38
【架构师(第三十八篇)】 服务端开发之本地安装最新版 MySQL 数据库
39
【架构师(第三十九篇)】 服务端开发之连接 MySQL 数据库
40
【架构师(第四十篇)】 服务端开发之连接 Mongodb 数据库
41
【架构师(第四十一篇)】 服务端开发之安装并连接 Redis数据库
42
【架构师(第四十二篇)】 服务端开发之常用的登录鉴权方式
43
【架构师(第四十三篇)】 服务端开发之单元测试和接口测试
44
【架构师(第四十四篇)】 服务端开发之 pm2 和 nginx 介绍
45
【架构师(第四十五篇)】 服务端开发之认识 Github actions
46
【架构师(第四十六篇)】 服务端开发之安装 Docker
47
【架构师(第四十七篇)】 服务端开发之认识 Docker
48
【架构师(第四十八篇)】 服务端开发之 Dockerfile
49
【架构师(第四十九篇)】 服务端开发之认识 Docker-compose
50
【架构师(第五十篇)】 服务端开发之自动发布到测试机
清单首页架构文章详情

【架构师(第十八篇)】脚手架之项目模板的安装


执行命令

utils\utils\lib\index.js 模块下新建两个方法

代码语言:javascript
复制
/**
 * @description: 封装一个 spawn 方法,兼容 mac 和 windows
 * windows : cp.spawn('cmd',['/c','node','-e',code],{})
 * mac : cp.spawn('node', ['-e', code],{})
 * @param {*} command 'cmd'
 * @param {*} args ['/c','node','-e',code]
 * @param {*} options {}
 * @return {*} cp.spawn(cmd, cmdArgs, options)
 */
function spawn(command, args, options = {}) {
  const cp = require('child_process');
  const win32 = process.platform === 'win32';
  const cmd = win32 ? 'cmd' : command;
  const cmdArgs = win32 ? ['/c'].concat(command, args) : args;
  return cp.spawn(cmd, cmdArgs, options);
}

/**
 * @description: 异步执行命令
 * @param {*} 参数同上
 * @return {*}
 */
function execAsync(command, args, options = {}) {
  return new Promise((resolve, reject) => {
    const p = spawn(command, args, options)
    p.on('error', e => {
      reject(e)
    })
    p.on('exit', c => {
      resolve(c)
    })
  })
}

module.exports = {
  spawn,
  execAsync
};

模板安装

commands\init\lib\index.js

在命令的执行阶段添加安装模板的方法。

代码语言:javascript
复制
/**
   * @description: 命令的执行阶段
   * @param {*}
   * @return {*}
   */
  async exec() {
    try {
      // 1.准备阶段
      const projectInfo = await this.prepare();
      if (projectInfo) {
        this.projectInfo = projectInfo;
        log.verbose('🚀🚀 ~ 项目详情', projectInfo);
        // 2.下载模板
        await this.downloadTemplate()
        // 3.安装模板
        await this.installTemplate()
      }
    } catch (error) {
      log.error(error.message);
    }
  }

安装模板,根据模板类型安装标准模板或者自定义模板。

代码语言:javascript
复制
const TEMPLATE_TYPE_NORMAL = 'normal';
const TEMPLATE_TYPE_CUSTOM = 'custom';
/**
   * @description: 安装模板
   * @param {*}
   * @return {*}
   */
  async installTemplate() {
    // 安装标准模板
    if (this.templateInfo.type === TEMPLATE_TYPE_NORMAL) {
      await this.installNormalTemplate()
    }
    // 安装自定义模板
    else if (this.templateInfo.type === TEMPLATE_TYPE_CUSTOM) {
      await this.installCustomTemplate()
    }
    // 位置类型
    else {
      throw new Error("无法解析的项目类型")
    }
  }

封装的执行命令的方法。

代码语言:javascript
复制
/**
   * @description: 执行命令
   * @param {*} cmd 命令
   * @param {*} message 执行后提示的消息
   * @return {*}
   */
  async execCommand(cmdInfo, message) {
    if (cmdInfo) {
      // npm install ==> [npm,install]
      const installCmd = cmdInfo.split(' ')
      // npm
      const cmd = checkCommand(installCmd[0])
      if (!cmd) {
        throw new Error("命令不存在")
      }
      // [install]
      const args = installCmd.slice(1)
      const installResult = await execAsync(cmd, args, {
        stdio: 'inherit',
        cwd: process.cwd()
      })
      if (installResult === 0) {
        log.warn(message)
      }
    }
  }

安装标准模板。

代码语言:javascript
复制
/**
   * @description: 安装标准模板
   * @param {*}
   * @return {*}
   */
  async installNormalTemplate() {
    const spinner = spinnerStart('模板安装中,请稍候...')
    await sleep()
    try {
      // 获取模板缓存路径
      // console.log('✅✅✅ ~ ', this.templateNpm);
      // 获取模板所在目录
      const templatePath = path.resolve(this.templateNpm.cacheFilePath, 'template')
      // 获取当前目录
      const targetPath = process.cwd()
      // 确保目录存在 不存在会创建
      fse.ensureDirSync(templatePath);
      fse.ensureDirSync(targetPath);
      // 拷贝模板到当前目录
      fse.copySync(templatePath, targetPath)
    } catch (error) {
      log.error(error.message)
    } finally {
      spinner.stop(true)
      log.warn('模板安装成功')
    }
    // ejs 模板渲染
    const ignore = ['node_modules/**', 'public/**']
    const ops = {
      ignore
    }
    await this.ejsRender(ops)
    const { installCommand, startCommand } = this.templateInfo
    // 依赖安装
    await this.execCommand(installCommand, '依赖安装成功')
    // 启动命令
    await this.execCommand(startCommand, '项目启动成功')
  }

安装自定义模板,暂时没有开发到这里。

代码语言:javascript
复制
  /**
   * @description: 安装自定义模板
   * @param {*}
   * @return {*}
   */
  async installCustomTemplate() {
    console.log('✅✅✅ ~ 安装自定义模板');
  }

检查命令,防止产生意外情况,命令必须是以下几种之一才会执行。

代码语言:javascript
复制
const WHITE_COMMAND = ['npm', 'cnpm', 'yarn']

/**
 * @description: 检查命令是否在白名单中
 * @param {*} cmd
 * @return {*}
 */
function checkCommand(cmd) {
  if (WHITE_COMMAND.includes(cmd)) {
    return cmd
  }
  return null
}

修改模板代码

使用 ejs 修改 hzw-cli-dev-template-vue3 这个模板 template 目录下的 package.json 文件

代码语言:javascript
复制
 "name": "<%= className %>",
 "version": "<%= version %>",

然后修改外层 package.json 的版本号 npm publish 发布新版本到 npm

PS: 因为发布到 npm 的版本号必须是正常的版本号,所以才需要嵌套一层,将外层作为 npm 模块,内层作为模板。

commands\init\lib\index.js

需要在 getInfo 这个方法中将 projectName 转换为链接线格式 project-name

代码语言:javascript
复制
if (info.project) {
  // kebab-case 这个库如果是大写字母开头会多出一个 - , 所以使用 replace 把第一个 - 给去掉
  info.project = require('kebab-case')(info.project).replace(/^-/, '')
}

使用 ejs 进行模板渲染

安装 ejsglob

代码语言:javascript
复制
lerna add ejs commands/init
lerna add glob commands/init

commands\init\lib\index.js

代码语言:javascript
复制
 /**
   * @description: ejs 模板渲染
   * @param {*}
   * @return {*}
   */
  ejsRender(options) {
    return new Promise((resolve, reject) => {
      // 遍历文件列表
      glob("**", {
        cwd: process.cwd(),
        nodir: true,
        ignore: options.ignore || []
      }, (err, files) => {
        if (err) {
          reject(err)
        }
        // 对文件列表使用 ejs 进行渲染
        Promise.all(files.map((file) => {
          const filePath = path.join(process.cwd(), file)
          return new Promise((resolve1, reject1) => {
            ejs.renderFile(filePath, this.projectInfo, {}, (err, res) => {
              if (err) {
                reject1(err)
              }
              // 将源文件替换成 ejs 渲染后的文件
              fse.writeFileSync(filePath, res)
              resolve1(res)
            })
          })
        }))
          .then(() => resolve(files))
          .catch((err) => reject(err))
      })
    })
  }

init 命令直接传入项目名称功能支持

代码语言:javascript
复制
/**
   * @description: 选择创建项目或者组件 获取项目的基本信息 return Object
   * @param {*}
   * @return {*} 项目的基本信息
   */
  async getInfo() {
    let info = {};
    // 选择创建项目或者组件;
    const {
      type
    } = await inquirer.prompt({
      type: 'list',
      message: '请选择初始化类型',
      name: 'type',
      default: TYPE_PROJECT,
      choices: [{
        name: '项目',
        value: TYPE_PROJECT,
      },
      {
        name: '组件',
        value: TYPE_COMPONENT,
      },
      ],
    });
    log.verbose('type', type);
    // 获取项目的基本信息;
    if (type === TYPE_COMPONENT) { }
    const isValidateName = (a) => {
      const reg =
        /^[a-zA-Z]+([-][a-zA-Z0-9]|[_][a-zA-Z0-9]|[a-zA-Z0-9])*$/;
      return reg.test(a)
    }
    // 是否在执行init 命令的时候就传入了正确的项目名称
    const isTrueName = isValidateName(this.projectName)
    console.log('🚀🚀 ~ InitCommand ~ this.projectName', this.projectName);
    console.log('🚀🚀 ~ InitCommand ~ isTrueName', isTrueName);

    const promptArr = [{
      type: 'input',
      message: '请输入项目版本号',
      name: 'version',
      default: '1.0.0',
      validate: (a) => {
        return !!semver.valid(a) || '请输入合法的版本号';
      },
      filter: (a) => {
        if (!!semver.valid(a)) {
          return semver.valid(a);
        }
        return a;
      },
    },
    {
      type: 'list',
      message: '请选择项目模板',
      name: 'template',
      default: 'vue3',
      choices: this.createTemplateChoices()
    },]
    if (type === TYPE_PROJECT) {
      const projectPrompt = {
        type: 'input',
        message: '请输入项目名称',
        name: 'project',
        validate: (a) => {
          if (isValidateName(a)) {
            return true;
          }
          return '要求英文字母开头,数字或字母结尾,字符只允许使用 - 以及 _ ';
        },
      }
      if (isTrueName) {
        info.project = this.projectName
      } else {
        promptArr.unshift(projectPrompt)
      }
      const answers = await inquirer.prompt(promptArr);
      info = {
        ...info,
        ...answers,
        type,
      };
    }

    // 将项目名称改成连接线形式
    if (info.project) {
      // kebab-case 这个库如果是大写字母开头会多出一个 - , 所以使用 replace 把第一个 - 给去掉
      info.className = require('kebab-case')(info.project).replace(/^-/, '')
    }
    return info;
  }
下一篇
举报
领券