Node.js & Gulp:真实的管道序列可能与书面的“.pipe()”链不同?

Node.js & Gulp: Real piping sequence could differ from written '.pipe()'s chain?

在下面的代码中,fileShouldBePreprocessedBySass() 将在 console.log('intercepted!'); 执行之前被调用。此外,在 fileShouldBePreprocessedBySass(targetFileAbsolutePath) 中,参数 targetFileAbsolutePath 将是 undefined:

let currentSourceFileAbsolutePath;

return gulp.src(entryPointsSourceFilesPathsOrGlobs)

    // "gulpPlugins.intercept" is "gulp-intercept"
    .pipe(gulpPlugins.intercept( sourceVynilFile => {
      console.log('intercepted!');
      currentSourceFileAbsolutePath = sourceVynilFile.path;
      console.log(currentSourceFileAbsolutePath); // OK here
      return sourceVynilFile;
    }))

    // "gulpPlugins.if" is "gulp-if"
    .pipe(gulpPlugins.if(
        // currentSourceFileAbsolutePath is undefined !!!
        fileShouldBePreprocessedBySass(currentSourceFileAbsolutePath),
        gulpPlugins.sass()
    ));

// ...

fileShouldBePreprocessedBySass(targetFileAbsolutePath) {

    console.warn('---');
    console.warn(targetFileAbsolutePath); // undefined!

    const targetFilenameExtension = Path.extname(targetFileAbsolutePath);
    let targetFilenameExtensionIsSupportedBySassPreprocessor = false;

    for (const filenameExtension of SUPPORTED_FILENAME_EXTENSIONS__SASS_PREPROCESSOR) {
      if (filenameExtension === targetFilenameExtension) {
        targetFilenameExtensionIsSupportedBySassPreprocessor = true;
        break;
      }
    }

    return targetFilenameExtensionIsSupportedBySassPreprocessor;
}

真的,原来的代码是用TypeScript写的,但是我改写成JavaScript让更多人看懂代码。我之所以这么说是因为 TypeScript 编译器以某种方式理解了 pipe(gulpPlugins.if(/*...*/)) 中的内容,参数 currentSourceFileAbsolutePath 未初始化,因此发生了 TS2454: Variable 'currentSourceFileAbsolutePath' is used before being assigned. 错误。

我很困惑,因为我有类似的任务可以正常工作(按正确的顺序):

let currentSourceFileAbsolutePath: string;

return gulp.src(entryPointsSourceFilesPathsOrGlobs)

    .pipe(gulpPlugins.intercept(sourceVynilFile => {
      currentSourceFileAbsolutePath = sourceVynilFile.path;
      return sourceFile;
    }))

    .pipe(gulpPlugins.pug())
    .pipe(gulpPlugins.intercept(compiledHtmlFile => {
      // currentSourceFileAbsolutePath is NOT undefined!
      if (shouldValidateCompiledHtmlRespectiveToSourceFile(currentSourceFileAbsolutePath)) {
        HtmlValidator.validateHtml(compiledHtmlFile);
      }
      return compiledHtmlFile;
    }))

    .pipe(gulp.dest(() => (
      // currentSourceFileAbsolutePath is NOT undefined!
      getOutputDirectoryForPreprocessedMarkupEntryPointFileByRespectiveSourceFile(currentSourceFileAbsolutePath)
    )));

第一个任务有什么问题?我错过了一些异步调用?

Gulp 本身不会对后续调用 .pipe 的内容重新排序。我不确定你的情况到底发生了什么,但你的代码 不必要地脆弱

考虑这个模式:

gulp.src("...")
  .pipe(pluginA)
  .pipe(pluginB)

并假设 pluginA 只是打印到控制台 pluginA 并且 pluginB 只是打印到控制台 pluginB。您可能认为输出将是:

pluginA
pluginB
pluginA
pluginB
pluginA
...

也就是说,每个文件都会经过pluginA,然后这个文件会经过pluginB,然后下一个文件会经过pluginA,等等。也就是说, pluginApluginB 处理完 pluginA 已完成处理的前一个文件之前,不会看到新文件。虽然在某些情况下此顺序正是将要发生的顺序,但对于一般情况,此顺序无法保证。你也可以拥有:

pluginA 
pluginA 
pluginA 
pluginB 
pluginA 
pluginB 
pluginB 
pluginB
pluginA 
...

文件必须首先被 pluginA 看到,但不能保证 pluginB 会在 pluginA 到达下一个文件之前处理文件。

如果处理顺序碰巧像我上面给出的第一个例子那样完美交错,您的代码就可以正常工作。但一般来说,这个顺序是不能保证的,你的代码会失败。

碰巧 您的代码的脆弱部分是不必要的。没有必要将文件的路径记录在文件流外部的变量中,然后在流的后面部分读取它。 gulp-if 会将它测试的文件传递给你给它的条件。这是一个例子:

const gulp = require("gulp");
const gulpIf = require("gulp-if");
gulp.task("default", () =>
          gulp.src("src/**")
          .pipe(gulpIf((file) => {
            console.log(file.path);
            return file.basename === "a";
          }, gulp.dest("out"))));

此任务会将基本名称为 "a" 的所有文件放入 out 子目录中。您还将在控制台上看到每个文件的绝对路径,这些文件通过给定 gulpIf 的条件。

您可以修改 fileShouldBePreprocessedBySass 以接受 Vinyl 文件,然后只检查 file.path 那里而不是保存它然后检索它。