Происхождение: https://github.com/aleen42/PersonalWiki/blob/master/post/auto_deployed_gitbook_projects/auto_deployed_gitbook_projects.md

В этой статье в основном обсуждается, как создать автоматически развертываемый проект GitBook самостоятельно, а не полагаться на исходную сборку на gitbook.io. Прежде чем читать эту статью, вам, возможно, придется узнать, что такое GitBook и что он может сделать для вас.

Почему Гитбук?

GitBook — это первый выбранный мной инструмент для создания проекта по написанию книг или проектных документов. До знакомства с этой цепочкой инструментов я уже написал этот проект PersonalWiki на GitHub, чтобы использовать документацию для записи знаний. Благодаря простому языку письма, Markdown, я могу легко организовать все знания в книгу с разбивкой на главы, в соответствии с доменами, хобби и т. д. С момента создания GitBook в 2014 году он вдохновил меня преобразовать мой проект в страницу, где люди могут читать более удобным способом, особенно с резюме (основная структура проекта GitBook) для управления всеми главами в PersonalWiki.

В этот момент GitBook также выпустил приложение для редактирования рабочего стола, поддерживающее запись и предварительный просмотр файлов Markdown на локальном компьютере. Это была потрясающая особенность в процессе его роста.

Болевые пятна

С написанием все большего количества статей в этом проекте и созданием некоторых сложных плагинов, таких как gitbook-treeview или gitbook-footer, я обнаружил, что процесс создания страниц GitBook становится все более и более трудоемким, особенно в случае, когда мне приходится каждый раз строить все дублированные файлы уценки, большая часть которых не была изменена.

Еще одна болезненная проблема заключается в том, что gitbook.io всегда не мог собрать книги, если ваша книга слишком велика для выделения достаточного количества памяти.

Вот почему я стараюсь сделать свой проект автоматически развертываемым.

Так как мне нужно автоматически развертывать обработанные страницы в GitHub, я решил использовать Travis CI для сборки и развертывания, когда я что-то коммитлю. Жесткое ограничение по времени? Travis CI ввел максимальное время выполнения задания 50 минут, что также является жестким ограничением. При создании почти 500 страниц в этом проекте я потратил не менее 3 часов на работу с клиентом GitBook, а это значит, что мне нужно сократить это время.

Как сделать?

Во-первых, я должен знать, почему процесс создания страниц стоит так много накладных расходов. Обычно топтание на месте — самый эффективный способ выяснить причину этой проблемы. Во время отладки вы можете обнаружить, что большая часть времени рендеринга страницы была потрачена на синтаксический анализ шаблона. После добавления маркера времени в gitbook/lib/templating/render.js:

var start = new Date();
return timing.measure(
    'template.render',
    Promise.nfcall(
        env.renderString.bind(env),
        content,
        context,
        {
            path: filePath
        }
    )
    .then(function(content) {
        console.log('rendering time: ', new Date() - start + 'ms');
        return TemplateOutput.create(content, blocks);
    })
);

Журнал, по-видимому, показал, что этот процесс требует в среднем около 10 секунд для рендеринга одной страницы каждый раз. ДОЛГО, НО НУЖНО! Почему? Потому что большинство проектов GitBook использовали множество типов плагинов для расширения функций своей страницы, таких как поиск, совместное использование, индексация заголовка и так далее. Во всех этих плагинах в этом процессе будет вызываться хук-функция типа page, page:before, page:after. Это означает, что какие-то сложные плагины всегда будут «виновниками» его долгой обработки.

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

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

Прекратить уборку

GitBook очищает все файлы в выходной папке перед сборкой, поэтому первый шаг — изменить ее так, чтобы очищались только те страницы, которые не включены в сводку текущего коммита.

Обратите внимание: папку gitbook не следует рассматривать

/** gitbook/lib/output/generateBook.js */
function generateBook(generator, book, options) {
    /** ... */
    return Promise(
        new Output({
         book: book,
         options: options,
         state: state,
         generator: generator.name
        })
    )
    
    /** Cleanup output folder */
    .then(function(output) {
        function walk(directory) {
            if (['gitbook'].indexOf(directory) > -1) {
                return;
            }
            
            var dirList = fs.readdirSync(directory);
            var isFolderDeleted = false;
            dirList.forEach(function (item) {
                if (isFolderDeleted) {
                    return;
                }
            
                var nextPath = path.resolve(directory, item);
                if (fs.statSync(nextPath).isDirectory()) {
                    walk(nextPath);
                } else if (/\.md$/i.test(item)) {
                    if (!book.getSummary().getByPath(path.relative(output.getRoot(), nextPath))) {
                        if (output.getRoot() === directory) {
                            /** delete file */
                            fs.unlinkSync(nextPath);
                        } else {
                            (function (dir) {
                                /** Delete folder when it does not exist in the book summary */
                                output.getLogger().debug.ln('cleanup folder: ', dir);
            
                                if (fs.existsSync(dir)) {
                                    fs.readdirSync(dir).forEach(function (file, index) {
                                        var curPath = path.resolve(dir, file);
                                        if (fs.statSync(curPath).isDirectory()) {
                                            /** recursive */
                                            deleteFolderRecursive(curPath);
                                        } else {
                                            /** delete file */
                                            fs.unlinkSync(curPath);
                                        }
                                    });
            
                                    fs.rmdirSync(dir);
                                }
                            })(directory);
            
                            isFolderDeleted = true;
                        }
                    }
                }
            });
        }
    
        !fs.existsSync(output.getRoot()) && fs.mkdirSync(output.getRoot());
        walk(output.getRoot());
        return output;
    })
 
    /** ... */
}

Скопируйте необходимые активы

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

/** gitbook/lib/output/generateAssets.js */
function generateAssets(generator, output) {
    /** ... */
    return Promise.reduce(assets, function(out, assetFile) {
        if (!/^style/gi.test(assetFile)) {
            /** only copy style files */
            return Promise(output);
        }
        
        /** ... */
    }, output);
}

Создать нужную страницу

Теперь, когда не должны генерироваться все страницы, мне может понадобиться добавить условие, чтобы указать инструменту GitBook, что он должен делать при создании страниц.

/** gitbook/lib/output/generatePages.js */
function generatePages(generator, output) {
    /** ... */
    return Promise.reduce(pages, function(out, page) {
        var file = page.getFile();
        var filePath = file.getPath();
        var absoluteFilePath = path.join(out.getBook().getContentRoot(), filePath);
        var outputFilePath = path.join(out.getRoot(), filePath);
        /** check equality of coresponding markdown file in the output folder */
        if (fs.existsSync(outputFilePath)
            && fs.readFileSync(absoluteFilePath, { encoding: 'utf-8' }).replace(/\r|\n|\t/gi, '') === fs.readFileSync(outputFilePath, { encoding: 'utf-8' }).replace(/\r|\n|\t/gi, '')
            && fs.existsSync(path.join(out.getRoot(), fileToOutput(out, filePath)))
        ) {
            return Promise(out, page);
        }
        /** copy new markdown file to the output directory */
        var folderPath = path.dirname(outputFilePath);
        var checkParentFolder = function (folder) {
            var parentFolder = path.resolve(folder, '..')
            if (!fs.existsSync(parentFolder)) {
                checkParentFolder(parentFolder);
                fs.mkdirSync(parentFolder);
            }
        };
        if (!fs.existsSync(folderPath)) {
            checkParentFolder(folderPath);
            fs.mkdirSync(folderPath);
        }
        fs.createReadStream(absoluteFilePath).pipe(fs.createWriteStream(outputFilePath));
        /** ... */
    }, output);
}

На данный момент GitBook строит только измененные страницы, и мне просто нужно настроить Travis CI для его создания. Чтобы использовать пользовательский GitBook для сборки, я изменил клиентские инструменты GitBook, чтобы принять local в качестве проверенной версии в book.json:

{
    "gitbook": "local"
}

Вот орудие;

/** gitbook-cli/lib/local.js */
function resolveVersion(condition) {
    if (condition === 'local') {
        var versionFolder = path.resolve(__dirname, '../../gitbook/');
        return Q({
            link: null,
            name: 'local',
            path: versionFolder,
            tag: 'latest',
            version: require(path.resolve(versionFolder, 'package.json')).version
        });
    } else {
        var versions = listVersions();
        var version = _.chain(versions)
            .find(function(v) {
                return tags.satisfies(v.name, condition);
            })
            .value();
        if (!version) return Q.reject(new Error('No version match: '+condition));
        return Q(version);
    }
}

Затем я могу просто запустить команду node gitbook-cli/bin/gitbook.js build, чтобы начать сборку книги с помощью пользовательских инструментов GitBook. Не забудьте клонировать выходную папку в локальный путь перед запуском сборки, и пользовательские инструменты GitBook будут создаваться только тогда, когда в сводку добавляется новый файл уценки или изменяется старый файл уценки.

Лунр

GitBook использует Lunr в качестве поисковой системы для поддержки поиска слов в книге. Вот плагин plugin-lunr, который вы можете установить в свои книги. Тем не менее, с теми же проблемами, которые я хочу создать только то, что я хочу, я модифицировал этот плагин для поддержки. Более подробную информацию можно проверить в запросе на слияние: https://github.com/GitbookIO/plugin-lunr/pull/5.