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: 這是什麼操做?
總結
謝謝寫代碼的人,謝謝開源的世界,我彷佛探尋到了一個更好玩的世界。