在在Docker中构建GitBook并整合GitLab Runner的使用经验分享一文中,自己在Docker
环境下基于GitLab Runner
实现了GitBook
的自动构建,同时基于实际使用需求添加了很多GitBook
插件,在此过程中积累了一些GitBook
插件的开发与使用经验。
本文简要介绍自己在GitBook
插件定制化修改过程中如何实现从多个不同的文件夹下加载js
和css
文件的实现过程。
问题背景
由于GitBook
官方默认的代码高亮插件highlight使用的是highlightjs,其功能没有prismjs完善,故最初将GitBook
的代码高亮采用prismjs
实现,对应的插件为gitbook-plugin-prism,其使用方式也很简单,只需要在book.json
中添加如下配置:
1
2
3
|
{
"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
使用完整路径,类似如下:
1
2
3
4
5
6
7
8
9
10
|
module.exports = {
book: {
assets: ['./assets1','./assets2'],
css: [
'codetabs1.css','codetabs2.css'
],
js: [
'codetabs1.js','codetabs2.js'
]
},
|
但此种方式明显不可行,通过gitbook serve
启动之后,终端控制台提示类似如下错误
1
|
TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received an instance of List
|
很明显assets
的值只能为单个字符串,不能为数组。
接下来尝试将assets
配置去掉,改为类似如下代码
1
2
3
4
5
6
7
8
|
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
代码高亮相关的文件混合在一起,问题产生!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
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
文件。
1
2
3
4
5
6
7
8
9
|
book: {
assets: './assets',
css: [
'codetabs.css'
],
js: [
'codetabs.js'
]
},
|
要想文件被加载,则必须要引入相关文件,要想文件能被正常访问,在_book
下对应的插件目录中必须要有对应的文件。
至此,问题的解决方案找到!
引入文件很容易实现,直接在数组中添加对应的文件即可,但如何引入相关文件呢?经过多处查看代码后,自己在index.js中找到了答案
1
2
3
4
5
6
|
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
函数进行文件写入:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
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
页面源码,可发现相关的静态文件均正常加载,至此问题解决!