UNPKG

hexo-swpp

Version:

这是 `swpp-backends` 的 hexo 端实现,绝大多数功能由 [swpp-backends](https://github.com/EmptyDreams/swpp-backends) 提供。

373 lines (372 loc) 15 kB
"use strict"; 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); });