本文基于 create-react-app v3.4.1版本

概述 - package.json

  • 先看 package.json,下面👇是完整内容 ```json { “name”: “create-react-app”, “version”: “3.4.1”, “keywords”: [ “react” ], “description”: “Create React apps with no build configuration.”, “repository”: { “type”: “git”, “url”: “https://github.com/facebook/create-react-app.git“, “directory”: “packages/create-react-app” }, “license”: “MIT”, “engines”: { “node”: “>=10” }, “bugs”: { “url”: “https://github.com/facebook/create-react-app/issues“ }, “files”: [ “index.js”, “createReactApp.js”, “yarn.lock.cached” ], “bin”: { “create-react-app”: “./index.js” }, “dependencies”: { “chalk”: “4.1.0”, “commander”: “4.1.0”, “cross-spawn”: “7.0.3”, “envinfo”: “7.5.1”, “fs-extra”: “9.0.1”, “hyperquest”: “2.1.3”, “inquirer”: “7.2.0”, “semver”: “7.3.2”, “tar-pack”: “3.4.1”, “tmp”: “0.2.1”, “validate-npm-package-name”: “3.0.0” } }
  1. - 除去基本描述,我们比较关注的主要有以下两个:
  2. ```json
  3. {
  4. ...
  5. "engines": {
  6. "node": ">=10"
  7. },
  8. ...
  9. "bin": {
  10. "create-react-app": "./index.js"
  11. }
  12. }

其中: engines 指明了运行所需的 node 版本至少为10以上 bin 指明了入口文件为 ./index.js

入口 - index.js

  1. #!/usr/bin/env node
  2. // 指明这个脚本的解释程序为 node,并告知系统在 PATH 中查找 node

详见:#!/usr/bin/env node 到底是什么?

  1. /**
  2. * Copyright (c) 2015-present, Facebook, Inc.
  3. * 版权所有,脸书公司,2015至今
  4. *
  5. * This source code is licensed under the MIT license found in the
  6. * LICENSE file in the root directory of this source tree.
  7. * 源代码遵循 MIT 开源许可,相关文件可以在根目录中找到
  8. */
  9. // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  10. // /!\ DO NOT MODIFY THIS FILE /!\
  11. // /!\ 不要修改这个文件 /!\
  12. // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  13. //
  14. // create-react-app is installed globally on people's computers. This means
  15. // that it is extremely difficult to have them upgrade the version and
  16. // because there's only one global version installed, it is very prone to
  17. // breaking changes.
  18. // create-react-app 在人们的电脑上都是全局安装的,这意味着版本升级会变得非常困难。
  19. // 也正因如此,哪怕仅仅是一点小小的修改也可能对其造成破坏性的改变。
  20. //
  21. // The only job of create-react-app is to init the repository and then
  22. // forward all the commands to the local version of create-react-app.
  23. // create-react-app 的唯一任务就是初始化一个仓库,
  24. // 然后按步执行本地版本的 create-react-app 中的每一条命令。
  25. //
  26. // If you need to add a new command, please add it to the scripts/ folder.
  27. // 如果你需要添加一条命令,请把它加在 scripts/ 文件夹中。
  28. //
  29. // The only reason to modify this file is to add more warnings and
  30. // troubleshooting information for the `create-react-app` command.
  31. // 只有当你想要在 `create-react-app` 命令行中添加一些警告或者错误提示信息的情况下,
  32. // 才应该考虑修改本文件。
  33. //
  34. // Do not make breaking changes! We absolutely don't want to have to
  35. // tell people to update their global version of create-react-app.
  36. // 不要擅自改动!我们完全不希望不得不让人们来升级 create-react-app 的版本
  37. //
  38. // Also be careful with new language features.
  39. // This file must work on Node 0.10+.
  40. // 还需注意的是新的语言特性
  41. // 这个文件必须运行在 node 10+ 版本
  42. //
  43. // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  44. // /!\ DO NOT MODIFY THIS FILE /!\
  45. // /!\ 不要修改这个文件 /!\
  46. // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

主要是一些提示信息,总结起来基本都是在说:不要修改这个文件!!!

  1. 'use strict';
  2. // 使用严格模式

详见:严格模式 - MDN

  1. var currentNodeVersion = process.versions.node;
  2. // 获取 node 的版本

其中 process.versions 返回的是一个对象,包含了 node 及相关依赖的版本信息(字符串形式) 详见nodejs - process.versions 例如我在 node 环境下输入 console.log(process.versions),打印值如下:

image.png

所以 process.versions.node 可以获取到 nodejs 的版本号(字符串形式)

  1. var semver = currentNodeVersion.split('.');
  2. var major = semver[0];
  3. // 获取版本号第一个数字,例如,我的node版本是 v10.19.0,major的值即为10

major 的值用于后续判断 create-react-app 是否可以执行

  1. if (major < 10) {
  2. console.error(
  3. 'You are running Node ' +
  4. currentNodeVersion +
  5. '.\n' +
  6. 'Create React App requires Node 10 or higher. \n' +
  7. 'Please update your version of Node.'
  8. );
  9. process.exit(1);
  10. }
  11. // 如果node的版本小于10,会输出错误信息并终止程序执行。

❌ 错误提示信息:

  • 你的node版本为x.x.x,
  • 运行create-react-app需要node10或更新版本
  • 请升级你node的版本
  1. require('./createReactApp');

运行 ./createReactApp.js

核心 - createReactApp.js

提示信息

  • 经过 index.js 中版本确认后,开始执行关键代码构建脚手架了。
    1. /**
    2. * 提示信息,此处省略1409个字符
    3. * /

    刚开始依旧是大段的提示信息

严格模式

  1. 'use strict';
  2. // 熟悉的严格模式

引入依赖

  • 接下来是大量依赖的引入,对于第三方库,这里给出对应的 github 地址。
    1. const chalk = require('chalk');

    美化文字样式,详见:chalk

  1. const commander = require('commander');

node.js 命令行接口的完整解决方案,详见:commander

  1. const envinfo = require('envinfo');

envinfo:开发环境的信息(系统、编程语言、数据库等),详见:envinfo

  1. const fs = require('fs-extra');

fs-extra:扩展文件系统能力,详见:node-fs-extra

  1. const hyperquest = require('hyperquest');

hyperquest:优化HTTP请求,详见:hyperquest

  1. const inquirer = require('inquirer');

inquirer:提升命令行交互能力,详见:inquirer

  1. const semver = require('semver');

semver:语义化版本号,详见:semver

  1. const spawn = require('cross-spawn');

cross-spawn:跨平台执行子进程的工具库,详见:node-cross-spawn

  1. const tmp = require('tmp');

tmp:用于生成临时文件或目录,详见:node-tmp

  1. const unpack = require('tar-pack').unpack;

tar-pack:压缩&解压缩工具,详见:tar-pack

  1. const validateProjectName = require('validate-npm-package-name');

validate-npm-package-name:验证一个字符串是否为npm包名,详见:validate-npm-package-name

  • 其他几个为内部定义的模块
    1. const dns = require('dns');
    2. const execSync = require('child_process').execSync;
    3. const os = require('os');
    4. const path = require('path');
    5. const url = require('url');

定义关键数据

  • 接下来定义了几个比较关键的数据 ```javascript const packageJson = require(‘./package.json’); // 获取到 package.json 中的信息

let projectName; // 一会可能要用到的由用户定义的项目名称

const program = new commander.Command(packageJson.name) // Command 对象是 node.js 中 EventEmitter 的扩展

  1. - `program` 后面还接了一大堆东西,就是当我们执行 `create-react-app` 时对不同参数的处理。
  2. ```javascript
  3. const program = new commander.Command(packageJson.name)
  4. // --version
  5. .version(packageJson.version)
  6. // 使用自定义项目名新建一个项目
  7. .arguments('<project-directory>')
  8. .usage(`${chalk.green('<project-directory>')} [options]`)
  9. .action(name => {
  10. projectName = name;
  11. })
  12. .option('--verbose', 'print additional logs')
  13. // --info 查看系统信息
  14. .option('--info', 'print environment debug info')
  15. .option(
  16. '--scripts-version <alternative-package>',
  17. 'use a non-standard version of react-scripts'
  18. )
  19. .option(
  20. '--template <path-to-template>',
  21. 'specify a template for the created project'
  22. )
  23. .option('--use-npm')
  24. .option('--use-pnp')
  25. .allowUnknownOption()
  26. // --help
  27. .on('--help', () => {
  28. console.log(` Only ${chalk.green('<project-directory>')} is required.`);
  29. console.log();
  30. console.log(
  31. ` A custom ${chalk.cyan('--scripts-version')} can be one of:`
  32. );
  33. console.log(` - a specific npm version: ${chalk.green('0.8.2')}`);
  34. console.log(` - a specific npm tag: ${chalk.green('@next')}`);
  35. console.log(
  36. ` - a custom fork published on npm: ${chalk.green(
  37. 'my-react-scripts'
  38. )}`
  39. );
  40. console.log(
  41. ` - a local path relative to the current working directory: ${chalk.green(
  42. 'file:../my-react-scripts'
  43. )}`
  44. );
  45. console.log(
  46. ` - a .tgz archive: ${chalk.green(
  47. 'https://mysite.com/my-react-scripts-0.8.2.tgz'
  48. )}`
  49. );
  50. console.log(
  51. ` - a .tar.gz archive: ${chalk.green(
  52. 'https://mysite.com/my-react-scripts-0.8.2.tar.gz'
  53. )}`
  54. );
  55. console.log(
  56. ` It is not needed unless you specifically want to use a fork.`
  57. );
  58. console.log();
  59. console.log(` A custom ${chalk.cyan('--template')} can be one of:`);
  60. console.log(
  61. ` - a custom template published on npm: ${chalk.green(
  62. 'cra-template-typescript'
  63. )}`
  64. );
  65. console.log(
  66. ` - a local path relative to the current working directory: ${chalk.green(
  67. 'file:../my-custom-template'
  68. )}`
  69. );
  70. console.log(
  71. ` - a .tgz archive: ${chalk.green(
  72. 'https://mysite.com/my-custom-template-0.8.2.tgz'
  73. )}`
  74. );
  75. console.log(
  76. ` - a .tar.gz archive: ${chalk.green(
  77. 'https://mysite.com/my-custom-template-0.8.2.tar.gz'
  78. )}`
  79. );
  80. console.log();
  81. console.log(
  82. ` If you have any problems, do not hesitate to file an issue:`
  83. );
  84. console.log(
  85. ` ${chalk.cyan(
  86. 'https://github.com/facebook/create-react-app/issues/new'
  87. )}`
  88. );
  89. console.log();
  90. })
  91. .parse(process.argv);

这里其实有用到 jQuery 中的链式调用的思想,每次调用方法都会返回一个 Command 对象。

当我们执行 create-react-app —help 时,会打印出上述一大段的 console.log,提示我们可以使用的参数,基本都可以在代码中找到,--version--verbose--script-version--template--use-npm--use-pnp--help

image.png

两个判断

第一个判断(info)

  • 接下来是两个判断,首先如果我们输入的是 create-react-app --info,会按照固定格式打印出系统信息
    1. if (program.info) {
    2. console.log(chalk.bold('\nEnvironment Info:'));
    3. console.log(
    4. `\n current version of ${packageJson.name}: ${packageJson.version}`
    5. );
    6. console.log(` running from ${__dirname}`);
    7. return envinfo
    8. .run(
    9. {
    10. System: ['OS', 'CPU'],
    11. Binaries: ['Node', 'npm', 'Yarn'],
    12. Browsers: ['Chrome', 'Edge', 'Internet Explorer', 'Firefox', 'Safari'],
    13. npmPackages: ['react', 'react-dom', 'react-scripts'],
    14. npmGlobalPackages: ['create-react-app'],
    15. },
    16. {
    17. duplicates: true,
    18. showNotFound: true,
    19. }
    20. )
    21. .then(console.log);
    22. }

    例如,当我输入 create-react-app —info 时,控制台输出的信息如下:

image.png

可以看到,同代码中的一模一样,打印出了 create-react-app版本、System信息、node相关信息、浏览器信息、react相关包信息、create-react-app相关信息(可能因为我是用yarn安装的所以没找到?)

第二个判断(projectName 项目名)

  • 判断我们是否给出了项目名,如果没给出,会输出错误提示并退出。
    1. if (typeof projectName === 'undefined') {
    2. console.error('Please specify the project directory:');
    3. console.log(
    4. ` ${chalk.cyan(program.name())} ${chalk.green('<project-directory>')}`
    5. );
    6. console.log();
    7. console.log('For example:');
    8. console.log(` ${chalk.cyan(program.name())} ${chalk.green('my-react-app')}`);
    9. console.log();
    10. console.log(
    11. `Run ${chalk.cyan(`${program.name()} --help`)} to see all options.`
    12. );
    13. process.exit(1);
    14. }
    image.png

开始创建(createApp)

调用 createApp()

  • 如果我们按照格式给出了项目名,那么接下来就开始创建App。
    1. createApp(
    2. projectName, // 用户定义的项目名
    3. program.verbose, // 额外日志
    4. program.scriptsVersion, // 使用其他的 react-scripts
    5. program.template, // 明确模板
    6. program.useNpm, // 使用npm
    7. program.usePnp // 使用pnpm?
    8. );

createApp() 函数概述

  • createApp 函数的主体如下

    1. function createApp(name, verbose, version, template, useNpm, usePnp) {
    2. ...
    3. }
  • 由于 createApp 函数代码比较长,我们抛弃一些细节,来试图分段理解一下。

1. 检查node版本

  1. const unsupportedNodeVersion = !semver.satisfies(process.version, '>=10');
  2. if (unsupportedNodeVersion) {
  3. console.log(
  4. chalk.yellow(
  5. `You are using Node ${process.version} so the project will be bootstrapped with an old unsupported version of tools.\n\n` +
  6. `Please update to Node 10 or higher for a better, fully supported experience.\n`
  7. )
  8. );
  9. // Fall back to latest supported react-scripts on Node 4
  10. version = 'react-scripts@0.9.x';
  11. }

2. 检查用户定义的项目名是否符合要求

  1. const root = path.resolve(name);
  2. const appName = path.basename(root);
  3. checkAppName(appName);
  4. fs.ensureDirSync(name);
  5. if (!isSafeToCreateProjectIn(root, name)) {
  6. process.exit(1);
  7. }
  8. console.log();

其中,值得注意的是 fs 作为文件读写的一个接口 另外一个值得注意的是 checkAppName() 这个函数,在800行左右可以看到它的源码如下。

  1. function checkAppName(appName) {
  2. const validationResult = validateProjectName(appName);
  3. if (!validationResult.validForNewPackages) {
  4. console.error(
  5. chalk.red(
  6. `Cannot create a project named ${chalk.green(
  7. `"${appName}"`
  8. )} because of npm naming restrictions:\n`
  9. )
  10. );
  11. [
  12. ...(validationResult.errors || []),
  13. ...(validationResult.warnings || []),
  14. ].forEach(error => {
  15. console.error(chalk.red(` * ${error}`));
  16. });
  17. console.error(chalk.red('\nPlease choose a different project name.'));
  18. process.exit(1);
  19. }
  20. // TODO: there should be a single place that holds the dependencies
  21. const dependencies = ['react', 'react-dom', 'react-scripts'].sort();
  22. if (dependencies.includes(appName)) {
  23. console.error(
  24. chalk.red(
  25. `Cannot create a project named ${chalk.green(
  26. `"${appName}"`
  27. )} because a dependency with the same name exists.\n` +
  28. `Due to the way npm works, the following names are not allowed:\n\n`
  29. ) +
  30. chalk.cyan(dependencies.map(depName => ` ${depName}`).join('\n')) +
  31. chalk.red('\n\nPlease choose a different project name.')
  32. );
  33. process.exit(1);
  34. }
  35. }

可以看到,基本就是做了两个判断

  • 一是判断用户自定义项目名是否符合命名规则,如果不符合,就会给出错误提示并退出程序:

image.png

  • 二是判断用户自定义的项目名是否和依赖冲突,如果冲突,会给出错误提示并退出程序:

image.png

3. 提示开始创建

  • 打印一行信息,告诉我们创建要开始了。
    1. console.log(`Creating a new React app in ${chalk.green(root)}.`);
    2. console.log();

4. 初始化 package.json

  • 初始化一些基本信息,例如 name(项目名)、version(0.1.0)、private(true),我们可以在生成的 cra-test 项目根目录下找到 package.json ,其中最开始几行就是这里初始化的。 ```javascript // 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 );

  1. <a name="5dZ2e"></a>
  2. #### 5. yarn? npm? pnpm?
  3. - 接下来是判断到底使用 yarn、npm、pnpm中的哪一个,此处的细节就不去深究了。我使用的是 `yarn`。
  4. ```javascript
  5. const useYarn = useNpm ? false : shouldUseYarn();
  6. const originalDirectory = process.cwd();
  7. process.chdir(root);
  8. if (!useYarn && !checkThatNpmCanReadCwd()) {
  9. process.exit(1);
  10. }
  11. if (!useYarn) {
  12. const npmInfo = checkNpmVersion();
  13. if (!npmInfo.hasMinNpm) {
  14. if (npmInfo.npmVersion) {
  15. console.log(
  16. chalk.yellow(
  17. `You are using npm ${npmInfo.npmVersion} so the project will be bootstrapped with an old unsupported version of tools.\n\n` +
  18. `Please update to npm 6 or higher for a better, fully supported experience.\n`
  19. )
  20. );
  21. }
  22. // Fall back to latest supported react-scripts for npm 3
  23. version = 'react-scripts@0.9.x';
  24. }
  25. } else if (usePnp) {
  26. const yarnInfo = checkYarnVersion();
  27. if (yarnInfo.yarnVersion) {
  28. if (!yarnInfo.hasMinYarnPnp) {
  29. console.log(
  30. chalk.yellow(
  31. `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` +
  32. `Please update to Yarn 1.12 or higher for a better, fully supported experience.\n`
  33. )
  34. );
  35. // 1.11 had an issue with webpack-dev-middleware, so better not use PnP with it (never reached stable, but still)
  36. usePnp = false;
  37. }
  38. if (!yarnInfo.hasMaxYarnPnp) {
  39. console.log(
  40. chalk.yellow(
  41. 'The --use-pnp flag is no longer necessary with yarn 2 and will be deprecated and removed in a future release.\n'
  42. )
  43. );
  44. // 2 supports PnP by default and breaks when trying to use the flag
  45. usePnp = false;
  46. }
  47. }
  48. }
  49. if (useYarn) {
  50. let yarnUsesDefaultRegistry = true;
  51. try {
  52. yarnUsesDefaultRegistry =
  53. execSync('yarnpkg config get registry').toString().trim() ===
  54. 'https://registry.yarnpkg.com';
  55. } catch (e) {
  56. // ignore
  57. }
  58. if (yarnUsesDefaultRegistry) {
  59. fs.copySync(
  60. require.resolve('./yarn.lock.cached'),
  61. path.join(root, 'yarn.lock')
  62. );
  63. }
  64. }

如果使用的是 yarn,会生成 yarn.lock 文件。

6. 执行 run() 函数(核心逻辑)

  • run() 函数应该是整个 create-react-app.js 的核心,其中用到了大量的 Promise,Promise 我目前理解地不太深入,所以打算先简单介绍一下,后续再回来补充。
  • run() 最重要的目的就是安装各种 packages,我们通过 run() 对其它函数的调用粗略地看一下整个过程。

run() 函数(核心中的核心)

  1. Promise.all([
  2. getInstallPackage(version, originalDirectory),
  3. getTemplateInstallPackage(template, originalDirectory),
  4. ]).then(([packageToInstall, templateToInstall]) => {
  5. const allDependencies = ['react', 'react-dom', packageToInstall];
  6. console.log('Installing packages. This might take a couple of minutes.');
  7. Promise.all([
  8. // 获取包名
  9. getPackageInfo(packageToInstall),
  10. getPackageInfo(templateToInstall),
  11. ])
  12. .then(([packageInfo, templateInfo]) =>
  13. checkIfOnline(useYarn).then(isOnline => ({
  14. // 检查是否在线
  15. isOnline,
  16. packageInfo,
  17. templateInfo,
  18. }))
  19. )
  20. .then(({ isOnline, packageInfo, templateInfo }) => {
  21. let packageVersion = semver.coerce(packageInfo.version);
  22. const templatesVersionMinimum = '3.3.0';
  23. // Assume compatibility if we can't test the version.
  24. // 查看npm包是否存在
  25. if (!semver.valid(packageVersion)) {
  26. packageVersion = templatesVersionMinimum;
  27. }
  28. // Only support templates when used alongside new react-scripts versions.
  29. const supportsTemplates = semver.gte(
  30. packageVersion,
  31. templatesVersionMinimum
  32. );
  33. if (supportsTemplates) {
  34. allDependencies.push(templateToInstall);
  35. } else if (template) {
  36. console.log('');
  37. console.log(
  38. `The ${chalk.cyan(packageInfo.name)} version you're using ${
  39. packageInfo.name === 'react-scripts' ? 'is not' : 'may not be'
  40. } compatible with the ${chalk.cyan('--template')} option.`
  41. );
  42. console.log('');
  43. }
  44. console.log(
  45. `Installing ${chalk.cyan('react')}, ${chalk.cyan(
  46. 'react-dom'
  47. )}, and ${chalk.cyan(packageInfo.name)}${
  48. supportsTemplates ? ` with ${chalk.cyan(templateInfo.name)}` : ''
  49. }...`
  50. );
  51. console.log();
  52. // 开始安装进程
  53. return install(
  54. root,
  55. useYarn,
  56. usePnp,
  57. allDependencies,
  58. verbose,
  59. isOnline
  60. ).then(() => ({
  61. packageInfo,
  62. supportsTemplates,
  63. templateInfo,
  64. }));
  65. })
  66. .then(async ({ packageInfo, supportsTemplates, templateInfo }) => {
  67. const packageName = packageInfo.name;
  68. const templateName = supportsTemplates ? templateInfo.name : undefined;
  69. checkNodeVersion(packageName);
  70. setCaretRangeForRuntimeDeps(packageName);
  71. const pnpPath = path.resolve(process.cwd(), '.pnp.js');
  72. const nodeArgs = fs.existsSync(pnpPath) ? ['--require', pnpPath] : [];
  73. await executeNodeScript(
  74. {
  75. cwd: process.cwd(),
  76. args: nodeArgs,
  77. },
  78. [root, appName, verbose, originalDirectory, templateName],
  79. `
  80. var init = require('${packageName}/scripts/init.js');
  81. init.apply(null, JSON.parse(process.argv[1]));
  82. `
  83. );
  84. if (version === 'react-scripts@0.9.x') {
  85. console.log(
  86. chalk.yellow(
  87. `\nNote: the project was bootstrapped with an old unsupported version of tools.\n` +
  88. `Please update to Node >=10 and npm >=6 to get supported tools in new projects.\n`
  89. )
  90. );
  91. }
  92. })
  93. .catch(reason => {
  94. console.log();
  95. console.log('Aborting installation.');
  96. if (reason.command) {
  97. console.log(` ${chalk.cyan(reason.command)} has failed.`);
  98. } else {
  99. console.log(
  100. chalk.red('Unexpected error. Please report it as a bug:')
  101. );
  102. console.log(reason);
  103. }
  104. console.log();
  105. // On 'exit' we will delete these files from target directory.
  106. const knownGeneratedFiles = [
  107. 'package.json',
  108. 'yarn.lock',
  109. 'node_modules',
  110. ];
  111. const currentFiles = fs.readdirSync(path.join(root));
  112. currentFiles.forEach(file => {
  113. knownGeneratedFiles.forEach(fileToMatch => {
  114. // This removes all knownGeneratedFiles.
  115. if (file === fileToMatch) {
  116. console.log(`Deleting generated file... ${chalk.cyan(file)}`);
  117. fs.removeSync(path.join(root, file));
  118. }
  119. });
  120. });
  121. const remainingFiles = fs.readdirSync(path.join(root));
  122. if (!remainingFiles.length) {
  123. // Delete target folder if empty
  124. console.log(
  125. `Deleting ${chalk.cyan(`${appName}/`)} from ${chalk.cyan(
  126. path.resolve(root, '..')
  127. )}`
  128. );
  129. process.chdir(path.resolve(root, '..'));
  130. fs.removeSync(path.join(root));
  131. }
  132. console.log('Done.');
  133. process.exit(1);
  134. });
  135. });

【未完待续】