Используйте «пошаговое» мышление для повышения эффективности проверки кода и создания пакетов.

JavaScript
Используйте «пошаговое» мышление для повышения эффективности проверки кода и создания пакетов.

Источник изображения:пикограф использует .co/rocket-cube…

Автор: Пань Ванцян

1. Введение

Противоположностью понятию «приращение» здесь является «полная сумма». Когда вам нужно сделать резервную копию данных или синхронизировать файлы между серверами в системе Linux, файл с именемrsyncинструмент, это будет быстрее, чемscp/cpКоманда быстрее, потому что она сначала определяет разницу между существующими данными и новыми данными и передает только разные части, то есть «добавочную» синхронизацию.

В области фронтенд-разработки в этой статье будет представлена ​​идея «инкрементной» для повышения скорости проверки, упаковки и построения кода, чтобы повысить эффективность процесса разработки.

2. Инкрементальная проверка кода

Внешний интерфейс использует ESLint для статической проверки спецификаций кода. По мере развития фронтенд-инжиниринга мы будем интегрировать проверку кода с рабочим процессом разработки, вперед отправкой кодаидо доставки кодаАвтоматически выполнять проверки ESLint. Проверка отправки кода должна запускать проверку ESLint через git-хуки каждый раз, когда разработчик делает коммит.Когда объем инженерного кода велик, разработчику даже приходится ждать несколько минут для каждой отправки кода, чтобы завершить проверку. Проверка доставки кода основана на процессе непрерывной интеграции, например, запуск проверки кода во время MR, который блокирует процесс MR.Часто бывает, что MR изменяет только одну строку кода, а нужно сканировать весь проект. серьезно повлияет на эффективность непрерывной интеграции. Поэтому в большинстве случаев нет необходимости выполнять полное сканирование ESLint, нас больше волнует, есть ли проблема с вновь добавленным кодом.

Затем мы реализуем возможности инкрементной проверки отправки кода для проекта, настроив скрипт ловушки git перед фиксацией.

2.1 Найти измененные файлы

Проверки ESLint в этом скрипте выполняются вплоть до детализации файла. Первым шагом в реализации инкрементной проверки кода является возможность найти инкрементный код, то есть какие файлы были изменены. Мы используем инструмент управления версиями git, чтобы найти разницу между промежуточной областью и HEAD при отправке, а также найти список измененных файлов.

  1. использоватьgit diffНайдите измененный файл в этой заявке, добавьте--diff-filter=ACMRПараметр должен удалить удаленный файл, и удаленный файл не нужно проверять снова;
  2. Используйте функцию exec модуля child_process для выполнения команд git в узле;
  3. Вывод представляет собой строку, состоящую из измененных файлов, и выполняется простая обработка строки для извлечения списка файлов, подлежащих проверке;
const exec = require('child_process').exec;
const GITDIFF = 'git diff --cached --diff-filter=ACMR --name-only';
// 执行 git 的命令
exec(GITDIFF, (error, stdout) => {
    if (error) {
        console.error(`exec error: ${error}`);
    }
    // 对返回结果进行处理,拿到要检查的文件列表
    const diffFileArray = stdout.split('\n').filter((diffFile) => (
        /(\.js|\.jsx)(\n|$)/gi.test(diffFile)
    ));
    console.log('待检查的文件:', diffFileArray);
});

2.2 Проверка кода на наличие измененных файлов

ESLint предоставляет одноименную функцию класса (ESLint) как вызов API Node.js (использовался до версии 7.0.0CLIEngineclass), чтобы мы могли выполнять проверки кода в сценариях узла и получать результаты проверки.

  1. Используйте функцию ESLint lintFiles для проверки кода списка файлов;
  2. Возвращаемый результат представляет собой массив, включающий результат проверки каждого файла, и этот массив обрабатывается для получения результата проверки и вывода подсказки;
const { ESLint } = require('eslint');
const linter = new ESLint();

// 上文拿到待检查的文件列表后

let errorCount = 0;
let warningCount = 0;
if (diffFileArray.length > 0) {
    // 执行ESLint代码检查
    const eslintResults = linter.lintFiles(diffFileArray).results;
    // 对检查结果进行处理,提取报错数和警告数
    eslintResults.forEach((result) => {
        // result的数据结构如下:
        // {
        //     filePath: "xxx/index.js",
        //     messages: [{
        //         ruleId: "semi",
        //         severity: 2,
        //         message: "Missing semicolon.",
        //         line: 1,
        //         column: 13,
        //         nodeType: "ExpressionStatement",
        //         fix: { range: [12, 12], text: ";" }
        //     }],
        //     errorCount: 1,
        //     warningCount: 1,
        //     fixableErrorCount: 1,
        //     fixableWarningCount: 0,
        //     source: "\"use strict\"\n"
        // }
        errorCount += result.errorCount;
        warningCount += result.warningCount;
        if (result.messages && result.messages.length) {
            console.log(`ESLint has found problems in file: ${result.filePath}`);
            result.messages.forEach((msg) => {
                if (msg.severity === 2) {
                    console.log(`Error : ${msg.message} in Line ${msg.line} Column ${msg.column}`);
                } else {
                    console.log(`Warning : ${msg.message} in Line ${msg.line} Column ${msg.column}`);
                }
                
            });
        }
    });
}

2.3 Дружественные подсказки и обработка ошибок

  1. Сделайте дружественную подсказку вывода в интерфейсе командной строки, пройдена ли эта проверка кода;
  2. Если в результате проверки есть ошибка, она выйдет с ненулевым значением, и git откажется от коммита.
if (errorCount >= 1) {
    console.log('\x1b[31m', `ESLint failed`);
    console.log('\x1b[31m', `✖ ${errorCount + warningCount} problems(${errorCount} error, ${warningCount} warning)`);
    process.exit(1);
} else if (warningCount >= 1) {
    console.log('\x1b[32m', 'ESLint passed, but need to be improved.');
    process.exit(0);
} else {
    console.log('\x1b[32m', 'ESLint passed');
    process.exit(0);
}

На этом этапе скрипт ловушки перед фиксацией завершен, и инкрементная проверка кода может быть реализована только путем настройки выполнения скрипта в файле package.json. Конечным результатом является то, что разработчикам больше не нужно ждать завершения полной проверки кода при отправке кода, а скрипт быстро найдет измененные файлы и проверит их.

Если вы хотите реализовать эту функцию в своем собственном проекте, вы можете напрямую использовать библиотеку с открытым исходным кодом.lint-stagedкомбинироватьhuskyиспользовать вместе.

Реализация инкрементной проверки во время доставки кода аналогична описанным выше шагам, и ключевым моментом является поиск инкрементной части.

2.4 сравнение результатов

В качестве примера возьмем проект среднего масштаба, содержащий 460 js-файлов. В левой части рисунка ниже показано время, затрачиваемое на полную проверку кода, а в правой части — время, затрачиваемое на инкрементную проверку кода:

Если разработчик изменяет только один файл, полная проверка занимает 38 секунд при отправке кода, а инкрементная проверка — всего 2 секунды.

2.5 Более подробные инкрементальные проверки

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

первый до сих пор пользуюсьgit diffКоманда находит измененную часть, где вам нужно выполнить некоторую обработку строк для извлечения блока кода, а затем использовать API ESLint Node.js вlintTextметод проверки блока кода. Заинтересованные студенты могут попробовать сами.

3. Инкрементальная упаковка и конструкция

Рассмотрим такой бизнес-сценарий: в большом многостраничном веб-приложении (MPA) с сотнями страниц сборка каждого полного пакета занимает десятки минут. Иногда разработчики меняют только одну страницу или общий компонент, но выпуск и выход в онлайн занимает много времени, что серьезно влияет на эффективность непрерывной интеграции и онлайн-решения проблем.

Взяв в качестве примера использование веб-пакета для упаковки и сборки, мы также попытаемся использовать «инкрементную» идею для оптимизации этой проблемы.

3.1 Найти измененные файлы

Как и в предыдущей статье, первым шагом по-прежнему является поиск добавочного кода, то есть файлов, которые были изменены в этом выпуске. Проще всего по-прежнему выбирать для использованияgit diffкоманда для достижения. В отличие от инкрементальной проверки кода, здесь мы сравниваем выпускаемую ветку интеграции и ствол, чтобы найти список файлов различий между ними.

const execSync = require('child_process').execSync;
const path = require('path').posix;
const GITDIFF = 'git diff origin/master --name-only';
// 执行 git 的命令
const diffFiles = execSync(GITDIFF, {
        encoding: 'utf8',
    })
    .split('\n')
    .filter((item) => item)
    .map((filePath) => path.normalize(filePath));

После получения списка измененных файлов упаковка веб-пакета не может быть запущена напрямую, необходимо найти файл записи в соответствии с эталонной связью между файлами и передать запись страницы, которую необходимо переупаковать, в веб-пакет.

3.2 Расчет добавочной записи

Идея состоит в том, чтобы сначала построить дерево зависимостей каждого файла входа страницы.Если дерево содержит вышеуказанные измененные файлы, это означает, что страницу необходимо переупаковать. как показано на рисунке:

Измененные файлы можно получить раньше, а следующим шагом будет создание каждого входного файла.дерево зависимостей. Существует множество модульных спецификаций внешнего интерфейса.Чтобы реализовать анализ зависимостей каждого файла самостоятельно, необходимо учитывать различные ситуации.Вот рекомендуемая библиотека с открытым исходным кодом.Madge, который преобразует код в абстрактное синтаксическое дерево для анализа и, наконец, возвращает дерево зависимостей.

В качестве примера возьмем два входных файла на рисунке выше, их дерево зависимостей выглядит следующим образом:

// 被修改的文件列表
const diffFiles = ['util/fetch.js'];

// 用 madge 库计算依赖树的示例代码,具体可查看官方文档
// Promise.all([madge('./demo/index.js'), madge('./demo/buy.js')]).then((result) => {
//     result.forEach((e) => {
//         console.log(e.obj());
//     })
// });
// 最后得到的依赖树如下
const relyTree = {
    // demo/index.js 文件的依赖树
    {
        'demo/a.jsx': ['util/fetch.js'],
        'demo/b.js': [],
        'demo/index.js': ['demo/a.jsx', 'demo/b.js'],
        'util/fetch.js': []
    },
    // demo/buy.js 文件的依赖树
    {
        'util/env.js': [],
        'demo/buy.js': ['demo/c.js', 'demo/d.js'],
        'demo/c.js': ['util/env.js'],
        'demo/d.js': []
    }
};

Глубоко просмотрите дерево зависимостей каждого файла записи и решите, нужно ли его перепаковать и построить, в зависимости от того, содержит ли он файлы в списке измененных файлов.Пример кода выглядит следующим образом:

/* 计算增量入口示例代码 */
// 全量页面入口
const entries = [
    'demo/index.js',
    'demo/buy.js',
];

// 判断两个数组是否存在交集
function intersection(arr1, arr2) {
    let flag = false;
    arr1.forEach((ele) => {
        if (arr2.includes(ele)) {
            flag = true;
        }
    });
    return flag;
}

// 计算增量入口
const incrementEntries = [];
for (const i in relyTree) {
    for (const j in relyTree[i]) {
        if (intersection(relyTree[i][j], diffFiles)) {
            incrementEntries.push(i);
        }
    }
}

Например, мы знаем, что этот выпуск измененutil/fetch.jsЭтот файл, проходящий через два вышеупомянутых дерева зависимостей, будет знать, что толькоdemo/indexЭта страница затронута. Изменение конфигурации веб-пакета, чтобы использовать этот файл только в качестве входного параметра для запуска упаковки, может значительно повысить скорость упаковки и создания.

3.3 Граничные случаи

Интерфейсный проект также имеет некоторые зависимости, которые представляют собой пакеты npm, описанные в файле package.json, которые устанавливаются в папку node_modules.Зависимости между модулями очень сложны. Для простоты, когда первый шагgit diffКогда обнаруживается, что в package.json есть обновление модуля, учитывая, что это не частое событие, его можно инициировать напрямуюПолный пакет.

3.4 Сравнение результатов

В качестве примера возьмем проект MPA с 50 страницами. В левой части рисунка ниже показано время, затрачиваемое на создание полного пакета, а в правой части — время, затрачиваемое на создание добавочного пакета:Если предположить, что разработчик изменяет две страницы, механизм инкрементной упаковки только передает эти две записи страницы в веб-пакет посредством вычислений, а весь процесс упаковки и построения будет сокращен с 7 минут до 50 секунд, что значительно повышает эффективность непрерывной интеграции.

резюме

Эта статья начинается сИнкрементальная проверка кодаиИнкрементальные сборки пакетовНа примере двух конкретных бизнес-сценариев показано, как «инкрементная» идея может повысить эффективность разработки интерфейсов. Эти два случая не могут быть напрямую скопированы в вашу инженерную практику.

использованная литература

Эта статья была опубликована сКоманда внешнего интерфейса NetEase Cloud Music, Любое несанкционированное воспроизведение статьи запрещено. Мы набираем front-end, iOS и Android круглый год.Если вы готовы сменить работу и любите облачную музыку, присоединяйтесь к нам на grp.music-fe(at)corp.netease.com!