前置:JS改造计划简介暨预备内容
发表于更新于
字数总计2.3k阅读时长9分钟阅读量
观前提示:JS改造计划与本人编写的SW一起食用效果最佳
- 将手动调用
pjax.loadUrl
改为调用pjax.refresh
- 修复多处调用
kms.readPostsJson
时可能重复发起网络请求的问题
介绍
JS改造是指为了配合SW缓存策略而将部分内嵌在HTML文件中的内容改为通过JS生成。
优劣对比:
- 将多个页面中的重复数据提取到一个文件中
- 降低缓存刷新频率
- 减小HTML文件大小
- 博文数量达到一定程度时JSON文件体积会非常庞大
- 不利于SEO
- 不兼容不支持JS的浏览器
生成 Json
注意:本系列教程均建立在用户已经使用了 abbrlink 的前提下,如果没有使用该插件,请注意修改代码中的相关片段!!!
注:本系列教程假设用户的博文URL路径为/posts/[abbrlink]
,如果你的URL不是这个格式,请自行修改代码中的相关片段。
在博客根目录中创建scripts
文件夹(已有的话不需要删除原有内容),然后在其中创建posts.js
文件(文件名可修改),键入以下内容:
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 | const logger = require('hexo-log')()
hexo.extend.generator.register('buildPostJson', async () => { const resultJson = {config: {}} const config = hexo.theme.config const list = hexo.locals.get('posts').data
const buildAbbrlinkInfo = () => { const json = {} for (let post of list) { const leftIndex = post.cover.indexOf('bg/b') + 4 const rightIndex = post.cover.lastIndexOf('.') const img = post.cover.substring(leftIndex, rightIndex) json[post.abbrlink] = { title: post.title, img, date: post.date, updated: post.updated, categories: post.categories.data.map(it => it.name), tags: post.tags.data.map(it => it.name), des: post.description } if (post.subtitle) json[post.abbrlink].subtitle = post.subtitle if (post.keywords) json[post.abbrlink].keywords = post.keywords } resultJson.info = json }
const buildRelatedJsonInfo = () => { if (!config.related_post.enable) return resultJson.config.related = config.related_post.date_type const maxCount = config.related_post.limit const categories = hexo.locals.get('categories').data const tags = hexo.locals.get('tags').data const findObj = (src, dist) => { for (let value of src) { if (value.name === dist) return value.posts.data } return [] } const getPostsByTags = tag => { const result = [] for (let value of findObj(tags, tag.name)) result.push(value) return result } const getPostsByCategories = cat => { const result = [] for (let value of findObj(categories, cat.name)) result.push(value) return result } const handle = post => { const map = new Map() const plusValue = (post, plus = 1) => { if (map.has(post)) map.set(post, map.get(post) + plus) else map.set(post, plus) } for (let tag of post.tags.data) { const list = getPostsByTags(tag) for (let value of list) plusValue(value) } for (let cat of post.categories.data) { const list = getPostsByCategories(cat) for (let value of list) plusValue(value, 2) } const result = [] map.forEach((value, key) => result.push({post: key, value: value})) result.sort((a, b) => b.value - a.value) return result }
for (let post of list) { const info = handle(post) const json = [] for (let value of info) { if (value.post.abbrlink === post.abbrlink) continue if (json.length === maxCount) break json.push(value.post.abbrlink.toString()) } for (; json.length !== maxCount;) { const index = Math.floor(Math.random() * list.length) const abbrlink = list[index].abbrlink.toString() if (abbrlink === post.abbrlink.toString() || json.indexOf(abbrlink) > -1) continue json.push(abbrlink) } resultJson.info[post.abbrlink].related = json } }
const buildAnnouncement = () => { if (config.aside.enable && config.aside.card_announcement.enable) resultJson.config.doc = config.aside.card_announcement.content }
const tasks = [buildAbbrlinkInfo, buildRelatedJsonInfo, buildAnnouncement] await Promise.all(tasks.map(it => new Promise(resolve => resolve(it())))) logger.info(`文章JSON构建成功(${list.length})`) return { path: 'postsInfo.json', data: JSON.stringify(resultJson) } })
|
文件描述
该方法构建出的JSON格式如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | { "config": { "related": "[相关推荐日期样式]", "doc": "[公告]" }, "info": { "[博文abbrlink]": { "title": "[标题]", "date": "[发布日期]", "updated": "[更新日期]", "img": "[封面]", "categories": ["[分类列表]"], "tags": ["标签列表"], "des": "[描述]", "subtitle": "[副标题]", "keywords": "[关键字]" } } }
|
生成的文件里面包含后面所有功能所需的全部数据,如果有部分功能你没有采用,这里可能会多出一部分数据,可以根据自己需要进行删减。
目前还不支持配置,如果需要修改json
的生成路径的话还请自行更改。
文件读取
现在我们成功的将文件生成了,现在我们需要在前端加载并读取该文件。
文中需要引入JS时,如果内容以`kms`包围,说明是将代码添加到之前引入的JS中,而非再重新声明一个`kms`,重新声明一个的话会覆盖掉前面声明的内容!
引入下面的JS:
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 | const kms = { readPostsJson: () => new Promise(async resolve => { const cache = kms._cache.postsJson if (cache) { if (cache.info) return resolve(cache) else { const id = setInterval(() => { const cache = kms._cache.postsJson if (cache.info) { clearInterval(id) resolve(cache) } }, 100) } } else { kms._cache.postsJson = {} const json = kms._cache.postsJson = await (await fetch(`/postsInfo.json`)).json() const info = json.info const dateSort = json.dateSort = [] const updatedSort = json.updatedSort = [] for (let abbrlink in info) { const value = info[abbrlink] value.img = `https://image.kmar.top/bg/b${value.cover}.jpg` dateSort.push(abbrlink) updatedSort.push(abbrlink) } dateSort.sort((a, b) => info[a].date < info[b].date ? 1 : -1) updatedSort.sort((a, b) => info[a].updated < info[b].updated ? 1 : -1) resolve(json) } }), readPostInfo: (json, abbrlink) => json.info[abbrlink] }
|
接下来,当需要访问这个文件的时候直接调用kms.readPostsJson()
即可。值得注意的是,外部调用不应当为了减少网络占用而缓存返回结果,因为函数内部已经通过sessionStorage
对结果进行了缓存。不过目前我没有找到监听用户主动刷新页面的方法,所以没有添加缓存自动删除的功能,如果有小伙伴知道解决方案的话可以分享一下。
数据收集
现在我们获取到的数据非常少,还需要再对其进行扩充:
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 | const kms = { collectJInfo: (pageNames) => new Promise(async resolve => { const json = await kms.readPostsJson() let flag = false for (let pageName of pageNames) { if (json.cache && json.cache[pageName]) continue flag = true const list = {} for (let abbrlink in json.info) { const info = json.info[abbrlink] for (let name of info[pageName]) { const value = list[name] || [] value.push(abbrlink) list[name] = value } } if (!json.cache) json.cache = {} json.cache[pageName] = list } if (flag) sessionStorage.setItem('postsJson', JSON.stringify(json)) resolve(json) }) }
|
代码执行 & pjax兼容
事件
为了通过异步的方式加载JS,所以对于所有JS代码,我们都选择通过类似于DOMContentLoaded
的事件将代码的执行推迟到页面加载完毕后执行。
没有启用pjax
的小伙伴可以直接这么写:
1 2 3 | document.addEventListener('DOMContentLoaded', () => { })
|
如果启用了pjax
则这么写:
1 2 3 4 5 6 7 8 9 10 | (() => { const task = () => { } document.addEventListener('DOMContentLoaded', task) document.addEventListener('pjax:success', function fun() { task() }) })()
|
其中有一行代码被注释了,这行代码的目的是保证事件执行后被删除,也就是在保证不论这段代码执行几次,事件同一时间内只会被注册一次。
这行代码在代码会在每一次加载页面都会执行或想让代码只在当前页面执行时应当使用,以保证事件的唯一性,下文同理。
这段话看不懂没关系,后面用到的时候会详细解释。
简易封装
如果每个地方都这么写未免有些太过费时,所以也可以选择自己发布一个事件,在任意一个可以在任何页面都会加载的JS中添加以下代码:
1 2 3 4 5 | document.addEventListener('DOMContentLoaded', () => { const pushEvent = () => document.dispatchEvent(new Event('kms:loaded')) pushEvent() document.addEventListener('pjax:loaded', pushEvent) })
|
接下来,需要执行代码的地方这么写即可:
1 2 3 4 | document.addEventListener('kms:loaded', function fun() { })
|
后续教程中,我们都会使用这个事件,没有pjax的小伙伴可以替换为DOMContentLoaded
事件。
链接跳转
没有启用pjax的小伙伴链接跳转时无需做额外处理,但是启用了pjax的小伙伴如果不做额外处理的话我们用js生成的元素用户点击时pjax并不会生效。
为了让pjax对于我们新加入的链接也有效果,我们需要调用pjax.refresh
函数,具体内容见:《Pjax README》。
前置:JS改造计划简介暨预备内容空 梦 | 山岳库博
更新于 2022-08-13
发布于 2022-07-30