closertb / closertb.github.io

浏览issue 或 我的网站,即可查看我的所有博客
https://closertb.site
32 stars 0 forks source link

前端脚手架,听起来玄乎,实际呢? #27

Open closertb opened 4 years ago

closertb commented 4 years ago

写于:2018-11-06

开篇

第一次听说脚手架, 是我刚接触Vue,跟着网上大佬的文章,用Vue-cli从0搭建了一个Vue项目,一步一步配置,然后npm i, npm run dev,打开链接,一个网页就这么写好了,当时对于npm,webpack这些前端工程化一无所知,嘴里不自觉的吐出了两个字:'NB'。一年以后,一位新猿在Segmentfault上发出了这样的提问

image

然后我就装着知道的样子就去回答了一下,答案是这样的:

image

粗狂的讲,这样的回答,好像没什么毛病。但既然是本着学习的态度,那这次就好好的讲一讲前端脚手架存在的意义,到底是个什么鬼,以及写一个脚手架到底有多难?所以接下来,文章将围绕下面几部分来讨论:

image

所以我们知道,create-react-app主要解析命令,执行文件的操作,react-scripts主要提供模板与模板所需要的项目工程化配置,如上图左侧所示,我们能看到其包含了webpack与jest测试的相关配置文件。 而vue-cli的实现与create-react-app稍微有点不一样。首先vue-cli新建工程是那种一步一步问答式命令来进行个性化定制的,而后者是一键搞定的;vue-cli的项目模板来源于github,支持多种模板(可通过vue list查看),通过git下载的,具体模板配置参照这里,而后者模板来自于react-scripts目录下的两个文件夹(上图中的template与template-typescript);vue-cli构建的项目其构建编译是直接依赖于browserify或webpack这样的构建工具,配置是完全暴露给使用者的,可以再次自定义,而后者是基于webpack进行了一层封装,然后将其暴露为一种新的构建命令,当然也可以运行npm run eject将配置暴露出来。dva是一个比较成熟的react解决方案,比较适合中后台系统,dva-cli与前两者具有较强的相似性,采用roadhog作为其构建编译工具,开发人员,无须关心构建配置,只需关心业务代码的实现(所以大厂的码农自己不注意的话容易发展成码畜)。其还有一个扩展功能,就是项目生成后,可以采用命令添加一个页面,这样确实也能减少一部分的ctrl + c, ctrl + v。 综上,前端脚手架的实质包含两项,命令式的构建项目(解析命令,拷贝项目到本地),提供项目的配置(构建,编译,代码规范检查)。

写一个属于自己的脚手架有多难

整理思路

从上两节的描述,大致整理一下即将要实现的功能:

  1. 命令的解析,这个可以借助commander实现;
  2. 文件的操作,复制,粘贴,增加,删除,文件内容的新增,替换;这个可以借助fs-extra实现;
  3. 模板文件,就以自己对前端工程化粗浅的认识,写一个最牛(cu)逼(lou)的模板项目;
  4. 申请一个npm账号,这个不算实现的功能,算附属工作; 以上,感觉是不是实现特简单。不是感觉,是确实很简单。流程如下:

image

实现代码

命令解析:

// 四种模板。对应我git仓库四个仓库地址
const tempIndex = {
  react: 'reactTemplate', // react 模板
  vue: 'vueTemplate', // vue 模板
  h5: 'h5Template', // h5模板
  dva: 'dvaTemplate', // dva模板
};

let projectName;  // 存储目录
let templateName; // 模板名称
let inputIndex; // 除了拷贝模板,还支持自定义模板路径下载,但感觉有点多此一举
const program = new commander.Command(packageJson.name)
  .version('v' + packageJson.version, '-v, --version')
  .arguments('<templateName>')
  .arguments('<projectName>')
  .option('-f, --force', 'force delete the exist director')
  .option('-d, --directly', 'copy the not specified template')
  .alias('cp')
  .description('create-doddle react myProject')
  .action(function (index,name) {
    inputIndex = index;
    // 允许目标项目名和要复制的模板类型名顺序颠倒
    if (tempIndex[index] || tempIndex[name]) {
      if (tempIndex[index]) {
        templateName = tempIndex[index];
        projectName = name;
      } else {
        templateName = tempIndex[name];
        projectName = index;
      }
    }
    if (program.directly) {
      templateName = index;
    }
  });
program.parse(process.argv)
// 没有输入任何参数,报语法错误,并打印help
if (program.args.length === 0) {
  console.log(chalk.red('syntax error'));
  program.help()
}

if (templateName) {
  excute(templateName, projectName, program.force);
} else {
  console.log(`the template ${inputIndex} you want download do not exist`);
}  

文件拷贝:

async function create(temp, project, force = false) {
  tempName = temp;
  projectName = project;
  forceDel = force;
  const file = currentPath + projectName;
  try {
    // 检测项目文件夹是否已存在, 若存在,抛出错误
    const res = await fs.pathExists(file);
    if (res) {
      if (forceDel) {
        console.log(green('force remove the exist directory'));
        await fs.remove(file);
        downloadByGit(renameFile, tempName);
      } else {
        // 抛出错误,并提示可以使用-f参数来强制删除已存在的项目
        console.log(chalk.red('Error, In this directory, the project name already exsits !'));
        console.log(chalk.green('you can use option -f to force delete the directory !'));
      }
      return;
    }
    // 若不存在,直接从git下载
    downloadByGit(renameFile, tempName);
  } catch (err) {
    console.error(red(err));
  }
}

关于chalk,这是一款颜色标记插件,将要打印的文字用不同的颜色标记出来,像下面这样:

image

git文件下载:

function downloadByGit(callback, template) {
  console.log(green('start download'));
  console.log(`git@github.com:closertb/${template}.git`);
  const result = spawn(
    'git',
    ['clone', `git@github.com:closertb/${template}.git`],
    { stdio: 'inherit' }
  );
  const error = result.error;
  if (error) {
    console.log(red(error));
    return;
  }
  // 定义回调;
  callback && callback();
}

主要就这三段代码,就实现了命令的解析,和从git源端拷贝模板到本地。

创建可执行命令

稍微对前端工程化了解的就知道,对于项目,想创建npm run start或npm run dev这样的可执行命令,只需要在package.json的scripts进行定义。而想要创建vue init webpack myapp或dva init myapp这样的命令又怎样做呢?广告之后,马上揭晓(自娱自乐中,请忽视O(∩~∩)O!) 第一步:在你项目的package.json中填上一个bin属性,表明它是可执行的, 并配置好可执行命令和入口文件

image

第二步: 在你的入口文件(我这里是根目录的index.js)首行加上一段代码: #!/usr/bin/env node,告诉操作系统执行这个脚本的时候,调用/usr/bin下的node解释器;

第三步:登录你的npm账号,运行npm publish发布你的npm包,参考过的链接 到此,一个简易的脚手架就写好了。

最后

人老了,总喜欢最后唠叨两句,其实不论vue-cli,还是create-react-app,或则dva,其核心功能(最牛逼的)不是命令的解析或者模板的拷贝,这部分只占了它脚手架很小的部分,看起来多,是因为它做了很多兼容,比如帮助、错误检测、系统网络环境检测、回滚这些操作,但这也不是核心。个人觉得核心还是是react-scripts或则roadhog这些构建编译脚本,但归根接地还是得对babel和webpack这些库的深入理解,送给观看这篇文章到这里的你,也勉励一下自己,继续加油。

项目源码地址: create-doddle
npm包地址:create-doddle
首发地址:Denzel Blog