likes
comments
collection
share

Node自定义脚手架

作者站长头像
站长
· 阅读数 49

自定义脚手架

创建自定义全局命令

  • 新建一个文件夹(项目)并创建一个js文件(全局命令文件)

  • 在项目根目录下初始化项目

    • npm init
      
  • 在项目根目录下(将项目挂载在全局命令行中)

    • npm link
      
  • 在js文件首行输入(解决了不同的用户node路径不同的问题,可以让系统动态的去查找node来执行你的脚本文件)

    #!/usr/bin/env node
    

命令参数接收处理

  • 方法一:process.argv

  • 方法二:commander包——命令行参数处理工具

    • 使用

      const program = require('commander')
      // or  const {program} = require('commander')
      // 查看版本号
      program.version(require('./package.json').version);
      ​
      program.parse(process.argv)
      
    • 命令行测试

      > mycli --help
      ​
      Usage: index [options]
      ​
      Options:
        -h, --help  display help for command
      
    • 添加选项

      const program = require('commander')
      ​
      program.option('-f --framwork <framwork>', 'select framwork')
      // -f简写,--framwork全称  <framwork>表示参数后必须跟一个选项  select framwork简介
      program.parse(process.argv)
      
    • 处理自定义指令选项

      const program = require('commander')
      ​
      program
          .command('create <project> [other...]') //自定义命令
          .alisa('crt')// 简写
          .description('clone a repository into a folder')
          .action((project,argv)={
              //命令行逻辑代码
          });
      

终端交互

  • inquirer包

    import inquirer from 'inquirer';
    ​
    inquirer
      .prompt([
        /* Pass your questions in here */
        {
            type:'list',//问题的类型:input, number, confirm, list, rawlist, expand, checkbox, password, editor
            name:'framwork',// 问题的答案将以name的值为key作为对象返回
            choices:['koa','express','egg'],//list选项
            message:"请选择",
            
        },
        {...}
      ])
      .then((answers) => {
        // Use user feedback for... whatever!!
        // 例:{framwork:'koa'}
      })
      .catch((error) => {
        if (error.isTtyError) {
          // Prompt couldn't be rendered in the current environment
        } else {
          // Something else went wrong
        }
      });
    

下载远程项目代码

  • download-git-repo包

    const download = require('download-git-repo')
      download('direct:' + url, target, { clone: true }, (err) => {
       console.log(err ? 'Error' : 'Success')
      })
    // url 远程代码地址   target 下载到的目标目录
  • 下载等待提示交互

    • ora 包

      • node中使用CommonJS模块化,不支持ES Module。无特殊必要可使用ora@5ora@6以后为ES Module方式引入该模块
      const ora = require('ora')
      ​
      //const spinner = ora('Loading unicorns').start();
      const spinner = ora().start()
      spinner.text = '代码正在下载……'
      ​
      setTimeout(() => {
        //spinner.color = 'yellow';
        //spinner.text = 'Loading rainbows';
        spinner.succeed('代码下载成功')
        //spinner.fail('代码下载失败')
      }, 1000);
      
  • 示例代码

    const download = require('download-git-repo')
    const ora = require('ora')
    const chalk = require('chalk')
    const downloadFun = function (url, project) {
      const spinner = ora().start()
      spinner.text = '代码正在下载……'
      download('direct:' + url, project, { clone: true }, (err) => {
        if (!err) {
          spinner.succeed('代码下载成功')
          console.log(chalk.blue.bold('Done!'), chalk.bold('you run:'));
          console.log('cd ' + project);
          console.log('npm install ');
          console.log('npm run dev ');
        } else {
          spinner.fail('代码下载失败')
        }
      })
    }
    

项目初始化完成提示

  • chalk 包(改变命令行字体样式)

    • chalk@5之后的版本使用ES Module
    const chalk = require('chalk')
    ​
    console.log(chalk.blue.bold('Done!'), chalk.bold('you run:'));
    

补充

将callback转化成promise/(async await)

const { promisify } = require('util')//node自带工具包
// callback -> promisify(函数) -> Promise -> async await
const download = promisify(require('download-git-repo'))
const { codeUrl } = require('../config/repo-config');
​
const createProjectAction = async (project) => {
// download('direct:' + url, project, { clone: true }, (err) => {
//   if (!err) {
//     spinner.succeed('代码下载成功')
//     console.log(chalk.blue.bold('Done!'), chalk.bold('you run:'));
//     console.log('cd ' + project);
//     console.log('npm install ');
//     console.log('npm run dev ');
//   } else {
//     spinner.fail('代码下载失败')
//   }
// })
  await download(codeUrl, project, { clone: true });
}
​

执行终端命令开启子进程

const { spawn } = require('child_process');// 开启子进程// npm install 本质上会开启一个子进程
//spawn(command,args,options)
const commandSpawn = (...args) => {
  return new Promise((resolve, reject) => {
    const childProcess = spawn(...args);//...args —— es6参数解构
    // childPromise进程中会有很多执行命令过程中的打印信息
    childProcess.stdout.pipe(process.stdout);// 显示打印信息——将当前进程的输出流放到ChildProcess
    childProcess.stderr.pipe(process.stderr);// 显示错误信息
    // 监听进程是否关闭
    childProcess.on("close", () => {
      resolve();
    })
  }).catch((error => {
    console.error(error)
  }))
}
​
​
module.exports = {
  commandSpawn
}

下载远程代码(升级)

  • 下载远程代码后安装依赖并运行项目
const { codeUrl } = require('../config/repo-config');
const createProjectAction = async (project) => {
​
  // 1.clone项目
  await download(codeUrl, project, { clone: true });
​
  // 2.执行npm install
    // command:要执行的命令;[]:命令执行的参数;cwd:子进程工作目录
    // 在window系统中执行npm的本质是执行npm.cmd
  const command = process.platform === 'win32' ? 'npm.cmd' : 'npm';
  await commandSpawn(command, ['install'], { cwd: `./${project}` })
​
  // 3.运行npm run serve
  commandSpawn(command, ['run', 'serve'], { cwd: `./${project}` });
​
  // 4.打开浏览器
  // open("http://localhost:8080/");
}

创建模板文件

  • .ejs→.vue

  • ejs 包

  • 步骤

    • 对应的ejs模块
    • 编译ejs模块得到一个result
    • 将result写入到.vue文件中
    • 将文件放到对应的文件夹中
  • 对应的ejs模块

    // vue-components.ejs
    <template>
      <div class="<%= data.lowerName %>">
        <h1>{{ msg }}</h1>
      </div>
    </template>
    ​
    <script>
      export default {
        name: '<%= data.name %>',
        props: {
          msg: String
        },
        components: {
        },
        mixins: [],
        data: function () {
          return {
            message: "<%= data.name %>"
          }
        },
        created: function () {
    ​
        },
        mounted: function () {
    ​
        },
        computed: {
    ​
        },
        methods: {
    ​
        }
      }
    </script>
    ​
    <style scoped>
      .<%=data.lowerName %> {}
    </style>
    ​
    // vue-router.ejs
    // 普通加载路由
    // import <%= data.name %> from './<%= data.name %>.vue'
        // 懒加载路由
    ​
        const <%= data.name %> = () => import('./<%= data.name %>.vue')
            export default {
            path: '/<%= data.lowerName %>',
              name: '<%= data.name %>',
                component: <%= data.name %>,
                  children: [
                  ]
                  }
    
  • 编译ejs模块得到一个result

    const path = require('path');
    const ejs = require('ejs');
    ​
    const compile = (templateName, data) => {
      const templatePosition = `../templates/${templateName}`
      const templatePath = path.resolve(__dirname, templatePosition)
      return new Promise((resolve, reject) => {
        ejs.renderFile(templatePath, { data }, {}, (err, result) => {
          if (err) {
            console.log(err);
            reject(err)
            return
          }
          resolve(result)
        })
      })
    }
    ​
    
  • 将文件放到对应的文件夹中

    const path = require('path');
    const fs = require('fs');
    ​
    const writeToFile = (path, content) => {
      return fs.promises.writeFile(path, content)
    }
    // 创建路径文件夹
    // source/components/category
    const createDirSync = (dirPath) => {
      if (fs.existsSync(dirPath)) {
        return true
      } else {
        if (createDirSync(path.dirname(dirPath))) {
          fs.mkdirSync(dirPath)
          return true
        }
      }
    }
    
  • 创建模板文件action

    const { compile, writeToFile, createDirSync } = require('../utils/utils')
    ​
    const addPageAndRouteAction = async (name, dest) => {
      // 1.编译ejs模板 result
      const data = { name, lowerName: name.toLowerCase() }
      const page = await compile("vue-components.ejs", data)
      const router = await compile("vue-router.ejs", data)
      // 2.写入文件的操作
      const targetPath = path.resolve(dest, name.toLowerCase())
      if (createDirSync(targetPath)) {
        const pagePath = path.resolve(targetPath, `${name}.vue`)
        const routerPath = path.resolve(targetPath, `router.js`)
        writeToFile(pagePath, page)
        writeToFile(routerPath, router)
      }
    }