The create - react - app (v3.7.2)
react
cli

Initialization command

// All CRA commands are as follows
/** NPM package commander: Command base tool */
const program = new commander.Command(packageJson.name)
  .version(packageJson.version)
  .arguments('<project-directory>')
  .usage(`${chalk.green('<project-directory>')} [options]`)
  .action(name= > {
    projectName = name;
  })
  .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(
    '--template <path-to-template>'.'specify a template for the created project'
  )
  .option('--use-npm')
  .option('--use-pnp')
  // TODO: Remove this in next major release.
  .option(
    '--typescript'.'(this option will be removed in favour of templates in the next major release of create-react-app)'
  )
  .allowUnknownOption()
  .on('--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(`    A custom ${chalk.cyan('--template')} can be one of:`);
    console.log(
      `      - a custom fork published on npm: ${chalk.green(
        'cra-template-typescript'
      )}`
    );
    console.log(
      `      - a local path relative to the current working directory: ${chalk.green(
        'file:.. /my-custom-template'
      )}`
    );
    console.log(
      `      - a .tgz archive: ${chalk.green(
        'https://mysite.com/my-custom-template-0.8.2.tgz'
      )}`
    );
    console.log(
      `      - a .tar.gz archive: ${chalk.green(
        'https://mysite.com/my-custom-template-0.8.2.tar.gz'
      )}`
    );
    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);
Copy the code

-v, –version Indicates the version number

  // The current create-react-app version is output
 new commander.Command(packageJson.name)
  .version(packageJson.version) // The command option is already generated by default
Copy the code

–verbose displays detailed logs

–info Displays information about the current system and environment

// In the source code, if the command has this parameter, it will be executed
/** NPM package: envinfo: Quick access to information about the current various software environments */
return envinfo
    .run(
      {
        System: ['OS'.'CPU'].Binaries: ['Node'.'npm'.'Yarn'].Browsers: ['Chrome'.'Edge'.'Internet Explorer'.'Firefox'.'Safari'].npmPackages: ['react'.'react-dom'.'react-scripts'].npmGlobalPackages: ['create-react-app'],}, {duplicates: true.showNotFound: true,
      }
    )
    .then(console.log);
Copy the code

–scripts-version Specifies a specific react-scripts script to run

–template Specifies the project template. You can specify a template of your own

— Use PNP –> what is PNP

Typescript uses TS development. Later versions will remove this option

This option will soon be deprecated and can be replaced with –template typescript

if (useTypeScript) {
    console.log(
      chalk.yellow(
        'The --typescript option has been deprecated and will be removed in a future release.'));console.log(
      chalk.yellow(
        `In future, please use ${chalk.cyan('--template typescript')}. `));console.log();
    if(! template) { template ='typescript'; }}Copy the code

Start creating the project

The createApp method is called to create the project. Node version >=8.10.0 is required

createApp

The createApp method is called first

createApp(
  projectName, // Project name
  program.verbose, // --verbose
  program.scriptsVersion, // --scripts-version
  program.template, // --template
  program.useNpm, // --use-npm
  program.usePnp, // --use-pnp
  program.typescript // --typescript
);
Copy the code

Create package. Json

const packageJson = {
    name: appName,
    version: '0.1.0 from'.private: true}; fs.writeFileSync( path.join(root,'package.json'),
    JSON.stringify(packageJson, null.2) + os.EOL
);
Copy the code

If yarn is used, copy the yarn.lock.cached file from the current directory to the root directory of the project and name it yarn.lock

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')); }}Copy the code

run

We then call run to continue creating the new project

/** NPM package semver: a tool library for checking and comparing version numbers */

run(
    root,
    appName,
    version, // scriptsVersion
    verbose,
    originalDirectory,
    template,
    useYarn,
    usePnp
  );
  
Copy the code

Handles react-scripts reference scripts and –template input arguments

// ...
let packageToInstall = 'react-scripts';
// ...
// The dependency collection that will be used
const allDependencies = ['react'.'react-dom', packageToInstall];
Promise.all([
    getInstallPackage(version, originalDirectory),
    getTemplateInstallPackage(template, originalDirectory),
])
Copy the code

GetInstallPackage is called to handle the use of react-scripts.

  • Standard version number: ‘1.2.3’, ‘@x.x.x’, etc., specified to usereact-scriptsThe version of the – >[email protected]
  • Specify a local custom file: ‘file: XXX/XXX ‘and return a local absolute path
  • Alternative paths (for tar.gz or Alternative Paths) : Can be an on-line NPM package that returns this path directly due to default supporttypescriptTemplate, if specifiedscriptsVersionforreact-scripts-ts, there will be a confirmation prompt
/** NPM package inquirer: input/output interactive processing tool */
const scriptsToWarn = [
    {
      name: 'react-scripts-ts'.message: chalk.yellow(
        `The react-scripts-ts package is deprecated. TypeScript is now supported natively in Create React App. You can use the ${chalk.green(
          '--template typescript'
        )}option instead when generating your app to include TypeScript support. Would you like to continue using react-scripts-ts? `),},];for (const script of scriptsToWarn) {
    if (packageToInstall.startsWith(script.name)) {
      return inquirer
        .prompt({
          type: 'confirm'.name: 'useScript'.message: script.message,
          default: false,
        })
        .then(answer= > {
          if(! answer.useScript) { process.exit(0);
          }

          returnpackageToInstall; }); }}Copy the code

Call getTemplateInstallPackage processing – the use of the template

  • Specify the template as a local file starting with file:
  • Links without protocols: / /ortgz|tar.gzCompressed package
  • similar@xxx/xxx/xxxxor@xxxxThe specified path or template name of
   const packageMatch = template.match(/ ^ @ ^ /] + \ /)? (. +) $/);
   const scope = packageMatch[1] | |' ';
   const templateName = packageMatch[2];
    if (
       templateName === templateToInstall ||
       templateName.startsWith(`${templateToInstall}- `)) {// Covers:
       // - cra-template
       // - @SCOPE/cra-template
       // - cra-template-NAME
       // - @SCOPE/cra-template-NAME
       templateToInstall = `${scope}${templateName}`;
     } else if (templateName.startsWith(The '@')) {
       // Covers using @SCOPE only
       templateToInstall = `${templateName}/${templateToInstall}`;
     } else {
       // Covers templates without the `cra-template` prefix:
       // - NAME
       // - @SCOPE/NAME
       templateToInstall = `${scope}${templateToInstall}-${templateName}`;
     }
     // cra-template: This is the official base template for Create React App.
Copy the code

@ XXX /cra-template, @ XXX /cra-template-xxx, crA-template-xxx, crA-template, The two officially specified templates are cra-template-typescript and CRA-template. Template specific content we can go to the official warehouse to view, you can customize or magic change some things

Then get the installation package information after the –scripts-version and –template processing

/** NPM package tem: Used to create temporary files and directories in node.js environment. Hyperquest: Convert HTTP requests to streams of output tar-pack: tar/gz compression or decompression */
Promise.all([
    getPackageInfo(packageToInstall),
    getPackageInfo(templateToInstall),
])

// getPackageInfo is a useful utility function
// Extract package name from tarball url or path.
function getPackageInfo(installPackage) {
  if (installPackage.match(/^.+\.(tgz|tar\.gz)$/)) {
    return getTemporaryDirectory()
      .then(obj= > {
        let stream;
        if (/^http/.test(installPackage)) {
          stream = hyperquest(installPackage);
        } else {
          stream = fs.createReadStream(installPackage);
        }
        return extractStream(stream, obj.tmpdir).then((a)= > obj);
      })
      .then(obj= > {
        const { name, version } = require(path.join(
          obj.tmpdir,
          'package.json'
        ));
        obj.cleanup();
        return { name, version };
      })
      .catch(err= > {
        // The package name could be with or without semver version, e.g. react-scripts-0.2.0-alpha.1.tgz
        // However, this function returns package name only without semver version.
        console.log(
          `Could not extract the package name from the archive: ${err.message}`
        );
        const assumedProjectName = installPackage.match(
          / ^. + \ / (. +?) (? :-\d+.+)? \.(tgz|tar\.gz)$/) [1];
        console.log(
          `Based on the filename, assuming it is "${chalk.cyan( assumedProjectName )}"`
        );
        return Promise.resolve({ name: assumedProjectName });
      });
  } else if (installPackage.startsWith('git+')) {
    // Pull package name out of git urls e.g:
    // git+https://github.com/mycompany/react-scripts.git
    / / git+ssh://github.com/mycompany/react-scripts.git#v1.2.3
    return Promise.resolve({
      name: installPackage.match(/([^/]+)\.git(#.*)? $/) [1]}); }else if (installPackage.match(+ @ /. /)) {
    // Do not match @scope/ when stripping off @version or @tag
    return Promise.resolve({
      name: installPackage.charAt(0) + installPackage.substr(1).split(The '@') [0].version: installPackage.split(The '@') [1]}); }else if (installPackage.match(/^file:/)) {
    const installPackagePath = installPackage.match(/^file:(.*)? $/) [1];
    const { name, version } = require(path.join(
      installPackagePath,
      'package.json'
    ));
    return Promise.resolve({ name, version });
  }
  return Promise.resolve({ name: installPackage });
}

function extractStream(stream, dest) {
  return new Promise((resolve, reject) = > {
    stream.pipe(
      unpack(dest, err => {
        if (err) {
          reject(err);
        } else{ resolve(dest); }})); }); }Copy the code

The main job of the run method is to process the packages provided by –scripts-version and –template, collecting project dependencies

install

The install method is called after the run process collects dependencies

return install(
  root, // Project name
  useYarn,
  usePnp,
  allDependencies,
  verbose,
  isOnline // If YARN is used, dns.lookup Checks whether the registry.yarnpkg.com is normal
)
// Install mainly deals with some command parameter processing before installation and the above collection of dependencies installation
function install(root, useYarn, usePnp, dependencies, verbose, isOnline) {
  return new Promise((resolve, reject) = > {
    let command;
    let args;
    if (useYarn) {
      command = 'yarnpkg';
      args = ['add'.'--exact'];
      if(! isOnline) { args.push('--offline');
      }
      if (usePnp) {
        args.push('--enable-pnp');
      }
      [].push.apply(args, dependencies);
      args.push('--cwd');
      args.push(root);

      if(! isOnline) {console.log(chalk.yellow('You appear to be offline.'));
        console.log(chalk.yellow('Falling back to the local Yarn cache.'));
        console.log(); }}else {
      command = 'npm';
      args = [
        'install'.'--save'.'--save-exact'.'--loglevel'.'error',
      ].concat(dependencies);

      if (usePnp) {
        console.log(chalk.yellow("NPM doesn't support PnP."));
        console.log(chalk.yellow('Falling back to the regular installs.'));
        console.log(); }}if (verbose) {
      args.push('--verbose');
    }

    const child = spawn(command, args, { stdio: 'inherit' });
    child.on('close', code => {
      if(code ! = =0) {
        reject({
          command: `${command} ${args.join(' ')}`});return;
      }
      resolve();
    });
  });
}

Copy the code

After installing dependencies, check whether the react-scripts version matches the current Node version. Check whether the react and react-dom versions are correctly installed. Rewrite the dependencies to package.json

await executeNodeScript(
  {
    cwd: process.cwd(),
    args: nodeArgs,
  },
  [root, appName, verbose, originalDirectory, templateName],
  `
var init = require('${packageName}/scripts/init.js');
init.apply(null, JSON.parse(process.argv[1]));
`
);

function executeNodeScript({ cwd, args }, data, source) {
  return new Promise((resolve, reject) = > {
    const child = spawn(
      process.execPath,
      [...args, '-e', source, The '-'.JSON.stringify(data)],
      { cwd, stdio: 'inherit'}); child.on('close', code => {
      if(code ! = =0) {
        reject({
          command: `node ${args.join(' ')}`});return;
      }
      resolve();
    });
  });
}
Copy the code

After checking for dependencies correctly, init initialization under the provided scripts script will be performed:

  1. forpackage.jsonaddscripts/eslintConfig/browserslistSuch as configuration,
  2. If there isREADME.md, rename itREADME.old.md
  3. Copy the template to the project directory, depending on whether it is usedyarn, the templateREADME.mdThe command is explained to theyarn
  4. Install template dependencies if yestsThe project initializes the associated configuration (verifyTypeScriptSetup)
  5. removenode_modulesThe template
  6. Initialize thegitrelated

At this point, the entire project is created

comb

  1. Initialize scaffold various commands
  2. createpackage.json
  3. Process and validate command parameters and collect dependencies
  4. Pre-installation dependencies
  5. To deal withpackage.jsonthereact/react-domDepend on the version number and verifynodeWhether the version meets requirements
  6. Verify what is providedreact-scriptsDependency, and calls the dependent under the child processreact-scripts/scripts/init.jsTo initialize the project template
  7. To install template dependenciestsInitializes its configuration
  8. Verify the code warehouse, do compatibility processing; If no warehouse is added, initialize itgit

Write in the last

Create-react-app is an example of how to use the command-line interface (CLI). Wen Zheng if there is incorrect place welcome to criticize!