hexo-swpp
Version:
这是 `swpp-backends` 的 hexo 端实现,绝大多数功能由 [swpp-backends](https://github.com/EmptyDreams/swpp-backends) 提供。
373 lines (372 loc) • 15 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs = __importStar(require("fs"));
const path_1 = __importDefault(require("path"));
const swpp_backends_1 = require("swpp-backends");
const logger = require('hexo-log').default();
const CONSOLE_OPTIONS = [
{ name: '-t, --test', desc: '尝试拉取指定链接' },
{ name: '-b, --build', desc: '构建 swpp,留空参数与使用该参数效果一致' }
];
let actions;
const configLoadWaitList = [];
/** 等待配置加载 */
function waitUntilConfig() {
if (actions)
return Promise.resolve();
return new Promise(resolve => configLoadWaitList.push(resolve));
}
function checkHexoConfig(config) {
// 类型检查
const typeMap = {
'enable': 'boolean',
'config_path': 'string',
'serviceWorker': 'boolean',
'auto_register': 'boolean',
'gen_dom': 'boolean',
'gen_diff': 'string',
'auto_exec': 'boolean',
'npm_url': 'string',
'sort_rules': 'object',
'track_link': 'boolean',
};
for (let configKey in config) {
const type = typeMap[configKey];
if (!type) {
throw new swpp_backends_1.RuntimeException('error', `yml 配置项中存在非法字段,不存在 [${configKey}] 配置项,请检查是否拼写错误`);
}
if (typeof config[configKey] !== type) {
throw new swpp_backends_1.RuntimeException('invalid_var_type', `yml 配置项类型错误,[${configKey}] 应当传入 ${type} 类型`, { your_value: config[configKey] });
}
}
// 特殊规则校验
const pluginConfig = config;
if (pluginConfig.gen_diff && !pluginConfig.gen_diff.endsWith('.json')) {
throw new swpp_backends_1.RuntimeException('invalid_value', `yml 配置项中值非法,[gen_diff] 的值应当以 '.json' 结尾`, { your_value: pluginConfig.gen_diff });
}
if (pluginConfig.npm_url && pluginConfig.npm_url.endsWith('/')) {
throw new swpp_backends_1.RuntimeException('invalid_value', `yml 配置项中值非法,[npm_url] 的值不应当以 '/' 结尾`, { your_value: pluginConfig.npm_url });
}
}
/** 运行插件 */
async function start(hexo) {
// @ts-ignore
globalThis.hexo = hexo;
const config = hexo.config;
const pluginConfig = config['swpp'] ?? config.theme_config['swpp'];
if (!pluginConfig?.enable)
return;
checkHexoConfig(pluginConfig);
let init = false;
hexo.on('generateBefore', async () => {
if (init)
return;
init = true;
buildServiceWorker(hexo, pluginConfig);
sort(hexo, pluginConfig);
await initRules(hexo, pluginConfig);
});
hexo.extend.console.register('swpp', 'Hexo Swpp 的相关指令', {
options: CONSOLE_OPTIONS
}, async (args) => {
const test = args.t ?? args.test;
// noinspection JSUnresolvedReference
const build = args.b ?? args.build;
if (test) {
if (typeof test == 'boolean' || Array.isArray(test) || !/^(https?):\/\/(\S*?)\.(\S*?)(\S*)$/i.test(test)) {
logger.error('[SWPP][CONSOLE] --test/-t 后应跟随一个有效 URL');
}
else {
await initRules(hexo, pluginConfig);
try {
const compilationData = actions.compilationData;
const response = await compilationData.compilationEnv.read('NETWORK_FILE_FETCHER').fetch(test);
if ([200, 301, 302, 307, 308].includes(response.status)) {
logger.info('[SWPP][LINK TEST] 资源拉取成功,状态码:' + response.status);
}
else {
logger.warn('[SWPP][LINK TEST] 资源拉取失败,状态码:' + response.status);
}
}
catch (e) {
logger.warn('[SWPP][LINK TEST] 资源拉取失败', e);
}
}
}
if (build) {
if (typeof build !== 'boolean') {
logger.warn('[SWPP][CONSOLE] -build/-b 后方不应跟随参数');
}
}
if (build || !test) {
try {
await runSwpp(hexo, pluginConfig);
}
catch (e) {
logger.error('执行 SWPP 指令时出现异常');
console.error(e);
process.exit(-1);
}
}
});
if (pluginConfig['auto_exec']) {
hexo.on('deployBefore', async () => {
try {
await runSwpp(hexo, pluginConfig);
}
catch (e) {
logger.error('执行 SWPP 指令时出现异常');
console.error(e);
process.exit(-1);
}
});
}
}
async function initRules(hexo, pluginConfig) {
if (!actions) {
try {
await loadConfig(hexo, pluginConfig);
configLoadWaitList.forEach(it => it());
if (process.argv.find(it => 'server'.startsWith(it)))
checkVersion(pluginConfig);
}
catch (e) {
logger.error("[SWPP] 加载时遇到错误。");
logger.error(e);
process.exit(0x114514);
}
}
}
/** 运行 swpp 指令 */
async function runSwpp(hexo, pluginConfig) {
const config = hexo.config;
if (!fs.existsSync(config.public_dir))
return logger.warn(`[SWPP] 未检测到发布目录,跳过指令执行`);
await initRules(hexo, pluginConfig);
const fileList = await actions.buildFiles(
// @ts-ignore
['serviceWorker', 'domJs', ...(pluginConfig.gen_diff ? [] : ['diffJson'])]);
for (let item of fileList) {
if (await item.path.exists()) {
throw new swpp_backends_1.RuntimeException('file_duplicate', `文件[${item.path.absPath}]已经存在`);
}
await fs.promises.mkdir(item.path.parent().absPath, { recursive: true });
await fs.promises.writeFile(item.path.absPath, item.content, 'utf-8');
}
}
/** 检查 swpp-backends 的版本 */
function checkVersion(pluginConfig) {
const compilationData = actions.compilationData;
const root = pluginConfig['npm_url'] ?? 'https://registry.npmjs.org';
const fetcher = compilationData.compilationEnv.read('NETWORK_FILE_FETCHER');
fetcher.fetch(`${root}/swpp-backends/${swpp_backends_1.swppVersion}`)
.then(response => {
if (![200, 301, 302, 307, 308].includes(response.status))
return Promise.reject(response.status);
return response.json();
}).then(json => {
if ('error' in json)
return Promise.reject(json.error);
if ('deprecated' in json) {
logger.warn(`[SWPP][VersionChecker] 您使用的 swpp-backends@${swpp_backends_1.swppVersion} 已被弃用,请更新版本!`);
logger.warn(`\t补充信息:${json['deprecated']}`);
logger.warn('请注意!!!当您看到这条消息时,表明您正在使用的后台版本存在漏洞,请务必更新版本!!!');
logger.info('可以使用 `npm update swpp-backends` 更新后台版本,或使用您自己常用的等效命令。');
}
else {
logger.info('[SWPP VersionChecker] 版本检查通成功,您使用的版本目前没有被废弃,注意定期检查版本更新。');
}
}).catch(err => {
const isSimple = ['number', 'string'].includes(typeof err);
logger.warn(`[SWPP][VersionChecker] 版本检查失败${isSimple ? ('(' + err + ')') : ''}`);
if (!isSimple)
logger.warn(err);
});
}
/** 加载配置文件 */
async function loadConfig(hexo, pluginConfig) {
const themeName = hexo.config.theme;
const publishPath = hexo.config.public_dir;
actions = await swpp_backends_1.BasicActions.build({
context: 'prod',
publicPath: /[/\\]$/.test(publishPath) ? publishPath : publishPath + '/',
isServiceWorker: pluginConfig.serviceWorker ?? true,
domJsPath: pluginConfig.gen_dom ? 'sw-dom.js' : undefined,
diffJsonPath: pluginConfig.gen_diff,
trackLink: pluginConfig.track_link
});
await actions.loadConfig({
compilationEnv: {
DOMAIN_HOST: new URL(hexo.config.root, hexo.config.url)
}
});
const configPath = pluginConfig['config_path'] ?? 'swpp.config.ts';
const configPaths = [configPath, `./themes/${themeName}/${configPath}`, `./node_modules/hexo-${themeName}/${configPath}`];
const isDirectory = configPath.endsWith('/');
for (let path of configPaths) {
if (!fs.existsSync(path))
continue;
if (isDirectory) {
const list = fs.readdirSync(path).sort();
for (let uri of list) {
await actions.loadConfig(path_1.default.resolve(path, uri));
}
}
else {
await actions.loadConfig(path_1.default.resolve(path));
}
}
actions.buildConfig();
}
/** 注册生成器 */
function buildServiceWorker(hexo, hexoConfig) {
const { serviceWorker, auto_register, gen_dom } = hexoConfig;
// 生成 sw
if (serviceWorker ?? true) {
hexo.extend.generator.register('build_service_worker', async () => {
await waitUntilConfig();
const build = (await actions.buildFiles(['tracker', 'version', 'diffJson', 'domJs'])).find(it => it.key === 'serviceWorker');
if (!build)
throw new swpp_backends_1.RuntimeException('error', '未找到 swpp-backends 生成的 sw');
return ({
path: build.path.fileName(),
data: build.content
});
});
}
// 生成注册 sw 的代码
if (auto_register ?? true) {
waitUntilConfig().then(() => {
const runtimeData = actions.runtimeData;
hexo.extend.injector.register('head_begin', `<script>(${runtimeData.domConfig.read('registry')})()</script>`);
});
}
// 生成 sw-dom.js
if (gen_dom ?? true) {
hexo.extend.injector.register('head_end', () => {
// noinspection HtmlUnknownTarget
return `<script defer src="/sw-dom.js"></script>`;
});
hexo.extend.generator.register('build_dom_js', async () => {
await waitUntilConfig();
const build = (await actions.buildFiles(['serviceWorker', 'tracker', 'version', 'diffJson'])).find(it => it.key === 'domJs');
if (!build)
throw new swpp_backends_1.RuntimeException('error', '未找到 swpp-backends 生成的 sw-dom.js');
return {
path: 'sw-dom.js',
data: build.content
};
});
}
}
/** 对 hexo 中的变量进行排序 */
function sort(hexo, pluginConfig) {
const version = hexo.version;
let Locals;
if (version.startsWith('7')) {
Locals = require(path_1.default.resolve('node_modules/hexo/dist/hexo/locals')).prototype;
}
else {
Locals = require(path_1.default.resolve('node_modules/hexo/lib/hexo/locals')).prototype;
}
const compare = (a, b) => {
const result = a.length === b.length ? a < b : a.length < b.length;
return result ? -1 : 1;
};
const sort = (obj, value, keyName) => {
if (!obj || !value)
return;
const target = obj.data ?? obj;
if ('sort' in target) {
if (typeof value === 'boolean') {
target.sort(compare);
}
else {
target.sort((a, b) => compare(a[value], b[value]));
}
}
else {
const keyList = Object.getOwnPropertyNames(target);
if (keyList.length === 0)
return;
let comparator;
if (typeof value === 'boolean') {
comparator = (a, b) => compare(a.value, b.value);
}
else if (typeof target[keyList[0]] == 'string') {
if (value != 'name') {
return console.warn(`排序时出现问题,某个键(该键的 key 为“${keyName}”)的排序规则存在问题`);
}
comparator = (a, b) => compare(a.value, b.value);
}
else if (value in target[keyList[0]]) {
comparator = (a, b) => compare(a.value[value], b.value[value]);
}
else {
return console.warn(`排序时出现问题,某个键(该键的 key 为“${keyName}”)的排序规则存在问题`);
}
const result = [];
for (let key of keyList) {
result.push({
value: target[key],
id: key
});
delete target[key];
}
result.sort(comparator);
for (let item of result) {
target[item.id] = item.value;
}
}
};
const list = {
posts: 'title',
pages: 'title',
tags: 'name'
};
Object.assign(list, pluginConfig.sort_rules ?? {});
const getter = Locals.get;
Locals.get = function (name) {
const result = getter.call(this, name);
if (name in list)
sort(result, list[name], name);
if ((typeof result == 'object') && 'forEach' in result) {
result.forEach((it) => {
for (let tag in list)
sort(it[tag], list[tag], tag);
});
}
return result;
};
}
start(hexo).catch(e => {
logger.error("[SWPP] 加载时遇到严重错误!");
logger.error(e);
process.exit(0x19491001);
});