如何让 `react-scripts build` 命令静默?

9
我正在使用一个包含多个Node包的存储库,这些包都是使用create-react-app创建的,并且由CI系统构建和测试。每个包的构建/测试(使用react-scripts build后跟react-scripts test --silent)当前会产生超过二十行的输出,导致构建日志中有超过一百行的材料,例如“gzip后的文件大小”和“在此处了解更多有关部署的信息”。这使得在日志中查看错误消息、警告或其他问题更加困难。
除了为每个包编写自定义构建脚本(可能还需要测试脚本),我是否有办法简化此过程?如果确实需要自定义脚本,最好的方法是尽可能重用现有的执行构建和测试的代码。

你解决了这个问题吗? - Lukas Gjetting
1
@LukasGjetting 我进行了调查,目前唯一的解决方案似乎是使用不同的构建脚本。请参见我刚刚发布的答案以获取详细信息。感谢您提供修复此问题的PR。 - cjs
你可以创建一个批处理文件来执行 react-scripts。这样可以让批处理文件静默运行。例如,在 Windows 上,您可以在该批处理文件的第一行上加上 @echo off。或者,您也可以将其输出重定向到一个文件中,使用 react-scripts build > file.txt - bvdb
@bvdb 这个构建是由一个 shell 脚本运行的,因此它已经像 .BAT 文件中的 @echo off 一样“静默”了。问题在于输出不是来自批处理文件而是构建本身。是的,我可以重定向它,但那也会重定向所有隐藏在冗长构建输出中的错误和警告消息。 - cjs
@CurtJ.Sampson,这可能会对您有所帮助:foo.exe | find /V "ignore this" | find /V "and ignore this" - bvdb
显示剩余3条评论
3个回答

7

react-scripts build会运行react-scripts包中的bin/react-scripts.js,该文件基本上只是从同一包中运行scripts/build.js

不幸的是,该build.js脚本(至少在2018年10月15日之前)被硬编码调用函数,例如printFileSizesAfterBuild()printHostingInstructions(),没有任何禁用它们的选项。因此,除非复制build.js并将其修改为不打印您不想要的内容,否则目前无法更改此设置。

有一个拉取请求PR#5429来添加--silent选项到构建脚本中。由于缺乏活动而已关闭它,而create-react-app开发人员在其他地方已经非常明确地表示他们并不打算使react-scripts非常可配置;他们建议的解决方案只是使用自己的build.js脚本。


这需要将项目弹出,对吗? - paul23
@paul23 你需要制作自己的 build.js 副本;无论你选择通过弹出窗口(如果我没记错的话,这也会复制很多其他脚本)还是其他方式来完成都取决于你。 - cjs

1
如果您从应用程序的根目录中复制/node_modules/react-scripts/scripts/build.js脚本,请将路径相对于const basepath = __dirname+'/node_modules/react-scripts/scripts/'进行调整并消除不必要的日志。将package.json脚本构建调整为:node build,然后您就可以为React应用程序创建一个非常安静的构建 :)。
示例构建脚本:

// @remove-on-eject-begin
/**
 * Copyright (c) 2015-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
// @remove-on-eject-end
'use strict';

// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'production';
process.env.NODE_ENV = 'production';
const basepath = __dirname + '/node_modules/react-scripts/scripts/';
// 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;
});

// Ensure environment variables are read.
require(basepath + '../config/env');
// @remove-on-eject-begin
// Do the preflight checks (only happens before eject).
const verifyPackageTree = require(basepath + 'utils/verifyPackageTree');
if (process.env.SKIP_PREFLIGHT_CHECK !== 'true') {
  verifyPackageTree();
}
const verifyTypeScriptSetup = require(basepath + 'utils/verifyTypeScriptSetup');
verifyTypeScriptSetup();
// @remove-on-eject-end

const path = require('path');
const chalk = require('react-dev-utils/chalk');
const fs = require('fs-extra');
const webpack = require('webpack');
const configFactory = require(basepath + '../config/webpack.config');
const paths = require(basepath + '../config/paths');
const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
const printHostingInstructions = require('react-dev-utils/printHostingInstructions');
const FileSizeReporter = require('react-dev-utils/FileSizeReporter');
const printBuildError = require('react-dev-utils/printBuildError');

const measureFileSizesBeforeBuild =
  FileSizeReporter.measureFileSizesBeforeBuild;
const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild;
const useYarn = fs.existsSync(paths.yarnLockFile);

// These sizes are pretty large. We'll warn for bundles exceeding them.
const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024;
const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024;

const isInteractive = process.stdout.isTTY;

// Warn and crash if required files are missing
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
  process.exit(1);
}

// Generate configuration
const config = configFactory('production');

// We require that you explicitly set browsers and do not fall back to
// browserslist defaults.
const {
  checkBrowsers
} = require('react-dev-utils/browsersHelper');
checkBrowsers(paths.appPath, isInteractive)
  .then(() => {
    // First, read the current file sizes in build directory.
    // This lets us display how much they changed later.
    return measureFileSizesBeforeBuild(paths.appBuild);
  })
  .then(previousFileSizes => {
    // Remove all content but keep the directory so that
    // if you're in it, you don't end up in Trash
    fs.emptyDirSync(paths.appBuild);
    // Merge with the public folder
    copyPublicFolder();
    // Start the webpack build
    return build(previousFileSizes);
  })
  .then(
    ({
      stats,
      previousFileSizes,
      warnings
    }) => {
      // if (warnings.length) {
      //   console.log(chalk.yellow('Compiled with warnings.\n'));
      //   console.log(warnings.join('\n\n'));
      //   console.log(
      //     '\nSearch for the ' +
      //       chalk.underline(chalk.yellow('keywords')) +
      //       ' to learn more about each warning.'
      //   );
      //   console.log(
      //     'To ignore, add ' +
      //       chalk.cyan('// eslint-disable-next-line') +
      //       ' to the line before.\n'
      //   );
      // } else {
      //   console.log(chalk.green('Compiled successfully.\n'));
      // }
      //
      // console.log('File sizes after gzip:\n');
      // printFileSizesAfterBuild(
      //   stats,
      //   previousFileSizes,
      //   paths.appBuild,
      //   WARN_AFTER_BUNDLE_GZIP_SIZE,
      //   WARN_AFTER_CHUNK_GZIP_SIZE
      // );
      // console.log();
      console.log(chalk.green('Compiled successfully.\n'));

      const appPackage = require(paths.appPackageJson);
      const publicUrl = paths.publicUrlOrPath;
      const publicPath = config.output.publicPath;
      const buildFolder = path.relative(process.cwd(), paths.appBuild);
      // printHostingInstructions(
      //   appPackage,
      //   publicUrl,
      //   publicPath,
      //   buildFolder,
      //   useYarn
      // );
    },
    err => {
      const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true';
      if (tscCompileOnError) {
        console.log(
          chalk.yellow(
            'Compiled with the following type errors (you may want to check these before deploying your app):\n'
          )
        );
        printBuildError(err);
      } else {
        console.log(chalk.red('Failed to compile.\n'));
        printBuildError(err);
        process.exit(1);
      }
    }
  )
  .catch(err => {
    if (err && err.message) {
      console.log(err.message);
    }
    process.exit(1);
  });

// Create the production build and print the deployment instructions.
function build(previousFileSizes) {
  // We used to support resolving modules according to `NODE_PATH`.
  // This now has been deprecated in favor of jsconfig/tsconfig.json
  // This lets you use absolute paths in imports inside large monorepos:
  if (process.env.NODE_PATH) {
    console.log(
      chalk.yellow(
        'Setting NODE_PATH to resolve modules absolutely has been deprecated in favor of setting baseUrl in jsconfig.json (or tsconfig.json if you are using TypeScript) and will be removed in a future major release of create-react-app.'
      )
    );
    console.log();
  }


  const compiler = webpack(config);
  return new Promise((resolve, reject) => {
    compiler.run((err, stats) => {
      let messages;
      if (err) {
        if (!err.message) {
          return reject(err);
        }

        let errMessage = err.message;

        // Add additional information for postcss errors
        if (Object.prototype.hasOwnProperty.call(err, 'postcssNode')) {
          errMessage +=
            '\nCompileError: Begins at CSS selector ' +
            err['postcssNode'].selector;
        }

        messages = formatWebpackMessages({
          errors: [errMessage],
          warnings: [],
        });
      } else {
        messages = formatWebpackMessages(
          stats.toJson({
            all: false,
            warnings: true,
            errors: true
          })
        );
      }
      if (messages.errors.length) {
        // Only keep the first error. Others are often indicative
        // of the same problem, but confuse the reader with noise.
        if (messages.errors.length > 1) {
          messages.errors.length = 1;
        }
        return reject(new Error(messages.errors.join('\n\n')));
      }
      if (
        process.env.CI &&
        (typeof process.env.CI !== 'string' ||
          process.env.CI.toLowerCase() !== 'false') &&
        messages.warnings.length
      ) {
        console.log(
          chalk.yellow(
            '\nTreating warnings as errors because process.env.CI = true.\n' +
            'Most CI servers set it automatically.\n'
          )
        );
        return reject(new Error(messages.warnings.join('\n\n')));
      }

      return resolve({
        stats,
        previousFileSizes,
        warnings: messages.warnings,
      });
    });
  });
}

function copyPublicFolder() {
  fs.copySync(paths.appPublic, paths.appBuild, {
    dereference: true,
    filter: file => file !== paths.appHtml,
  });
}

和示例 package.json 脚本选项:

"scripts": {
  "start": "react-scripts start", "build": "node build"
}

感谢您的代码,不过我应该指出问题的重点在于如何在不必提取和定制构建脚本的情况下完成此操作。(对于一个项目来说这并不太难,但是对于十几个项目来说,这样做和维护就需要更多的工作了。) - cjs
这些更改非常简单,以至于一个人可以轻松编写脚本来自动化该过程:从路径中复制构建脚本,注入基路径,搜索路径并更改它们,运行新脚本,事后删除脚本。但是,你是正确的 :) - bluehipy
我认为,如果一个人打算开始编写更多的代码,那么长期维护起来可能更简单、更便宜的方法是基于现有的包创建一个新的包,并进行所需的修改。(尽管最终我相信我们将所需的更改与我们的自定义构建系统集成了起来。) - cjs

0

我突然想到chalk。至少你可以对响应进行颜色编码。听起来你正在使用的应用程序还没有(或者说尚未)被弹出。只有在应用程序被弹出后,才能修改create-react-app基本文件。不幸的是,一旦弹出,就无法撤消影响。如果有帮助,请告诉我。


部分原因是不需要弹出应用程序,因为这样您就需要手动复制任何脚本升级,而不仅仅是更新依赖的 create-react-app 包。 - cjs
只是指出了选项。现在问题只是你有多想要它。我认为不值得。很抱歉我不能提供更多帮助。 - HJW
好的,谢谢,但我希望从“除非编写自己的定制构建脚本”中已经清楚,我想使用包中的标准脚本。 - cjs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接