如何写一个可爱的脚手架 (一)

本文首发于我的 Github,欢迎 issue / fxxk。css

前言

相信你们都写过vue,react或者angular的各位同窗,也必定不会对如下库陌生:html

体验过上述工具的同窗,有没有发现他们都有一个共同点——提供了一个可供快速开发的样板文件(boilerplate)。本文就将从样板文件入来进行阐述。经过本文,你将学到:前端

  1. 一个CLI工具须要解决的问题;
  2. 写一个CLI的基本思路;
  3. 写一个CLI须要作哪些准备;

PS:因为脚手架的英文 scaffolding 太长,本文我将以更可爱的 cli 来代替。vue

预热

因为篇幅有限,本节将以create-react-appvue-cli为例(真地很难说服你们我是ng起家的...),回顾其使用过程:node

首先是create-react-app, 按照README,咱们生成出一个基本项目后,打开目录,其目录结构以下:react

my-app
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public 
│   └── favicon.ico
│   └── index.html
│   └── manifest.json 
└── src
    └── App.css
    └── App.js
    └── App.test.js
    └── index.css
    └── index.js
    └── logo.svg
    └── registerServiceWorker.js
复制代码

说一下其中两个有意思的点:webpack

  • public/manifest.json: 这是PWA 的一部分,用来描述应用相关的信息。之前开发cordova的时候,这个还用来作过热更新。
  • src/registerServiceWorker.js: 安装Service Workers文件。

好久没用它,原来已经默认支持PWA了,nice, 还不会的同窗赶忙学起来,这里就不展开了。git

接着,咱们打开package.json,探一下究竟:github

{
  "name": "my-app",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "^16.2.0",
    "react-dom": "^16.2.0",
    "react-scripts": "1.1.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  }
}
复制代码

很是简洁,可是有一个react-scripts。看起来很陌生,可是若是我告诉你它的依赖中有babelwebpackwebpack-dev-serverautoprefixer这些常见的前端流氓,我想你也清楚了react-scripts到底作了什么:web

包装了 webpackwebpack-dev-server,提供一套默认的配置(内置css,file,svg等等各类loader),在 react-scripts startreact-scripts build 时直接运行这些配置。

一句话归纳,就是为了帮你简化构建时的配置(Create React apps with no build configuration.😂)。

固然,零配置并不是适用于大型定制项目的开发,react-scripts就像一个巨大的黑盒同样,总有存在一些其未提供 API 或者指令的场景,让你无从下手。react-scripts固然也不傻,还提供了一个比较fancy的指令:

  • react-scripts eject: 将全部的工具(配置文件和 package.json 依赖库)解压到应用所在的路径。

厉害了😅,来运行看看:

当全部的依赖暴露出来是,果真package.json瞬间爆炸性增加了,顺时有点怀念从前的美好了。

好了,说到这里,create-react-app 要解决的问题就是:

  1. 根据样板文件生成统一的项目骨架,让开发者快速投入开发;
  2. 预置了一个易于开发的react-scripts,让开发者省略痛苦的配置流程.(webpack配置工程师看来要失业了😇)

接下来,再回顾一下 vue-cli。截止本文书写日期, vue-cli@3.0 仍然处于 beta 阶段,所以本文将以 2.x.x 为例,咱们建立一个名为 my-vue-app,模板为 webpack-simple 的 vue 项目:

可见,vue-cli在生成项目以前多了一个很是重要的一步 —— Prompt,也就是问询,根据询问的内容最终生成你的项目。两条FYI:

  1. vue-cli 的问询功能是使用 Inquirer.js 这个库完成的。
  2. webpack-simple 源码你能够在这里找到 webpack-simple

对本节作一下结,一个脚手架一般由如下基本几部分组成:

  1. 问询(Prompts)
  2. 样板文件(Boilerplate)
  3. 生成文件(Generate)

实战

通过上一节的洗礼,你可能已经有大体的思路了,而后,让咱们以 vue-cli 为例,直接进入实战吧。

基本思路

  1. 定义一个模板包的规则,这里采用 vue-cli 的规则:template 为源文件,meta.js/meta.json 为配置入口文件。
my-first-package
├── meta.js
└── template
复制代码
  1. 解析包的meta.js,得到要询问的问题(prompts),并运行它,将最终用户的答案分配到一个上下文对象中;
  2. 读取 template 中的内容,用刚刚获取的上下文对象去渲染它。

伪代码实现

如下是一个CLI的伪代码实现:

// 10行伪代码实现一个CLI
function CLI(packageSourcePath) {
    const context = {}
    const meta = require(path.join(packageSourcePath, 'meta.js'))
    const templatePath = path.join(packageSourcePath, 'template')
    const { prompts } = meta
    return promptsRunner(prompts).then(anwsers => {
        Object.assign(context, anwsers)
        return generateFiles(templatePath, context)
    })
    .then(() =>  console.log('[OK]'))
    .error(() => console.log('[Error]'))
}
复制代码

其中,promptsRunner用来问询,generateFiles 用来渲染并生成文件。哇!原来这么简单。

技术选型

这是 vue-cli 的技术选型:

  1. 问询:Inquirer.js
  2. 命令行解析:commander.js
  3. 模板渲染:handlebars.js
  4. 文件生成:metalsmith

热衷于看这个世界的你,是否已经跃跃欲试了呢?

更多

固然,若是只是这样的一个 CLI,很显然只是玩具,你能够考虑支持如下可爱的特性:

  1. 在Context中注入默认的一些属性(如git的username)
  2. 支持文件过滤(根据Context过滤)
  3. 支持文件重命名(vue-cli默认不直接支持,但能够经过 metalsmith 的插件实现)
  4. 支持包管理(如包的生成,缓存,拉取,更新,删除,自动化测试)
  5. 提供一些生命周期的钩子(如beforePrompt,beforeRender,beforeExit等等)
  6. 动态的输出路径(这个对于我来讲颇有用)

固然,还有不少了,只要你想获得。

甜点时刻

是时候给你们介绍一些甜点了。

SAO

Github传送门:github.com/saojs/sao

一个听起来很骚气的名字,这是咱们可爱的 EGOIST 写的一个库,基本上实现了上述我说的全部特性。

最为关键的是,SAO目前已经提供了大量的高质量的样板文件:

name description
template Template for scaffolding out an SAO template
lass Lass scaffolds a modern package boilerplate for node
lad Lad scaffolds a Koa webapp and API framework for node
vue Kickstart a Vue project with Poi
gi Generate .gitignore file in your project
nm Scaffold out a node module
vue-webpack Vue.js offcial webpack template (SAO port)
basic Basic project skeleton
react SAO template for react with vbuild
micro-service Scaffolding out a micro-service
node-cli Scaffold a node cli tool
next Scaffold out a Next.js project
electron Scaffold out an Electron project
expo Scaffold out an Expo app

太强大了,这也大概就是我不得不爱 EGOIST 的缘由了吧。

poz

Github传送门:github.com/ulivz/poz

因为在实际生产中须要支持 重命名 和 动态输出路径,我写了 POZ 这个库,在前人的基础上,基于彻底不同的实现,实现了同样的功能,并加了一点儿特效,这大概就是造轮子的乐趣吧。欢迎你们 Fxxk/Issue。

最近 POZ 也计划开始开发 1.0的稳定版本了,快来看看这个 RoadMap,太有野心了有木有。

alphax

Github传送门:github.com/ulivz/alpha…

这是 poz 底层使用的一个库,基于Stream,灵感来源于 metalsmith 和 SAO 底层的 majo,最近我完全重写了该库,让其有了如下可爱的特性:

  1. 极简的API;
  2. 任务流控制;
  3. 中间件;
  4. 基于纯函数或JSON的的文件过滤;
  5. 基于纯函数或JSON的文件重命名;
  6. 支持不写入硬盘,易于测试;

它使用起来像这样:

alphax()
  .src('**')
  .task(task1)
  .task(task2)
  .task(task3)
  .use(file => file.content += Date.now())
  .rename(filepath => filepath.replace('{name}', name))
  .rename(filepath => filepath.replace('{age}', age))
  .transform(content => content.replace('{name}', name))
  .filter(filepath => filepath.endWith('.js'))
  .filter(filepath => !filepath.startWith('test'))
  .dest('dist')
  .then(files => console.log(files))
  .catch(error => console.log(error))
复制代码

若是你不喜欢函数,也能够用配置的方式来:

const config = {
  tasks: [task1, task3, task3],
  use: file => file.content += Date.now(),
  rename: {
    '{name}': name,
    '{age}': age
  },
  filter: {
    'app.js': true,
    'test.js': false
  },
  transform(content) {
    return content.replace('{name}', name)
  }
}

alphax()
  .src('**', config)
  .dest('dist')
  .then(files => console.log(files))
  .catch(error => console.log(error))
复制代码

相信这个lib已经解决掉你大部分的 meta.js 的API设计问题了😄。附上用可爱的 docute 写的文档地址: Documentation

总结

一个CLI归根究竟是要解决的是生产力和统一性的问题,可是,对于create-react-app这种过分封装,和 vue-cli@2.x.x 的过分松散,彷佛都不是最佳方案。配置少和拓展性高这原本就是两个互相矛盾的话题,从长远来看,选择怎样的CLI仍是依赖于具体的场景,但做为一个CLI开发者,若是作到更好的平衡,还值得多多思考。

本文仅谈及了写一个CLI工具的第一部分,其基本思路较为简单,只是实现层面会有较多的优化点和 error catch 😅,Good luck!

下文,我将继续阐述相似于 create-react-app 中 react-scripts 的基本实现原理及其思路,实际上,这也是 vue-cli@3.x.x 和 poi 所具备的功能,敬请关注。

以上,全文终。)

相关文章
相关标签/搜索