本文首发于我的 Github,欢迎 issue / fxxk。css
相信你们都写过vue
,react
或者angular
的各位同窗,也必定不会对如下库陌生:html
体验过上述工具的同窗,有没有发现他们都有一个共同点——提供了一个可供快速开发的样板文件(boilerplate)。本文就将从样板文件入来进行阐述。经过本文,你将学到:前端
CLI
工具须要解决的问题;CLI
的基本思路;CLI
须要作哪些准备;PS:因为脚手架的英文 scaffolding 太长,本文我将以更可爱的 cli 来代替。vue
因为篇幅有限,本节将以create-react-app
和vue-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
。看起来很陌生,可是若是我告诉你它的依赖中有babel
、webpack
、webpack-dev-server
和autoprefixer
这些常见的前端流氓,我想你也清楚了react-scripts
到底作了什么:web
包装了
webpack
和webpack-dev-server
,提供一套默认的配置(内置css,file,svg等等各类loader),在react-scripts start
和react-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 要解决的问题就是:
react-scripts
,让开发者省略痛苦的配置流程.(webpack
配置工程师看来要失业了😇)接下来,再回顾一下 vue-cli
。截止本文书写日期, vue-cli@3.0 仍然处于 beta 阶段,所以本文将以 2.x.x 为例,咱们建立一个名为 my-vue-app,模板为 webpack-simple 的 vue 项目:
可见,vue-cli在生成项目以前多了一个很是重要的一步 —— Prompt,也就是问询,根据询问的内容最终生成你的项目。两条FYI:
对本节作一下结,一个脚手架一般由如下基本几部分组成:
通过上一节的洗礼,你可能已经有大体的思路了,而后,让咱们以 vue-cli 为例,直接进入实战吧。
my-first-package
├── meta.js
└── 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 的技术选型:
热衷于看这个世界的你,是否已经跃跃欲试了呢?
固然,若是只是这样的一个 CLI,很显然只是玩具,你能够考虑支持如下可爱的特性:
固然,还有不少了,只要你想获得。
是时候给你们介绍一些甜点了。
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 的缘由了吧。
Github传送门:github.com/ulivz/poz
因为在实际生产中须要支持 重命名 和 动态输出路径,我写了 POZ 这个库,在前人的基础上,基于彻底不同的实现,实现了同样的功能,并加了一点儿特效,这大概就是造轮子的乐趣吧。欢迎你们 Fxxk/Issue。
最近 POZ 也计划开始开发 1.0的稳定版本了,快来看看这个 RoadMap,太有野心了有木有。
Github传送门:github.com/ulivz/alpha…
这是 poz 底层使用的一个库,基于Stream,灵感来源于 metalsmith 和 SAO 底层的 majo,最近我完全重写了该库,让其有了如下可爱的特性:
它使用起来像这样:
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 所具备的功能,敬请关注。
以上,全文终。)