SAO 源码阅读vue
更好的观看体验,移步飞书
https://bytedance.feishu.cn/docs/doccnPsquOewJCVXAGENOO1XWBb#node
SAO 是什么?react
代码仓库地址 https://github.com/saojs/saogit
SAO 是一个脚手架,不一样于 vue/react 这类的cli 脚手架, 能够经过 sao 新建不少个模板,根据模板批量生产初始化代码。github
参考这里 https://github.com/saojs/awesome-saonpm
sao [nm](https://github.com/saojs/sao-nm) dirname
json
sao react dirname
api
SAO 作了什么缓存
{
async
type:"input",
name: 'name',
message: 'What is the name of the new project',
default: this.outFolder
},
{
type:"input",
name: 'description',
message: 'How would you describe the new project',
default: `my ${superb()} project`
},
{
type:"input",
name: 'author',
message: 'What is your name',
default: this.gitUser.name,
store: true,
required: true
},
{
type:"input",
name: 'username',
message: 'What is your GitHub username',
default: ({ author }) => this.gitUser.username ,
store: true
}
参考代码 https://github.com/saojs/sao-nm/blob/master/saofile.js
actions 处理了对文件的增删改移动,会默认读取 template 文件。
在执行前 执行 prepare 函数
在执行后 执行 completed 函数
配置文件的 this 指的是 sao 的 instance
SAO 是怎么实现的?
目录
从 cli.ts 开始到 index.ts 以后文件来回跳转,总体是这么一个阅读流程。
utils 写了公用的方法,cmd 处理了 cli 命令。
每一个文件只有几百行,他写的可读性很高,读起来还比较简单易懂。
开始
const { runCLI, handleError } = require('.') // 进入index
_runCLI_()._catch_(handleError)
_export_ { runCLI } _from_ './cli-engine'// 进入cli-engine
下面走主线
cli
._command_('[generator] [outDir]', 'Run a generator')
._action_((generator, outDir) =>
import('./cmd/main')._then_((res) => res._main_(cli)(generator, outDir))
)
const options_:_ _Options_ = {
generator,
outDir: outDir || '.',
updateCheck: true,
answers: cli.options.yes ? true : cli.options.answers,
...cli.options,
}
_try_ {
const sao = new _SAO_(options)
const g = sao.parsedGenerator
...
}
new 一个 SAO 对象 ,把options 参数传入
SAO 对传入的 options 作了一些处理
onstructor(opts_:_ _Options_) {
this.opts = {
...opts,
outDir: path._resolve_(opts.outDir || '.'),
logLevel: opts.logLevel || 3,
}
// 若是输入 --debug 这种控制,就会在控制行中显示 log, 好比 logger.debug(path) 这个使用
_if_ (opts.debug) {
this.opts.logLevel = 4
} _else_ _if_ (opts.quiet) {
this.opts.logLevel = 1
}
logger._setOptions_({
logLevel: this.opts.logLevel,
mock: this.opts.mock,
})
// 缓存配置读取
// {
// type: 'repo',
// prefix: 'github',
// user: 'saojs',
// repo: 'sao-nm',
// version: 'master',
// hash: 'fd96efa8',
// path: '/Users/yourname/.sao/V2/repos/fd96efa8'
// }
this.generatorList = generatorList
//读取配置文件并解析,出来的是对象
//{
// type: 'local',
// path: '/Users/yourname/Desktop/sao-nm',
// hash: '5e247f02',
// subGenerator: undefined
//}
this.parsedGenerator = _parseGenerator_(this.opts.generator)
// _Sub generator can only be used in an existing_
}
this.generatorList 读取的是缓存历史配置
store: [
{
type: 'repo',
prefix: 'github',
user: 'saojs',
repo: 'sao-nm',
version: 'master',
hash: 'fd96efa8',
path: '/Users/yourname/.sao/V2/repos/fd96efa8'
},
{
type: 'npm',
name: 'sao-ts',
version: 'latest',
slug: 'sao-ts',
hash: '096eb060',
path: '/Users/yourname/.sao/V2/packages/096eb060/node_modules/sao-ts'
}
this.parsedGenerator 是对于目前的模板仓库作了分析缓存
{
type: 'local',
path: '/Users/yourname/Desktop/sao-nm',
hash: '5e247f02',
subGenerator: undefined
}
读取配置
-- 这个 help 我还没读取明白由于我写 help 的时候就会执行另外一个逻辑了
_if_ (cli.options.help) {
async _getGenerator_(
generator_:_ _ParsedGenerator_ = this.parsedGenerator,
hasParent?_:_ _boolean_
)_:_ _Promise_<{ generator_:_ _ParsedGenerator_; config_:_ _GeneratorConfig_ }> {
const loaded = _await_ _loadGeneratorConfig_(generator.path)
const config_:_ _GeneratorConfig_ =
loaded.path && loaded.data ? loaded.data : defautSaoFile
...
}}
使用了 JoyCon 这个也是他写的,下载量很高,小巧玲珑的功能很好用,虽然星星很少,用的不少
const joycon = new _JoyCon_({
files: ['saofile.js', 'saofile.json'],
})
会读取你写的文件
最后返回了
_return_ {
generator,
config,
}
逻辑走 else
_await_ sao._run_()
async _run_()_:_ _Promise_<_void_> {
const { generator, config } = _await_ this._getGenerator_()
_await_ this._runGenerator_(generator, config)
}
async _runGenerator_(
generator_:_ _ParsedGenerator_,
config_:_ _GeneratorConfig_
)_:_ _Promise_<_void_> {
_if_ (config.description) {
logger._status_('green', 'Generator', config.description)
}
// 执行 prepare 函数
_if_ (typeof config.prepare === 'function') {
_await_ config.prepare._call_(this, this)
}
_if_ (config.prompts) {
// 交互式问题
// 使用 enquirer 处理了prompts,在 this 上挂载了 answers
const { runPrompts } = _await_ import('./run-prompts')
_await_ _runPrompts_(config, this)
} _else_ {
this._answers = {}
}
this._data = config.data ? config.data._call_(this, this) : {}
// 文件处理,添加,过滤,移动,删除,修改
// 用他本身写的 majo,跳去 majo 看了看三年前开始写的,这个也颇有意思
_if_ (config.actions) {
const { runActions } = _await_ import('./run-actions')
_await_ _runActions_(config, this)
}
//执行 completed 函数
_if_ (!this.opts.mock && config.completed) {
_await_ config.completed._call_(this, this)
}
}
async completed() {
// git init 创建仓库,捂脸,当初我本身写就spaw 的 git init 真是太蠢了,还能够封装用别人的,真是学习了🤦♀️
_await_ this.gitInit()
// 下载包
_await_ this.npmInstall({ packageManager: this.answers.pm })
// show tiops 完成
this.showProjectTips()
}
里面没有看明白或者说还没看的地方
总体大的流程,看了几乎 85% 的代码 ,一行一行看的,用了 大概 断断续续的 8 个小时(画一幅画的时间),写文章用了 1 个小时,查看不少 node path 的 api 和一些其余人 sindresorhus 的库,等等,目前下载的是 2.0.0-beta0.1 但是发布的只有 1.7.1 , 我还处理了不少 bug 😭😭😭
里面还有一些缓存配置的处理,hash 的生成,一些颇有意思的 util 文件,有兴趣的能够一块儿深刻研究讨论下。
一些缺点和优势
在写模板的时候,感受要对 sao 很熟悉,文档不是很全
_await_ this.gitInit()
_await_ this.npmInstall({ packageManager: this.answers.pm })
this.showProjectTips()
这种写法就得看 sao 源码
{
type:"input",
name: 'name',
message: 'What is the name of the new project',
default: this.outFolder
},
还有这种写法,this 就显得有点莫名其妙,应该能够有更好的展现方式吧
parse-generator.ts 这个文件有点凌乱
// ?若是也是 prefix === 'local'
_if_ (_isLocalPath_(generator)) {
_return_ {
type: 'local',
path: absolutePath,
hash: _sum_(absolutePath),
subGenerator,
}
}
_if_ (prefix === 'npm') {
...
return {...}
}
return {}
//感受后面的 return 能够再次作一个判断啊 , 强迫症
if(prefix === 'github') {
return {}
}
还有更凌乱的
// _Infer prefix for naked generate name (without prefix)_
// 推断前缀,加上前缀 npm _github_
_if_ (!GENERATOR_PREFIX_RE._test_(generator)) {
_if_ (generator._startsWith_('@')) {
generator = `npm:${generator._replace_(//(sao-)?/, '/sao-')}`
} _else_ _if_ (generator._includes_('/')) {
generator = `github:${generator}`
} _else_ {
generator = `npm:${generator._replace_(/_^_(sao-)?/, 'sao-')}`
}
}
// _Get generator type, e.g. `npm` or `github`_
let prefix_:_ _GeneratorPrefix_ = 'npm'
let m_:_ _RegExpExecArray_ | null = null
_if_ ((m = GENERATOR_PREFIX_RE._exec_(generator))) {
prefix = m[1] _as_ _GeneratorPrefix_
// 去掉前缀 npm _github_
generator = generator._replace_(GENERATOR_PREFIX_RE, '')
}
给变量加了 npm: 最后又去掉了 npm: 这是什么操做?
总结
谢谢写代码的人,谢谢开源的世界,我彷佛探寻到了一个更好玩的世界。