关于前端:SAO-源码阅读

33次阅读

共计 6121 个字符,预计需要花费 16 分钟才能阅读完成。

SAO 源码浏览

更好的观看体验,移步飞书
https://bytedance.feishu.cn/docs/doccnPsquOewJCVXAGENOO1XWBb#

SAO 是什么?

代码仓库地址 https://github.com/saojs/sao

SAO 是一个脚手架,不同于 vue/react  这类的 cli 脚手架,能够通过 sao 新建很多个模板,依据模板批量生产初始化代码。

参考这里 https://github.com/saojs/awesome-sao

sao [nm](https://github.com/saojs/sao-nm) dirname

sao react dirname

SAO 做了什么

  1. 模板能够是本地,github 或者 npm,能够主动补充前缀,推断是什么模板。
  2. 增加 saofile.js 读取配置,交互式弹窗,同时也会记住你上次填写的内容,下次默认输出。
  • 高低比照下

{

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 棒棒哒

总结

谢谢写代码的人,谢谢开源的世界,我仿佛探寻到了一个更好玩的世界。

正文完
 0