This is the fifth day of my participation in the November Gwen Challenge. Check out the details: The last Gwen Challenge 2021

sequence

Before we start parsing the source code, let’s take a look at CRA. You’ve probably thought about these questions:

Why use NPX instead of global installation?

NPM 5.2.0 provides NPX command. When NPX is called and your $PATH does not have , the package with this name will be automatically installed and executed. After execution, the installed package is cleaned up. Using NPX to perform CRA ensures that the scaffolding you execute is always up to date. About blog.npmjs.org/post/162869 NPX specific document can reference here…

This feature is ideal for things like generators, too. Tools like yeoman or create-react-app only ever get called once in a blue moon. By the time you run them again, they’ll already be far out of date, so you end up having to run an install every time you want to use them anyway.

Once in a blue moon means once in a blue moon.

How is the create-react-app command provided?

The bin property in package.json specifies a file that, when installed globally, will be linked to the global bin, so it can be called from anywhere.

A lot of packages have one or more executable files that they’d like to install into the PATH. npm makes this pretty easy … To use this, supply a bin field in your package.json which is a map of command name to local file name. When this package is installed globally, that file will be linked where global bins go so it is available to run by name.

If multiple commands are provided in a package, you can also specify the directories. Bin property and NPM will consider all files in that directory to be executable.

Why can it be executed with the NPM init command

npm init

… initializer in this case is an npm package named create-

, which will be installed by npx, and then have its main bin executed — presumably creating or updating package.json and running any other initialization-related operations.

NPM init xx is the same as NPX create-xx

#! What is /usr/bin/env node

Please make sure that your file(s) referenced in bin starts with #! /usr/bin/env node, otherwise the scripts are started without the node executable!

The #! It’s called a Shebang. This row is called a Shebang row. The first line of a script usually specifies the interpreter, #! The /usr/bin/env NAME syntax tells the Shell to look for the first matching NAME in the $PATH environment variable.

And worry that Windows doesn’t support Shebang lines. If the package is installed by NPM, the link generated by NPM actually looks like this:

Node execution is specified here.

Once we get this information we can look at it in the same order as the files specified by the bin property in package.json.

create-react-app/index.js

We follow package.json to index.js, which is a very simple file, determine the Node version, and then execute the init method in createreactapp.js.

const currentNodeVersion = process.versions.node;
const semver = currentNodeVersion.split('.');
const major = semver[0];

if (major < 14) {
  console.error(
    'You are running Node ' +
      currentNodeVersion +
      '.\n' +
      'Create React App requires Node 14 or higher. \n' +
      'Please update your version of Node.'
  );
  process.exit(1);
}

const { init } = require('./createReactApp');

init();

Copy the code

Note that earlier versions of Node are not as new as this, and there is a demotion scheme for nodes with earlier versions, which becomes redundant as we iterate.

create-react-app/createReactApp.js

This file is the main code, over 1000 lines, so let’s cut it down a little bit

This part can be deleted. Program.info comes from user input--info, just print some environment information; When there is no projectName, it just logs out and does not affect the main process.

This demotion can also be deleted because the entry determines that node is less than 14 and exits.

Other kinds of logs can also be cut, because console.log does not wrap, so you can see them in the code

console.log(...) ; console.log();Copy the code

init

The first is to parse the user’s input as follows: const program = new commander.Command(packagejson.name)…. option()…. parse(process.argv)

Note that the create-react-app command is not provided by Commander, which parses user input and provides feedback.

This step takes several variables from user input

  • projectName
  • verbose:print additional logs
  • scriptsVersion:use a non-standard version of react-scripts
  • template:specify a template for the created project
  • usePnp

Additional follow-up by (process. The env. Npm_config_user_agent | | “). The indexOf (‘ yarn ‘) to get another important variable useYarn;

The next step is to check the crA version checkForLatestVersion,

checkForLatestVersion

There are two ways to obtain the latest crA version number:

 https
      .get(
        'https://registry.npmjs.org/-/package/create-react-app/dist-tags',
        ...
Copy the code

Use:

execSync('npm view create-react-app version').toString().trim();
Copy the code

Cra version follow semver specification at https://semver.org/lang/zh-CN/

Check whether it is the latest version, not exit. So if NPX is not used to start the scaffolding, it is likely to stop at this stage.

Now, let’s go to the createApp method, which by its name is the main method

createApp

It was a very careful operation at the beginning:

  const root = path.resolve(name);
  const appName = path.basename(root);
Copy the code

This may seem redundant, but it takes into account the case where the input projectName has a path, such as ‘/aa/bb’,’./aa’.

Then it’s time to do some tests:

checkAppName

‘react’, ‘react-dom’, and ‘react-scripts’ are the same names as ‘react’, ‘react-dom’, and ‘react-scripts’.

isSafeToCreateProjectIn

Whether there are files in the directory where the project is to be created. All files except these files will terminate the command because of overwriting risk

Check whether yarn, — CWD, and PNP are supported

Check CWD is from an issue: github.com/facebook/cr… Then you run run

run

This can also be simplified:

Promise.all([getInstallPackage(),getTemplateInstallPackage()])
.then(()=>checkIfOnline())
.then(()=>install())
.then(()=>executeNodeScript())
Copy the code

Determine the packages to install

  • Determine which version of React-Scripts you want to install. Others are also supportedreact-scripts
  • Determine the package and version of template, which can be CRA-template or other supportedtemplate
  • Install react and react-dom

Check the network

Yarn supports offline dependency installation

install

Determine the installation command and parameters, respectively

yarnpkg add --exact --offline --enable-pnp --cwd --verbose ...

npm install --no-audit --save --save-exact --loglevel error --verbose

Spawn (command, args, {stdio: ‘inherit’}); perform

executeNodeScript

This step is to execute the init method in the React-scripts package, which is covered in the next section

Exception handling

Cra also takes care to delete the generated files if something goes wrong during execution

console.log('Aborting installation.'); // On 'exit' we will delete these files from target directory. ... console.log(`Deleting generated file... ${chalk.cyan(file)}`); fs.removeSync(path.join(root, file)); .Copy the code

Note: The VERSION of this series CRA is 4.0.3