You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
let projectName;
const program = new commander.Command(packageJson.name)
.version(packageJson.version)//create-react-app -v 时候输出的值 packageJson 来自上面 const packageJson = require('./package.json');
.arguments('<project-directory>') //定义 project-directory ,必填项
.usage(`${chalk.green('<project-directory>')} [options]`)
.action(name => {
projectName = name;//获取用户的输入,存为 projectName
})
.option('--verbose', 'print additional logs')
.option('--info', 'print environment debug info')
.option(
'--scripts-version <alternative-package>',
'use a non-standard version of react-scripts'
)
.option('--use-npm')
.option('--use-pnp')
.option('--typescript')
.allowUnknownOption()
.on('--help', () => {// on('option', cb) 语法,输入 create-react-app --help 自动执行后面的操作输出帮助
console.log(` Only ${chalk.green('<project-directory>')} is required.`);
console.log();
console.log(
` A custom ${chalk.cyan('--scripts-version')} can be one of:`
);
console.log(` - a specific npm version: ${chalk.green('0.8.2')}`);
console.log(` - a specific npm tag: ${chalk.green('@next')}`);
console.log(
` - a custom fork published on npm: ${chalk.green(
'my-react-scripts'
)}`
);
console.log(
` - a local path relative to the current working directory: ${chalk.green(
'file:../my-react-scripts'
)}`
);
console.log(
` - a .tgz archive: ${chalk.green(
'https://mysite.com/my-react-scripts-0.8.2.tgz'
)}`
);
console.log(
` - a .tar.gz archive: ${chalk.green(
'https://mysite.com/my-react-scripts-0.8.2.tar.gz'
)}`
);
console.log(
` It is not needed unless you specifically want to use a fork.`
);
console.log();
console.log(
` If you have any problems, do not hesitate to file an issue:`
);
console.log(
` ${chalk.cyan(
'https://github.com/facebook/create-react-app/issues/new'
)}`
);
console.log();
})
.parse(process.argv);
关于 commander 的使用,这里就不介绍了,对于 create-react-app 的流程我们需要知道的是,它,初始化了一些 create-react-app 的命令行环境,这一波操作后,我们可以看到 program 张这个样纸:
function createApp(
name,
verbose,
version,
useNpm,
usePnp,
useTypescript,
template
) {
const root = path.resolve(name);//path 拼接路径
const appName = path.basename(root);//获取文件名
checkAppName(appName);//检查传入的文件名合法性
fs.ensureDirSync(name);//确保目录存在,如果不存在则创建一个
if (!isSafeToCreateProjectIn(root, name)) { //判断新建这个文件夹是否安全,否则直接退出
process.exit(1);
}
console.log(`Creating a new React app in ${chalk.green(root)}.`);
console.log();
const packageJson = {
name: appName,
version: '0.1.0',
private: true,
};
fs.writeFileSync(
path.join(root, 'package.json'),
JSON.stringify(packageJson, null, 2) + os.EOL
);//写入 package.json 文件
const useYarn = useNpm ? false : shouldUseYarn();//判断是使用 yarn 呢还是 npm
const originalDirectory = process.cwd();
process.chdir(root);
if (!useYarn && !checkThatNpmCanReadCwd()) {//如果是使用npm,检测npm是否在正确目录下执行
process.exit(1);
}
if (!semver.satisfies(process.version, '>=8.10.0')) {//判断node环境,输出一些提示信息, 并采用旧版本的 react-scripts
console.log(
chalk.yellow(
`You are using Node ${
process.version
} so the project will be bootstrapped with an old unsupported version of tools.\n\n` +
`Please update to Node 8.10 or higher for a better, fully supported experience.\n`
)
);
// Fall back to latest supported react-scripts on Node 4
version = 'react-scripts@0.9.x';
}
if (!useYarn) {//关于 npm、pnp、yarn 的使用判断,版本校验等
const npmInfo = checkNpmVersion();
if (!npmInfo.hasMinNpm) {
if (npmInfo.npmVersion) {
console.log(
chalk.yellow(
`You are using npm ${
npmInfo.npmVersion
} so the project will be bootstrapped with an old unsupported version of tools.\n\n` +
`Please update to npm 5 or higher for a better, fully supported experience.\n`
)
);
}
// Fall back to latest supported react-scripts for npm 3
version = 'react-scripts@0.9.x';
}
} else if (usePnp) {
const yarnInfo = checkYarnVersion();
if (!yarnInfo.hasMinYarnPnp) {
if (yarnInfo.yarnVersion) {
console.log(
chalk.yellow(
`You are using Yarn ${
yarnInfo.yarnVersion
} together with the --use-pnp flag, but Plug'n'Play is only supported starting from the 1.12 release.\n\n` +
`Please update to Yarn 1.12 or higher for a better, fully supported experience.\n`
)
);
}
// 1.11 had an issue with webpack-dev-middleware, so better not use PnP with it (never reached stable, but still)
usePnp = false;
}
}
if (useYarn) {
let yarnUsesDefaultRegistry = true;
try {
yarnUsesDefaultRegistry =
execSync('yarnpkg config get registry')
.toString()
.trim() === 'https://registry.yarnpkg.com';
} catch (e) {
// ignore
}
if (yarnUsesDefaultRegistry) {
fs.copySync(
require.resolve('./yarn.lock.cached'),
path.join(root, 'yarn.lock')
);
}
}
run(
root,
appName,
version,
verbose,
originalDirectory,
template,
useYarn,
usePnp,
useTypescript
);
}
代码非常简单,部分注释已经加载代码中,简单的说就是对一个本地环境的一些校验,版本检查啊、目录创建啊啥的,如果创建失败,则退出,如果版本较低,则使用对应低版本的create-react-app,最后调用 run 方法
前言
对于前端工程构建,很多公司、BU 都有自己的一套构建体系,比如我们正在使用的 def,或者 vue-cli 或者 create-react-app,由于笔者最近一直想搭建一个个人网站,秉持着呼吸不停,折腾不止的原则,编码的过程中,还是不想太过于枯燥。在 coding 之前,搭建自己的项目架构的时候,突然想,为什么之前搭建过很多的项目架构不能直接拿来用,却还是要从 0 到 1 的去写 webpack 去下载相关配置呢?遂!学习下 create-react-app 源码,然后自己搞一套吧~
create-react-app 源码
代码的入口在
packages/create-react-app/index.js
下,核心代码在createReactApp.js
中,虽然有大概 900+行代码,但是删除注释和一些友好提示啥的大概核心代码也就六百多行吧,我们直接来看index.js
index.js 的代码非常的简单,其实就是对 node 的版本做了一下校验,如果版本号低于 8,就退出应用程序,否则直接进入到核心文件中,
createReactApp.js
中createReactApp.js
createReactApp 的功能也非常简单其实,大概流程:
create-react-app --info
的输出等react-script
下的模板文件准备工作:配置 vscode 的 debug 文件
这里我们添加三种环境,其实就是 create-react-app 的不同种使用方式
create-react-app study-create-react-app-source
create-react-app
create-react-app study-create-react-app-source-ts --typescript
commander 命令行处理程序
commander 文档传送门
关于 commander 的使用,这里就不介绍了,对于 create-react-app 的流程我们需要知道的是,它,初始化了一些 create-react-app 的命令行环境,这一波操作后,我们可以看到 program 张这个样纸:
接着往下走
当我们 debug 启动
noArgs
环境的时候,走到这里就结束了,判断 projectName 是否为 undefined,然后输出相关提示信息,退出~createApp
在查看 createApp function 之前,我们再回头看下命令行的一些参数定义,方便我们理解 createApp 的一些参数
我们使用
debugger 我们项目的时候,就可以看到,
program.typescript
为true
,useNpm
为true
,当然,这些也都是我们在commander
中定义的 options,所以源码里面 createApp 中,我们传入的参数分别为:代码非常简单,部分注释已经加载代码中,简单的说就是对一个本地环境的一些校验,版本检查啊、目录创建啊啥的,如果创建失败,则退出,如果版本较低,则使用对应低版本的
create-react-app
,最后调用 run 方法checkAppName
这些工具方法,其实在写我们自己的构建工具的时候,也可以直接 copy 的哈,所以这里我们也是简单看下里面的实现,
checkAPPName 方法主要的核心代码是
validate-npm-package-name
package,从名字即可看出,检查是否为合法的 npm 包名最终,checkAPPName返回的东西如截图所示,后面写代码可以直接拿来借鉴!借鉴~
isSafeToCreateProjectIn
所谓安全性校验,其实就是检查当前目录下是否存在已有文件。
checkNpmVersion
后面的代码也都比较简单,这里就不展开说了,版本比较实用的是一个semver package.
run
代码跑到这里,该检查的都检查了,鸡也不叫了、狗也不咬了,该干点正事了~
run 主要做的事情就是安装依赖、拷贝模板。
getInstallPackage
做的事情非常简单,根据传入的 version 和原始路径 originalDirectory 去获取要安装的 package 列表,默认情况下version 为 undefined,获取到的 packageToInstall 为react-scripts
,也就是我们如上图的 resolve 回调。最终,我们拿到需要安装的 info 为
当我们梳理好需要安装的 package 后,就交给 npm 或者 yarn 去安装我们的依赖即可
在
spawn
执行完命令后会有一个回调,判断code是否为 0,然后 resolve Promise,在
create-react-app
之前的版本中,这里是通过调用react-script
下的init
方法来执行后续动作的。这里通过调用executeNodeScript
方法executeNodeScript
方法主要是通过 spawn 来通过 node命令执行react-script
下的 init 方法。所以截止当前,create-react-app
完成了他的工作:npm i
,react-script/init.js
修改 vscode 的 debugger 配置,然后我们来 debugger react-script 下的 init 方法
初始化方法主要做的事情就是修改目标路径下的 package.json,添加一些配置命令,然后 copy!react-script 下的模板到目标路径下。
走到这一步,我们的项目基本已经初始化完成了。
所以我们 copy 了这么多 scripts
究竟是如何工作的呢,其实也不难,就是一些开发、测试、生产的环境配置。鉴于篇幅,咱就下一篇来分享下大佬们的前端构建的代码写法吧~~
总结
本来想用一张流程图解释下,但是。。。create-react-app 着实没有做啥!咱还是等下一篇分析完,自己写构建脚本的时候再画一下整体流程图(架构图)吧~
ok~ 简单概述下:
cross-spawn
来用命令行执行所有的安装通篇看完 package 的职能后,发现,哇,这有点简答啊~~其实,我们学习源码的其实就是为了学习大佬们的一些边界情况处理,在后面自己开发的时候再去 copy~ 借鉴一些判断方法的编写。后面会再简单分析下react-scripts,然后写一个自己的一些项目架构脚本~
The text was updated successfully, but these errors were encountered: