前端脚手架如何搭建

分类:前端来源:站内 最近更新:2020-09-23 17:28:56浏览:77留言:0

    现在前端开发越来越把重心放到业务层上,有效的缩短了研发周期,找个开箱即用的脚手架模板项目,直接开搞。比如vue-cli,creat-react-app,umi等等。如果我们自己想要搭建一个自己的脚手架,该如何做。

    首先得分析一下脚手架做了什么?脚手架主要有两个部分组成,初始化命令模块和模板项目。以vue-cli为例,脚手架包含了询问的能力,比如目录的名称,项目的描述,是否需要css预处理器,路由组件,单元测试模块等等。其次,脚手架还包含了复制下载模板项目的能力,大部分的脚手架都会把模板放到github上或者脚手架自己的目录结构里,不要以为一个空白的目录里多出的文件是写进去的,没那么复杂,大部分都是先搭建一个独立的项目作为之后复制的模板。脚手架只是替换模板里的变量,然后复制模板到项目的路径里。最后,有的脚手架还带了git初始化和安装依赖的能力。

    所以,脚手架一般的步骤可分为:询问==》下载模板==》复制并写入模板==》git初始化和安装依赖。

    初始化能力

    安装脚手架的能力,我们先安装以下几个重要的依赖

  • commander  //完整的node.js命令行解决方案,帮您通过shell里打命令的,

  • inquirer  //node.js交互式命令行,处理一些询问的功能

  • download-git-repo //负责从git下载仓库到本地文件夹

  • mem-fs-editor //负责编辑模板里变量

    其实把这几个主要的依赖包API文档看下来,就基本知道我们改如何下手单间初始化工具了。

    除了上述主要依赖包,还有其他辅助依赖

const chalk = require('chalk');   // 带颜色的命令提示
const ora = require('ora');   // 提供交互spinner,就像VUE-ClI中的各种小图标


第一步:建立相关目录,和文件

├── bin   //脚手架启动目录,
|   ├── index //入口的启动文件,和package.json总的main值对应
├── src       
|   ├── project.js  //脚手架构造函数     
|   └── utils.js //辅助类工具 
└── package.json


第二步:编写入口文件bin/index

#! /usr/bin/env node
const program = require('commander');
const fse = require('fs-extra');
const inquirer = require('inquirer');
const Project = require('../src/project');
const {getPackageVersion} = require('../src/utils');
const creactProject = (projectName) => {
  const project = new Project({projectName});
  project.create();
};

function setProjectName(msg) {
  let prompts = [];
  prompts.push({
    type: 'input',
    name: 'projectName',
    message: msg,
    validate(value) {
      if (!value) {
        return '项目名不能为空';
      }
      if (fse.existsSync(value)) {
        return '当前目录已存在同名项目,请更换项目名';
      }
      return true;
    }
  });
  inquirer.prompt(prompts).then(answer => {
    creactProject(answer.projectName);
  });
}

program
  .version(getPackageVersion(), '-v, --version', '当前版本')
  .command('init [dirname]')
  .description('创建新引用')
  .action(dirname => {
    if (dirname && fse.existsSync(dirname)) {
      setProjectName('当前目录已存在同名项目,请更换项目名')
    } else if (dirname) {
      creactProject(dirname);
    } else {
      let prompts = [
        {
          type: 'confirm',
          name: 'empty',
          message: '确定在当前目录下建立项目吗?',
          default: false
        }
      ];
      inquirer.prompt(prompts).then(answer => {
        if (answer.empty) {//如果是当前目录
          creactProject(dirname);
        } else {
          setProjectName('请输入目录名称')
        }
      });
    }
  });
program.parse(process.argv);


第三步:在/src/project.js编写脚手架构造函数,添加初始化方法属性,和渲染属性

/* 项目初始化 */
/* ================================================== */
project.prototype.create = function () {
  let __this = this;
  let questions = [
    {
      type: 'input',
      name: 'appName',
      message: "您的项目名称是什么?",
      validate: function (value) {
        let pass = value.match(/^[a-zA-Z_\-]+$/);
        if (pass) {
          return true;
        }
        return '项目名称最好是英文,下划线或者-';
      },
      default: 'App'
    },
    //…………
  ];
  inquirer.prompt(questions).then(answers => {
    __this.options = Object.assign(__this.options, answers);
    __this.generate();
  });
};
/* 渲染项目 */
/* ================================================== */
project.prototype.generate = function () {
  const __this = this;
  const projectPath = path.join(process.cwd(), this.options.projectName);//新项目绝对路径
  const downloadPath = path.join(projectPath, '__download__');//模板copy临时目录
  const downloadSpinner = ora('正在下载模板,请稍等...');
  downloadSpinner.start();
  /*开始下载模板*/
  const gitRepositories = 'direct:https://xxxx.git';//模板项目仓库地址
  download(gitRepositories, downloadPath, {clone: true}, (err) => {
    if (err) {
      downloadSpinner.color = 'red';
      downloadSpinner.fail(err.message);
      return;
    }
    downloadSpinner.color = 'green';
    downloadSpinner.succeed('下载模板成功');
    const filesSpinner = ora('开始创建项目文件,请稍等...');
    const copyFiles = getDirFileName(downloadPath);
    const injectFiles = ['package.json', 'app.config.js'];

    /* 复制文件 */
    copyFiles.forEach((file) => {
      fse.copySync(path.join(downloadPath, file), path.join(projectPath, file));
    });

    /* 写入变量 */
    injectFiles.forEach((file) => {
      this.memFsEditor.copyTpl(path.join(downloadPath, file), path.join(this.options.projectName, file), {
        appName: __this.options.appName,
        description: __this.options.description
      });
    });

    /* 变量写入完成后 */
    this.memFsEditor.commit(() => {
      fse.remove(downloadPath);//移除临时文件
      process.chdir(projectPath);//cd 到项目文件夹目录
      downloadSpinner.color = 'green';
      downloadSpinner.succeed('项目创建完成');
      //…………
    });

  })
};

第4步:自己写好模板项目,把你要改变的变量修改一下成<%= 变量名称 %>,在调用copyTpl时,data字段中的key-value将被写入到模板中。

    如packjson

{
  "name": "<%= appName %>",
  "version": "1.0.0",
  "description": "<%= description %>",
  "main": "index.js",
  "scripts": {
    "lint": "eslint --quiet --no-inline-config  src/",
  },


发布脚手架

    编辑好自己的脚手架之后就可以发布到npm上,供大家使用了。

npm publish

    关于如何在npm发布依赖包之前有介绍过,可以点击这里


    查看完整的脚手架代码,可以查看创蓝脚手架:https://github.com/chanlan253/cl253-cli


1

发表评论

评论列表(0)

  • 暂时没有留言
热门