写一个脚手架吗

268 阅读4分钟

啥是脚手架啊

百度解释:脚手架是为了保证各施工过程顺利进行而搭设的工作平台,它长这样... c995d143ad4bd1138349792958afa40f4bfb0521.jpg

那么我们在开发中说的脚手架又是啥呢?通常来说,编程中的脚手架指的是能帮助开发人员快速搭建项目、文件框架的工具,再简单点就是复制模板生成文件,当然脚手架可以做的不止是“复制”这么简单,今儿咱们只说说这个最常用的。

开干!!!

初始化项目

在命令行一次执行如下命令

mkdir my-cli
cd my-cli
npm init
npm link

mkdir my-cli创建my-cli文件夹,在该文件夹下(cd my-cli切换到my-cli文件)执行npm init初始化项目,npm link生成package-lock.json文件。

在根目录创建脚手架入口文件 cli.js,并配置在packgage.json

package.png

接下来,开始写咱们的第一个脚手架吧。 cli.js

#!/usr/bin/env node
console.log("This is my-cli!");

#!/usr/bin/env node必须顶格书写,它的作用是告诉操作系统执行该脚本的时候去哪里找node解释器。

在当前目录执行my-cli测试一下:

1632309233(1).png

OK,飞起来了!

使用模板生成文件

现在我们的脚手架已经飞起来了,接下来让它拥有“复制”功能。逻辑很简单,就是先读取文件A的内容,再创建一个文件B,将A的内容写到B。

首先,创建templates文件夹来放置模板文件,然后创建模板文件index.vue:

/**
 * templates\index.vue
 *
 */
 <template>
  <div>
    <section class="search-area">
      这里是搜索区
    </section>
    <section class="table-area">
      这里是表格区
    </section>
  </div>
</template>

<script type="text/javascript">
  export default {
    name: "template",
    data(){
      return {
        //查询数据
        searchData: {},
        //表格数据
        tableData: []
      }
    },
    created(){
      this.getDataList();
    },
    methods: {
      //获取数据
      getDataList(){},
      //添加数据
      addData(){},
      //编辑数据
      editData(){},
      //删除数据
      deleteData(){}
    }
  }
</script>

<style lang="scss">	
</style>

修改cli.js:

#!/usr/bin/env node

const fs = require("fs"); // Node.js的文件处理模块
const path = require("path"); // Node.js的处理文件和目录路径处理模块

const templatePath = path.join(__dirname, "templates/index.vue"); // 获取templates绝对路径
const currentPath = process.cwd(); // process.cwd方法返回Node.js进程的当前工作目录
const writePath = path.join(currentPath, "index.vue"); // 文件的写入路径

// readFileSync 读取文件内容
fs.readFile(templatePath, {}, function (err, fileContent) {
  if(err) throw err;
  // writeFileSync 写文件内容
  fs.writeFileSync(writePath, fileContent);
});

在当前目录执行my-cli测试一下:

image.png

my-cli文件夹下有了我们复制的模板文件。

commander 接收命令行指令参数

现在我们的文件“复制”成功了,但是我们不想让它叫index.vue,想自定义“复制”文件的名称,又该咋个办呢?

这时候就该我们的命令行交互工具 commander 出场啦!

npm install commander安装commander,修改cli.js:

#!/usr/bin/env node

const fs = require("fs"); // Node.js的文件处理模块
const path = require("path"); // Node.js的处理文件和目录路径处理模块
const { program } = require("commander");

/**
 * command 设置命令
 * create为自定义的命令名称
 * <> 包裹的name为必须输入的参数,不输入会报错
 * action为命令callback,参数为commond中配置的参数
 *  
 **/
program
  .command("create <name>")
  .action((name) => {
    createFileByTemplate(name);
  });

/**
 * parse 处理命令行参数
 * process.argv 返回命令行参数,process是Node.js进程控制模块
 **/
program.parse(process.argv);

function createFileByTemplate(name){
  const templatePath = path.join(__dirname, "templates/index.vue");
  const currentPath = process.cwd();
  const writePath = path.join(currentPath, `${name}.vue`); // 使用name参数设置文件的写入路径

  // readFileSync 读取文件内容
  fs.readFile(templatePath, {}, function (err, fileContent) {
    if(err) throw err;
    // writeFileSync 写文件内容
    fs.writeFileSync(writePath, fileContent);
  });
}

在命令行执行my-cli create xx 测试一下:

image.png

可以看到,不输入name的时候是会报错的,因为它是必要参数。

image.png

inquirer 命令行交互

命令参数接收到了,那能不能接收用户实时输入,跟用户互动一下呢?命令行交互工具inquirer这不就来了吗。

老规矩npm install inquirer安装,在cli.js中引入使用 const inquirer = require("inquire");,修改cli.js:

function createFileByTemplate(name){
  const templatePath = path.join(__dirname, "templates/index.vue");
  const currentPath = process.cwd();

  const context = { name };
  prompt(templatePath, currentPath, context);
}

function prompt(readPath, writePath, context){
  // 使用inquirer进行交互
  inquirer.prompt({
    type: "input", // 交互类型: 输入
    message: "confirm name: ", //提示信息
    name: "name", // 参数名称
    default: context.name // 默认值
  }).then(answer => {
    // answer 是用户输入的回答
    writePath = path.join(writePath, `${answer.name}.vue`);
    
    loadFile(readPath, writePath, answer);
  });
}

//加载文件
function loadFile(readPath, writePath, context){
  fs.readFile(readPath, context, function (err, fileContent) {
    if(err) throw err;
    fs.writeFileSync(writePath, fileContent);
  });
}

命令行执行一下:

image.png

这里会提示确认文件名称,直接回车会使用默认值,否则使用这里输入的名称:

image.png

image.png

ejs向模板传递数据

现在文件名称修改成功啦,要是我们还想修改这个组件的name属性,又要咋办叻?

当然是用ejs模板引擎啦!

npm install ejs安装ejs,在cli.js中引入使用 const ejs = require("ejs");

修改 loadFile 方法,

function loadFile(readPath, writePath, context){
  ejs.renderFile(readPath, context, (err, result) => {
    if(err) throw err;
    fs.writeFileSync(writePath, result)
  });
}

修改index.vue模板:

image.png

再次执行create命令,查看生成文件中的name属性,已经是我们自定义的名称了:

image.png

处理多个模板文件

上边我们已经完成了单个模板文件的“复制”,但是通常我们需要“复制”的不止一个文件,加入我们的模板是这样的呢:

image.png

这时候我们就需要遍历该目录下的文件,逐个复制了,修改cli.js:

function createFileByTemplate(name){
  const templatePath = path.join(__dirname, "templates");
  const currentPath = process.cwd();

  const context = { name };
  prompt(templatePath, currentPath, context);
}

function prompt(readPath, writePath, context){
  // 使用inquirer进行交互
  inquirer.prompt({
    type: "input", // 交互类型: 输入
    message: "confirm name: ", //提示信息
    name: "name", // 参数名称
    default: context.name // 默认值
  }).then(answer => {
    // answer 是用户输入的回答
    writePath = path.join(writePath, answer.name);
    loadDirectory(readPath, writePath, answer);
  });
}

// 加载目录
function loadDirectory(readPath, writePath, context = {}){
  writePath = createDirectory(writePath);

  // readdir 读取readPath目录内容,files为目录下的文件列表
  fs.readdir(readPath, (err, files) => {
    if(err) throw err;

    //遍历列表
    files.forEach(file => {
      const _readPath = path.join(readPath, file); // 待“复制”文件的绝对路径
      const _writePath =  path.join(writePath, file); // 待写入文件的绝对路径

      // stat 获取文件对象描述
      fs.stat(_readPath, (err, stats) => {
        if(err) throw err;
        if(stats.isDirectory()){
          // 目录
          loadDirectory(_readPath, _writePath, context);
        } else if(stats.isFile()){
          //文件
          loadFile(_readPath, _writePath, context);
        }
      });
    });
  });
}

// 创建文件(夹)
function createDirectory(directory){
  //如果文件(夹)不存在,则新建
  if (!fs.existsSync(directory)) {
    //创建文件(夹)
    fs.mkdir(directory, (error,result) => {
      if(error) throw error;
    });
  }
  return directory;
}

再次执行my-cli create user:

image.png

我们又成功啦!!!

发布到npm

所谓好东西要大家一起分享嘛,快把你的脚手架分享给小伙伴们一起摇起来~~~ 根目录执行:

npm adduser
npm publish

然后再安装npm install my-cli -g,成功后查看npm文件夹:

image.png

然后咱就可以在全局使用my-cli啦!