GitMaster
里面展示项目结构时,同时也显示了对应的icon
。
看起来和Octotree
是没什么区别,但其实在维护和更新上是有显著区别的。
Octotree
是直接从file-icons/atom
复制相关样式和字体文件到项目里,这样耦合的方式很不利于维护,所以我在处理文件图标时进行了额外的处理,把所有文件图标通过npm
包的形式引入。
大家可能好奇为什么不直接用file-icons/atom
,没有采用的原因有几个:
-
css
样式经过Content Script
方式注入会污染全局样式 - 缺少
Octicons
图标 -
woff2
文件指向不对
方案
经过考量,最终采用通过脚本处理文件,然后发布npm
包: ineo6/file-icons。
下载 file-icons/atom
使用download-git-repo
从GitHub
下载代码。
还使用npm
开发过程中常用的chalk
和ora
。
ora
是一个终端加载动画的库,有了它,你的终端不会再苍白。
chalk
的作用是丰富终端文字样式。
const path = require('path');
const chalk = require('chalk');
const fs = require('fs');
const os = require('os');
const ora = require('ora');
const download = require('download-git-repo');
const cwd = process.cwd();
const origin = 'file-icons/atom';
const branch = "#master";
const tmpDirPrefix = path.join(os.tmpdir(), '.tmp');
const tmpDir = fs.mkdtempSync(tmpDirPrefix);
const spinner = ora(`downloading ${origin}...`);
spinner.start();
download(`${origin}${branch}`, tmpDir, { clone: false }, function (err) {
spinner.stop();
if (err) {
console.log(chalk.red(`Failed to download repo https://github.com/${origin}${branch}`, err));
} else {
console.log(chalk.green(`Success to download repo https://github.com/${origin}${branch}`));
const spinnerExtract = ora('Extract Data...');
spinnerExtract.start();
try {
// 处理代码的逻辑
spinnerExtract.stop();
console.log(chalk.green('Done!'));
} catch (e) {
spinnerExtract.stop();
console.log(e.message);
}
}
})
less处理
替换@font-face url
文件 styles/fonts.less 里面的内容是如下格式:
@font-face {
font-family: FontAwesome;
font-weight: normal;
font-style: normal;
src: url("atom://file-icons/fonts/fontawesome.woff2");
}
这个显然无法在前端项目甚至Chrome
扩展里正确引用woff2
字体。
因为在Chrome
扩展里无法引入远程的woff2
,所以改为引入扩展目录中的字体,即改成如下格式:
@font-face {
font-family: FontAwesome;
font-weight: normal;
font-style: normal;
src: url("@{ICON_PATH}/fonts/fontawesome.woff2");
}
然后在webpack
里设置less
变量ICON_PATH
'less-loader',
{
loader: 'less-loader',
options: {
javascriptEnabled: true,
modifyVars: {
ICON_PATH: 'chrome-extension://__MSG_@@extension_id__'
},
},
},
如何修改less文件
推荐使用gonzales-pe
,它能够解析SCSS
, Sass
, LESS
,并转为AST
抽象语法树。
然后我们根据需要修改AST
的结构,最终调用astNode.tostring()
转换得到代码。
const { parse } = require('gonzales-pe');
const fs = require('fs');
const chalk = require('chalk');
function replaceAtomHost(content) {
if (content.includes('atom://file-icons/')) {
content = content.replace('atom://file-icons/', '@{ICON_PATH}/');
}
return content;
}
function replaceUrlHost(ast) {
ast.traverseByType('uri', (node) => {
node.traverse(item => {
if (item.is('string')) {
item.content = replaceAtomHost(item.content)
}
});
});
return ast;
}
function replaceDeclaration(ast) {
ast.traverseByType('declaration', (decl) => {
let isVariable = false;
decl.traverse((item) => {
if (item.type === 'property') {
item.traverse((childNode) => {
if (childNode.content === 'custom-font-path') {
isVariable = true;
}
});
}
if (isVariable) {
if (item.type === 'value') {
const node = item.content[0];
node.content = replaceAtomHost(node.content)
}
}
return item;
});
});
return ast;
}
function processFonts(lessFile) {
const content = fs.readFileSync(lessFile).toString();
if (content && content.length > 0) {
let astTree;
try {
astTree = parse(content, {
syntax: 'less'
})
} catch (e) {
console.log(chalk.red(`parse error: ${e}`));
return;
}
try {
astTree = replaceUrlHost(astTree);
astTree = replaceDeclaration(astTree);
return astTree;
} catch (e) {
console.log(chalk.red(`transform error: ${e}`));
}
}
}
module.exports = function (file) {
const ast = processFonts(file);
if (ast) {
fs.writeFileSync(file, ast.toString());
}
}
文件处理
.
├── bin
├── index.js
├── index.less // 入口样式
├── lib // 完成的样式,字体
└── resource // 待合并资源
从file-icons/atom
复制以下文件到lib
:
fonts
styles
lib/icons
lib/utils.js
从resource
里面内容复制到lib
。
在index.less
里面内容如下:
@import "lib/styles/colours.less";
@import "lib/styles/fonts.less";
@import "lib/styles/octicons.less";
.file-icons-wrapper {
@import "lib/styles/icons.less";
@import "lib/styles/fix.less";
}
这里通过添加父级file-icons-wrapper
来控制样式影响范围。
至此,大致完成了针对file-icons/atom
的定制工作。
总结
最终我们通过npm run build
命令完成拉取代码,处理文件的。
对应的脚本在bin/update.js
当然最后可以优化的是让任务自动执行,这点可以结合GitHub Actions
的定时任务实现。本文就暂不花费篇幅介绍了,感兴趣的可以摸索下。