run函数
在create-react-app脚手架的目录为/packages/create-react-app/createReactApp.js
函数定义:执行app创建
源码如下:
function run(
root,
appName,
version,
verbose,
originalDirectory,
template,
useYarn,
usePnp
) {
Promise.all([
getInstallPackage(version, originalDirectory),
getTemplateInstallPackage(template, originalDirectory),
]).then(([packageToInstall, templateToInstall]) => {
const allDependencies = ['react', 'react-dom', packageToInstall];
console.log('Installing packages. This might take a couple of minutes.');
Promise.all([
getPackageInfo(packageToInstall),
getPackageInfo(templateToInstall),
])
.then(([packageInfo, templateInfo]) =>
checkIfOnline(useYarn).then(isOnline => ({
isOnline,
packageInfo,
templateInfo,
}))
)
.then(({
isOnline, packageInfo, templateInfo }) => {
let packageVersion = semver.coerce(packageInfo.version);
const templatesVersionMinimum = '3.3.0';
// Assume compatibility if we can't test the version.
if (!semver.valid(packageVersion)) {
packageVersion = templatesVersionMinimum;
}
// Only support templates when used alongside new react-scripts versions.
const supportsTemplates = semver.gte(
packageVersion,
templatesVersionMinimum
);
if (supportsTemplates) {
allDependencies.push(templateToInstall);
} else if (template) {
console.log('');
console.log(
`The ${
chalk.cyan(packageInfo.name)} version you're using ${
packageInfo.name === 'react-scripts' ? 'is not' : 'may not be'
} compatible with the ${
chalk.cyan('--template')} option.`
);
console.log('');
}
console.log(
`Installing ${
chalk.cyan('react')}, ${
chalk.cyan(
'react-dom'
)}, and ${
chalk.cyan(packageInfo.name)}${
supportsTemplates ? ` with ${
chalk.cyan(templateInfo.name)}` : ''
}...`
);
console.log();
return install(
root,
useYarn,
usePnp,
allDependencies,
verbose,
isOnline
).then(() => ({
packageInfo,
supportsTemplates,
templateInfo,
}));
})
.then(async ({
packageInfo, supportsTemplates, templateInfo }) => {
const packageName = packageInfo.name;
const templateName = supportsTemplates ? templateInfo.name : undefined;
checkNodeVersion(packageName);
setCaretRangeForRuntimeDeps(packageName);
const pnpPath = path.resolve(process.cwd(), '.pnp.js');
const nodeArgs = fs.existsSync(pnpPath) ? ['--require', pnpPath] : [];
await executeNodeScript(
{
cwd: process.cwd(),
args: nodeArgs,
},
[root, appName, verbose, originalDirectory, templateName],
`
const init = require('${
packageName}/scripts/init.js');
init.apply(null, JSON.parse(process.argv[1]));
`
);
if (version === '[email protected]') {
console.log(
chalk.yellow(
`\nNote: the project was bootstrapped with an old unsupported version of tools.\n` +
`Please update to Node >=14 and npm >=6 to get supported tools in new projects.\n`
)
);
}
})
.catch(reason => {
console.log();
console.log('Aborting installation.');
if (reason.command) {
console.log(` ${
chalk.cyan(reason.command)} has failed.`);
} else {
console.log(
chalk.red('Unexpected error. Please report it as a bug:')
);
console.log(reason);
}
console.log();
// On 'exit' we will delete these files from target directory.
const knownGeneratedFiles = ['package.json', 'node_modules'];
const currentFiles = fs.readdirSync(path.join(root));
currentFiles.forEach(file => {
knownGeneratedFiles.forEach(fileToMatch => {
// This removes all knownGeneratedFiles.
if (file === fileToMatch) {
console.log(`Deleting generated file... ${
chalk.cyan(file)}`);
fs.removeSync(path.join(root, file));
}
});
});
const remainingFiles = fs.readdirSync(path.join(root));
if (!remainingFiles.length) {
// Delete target folder if empty
console.log(
`Deleting ${
chalk.cyan(`${
appName}/`)} from ${
chalk.cyan(
path.resolve(root, '..')
)}`
);
process.chdir(path.resolve(root, '..'));
fs.removeSync(path.join(root));
}
console.log('Done.');
process.exit(1);
});
});
}
函数解析
1. 主题结构
Promise.all([package, template])
.then(([p, t]) => {
Promise.all([pi, ti])
.then()
.then()
.then()
.catch()
})
2. 第一个Promise.then执行结果
如上所看到的,两个包react-scripts
、cra-template
,这两个也是create-react-app
脚手架packages
中的两个主要的子项目
3. 执行checkOnline
两处关键的代码注释翻译
// 不要 ping Yarn 注册表。
// 我们只假设最好的情况。
// 如果定义了代理,我们可能无法解析外部主机名。
// 尝试将代理名称解析为连接的指示。
主要是判断yarn仓库连接检测
3. 第二个Promise.then执行结果
4. 继续执行,关键信息打印
控制台信息打印:
5. 项目执行install函数
command
、args
debug数据:
控制台运行效果如下:
运行完成:
- 生成的项目目录新增了
node_modules
和package-lock.json
- 此时控制台打印如下:
- 项目的
package.json
内容如下:
6. install返回数据如下
-
checkNodeVersion
的packageJson运行后的数据'create-react-app/packages/my-app/node_modules/react-scripts/package.json'
-
setCaretRangeForRuntimeDeps
const packagePath = path.join(process.cwd(), 'package.json');
const packageJson = require(packagePath);
debug代码如下,其实就是生成的项目的pacakage.json
里的内容
-
makeCaretRange
使插入符号范围
举例:
react: "18.2.0"
返回react: "^18.2.0"
,具体差异,自行查阅其他资料 -
fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2) + os.EOL);
重新将更新后的内容异步写入到package.json
,如下
对比上述,多了扩展符号^
7. pnpPath
debug
值
'/create-react-app/packages/my-app/.pnp.js'
.pnp.js
是什么呢?
nodeArgs
空数组
8. executeNodeScript
使用到了react-scripts
,后续单独再补充
源代码如下:
function executeNodeScript({
cwd, args }, data, source) {
return new Promise((resolve, reject) => {
const child = spawn(
process.execPath,
[...args, '-e', source, '--', JSON.stringify(data)],
{
cwd, stdio: 'inherit' }
);
child.on('close', code => {
if (code !== 0) {
reject({
command: `node ${
args.join(' ')}`,
});
return;
}
resolve();
});
});
}
debug
代码参数情况:
开始运行如下:
到这里,新的项目基本就完成了,此时,去新建的项目my-app
查看,如下:
具体做了哪些事情,需要进一步去解读react-scripts
,后续有时间再进一步分析;