// Copy the files for the user
const templateDir = path.join(templatePath, 'template');
if (fs.existsSync(templateDir)) {
fs.copySync(templateDir, appPath);
} else {
console.error(
`Could not locate supplied template: ${chalk.green(templateDir)}`
);
return;
}
最近在弄一个项目的模版,之前是以fork的方式新建;这种方式不太友好,所以想着参考cra,用cli的方式创建模版;也趁这个机会了解cra创建项目的过程。如果想了解概要过程,直接拉到页面底部即可。
工具概览
先大概了解cra所用到的工具,在入口文件可以看到,下面写一些简单工具的简单描述与使用目的,对所使用工具熟悉,看源码起来会比较有帮助。熟悉的话可以跳过...
创建流程
输入处理
可以看到处理输入的文件夹名称与部分配置等;通过
commander
的处理转换成对象形式,更容易操作createApp函数
当判断输入已经没问题之后,就执行
createApp
函数,函数执行首先是判断node版本,对于低版本进行提醒或退出,这个取决于是否用到
typescript
;这里判断node版本信息通过semver
处理,需要node版本>=8.10.0
。检查node版本之后,就会检测输入的文件夹名称是否负责npm包名称的规范,检测的方法为:
checkAppName
,其中利用到的工具库是validateProjectName
;接着处理目标文件夹,
fs.ensureDirSync(name)
,这里的fs
是指fs-extra
模块;fs.ensureDirSync
方法的作用是:如果目标文件夹不存在,则创建对应的文件夹;那如果目标文件夹存在怎么办?cra会对目标文件夹的文件进行判断,如果目标文件夹的文件不影响新建项目,则还是可以继续进行;cra会维护一些文件的白名单还有部分规则,具体可以看isSafeToCreateProjectIn函数
文件夹准备完成之后,就会往目标文件夹写入一个简单的
package.json
文件然后判断使用哪一个依赖管理器,默认是
yarn
,也可以指定npm
和pnpm
;依赖的npm
版本需要大于5,yarn
版本需要大于1.12.0;当处理完管理器之后;如果确定使用yarn
且没有更改yarn
的仓库地址(默认是:https://registry.yarnpkg.com
);则会拷贝yarn.lock.cache
当作yarn.lock
,用于保证安装依赖是正确的;如果不是指定yarn
或者指定了别的仓库地址,则按最新版本安装。这里简单说一下
pnpm
,npm
和yarn
会熟悉一点,但是这个pnpm
用得比较少。这个管理器号称速度比yarn
和npm
都要快3倍(2017年数据),而且省空间;因为yarn
不同项目如果依赖相同版本的npm包,如果本地已安装,是通过复制文件到不同的项目中去;而pnpm
是通过硬链接代替复制。具体更详细可以看这篇文章why-should-we-use-pnpm?。准备的工具和文件夹都ok了,就开始安装依赖~
run函数
run
函数最开始是获取react-scripts
与cra-template
的安装路径与对应版本。源码在这。需要注意的是,cra-template
是从v3.3.0版本开始才增加,之前的版本中,cra-template的内容也是在react-scripts
中。为什么要获取安装的路径呢?因为这两个安装包的安装路径,cra支持多种方式:
file://
),猜测是为了更方便的debug过程tar.gz
)文件,本地或http链接。获取完依赖包的信息之后,就开始下载
如果指定是typescript的环境,则还会增加相应的包:
install函数
当确认好使用的包管理工具,依赖的包版本与地址信息之后;进入
install
方法后,还需要对当前网络环境进行判断;因为使用yarn
是支持离线下载的;这个判断就使用到dns
模块,对registry.yarnpkg.com
域名进行解析,若解析成功则为在线,反之则是离线。一切就绪就开始进行下载,执行下载的命令需要对上述工具与信息拼接,然后使用
spawn
方法调用起子进程,让子进程去执行我们的安装命令,例如我们平常的安装命令yarn add lodash
。到这里会有一个疑问,看到文件顶部引入的execSync
与spawn
都是子进程的执行方法,这两个方法会有什么区别?这两个方法最主要的两个区别是:
spawn()
返回的是一个流stream
,stream
会触发data
和end
等事件,通过触发事件返回数据;文章中称之为"异步的异步"exec()
返回是一个buffer
,也就是对执行命令的输出一次性返回,这个buffer
默认是200k;如果输出超过这个值,就会报错。文章中称之为"同步的异步"所以通常对于输出数据比较大的选用
spawn
输出数据比较简单的,选用exec
。更详细可以看这篇文章,difference-between-spawn-and-exec回到
install
函数,spawn
执行安装,当安装完毕后,通过close
事件确认是否安装成功:安装完毕之后,会回到原来的
run
函数,接着还有对环境进行检查:checkNodeVersion
,检查当前node的环境版本是否符合react-scripts
最低的node版本要求;环境检查完毕后,对所依赖的包
react-scripts
的版本修正:setCaretRangeForRuntimeDeps
,例如下载react-scripts
的时候指定v3.3.0
版本,则在新建的项目中的package.json
的dependencies
修正为:^3.3.0
。检查都通过之后,准备对模版文件进行处理;因为安装的
cra-template
已经包含了我们需要的源文件,是直接拷贝到目标文件就可以了?这个时候执行另外一段命令,地址:这段命令是执行
react-scripts/scripts/init.js
的方法。后续的操作就交给init
方法处理,这个暂时先放一下,后面再展开;再看一下如果该段代码执行出错,或者在install
过程中出错,就会跳到最后的catch
方法:这个方法主要是对已生成的文件进行删除,错误代码处理过程:源码如果在目标文件夹已生成上面的文件列表,则会对这些文件移除;若移除后文件夹为空,则会对文件夹也删除。
react-scripts/script/init.js
到这一步的时候,新建的项目中主动安装的依赖有:
react
,react-dom
,react-scripts
,cra-template
这个方法主要是对cra-template
的项目的模版文件进行处理,安装一些缺失的额外依赖与更改新建项目的package.json
进行优化处理。cra-template/template
文件到新建项目目录:cra-template/template.json
template.json
内容为:由于这些依赖没有在
react-scripts
中,因为需要在这里需要再次安装该部分依赖。cra-template
前面可以知道我们安装的时候会包含这个
cra-template
的依赖,而这个依赖的作用是提供模版文件,现在已经复制到目标文件夹了,因此可以执行命令删除:git init
初始化新建项目文件夹为git仓库,并把初始化的文件加入到commitInitial commit from Create React App
至此整个流程已经安装完毕
小结
安装的过程梳理为以下几点:
typescript
等package.json
react
,react-dom
,react-scripts
,cra-template
cra-template
到目标文件夹,更改package.json
内容cra-template
指定额外依赖commit
上述过程实现起来并不困难,但是需要兼顾到非常多的环境问题与版本处理等比较零碎的边界值等。需要对文件系统的操作了解很多,对于优化用户提示与用户交互所用到工具也要较为熟悉。整体流程需要十分严谨,才能在各种环境中处理正常。
react-scripts
是项目的核心处理,包括npm script
命令,还有项目的依赖。还有一些该项目的其他问题要处理,例如:cra-template
项目如何维护?yarn.lock.cached
如何更新处理?这些问题解决方式都可以从
create-react-app
根目录的tasks
文件夹与根目录的package.json
中找到。