SAO 源碼閱讀

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 作了什麼緩存

  1. 模板能夠是本地,github 或者 npm,能夠自動補充前綴,推斷是什麼模板。
  2. 添加 saofile.js 讀取配置, 交互式彈窗,同時也會記住你上次填寫的內容,下次默認輸入。
  • 上下對比下

{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 命令。

每一個文件只有幾百行,他寫的可讀性很高,讀起來還比較簡單易懂。

開始

  1. cli.ts

const { runCLI, handleError } = require('.') // 進入index

_runCLI_()._catch_(handleError)

_export_ { runCLI } _from_ './cli-engine'// 進入cli-engine

  1. 處理命令行

  1. generator 就是模板倉庫的意思 後面的參數是文件地址
  2. 能夠給  generator 設置別名 set-alias 和 get-alias
  3. list 能夠展現因此的目前的 generator 倉庫,它給倉庫作了存儲,若是模板倉庫什麼也不傳遞,代碼會走緩存的倉庫

下面走主線

  1. cmd/main

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 作了一些處理

  1. SAO

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()

}

裏面沒有看明白或者說還沒看的地方

  1. Mock 的控制
  2. 模板是這麼帶入參數的
  3. majo 的文件操做

總體大的流程,看了幾乎 85% 的代碼 ,一行一行看的,用了 大概 斷斷續續的 8 個小時(畫一幅畫的時間),寫文章用了 1 個小時,查看不少 node path 的 api 和一些其餘人 sindresorhus 的庫,等等,目前下載的是 2.0.0-beta0.1 但是發佈的只有 1.7.1 , 我還處理了不少 bug 😭😭😭

裏面還有一些緩存配置的處理,hash 的生成,一些頗有意思的 util 文件,有興趣的能夠一塊兒深刻研究討論下。

一些缺點和優勢

  1. 缺點

在寫模板的時候,感受要對 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: 這是什麼操做?

  1. 優勢
  • 可讀性很高,英語很好,對庫的使用非常熟能生巧,連註釋都是用英語寫的,還有文檔,英語很地道,讓人都懷疑是外國人了。
  • ts 的使用也很好,並且仍是 import 使用 node 的手法,真可愛,還特意的創建了一個目錄
  • https://github.com/saojs 棒棒噠

總結

謝謝寫代碼的人,謝謝開源的世界,我彷佛探尋到了一個更好玩的世界。

相關文章
相關標籤/搜索