走读 CRA(create react app 4.0.3) - yarn eject 命令

create react app将webpack配置通过react-scripts封装了起来。
如果想自定义webpack操作就需要ejct(或者其他插件),eject 就是将原来封装在react-scripts的webpack配置重新展示在项目里,这个过程不可逆。
 
// Makes the script crash on unhandled rejections instead of silently
// ignoring them. In the future, promise rejections that are not handled will
// terminate the Node.js process with a non-zero exit code.
process.on('unhandledRejection', err => {
  throw err;
});
使脚本在未处理的reject上崩溃,而不是默默地忽略它们。
将来,未经处理的reject promise将终止Node.js进程。
 
const fs = require('fs-extra'); // node中fs的扩展,在支持fs所有api的基础上,还支持promise写法
const path = require('path'); // 获取目录模块
const execSync = require('child_process').execSync; // 执行同步命令
const chalk = require('react-dev-utils/chalk'); // 修改log字体颜色
const paths = require('../config/paths'); // 对于路径的处理
const createJestConfig = require('./utils/createJestConfig'); // 创建单元测试配置
const inquirer = require('react-dev-utils/inquirer'); // 常用交互式命令行用户界面的集合
const spawnSync = require('react-dev-utils/crossSpawn').sync; // 跨平台执行系统命令
const os = require('os'); // 操作系统的方法

const green = chalk.green; // 绿色
const cyan = chalk.cyan; // 青色
 

/**
 * 获取git状态
 */
function getGitStatus() {
  try {
    let stdout = execSync(`git status --porcelain`, {
      stdio: ['pipe', 'pipe', 'ignore'],
    }).toString();
    return stdout.trim();
  } catch (e) {
    return '';
  }
}
/**
 * 提交根目录下config和scripts文件夹下修改或者更新的内容,但是不包括删除部分
 */
function tryGitAdd(appPath) {
  try {
    spawnSync(
      'git',
      ['add', path.join(appPath, 'config'), path.join(appPath, 'scripts')],
      {
        stdio: 'inherit',
      }
    );

    return true;
  } catch (e) {
    return false;
  }
}
// 一段提示信息,说明一下不需要eject也支持ts、sass、css
console.log(
  chalk.cyan.bold(
    'NOTE: Create React App 2+ supports TypeScript, Sass, CSS Modules and more without ejecting: ' +
      'https://reactjs.org/blog/2018/10/01/create-react-app-v2.html'
  )
);
console.log();
/**
 * 确认是否eject,提示过程不可逆
 * 否退出程序
 * 是,
 * 1.先检查['config', 'config/jest', 'scripts']目录下的文件git状态,再复制文件
 * 2.更新package dependencies依赖,scripts命令
 * 3.添加Jtest、babel、ESlint配置
 * 4.remove react-scripts and react-scripts binaries from app node_modules
 * 5.yarn or npm 加载包
 */
prompts({
  type: 'confirm',
  name: 'shouldEject',
  message: 'Are you sure you want to eject? This action is permanent.',
  initial: false,
}).then(answer => {
  if (!answer.shouldEject) {
    console.log(cyan('Close one! Eject aborted.'));
    return;
  }

  const gitStatus = getGitStatus();
  if (gitStatus) {
    console.error(
      chalk.red(
        'This git repository has untracked files or uncommitted changes:'
      ) +
        '\n\n' +
        gitStatus
          .split('\n')
          .map(line => line.match(/ .*/g)[0].trim())
          .join('\n') +
        '\n\n' +
        chalk.red(
          'Remove untracked files, stash or commit any changes, and try again.'
        )
    );
    process.exit(1);
  }

  console.log('Ejecting...');

  const ownPath = paths.ownPath;
  const appPath = paths.appPath;

  function verifyAbsent(file) {
    if (fs.existsSync(path.join(appPath, file))) {
      console.error(
        `\`${file}\` already exists in your app folder. We cannot ` +
          'continue as you would lose all the changes in that file or directory. ' +
          'Please move or delete it (maybe make a copy for backup) and run this ' +
          'command again.'
      );
      process.exit(1);
    }
  }

  const folders = ['config', 'config/jest', 'scripts'];

  // Make shallow array of files paths
  const files = folders.reduce((files, folder) => {
    return files.concat(
      fs
        .readdirSync(path.join(ownPath, folder))
        // set full path
        .map(file => path.join(ownPath, folder, file))
        // omit dirs from file list
        .filter(file => fs.lstatSync(file).isFile())
    );
  }, []);

  // Ensure that the app folder is clean and we won't override any files
  folders.forEach(verifyAbsent);
  files.forEach(verifyAbsent);

  // Prepare Jest config early in case it throws
  const jestConfig = createJestConfig(
    filePath => path.posix.join('<rootDir>', filePath),
    null,
    true
  );

  console.log();
  console.log(cyan(`Copying files into ${appPath}`));

  folders.forEach(folder => {
    fs.mkdirSync(path.join(appPath, folder));
  });

  files.forEach(file => {
    let content = fs.readFileSync(file, 'utf8');

    // Skip flagged files
    if (content.match(/\/\/ @remove-file-on-eject/)) {
      return;
    }
    content =
      content
        // Remove dead code from .js files on eject
        .replace(
          /\/\/ @remove-on-eject-begin([\s\S]*?)\/\/ @remove-on-eject-end/gm,
          ''
        )
        // Remove dead code from .applescript files on eject
        .replace(
          /-- @remove-on-eject-begin([\s\S]*?)-- @remove-on-eject-end/gm,
          ''
        )
        .trim() + '\n';
    console.log(`  Adding ${cyan(file.replace(ownPath, ''))} to the project`);
    fs.writeFileSync(file.replace(ownPath, appPath), content);
  });
  console.log();

  const ownPackage = require(path.join(ownPath, 'package.json'));
  const appPackage = require(path.join(appPath, 'package.json'));

  console.log(cyan('Updating the dependencies'));
  const ownPackageName = ownPackage.name;
  if (appPackage.devDependencies) {
    // We used to put react-scripts in devDependencies
    if (appPackage.devDependencies[ownPackageName]) {
      console.log(`  Removing ${cyan(ownPackageName)} from devDependencies`);
      delete appPackage.devDependencies[ownPackageName];
    }
  }
  appPackage.dependencies = appPackage.dependencies || {};
  if (appPackage.dependencies[ownPackageName]) {
    console.log(`  Removing ${cyan(ownPackageName)} from dependencies`);
    delete appPackage.dependencies[ownPackageName];
  }
  Object.keys(ownPackage.dependencies).forEach(key => {
    // For some reason optionalDependencies end up in dependencies after install
    if (
      ownPackage.optionalDependencies &&
      ownPackage.optionalDependencies[key]
    ) {
      return;
    }
    console.log(`  Adding ${cyan(key)} to dependencies`);
    appPackage.dependencies[key] = ownPackage.dependencies[key];
  });
  // Sort the deps
  const unsortedDependencies = appPackage.dependencies;
  appPackage.dependencies = {};
  Object.keys(unsortedDependencies)
    .sort()
    .forEach(key => {
      appPackage.dependencies[key] = unsortedDependencies[key];
    });
  console.log();

  console.log(cyan('Updating the scripts'));
  delete appPackage.scripts['eject'];
  Object.keys(appPackage.scripts).forEach(key => {
    Object.keys(ownPackage.bin).forEach(binKey => {
      const regex = new RegExp(binKey + ' (\\w+)', 'g');
      if (!regex.test(appPackage.scripts[key])) {
        return;
      }
      appPackage.scripts[key] = appPackage.scripts[key].replace(
        regex,
        'node scripts/$1.js'
      );
      console.log(
        `  Replacing ${cyan(`"${binKey} ${key}"`)} with ${cyan(
          `"node scripts/${key}.js"`
        )}`
      );
    });
  });

  console.log();
  console.log(cyan('Configuring package.json'));
  // Add Jest config
  console.log(`  Adding ${cyan('Jest')} configuration`);
  appPackage.jest = jestConfig;

  // Add Babel config
  console.log(`  Adding ${cyan('Babel')} preset`);
  appPackage.babel = {
    presets: ['react-app'],
  };

  // Add ESlint config
  if (!appPackage.eslintConfig) {
    console.log(`  Adding ${cyan('ESLint')} configuration`);
    appPackage.eslintConfig = {
      extends: 'react-app',
    };
  }

  fs.writeFileSync(
    path.join(appPath, 'package.json'),
    JSON.stringify(appPackage, null, 2) + os.EOL
  );
  console.log();

  if (fs.existsSync(paths.appTypeDeclarations)) {
    try {
      // Read app declarations file
      let content = fs.readFileSync(paths.appTypeDeclarations, 'utf8');
      const ownContent =
        fs.readFileSync(paths.ownTypeDeclarations, 'utf8').trim() + os.EOL;

      // Remove react-scripts reference since they're getting a copy of the types in their project
      content =
        content
          // Remove react-scripts types
          .replace(
            /^\s*\/\/\/\s*<reference\s+types.+?"react-scripts".*\/>.*(?:\n|$)/gm,
            ''
          )
          .trim() + os.EOL;

      fs.writeFileSync(
        paths.appTypeDeclarations,
        (ownContent + os.EOL + content).trim() + os.EOL
      );
    } catch (e) {
      // It's not essential that this succeeds, the TypeScript user should
      // be able to re-create these types with ease.
    }
  }

  // "Don't destroy what isn't ours"
  if (ownPath.indexOf(appPath) === 0) {
    try {
      // remove react-scripts and react-scripts binaries from app node_modules
      Object.keys(ownPackage.bin).forEach(binKey => {
        fs.removeSync(path.join(appPath, 'node_modules', '.bin', binKey));
      });
      fs.removeSync(ownPath);
    } catch (e) {
      // It's not essential that this succeeds
    }
  }

  if (fs.existsSync(paths.yarnLockFile)) {
    const windowsCmdFilePath = path.join(
      appPath,
      'node_modules',
      '.bin',
      'react-scripts.cmd'
    );
    let windowsCmdFileContent;
    if (process.platform === 'win32') {
      // https://github.com/facebook/create-react-app/pull/3806#issuecomment-357781035
      // Yarn is diligent about cleaning up after itself, but this causes the react-scripts.cmd file
      // to be deleted while it is running. This trips Windows up after the eject completes.
      // We'll read the batch file and later "write it back" to match npm behavior.
      try {
        windowsCmdFileContent = fs.readFileSync(windowsCmdFilePath);
      } catch (err) {
        // If this fails we're not worse off than if we didn't try to fix it.
      }
    }

    console.log(cyan('Running yarn...'));
    spawnSync('yarnpkg', ['--cwd', process.cwd()], { stdio: 'inherit' });

    if (windowsCmdFileContent && !fs.existsSync(windowsCmdFilePath)) {
      try {
        fs.writeFileSync(windowsCmdFilePath, windowsCmdFileContent);
      } catch (err) {
        // If this fails we're not worse off than if we didn't try to fix it.
      }
    }
  } else {
    console.log(cyan('Running npm install...'));
    spawnSync('npm', ['install', '--loglevel', 'error'], {
      stdio: 'inherit',
    });
  }
  console.log(green('Ejected successfully!'));
  console.log();

  if (tryGitAdd(appPath)) {
    console.log(cyan('Staged ejected files for commit.'));
    console.log();
  }

  console.log(green('Please consider sharing why you ejected in this survey:'));
  console.log(green('  http://goo.gl/forms/Bi6CZjk1EqsdelXk1'));
  console.log();
});
posted @ 2021-04-06 15:22  远方的少年🐬  阅读(327)  评论(0)    收藏  举报