<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 | 輸出當前環境的信息 |
// dirname 爲目錄名,須要添加其餘參數只須要在命令後面相應添加便可 node --inspect-brk index.js dirname
chrome://inspect/#devices
環境 | 版本 |
---|---|
系統 | macOS 10.14.5 |
node | v8.10.0 |
npm | 6.10.2 |
yarn | 1.17.3 |
create-react-app | 3.0.1 |
// 檢查當前環境安裝的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');
/** * 自定義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(); }); }); }