create-react-app脚手架核心源码之/packages/create-react-app解读(五)

104 阅读2分钟

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 === 'react-scripts@0.9.x') {
          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执行结果

image.png

如上所看到的,两个包react-scriptscra-template,这两个也是create-react-app脚手架packages中的两个主要的子项目

3. 执行checkOnline

两处关键的代码注释翻译

// 不要 ping Yarn 注册表。
// 我们只假设最好的情况。


// 如果定义了代理,我们可能无法解析外部主机名。
// 尝试将代理名称解析为连接的指示。

主要是判断yarn仓库连接检测

3. 第二个Promise.then执行结果

image.png

image.png

4. 继续执行,关键信息打印

image.png

控制台信息打印:

image.png

5. 项目执行install函数

image.png command argsdebug数据: image.png

控制台运行效果如下: image.png

运行完成:

  • 生成的项目目录新增了node_modulespackage-lock.json

image.png

  • 此时控制台打印如下:

image.png

  • 项目的package.json内容如下:

image.png

6. install返回数据如下

image.png

  • 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里的内容

image.png

  • makeCaretRange使插入符号范围 举例: react: "18.2.0"返回react: "^18.2.0",具体差异,自行查阅其他资料

  • fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2) + os.EOL);

重新将更新后的内容异步写入到package.json,如下

image.png 对比上述,多了扩展符号^

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代码参数情况:

截屏2022-07-20 12.14.35.png

开始运行如下:

image.png

到这里,新的项目基本就完成了,此时,去新建的项目my-app查看,如下:

image.png 具体做了哪些事情,需要进一步去解读react-scripts,后续有时间再进一步分析;