GitBook插件中实现从多个不同的文件夹下加载css和js文件
在在Docker中构建GitBook并整合GitLab Runner的使用经验分享一文中,自己在Docker环境下基于GitLab Runner实现了GitBook的自动构建,同时基于实际使用需求添加了很多GitBook插件,在此过程中积累了一些GitBook插件的开发与使用经验。
本文简要介绍自己在GitBook插件定制化修改过程中如何实现从多个不同的文件夹下加载js和css文件的实现过程。
问题背景
由于GitBook官方默认的代码高亮插件highlight使用的是highlightjs,其功能没有prismjs完善,故最初将GitBook的代码高亮采用prismjs实现,对应的插件为gitbook-plugin-prism,其使用方式也很简单,只需要在book.json中添加如下配置:
{
"plugins": ["prism", "-highlight"]
}
该插件使用起来一切正常,由于部门GitBook的一大用途是记录设计方案与实现的代码,经常会有多个功能相似的对比代码进行展示,占用了大量的空间篇幅且阅读也不是很方便,需要添加一个tab效果对其进行分组归类。
在网上搜索相关的GitBook插件后,主要有gitbook-plugin-codegroup以及gitbook-plugin-codetabs,它们的对比如下:
gitbook-plugin-codegroup |
gitbook-plugin-codetabs |
|
|---|---|---|
代码高亮实现 |
highlight |
prism |
代码写入方式 |
进行markdown中代码块原生语法,typora展示更方便 |
GitBook代码块,typora展示不友好 |
插件改造难度 |
高 | 低 |
综合对比后,决定采用gitbook-plugin-codetabs结合gitbook-plugin-prism两者结合,同时代码块的输入还是采用类似 ``` 包裹的方式以便与markdown和typora的使用方式保持一致。
确定好方案后,接下来就需要将这两个插件组合到一起,查看它们的源码,发现都各自引入了静态文件

自己首先想到的是添加多个assets然后在js和css使用完整路径,类似如下:
module.exports = {
book: {
assets: ['./assets1','./assets2'],
css: [
'codetabs1.css','codetabs2.css'
],
js: [
'codetabs1.js','codetabs2.js'
]
},
但此种方式明显不可行,通过gitbook serve启动之后,终端控制台提示类似如下错误
TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received an instance of List
很明显assets的值只能为单个字符串,不能为数组。
接下来尝试将assets 配置去掉,改为类似如下代码
book: {
css: [
'./assets1/codetabs1.css','./assets2/codetabs2.css'
],
js: [
'./assets1/codetabs1.js','./assets2/codetabs2.js'
]
},
此时通过gitbook serve启动时,终端控制台提示一切正常,但访问对应的页面时,显示效果不正常,通过Chrome调试工具可发现上述的js和css都显示为404,都未能正常加载,导致页面显示不正常。
看来assets属性不能省略,且不能以数组方式配置,此时很容易想到的是将这些静态资源文件都放置到一个文件夹下,不就解决了此问题?
理论上确实可行,但是理想很丰满,现实很骨感,此种方式并不能满足自己的实际需求!
原因为gitbook-plugin-prism插件的index.js中有如下代码,通过此代码可知gitbook-plugin-prism中是通过去npm仓库中获取prismjs的源文件,而不是直接将primsjs下载到该插件的资源文件目录下,通过这种方式可实现gitbook-plugin-prism与prismjs的解耦,且能快速的升级prismjs,是一个合理且优秀的设计!
作为一个对代码编写要求很高的人,当前要将别人优秀的习惯保持下来,这样的话就不能将代码分组的静态文件与prismjs代码高亮相关的文件混合在一起,问题产生!
function getAssets() {
var cssFiles = getConfig(this, 'pluginsConfig.prism.css', []);
var cssFolder = null;
var cssNames = [];
var cssName = null;
if (cssFiles.length === 0) {
cssFiles.push('prismjs/themes/prism.css');
}
cssFiles.forEach(function(cssFile) {
var cssPath = require.resolve(cssFile);
cssFolder = path.dirname(cssPath);
cssName = path.basename(cssPath);
cssNames.push(cssName);
});
return {
assets: cssFolder,
css: cssNames
};
}
解决方案
既不能同时支持多个assets属性,自己又不想破坏prismjs本身的结构,怎么办?
一开始自己通过Google和GitHub去查找相关的资料,没有找到自己想要的东西,后来用ChatGPT连续切换了好几次提示词,结果答案都是错的.
PS:个人鄙视在编程中遇到问题无脑使用
ChatGPT,个人观点是要明白其底层原理,找到合适的使用方法。ChatGPT是基于训练的,网络上相关资料越丰富,ChatGPT训练的材料也就越多,其给出的答案也就越准确,反之,对于新出现的技术,网上资料很少或者ChatGPT来不及训练,此时就不能给出准确的答案了。
通过前述方式没有找到自己想要的答案,没办法只能在Stackoverflow上用英文提问,希望能有人给回复,遗憾的提出问题之后一周也没人回复,只能另想它法了。
分析GitBook中插件的生成结果,发现要想某个静态资源文件能够被访问到,其必须在_book目录下对应的插件中存在,之前的去掉assets后gitbook serve不报错,但是js和css 加载出现404即是这个原因。

找到问题原因后,接下来自己尝试手工把相关的js和css文件加到_book下对应的插件目录中,此时可通过浏览器地址直接访问,不会出现404问题,但是相关的静态资源文件仍然没有加载,在生成的html源码中也没有看见。

问题原因为我们没有在下述代码中引入对应的js和css文件。
book: {
assets: './assets',
css: [
'codetabs.css'
],
js: [
'codetabs.js'
]
},
要想文件被加载,则必须要引入相关文件,要想文件能被正常访问,在_book下对应的插件目录中必须要有对应的文件。
至此,问题的解决方案找到!
引入文件很容易实现,直接在数组中添加对应的文件即可,但如何引入相关文件呢?经过多处查看代码后,自己在index.js中找到了答案
try {
fs.writeFileSync(outputFile, fs.readFileSync(inputFile));
} catch (e) {
console.warn('Failed to write prism-ebook.css. See https://git.io/v1LHY for side effects.');
console.warn(e);
}
利用相关的JavaScript API手工将文件写入_book下对应插件的目录中!之后将assets的值设置为对应插件的名称,既能同时访问多个文件!
经过多次测试,最终捣鼓出可用的代码,在不破坏prismjs完整性的同时还能加载插件自身的js和css文件,完整代码参见gitbook-plugin-prism-codetab-fox
核心的index.js代码如下,主要是通过syncFile函数进行文件写入:
var Prism = require('prismjs');
var languages = require('prismjs').languages;
var path = require('path');
var fs = require('fs');
var cheerio = require('cheerio');
var mkdirp = require('mkdirp');
var codeBlocks = require('gfm-code-blocks');
var trim = require('lodash/trim');
const includes = require('lodash/includes');
const get = require('lodash/get');
var DEFAULT_LANGUAGE = 'markup';
var DEFAULT_CODE_TAB_SEPERATOR = '::';
var MAP_LANGUAGES = {
'py': 'python',
'js': 'javascript',
'rb': 'ruby',
'cs': 'csharp',
'sh': 'bash',
'html': 'markup'
};
// Base languages syntaxes (as of prism@1.6.0), extended by other syntaxes.
// They need to be required before the others.
var PRELUDE = [
'markup-templating', 'clike', 'javascript', 'markup', 'c', 'ruby', 'css',
// The following depends on previous ones
'java', 'php'
];
PRELUDE.map(requireSyntax);
/**
* Load the syntax definition for a language id
*/
function requireSyntax(lang) {
require('prismjs/components/prism-' + lang + '.js');
}
function getConfig(context, property, defaultValue) {
var config = context.config ? /* 3.x */ context.config : /* 2.x */ context.book.config;
return config.get(property, defaultValue);
}
function isEbook(book) {
// 2.x
if (book.options && book.options.generator) {
return book.options.generator === 'ebook';
}
// 3.x
return book.output.name === 'ebook';
}
function getAssets() {
var cssFiles = getConfig(this, 'pluginsConfig.prism.css', []);
var cssFolder = null;
var cssNames = [];
var cssName = null;
if (cssFiles.length === 0) {
cssFiles.push('prismjs/themes/prism.css');
}
cssFiles.forEach(function(cssFile) {
var cssPath = require.resolve(cssFile);
cssFolder = path.dirname(cssPath);
cssName = path.basename(cssPath);
cssNames.push(cssName);
});
cssNames.push('codetab/codetab.css');
return {
assets: cssFolder,
css: cssNames,
js: ['codetab/codetab.js']
};
}
function syncFile(book, outputDirectory, outputFile, inputFile) {
outputDirectory = path.join(book.output.root(), '/gitbook/gitbook-plugin-prism-codetab-fox/' + outputDirectory);
outputFile = path.resolve(outputDirectory, outputFile);
inputFile = path.resolve(__dirname, inputFile);
mkdirp.sync(outputDirectory);
try {
fs.writeFileSync(outputFile, fs.readFileSync(inputFile));
} catch (e) {
console.warn('Failed to write ' + inputFile);
console.warn(e);
}
}
module.exports = {
book: getAssets,
ebook: function() {
// Adding prism-ebook.css to the CSS collection forces Gitbook
// reference to it in the html markup that is converted into a PDF.
var assets = getAssets.call(this);
assets.css.push('prism-ebook.css');
return assets;
},
blocks: {
code: // xxxx
codetab: // xxxx
},
hooks: {
// Manually copy prism-ebook.css into the temporary directory that Gitbook uses for inlining
// styles from this plugin. The getAssets() (above) function can't be leveraged because
// ebook-prism.css lives outside the folder referenced by this plugin's config.
//
// @Inspiration https://github.com/GitbookIO/plugin-styles-less/blob/master/index.js#L8
init: function() {
var book = this;
syncFile(book, 'codetab', 'codetab.js', './codetab/codetab.js');
syncFile(book, 'codetab', 'codetab.css', './codetab/codetab.css');
// If failed to write prism-ebook.css. See https://git.io/v1LHY for side effects.
if (isEbook(book)) {
syncFile(book, '', 'prism-ebook.css', './prism-ebook.css');
}
},
page: // xxx
}
};
自己改造后的插件名为gitbook-plugin-prism-codetab-fox,将该插件在book.json中启用并运行gitbook install && gitbook serve命令后,可以看见在对应的GitBook工程下分别将prismjs和codetab相关的插件都安装到node modules模块下。

分别检查这两个js模块,可发现其内容都正常

检查_book目录,发现生成的插件同时包含prismjs以及 codetab相关的文件,此时该插件已经将相关的内容合并了。

查看GitBook中生成的HTML页面源码,可发现相关的静态文件均正常加载,至此问题解决!
