<h1>create-react-app的使用</h1>javascript
參數解析
參數<br/> |
參數解析 |
默認值 |
是否必填 |
<project-directory> |
項目文件夾名 |
無 |
是 |
verbose |
會打印執行(包括依賴包的安裝信息等) 的詳細信息 |
false |
否 |
info |
打印環境的信息(系統、cpu、npm、yarn、chrome、react等) |
false |
否 |
use-npm |
是否使用npm |
false,默認使用yarn |
否 |
use-pnp |
yarn解決node_modules的一種解決方案,將node_modules放在全局緩存,不放在單獨項目中 |
false |
否 |
typescript |
是否使用typescript |
false |
否 |
internal-testing-template |
這個參數的值將替代react-script中的template目錄,通常用於開發者使用 |
false |
|
scripts-version |
直接傳入版本號<br/> --scripts-version=@6.10.2 |
react-scripts |
否 |
npm方式 <br/> --scripts-version=douchi-react-script <br/> |
git+https方式 <br/> --scripts-version=git+https://github.com/mcfly001/d... <br/> |
git+ssh方式 <br/> --scripts-version=git+ssh://git@github.com:mcfly001/douchi-react-script.git <br/> |
file + url方式 <br/> --scripts-version=file:../file-react-script |
壓縮包的方式(以tgz|tar.gz結尾)<br/> --scripts-version=./file-react-script.tar.gz |
主要依賴包做用分析
包 |
包做用 |
validate-npm-package-name |
判斷給定的字符串是否是符合npm包名稱的規範 |
chalk |
給命令行輸入的log設置顏色 |
commander |
TJ寫的,自定義shell命令的工具,具體使用能夠查看commander |
semver |
用於版本比較的工具,好比哪一個版本大,版本命名是否合法等 |
envinfo |
輸出當前環境的信息 |
如何斷點調試
經過vscode(推薦使用)

- 經過上述操做便可斷點調試create-react-app
經過chrome
- 進入到packages/create-react-app目錄
- 執行如下命令
// dirname 爲目錄名,須要添加其餘參數只須要在命令後面相應添加便可
node --inspect-brk index.js dirname
chrome://inspect/#devices
- 點擊Remote Target下面的inspect便可
代碼分析
環境 |
版本 |
系統 |
macOS 10.14.5 |
node |
v8.10.0 |
npm |
6.10.2 |
yarn |
1.17.3 |
create-react-app |
3.0.1 |
入口文件在packages/create-react-app/index.js
// 檢查當前環境安裝的node版本,若是版本是小於8.0.0就報錯須要安裝更高的node版本,同時結束進程。若是符合條件接下來正式進入create-react-app
'use strict';
var currentNodeVersion = process.versions.node;
var semver = currentNodeVersion.split('.');
var major = semver[0];
if (major < 8) {
console.error(
'You are running Node ' +
currentNodeVersion +
'.\n' +
'Create React App requires Node 8 or higher. \n' +
'Please update your version of Node.'
);
process.exit(1);
}
require('./createReactApp');
如下代碼在 packages/create-react-app/createReactApp.js
/**
* 自定義create-react-app 這個shell命令
* 首先從同級目錄的package.json中獲取字段name,即create-react-app)做爲命令名稱
* 同時將package.json中的version做爲該命令的版本號
* 其餘參數見上文詳解
*/
let projectName;
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('--use-npm')
.option('--use-pnp')
.option('--typescript')
.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(
` 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);
// 若是crete-react-app中傳入了-info,就將系統信息、npm、node、chrom、react等基本信息輸出
if (program.info) {
console.log(chalk.bold('\nEnvironment Info:'));
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);
}
/**
* 檢查是否有<project-directory>這個參數,若是沒有就輸入必填提示,同時結束進程
*/
if (typeof projectName === 'undefined') {
console.error('Please specify the project directory:');
console.log(
` ${chalk.cyan(program.name())} ${chalk.green('<project-directory>')}`
);
console.log();
console.log('For example:');
console.log(` ${chalk.cyan(program.name())} ${chalk.green('my-react-app')}`);
console.log();
console.log(
`Run ${chalk.cyan(`${program.name()} --help`)} to see all options.`
);
process.exit(1);
}
function printValidationResults(results) {
if (typeof results !== 'undefined') {
results.forEach(error => {
console.error(chalk.red(` * ${error}`));
});
}
}
/**
* 在create-react-app 中還有一個參數--internal-testing-template
* 該參數用於替換react-scripts中的template
*/
const hiddenProgram = new commander.Command()
.option(
'--internal-testing-template <path-to-template>',
'(internal usage only, DO NOT RELY ON THIS) ' +
'use a non-standard application template'
)
.parse(process.argv);
接下來正式進入create-react-app的具體內容java
function createApp(
name,
verbose,
version,
useNpm,
usePnp,
useTypescript,
template
) {
const root = path.resolve(name);
const appName = path.basename(root);
// 判斷項目名是否是符合規範
checkAppName(appName);
// 建立文件夾
fs.ensureDirSync(name);
// 判斷當前目錄下是否是存在除了(.git、doc、DS_Store等文件)外的文件
if (!isSafeToCreateProjectIn(root, name)) {
process.exit(1);
}
console.log(`Creating a new React app in ${chalk.green(root)}.`);
console.log();
// 生成package.json文件
const packageJson = {
name: appName,
version: '0.1.0',
private: true,
};
fs.writeFileSync(
path.join(root, 'package.json'),
JSON.stringify(packageJson, null, 2) + os.EOL
);
const useYarn = useNpm ? false : shouldUseYarn();
const originalDirectory = process.cwd();
process.chdir(root);
// 檢測npm是否在正確目錄下執行
if (!useYarn && !checkThatNpmCanReadCwd()) {
process.exit(1);
}
// 判斷node 版本是否是小於8.10.0, 若是是就會警告,同時react-scripts包會變成0.9.x
if (!semver.satisfies(process.version, '>=8.10.0')) {
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) {
// 返回一個對象{hasMinNpm: '是否大於5.0.0', npmVersion: '具體版本信息'}
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) {
// 返回一個對象{hasMinYarnPnp: '是否大於1.12.0',yarnVersion: 'yarn的具體版本'}
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;
}
}
//若是默認yarn的代理就會生成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')
);
}
}
// 安裝箇中依賴
run(
root,
appName,
version,
verbose,
originalDirectory,
template,
useYarn,
usePnp,
useTypescript
);
}
function run(
root,
appName,
version,
verbose,
originalDirectory,
template,
useYarn,
usePnp,
useTypescript
) {
// 獲取npm或者yarn關於react-scripts的安裝的地址
getInstallPackage(version, originalDirectory).then(packageToInstall => {
const allDependencies = ['react', 'react-dom', packageToInstall];
if (useTypescript) {
allDependencies.push(
// TODO: get user's node version instead of installing latest
'@types/node',
'@types/react',
'@types/react-dom',
// TODO: get version of Jest being used instead of installing latest
'@types/jest',
'typescript'
);
}
console.log('Installing packages. This might take a couple of minutes.');
// 獲取安裝的包名稱
getPackageName(packageToInstall)
.then(packageName =>
checkIfOnline(useYarn).then(isOnline => ({
isOnline: isOnline,
packageName: packageName,
}))
)
.then(info => {
const isOnline = info.isOnline;
const packageName = info.packageName;
console.log(
`Installing ${chalk.cyan('react')}, ${chalk.cyan(
'react-dom'
)}, and ${chalk.cyan(packageName)}...`
);
console.log();
// 安裝依賴
return install(
root,
useYarn,
usePnp,
allDependencies,
verbose,
isOnline
).then(() => packageName);
})
.then(async packageName => {
// 判斷node_modules下面全部包中對於node版本的最低要求
checkNodeVersion(packageName);
// 給package.json中的依賴添加^
setCaretRangeForRuntimeDeps(packageName);
const pnpPath = path.resolve(process.cwd(), '.pnp.js');
const nodeArgs = fs.existsSync(pnpPath) ? ['--require', pnpPath] : [];
// 安裝依賴同時copy react-scripts下面的template到當前目錄
await executeNodeScript(
{
cwd: process.cwd(),
args: nodeArgs,
},
[root, appName, verbose, originalDirectory, template],
`
var init = require('${packageName}/scripts/init.js');
console.log(33333)
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 >=8.10 and npm >=5 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',
'yarn.lock',
'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. 安裝依賴(爲何要先安裝依賴,由於第二步,要否則找不到react-scripts中的scripts中的init.js文件)
* 2. 在依賴的node_modules中找到react-scripts中的scripts裏面的init.js並執行其對外的方法
* 2.1 修改package.json,寫入一些start、build、test、eject 方法
* 2.2 修改README.md文件,輸入一些幫助信息
* 2.3 copy template中的文件到項目文件夾下面
* 2.4 從新install一下
*/
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();
});
});
}
