Объединенные задачи gulp никогда не запускают событие `end` - PullRequest
0 голосов
/ 02 октября 2018

У меня есть задание gulp, которое просматривает папку в поисках подпапок и выводит файл JavaScript на основе содержимого каждой папки.Ниже приведен более наглядный пример.

  • src
    • assets
      • скриптов
        • критических
          • loadCSS.init.js
        • legacy
          • compatibility.init.js
          • picturefill.init.js
        • modern
          • connectivity.js
          • layzr.init.js
          • menu_list.js
          • scroll-hint.init.js
          • slideout.init.js
          • swiper.init.js
        • service-worker
          • service-worker.js

становится:

  • dev
    • assets
      • scripts
        • crit.js
        • legacy.js
        • modern.js
        • service-worker.js

Это достигается путем чтения содержимого каталога src/assets/scripts, затем выполнения цикла для каждой папки (critical, legacy, modern, service-worker) иотправив содержимое каждой папки вЗадачи Gulp, которые объединяются вместе с merge-stream .

Все это прекрасно работает, за исключением того, что после объединения задач я хочу вызвать уведомление, если компиляция прошла успешно.Если я пытаюсь что-то передать по потоку в объединенные потоки, это не сработает.Он просто возвращает объединенные потоки и никогда не продолжает работу.

Если я отменяю обещание моей функции PROCESS_SCRIPTS и не использую поток слияния (то есть обрабатываю только одну указанную вручную папку), она работает нормально,так что я в растерянности относительно того, что происходит.

Вот мое полное задание:

module.exports = {
    scripts(gulp, plugins, ran_tasks, on_error) {
        // task-specific plugins
        const ESLINT  = require("gulp-eslint");
        const WEBPACK = require("webpack-stream");

        // process scripts
        const PROCESS_SCRIPTS = (js_directory, destination_file_name = "modern.js", compare_file_name = "modern.js", source = [global.settings.paths.src + "/assets/scripts/*.js"]) => {
            return new Promise((resolve, reject) => {
                const WEBPACK_CONFIG = {
                    mode: "development",
                };

                // update webpack config for the current target destination and file name
                WEBPACK_CONFIG.mode   = plugins.argv.dist ? "production" : WEBPACK_CONFIG.mode;
                WEBPACK_CONFIG.output = {
                    filename: destination_file_name
                };

                const TASK = gulp.src(source)
                    // prevent breaking on error
                    .pipe(plugins.plumber({errorHandler: on_error}))
                    // check if source is newer than destination
                    .pipe(plugins.newer(js_directory + "/" + compare_file_name))
                    // lint all scripts
                    .pipe(ESLINT())
                    // print lint errors
                    .pipe(ESLINT.format())
                    // run webpack
                    .pipe(WEBPACK(WEBPACK_CONFIG))
                    // generate a hash and add it to the file name
                    .pipe(plugins.hash({template: "<%= name %>.<%= hash %><%= ext %>"}))
                    // output scripts to compiled directory
                    .pipe(gulp.dest(js_directory))
                    // generate a hash manfiest
                    .pipe(plugins.hash.manifest(".hashmanifest-scripts", {
                        deleteOld: true,
                        sourceDir: js_directory
                    }))
                    // output hash manifest in root
                    .pipe(gulp.dest("."))
                    // reject after errors
                    .on("error", () => {
                        reject(TASK);
                    })
                    // return the task after completion
                    .on("end", () => {
                        resolve(TASK);
                    });
            });
        };

        // scripts task, lints, concatenates, & compresses JS
        return new Promise ((resolve) => {
            // set JS directory
            const JS_DIRECTORY = plugins.argv.dist ? global.settings.paths.dist + "/assets/scripts" : global.settings.paths.dev + "/assets/scripts";

            // set the source directory
            const SOURCE_DIRECTORY = global.settings.paths.src + "/assets/scripts";

            // set up an empty merged stream
            const MERGED_STREAMS = plugins.merge();
            // get the script source folder list
            const SCRIPT_FOLDERS = plugins.fs.readdirSync(SOURCE_DIRECTORY);
            // get the script destination file list
            const SCRIPT_FILES   = plugins.fs.existsSync(JS_DIRECTORY) ? plugins.fs.readdirSync(JS_DIRECTORY) : false;

            // process all the script folders
            const PROCESS_SCRIPT_FOLDERS = () => {
                return Promise.resolve().then(() => {
                    // shift to the next folder
                    const FOLDER_NAME = SCRIPT_FOLDERS.shift();

                    // find the existing destination script file name
                    const FILE_NAME   = SCRIPT_FILES ? SCRIPT_FILES.find((name) => {
                        return name.match(new RegExp(FOLDER_NAME + ".[a-z0-9]{8}.js"));
                    }) : FOLDER_NAME + ".js";

                    // process all scripts, update the stream
                    return PROCESS_SCRIPTS(JS_DIRECTORY, FOLDER_NAME + ".js", FILE_NAME, SOURCE_DIRECTORY + "/" + FOLDER_NAME + "/**/*").then((processed) => {
                        MERGED_STREAMS.add(processed);
                    });
                }).then(() => SCRIPT_FOLDERS.length > 0 ? PROCESS_SCRIPT_FOLDERS() : resolve());
            };

            PROCESS_SCRIPT_FOLDERS().then(() => {
                // wrap up
                return MERGED_STREAMS
                    // prevent breaking on error
                    .pipe(plugins.plumber({
                        errorHandler: on_error,
                    }))
                    // notify that task is complete, if not part of default or watch
                    .pipe(plugins.gulpif(gulp.seq.indexOf("scripts") > gulp.seq.indexOf("default"), plugins.notify({
                        title:   "Success!",
                        message: "Scripts task complete!",
                        onLast:  true,
                    })))
                    // push task to ran_tasks array
                    .on("data", () => {
                        if (ran_tasks.indexOf("scripts") < 0) {
                            ran_tasks.push("scripts");
                        }
                    })
                    // resolve the promise on end
                    .on("end", () => {
                        resolve();
                    });
            });

        });
    }
};

Также отображается на моем GitHub: https://github.com/JacobDB/new-site/blob/master/gulp-tasks/scripts.js


РЕДАКТИРОВАТЬ: Я попробовал несколько вещей, я опишу их здесь.

  1. console.log("hello world") никогда не срабатывает после MERGED_STREAMS.on("data"), MERGED_STREAMS.on("error") или MERGED_STREAMS.on("end").
  2. Перемещение const MERGED_STREAMS = plugins.merge(); в переменную уровня модуля (т. Е. Сразу после const WEBPACK = require("webpack-stream")) не меняет результат.
  3. Выполнение # 2 и затем использование MERGED_STREAMS.add(gulp.src(source) ...) вместо добавленияпоток после выполнения обещания не меняет результат, кроме как при выходе из .pipe(gulp.dist(".")), который необходим для вывода .hashmanifest, и всегда помечает задачу как выполненную.
  4. Отключение webpack, hash или eslint в любой комбинации не имеет значения.
  5. Изменение PROCESS_SCRIPTS с возврата обещания вернуть поток, затем продолжитьИспользование каждой папки как отдельных переменных, а затем их объединение вручную, по-видимому, корректно запускает задачу при запуске, но webpack может быть запущен только один раз, поэтому выводится только один файл - critical.hash.js. Примечание: я не тестировал этот метод в связи с отключением hash, что может привести к тому, что он будет помечен как правильно запущенный, если .hashmanifest всегда выводится.
  6. Разделениешаг linting и шаг webpack для разделения задачи вида заставляют задачу правильно помечаться как выполненную, но только если задача lint не является обещанием, что приводит к ошибкам unexpected end of stream вконсоль.

РЕДАКТИРОВАТЬ 2: Обновлено с пересмотренной версией моей задачи, основанной на совете @ Луи.

1 Ответ

0 голосов
/ 08 октября 2018

Есть много проблем с кодом выше.Одна из основных проблем, которая делает код трудным для отслеживания и отладки, заключается в том, что вы используете new Promise там, где вам это не нужно. Как правило, если у вас есть new Promise и логика в исполнителе обещания разрешит или отклонит в зависимости от результата другого обещания , вам не нужно использовать new Promise.

Иногда у людей есть такой код:

function foo() {
  const a = getValueSynchronously();
  const b = getOtherValueSynchronously();
  return doSomethingAsynchronous(a, b).then(x => x.someMethod());
}

Предположим, что doSomethigAsynchronous возвращает обещание.Проблема с функцией foo, описанной выше, состоит в том, что если getValueSynchronously и getOtherValueSynchronously не удаются, то foo вызовет исключение, но если doSomethingAsynchronous не удастся, то он отклонит обещание.Поэтому код, использующий foo, должен обрабатывать синхронные исключения и асинхронные отклонения, если он хочет обработать все возможные сбои.Иногда люди чувствуют, что могут решить проблему, вызвав отказ от обещаний:

function foo() {
  return new Promise((resolve, reject) => {
    const a = getValueSynchronously();
    const b = getOtherValueSynchronously();
    doSomethingAsynchronous(a, b).then(x => x.someMethod()).then(resolve, reject);
  });
}

В приведенном выше коде, если getValueSynchronously или getOtherValueSynchronously не пройден, это отказ от обещания.Но проблема с кодом выше в том, что легко ошибиться.Вы можете забыть позвонить reject везде, где это необходимо.(На самом деле, у вас do есть эта ошибка в вашем коде. У вас есть вложенные обещания, отклонение которых не будет распространяться вверх. Они просто теряются, что означает, что при возникновении ошибки выкод просто остановится, и вы не поймете, почему. ) Или у вас может возникнуть соблазн вызвать `решить путь вниз во вложенной функции, что затрудняет выполнение логики.

Вы также можете сделать это:

function foo() {
  return Promise.resolve().then(() => {
    const a = getValueSynchronously();
    const b = getOtherValueSynchronously();
    return doSomethingAsynchronous(a, b);
  }).then(x => x.someMethod());
}

Вы можете использовать Promise.resolve(), чтобы войти в обетованный мир (хм ... "земля обетованная?").В приведенном выше коде вы не должны помнить, чтобы позвонить reject.Если по какой-либо причине код внутри обратного вызова .then завершается с ошибкой , вы получаете отклоненное обещание.

Я также заметил, что в некоторых местах вы возвращаете значение из функции executor, которую выперейти к new Promise.Ваш код будет вести себя точно так же, если вы не используете return там.Чтобы проиллюстрировать это, код:

function foo() {
  return new Promise((resolve, reject) => {
    return doSomethingAsynchronous().then(resolve, reject);
  });
}

ведет себя точно так же, как этот код:

function foo() {
  return new Promise((resolve, reject) => {
    doSomethingAsynchronous().then(resolve, reject);
  });
}

Значение, возвращаемое исполнителем, игнорируется,Конец истории.Если вы считаете, что значение, которое вы возвращаете от своих исполнителей, что-то делает, то это неверно.

...