本文我们将介绍 steemblog 的最新的进展。
Image Source: Pixabay
steemblog 是一项 Steem 文章镜像服务。
如果你还不了解这个服务,或许你可以通过访问:https://steemblog.github.io/@team-cn/ 作为一个案例体验一下。
在文章《用GitHub创建Steem文章镜像 | Mirroring Your Steem Blogs on GitHub | 免费博客备份服务: steemblog》中,我们提到过创建steemblog 服务的缘由,主要是为了更好的查看历史文章。
现在来看,它主要带来了以下几点助益:
根据上一篇文章和微信群中的留言,我们已经帮以下12个账号部署了每日更新的博客镜像服务:
每日北京时间凌晨0点,steemblog 会自动同步镜像一次。
总体而言,对我来说 steemblog 是一个还比较好用的时光机(Time Machine),可以更好地学习和了解 steem 上各种有趣的事情。
下面举几个例子来看看吧:
以下是steemblog的几个有意思的应用场景。
初入CN区或者新手村的新人,想要了解一下社区内的朋友,其实没有看上去那么容易。去逐个翻看各个用户的历史文章,效率很低。
但新手村访谈部分解决了这个问题,我们只要看 #cn-interview 这个标签下的文章就行,但由于steemit / busy 查看标签历史或多或少都有点问题,所以也很难流畅地阅读。在以前,大概有2种方式:
但这两种方法更新可能不太及时,最近的访谈没有录入,而 steemblog 是及时更新的。 steemblog 的搜索功能也更快速和有针对性。
通过链接:https://steemblog.github.io/@team-cn/tags/cn-interview/,我们可以看到新手村历史上所有的访谈的记录:
最新的访谈:
最初的访谈:
按村名搜索:
screenshots from steemblog: https://steemblog.github.io/@team-cn/tags/cn-interview/
想要了解新手村 @team-cn 里的各位朋友,用 steemblog 看一看就一目了然了,确实很有趣。 这对新人也很有帮助,建议你们来看看。
Steem是个社区,社区是由人组成的,其中有些人由于各种原因成为舆论的焦点或实际的掌权派,总是在历史上不断发生的事情。
----
最近 #sct 标签称霸舆论,对于创始人之一的 Jack @jack8831 也接受到了大量的八卦和追捧。但要看家里有矿的男神的生辰八字,也不是这么容易的事啊。
steemblog 可以帮你解决这个问题。昨天服务器花了6分钟备份了 Jack @jack8831 的历史文章:https://steemblog.github.io/@jack8831/
然后,要阅读 Jack 的最新动态、历史观点、常用的标签,就容易多了。
不过韩文/朝鲜语我不懂,所以读起来还是需要借助机器翻译的力量。但不可思议的是,下面早期文章的图中的“基本英语”我居然看懂了!!
Jack 好像曾经喜欢给韩国朋友们分享英文,标题中也有“鲑鱼”(연어)的前缀:https://steemblog.github.io/@jack8831/tags/englishforkorean/
所以 steemcoinpan 上鲑鱼泛滥,似乎也在情理之中了。
screenshots from steemblog: https://steemblog.github.io/@jack8831/
Jack和团队做的 steemcoinpan 风头正劲,要向韩国朋友们学习,也不妨了解下他们的过往~
----
回到CN区。
我之前读过《Steem指南》中的 《Steem 十讲》,觉得 刘美女 @deanliu 的观点和文章很独到,所以特地同步了刘美女的文章到 steemblog (服务器时间 18 分钟,刘美女的文章和标签都很多,同步较慢):https://steemblog.github.io/@deanliu/
刘美女已经写了 1400+ 文章,可谓笔耕不辍、涉猎广泛。
最常用的首标签(类别):除了CN区话题,摄影、旅游、美食、讨论 steem / blockchain、以及聊娃(TT)是日常。
最初的文章都是英文,拓荒时期CN区还没有形成规模吧:
老道茶馆的一些过往:https://steemblog.github.io/@deanliu/tags/laodr-teahouse/
screenshots from steemblog: https://steemblog.github.io/@deanliu/
不过其实之前 大鹏 @dapeng 也曾经帮助 刘美女
@deanliu 备份过文章( GitHub上的 文章 Markdown 源码 / Netlify 备份站点 ),只是并没有设置成自动更新、以及网站模板较简单罢了。从备份的角度,steemblog 也只是另一种工具和服务而已,并不新奇,但希望它也有所帮助。
---
我不是很喜欢八卦,所以八卦社区名人和豪杰们的任务,就交给大家自己去完成了。不过想要用 steemblog 来八卦哪些名人倒是可以给我提供一个名单,我可以帮助备份到 steemblog。
活动一般以标签的形式进行推广,所以与上面看到的以个人账户为主的文章展示方式并不相同。对于标签文章的备份,steemblog 也可以支持,但目前并没有部署单独对标签进行备份的案例,而且需要对界面做少量修改。
这里展示一下 我 @robertyan 主办的对联活动如何查看。
以 https://steemblog.github.io/@{username}/tags/{tag}/ 的格式,就能查看标签下的文章。由于对联活动的文章都是我发表的,所以可以这样查看:https://steemblog.github.io/@robertyan/tags/cn-couplet/
screenshot from steemblog: https://steemblog.github.io/@robertyan/tags/cn-couplet/
已经办到第四期了,欢迎对语言和文化有兴趣的朋友参与。这期的题目是给刚刚升级到50/60/70级的朋友们写寿联:https://busy.org/@robertyan/qs55nyh8x9
第四种用途对我还是挺有帮助的:Steem上有一些极度认真的作者,对于喜欢看好文章用户,现在的steemit/busy/steempeak等界面都满足不了需求。
上面提到过我推崇 刘美女 @deanliu 的文章并在昨天同步到了 steemblog。其实更早之前,我还同步了 维参
@crowflew 的文章,是我之前在 steem 看到的最有意思的文章系列之一吧:https://steemblog.github.io/@crowflew/
对于阅读博客,steemblog 会显示目录方便作者通览全文和跳转到章节:
可以方便的跳转到前后文章、找到 steem的原文,以及回到顶部等:
screenshots from steemblog: https://steemblog.github.io/@crowflew/or-or-or-or/
当然,steem 上的奇文还有很多,如果你有觉得仔细希望阅读的作者,也不妨把他的账户分享给我,一起欣赏一下。
steemblog 不但能让有用的信息能够被快速查询到,还能让罪行无处遁形。是不是听着挺像一位侦探的?
好吧,下面我们来看看 机机 @julian2013 和 村长
@ericet 是如何互黑的。(其实你们是不是更愿意看 软哥
@softmetal 和 小P
@pgr 互黑的桥段?)
到 steemblog 页面上,点击右上角的搜索进行“侦查”。
----
机机谈论村长:
screenshots from https://steemblog.github.io/@julian2013/
----
村长评价机机:
screenshots from https://steemblog.github.io/@ericet/
----
看上去机机从来没说过村长“坏”话,而村长可不是这样。真的是这样吗?
那么到底究竟说了什么呢?还是要各位自己去看才行。真相永远只有一个,看了以后不妨告诉我。
其实相比查看人的文章,我更感兴趣组织的发展历史和起源,以及它们的生长过程,比如 @team-cn、
@steemstem、
@utopian-io,这里也为他们备份了镜像以便于追根溯源、温故知新:
screenshot from https://steemblog.github.io/@team-cn/page/45/
----
screenshot from https://steemblog.github.io/@steemstem/page/28/
----
screenshot from https://steemblog.github.io/@utopian-io/page/33/
希望这些团队不忘初心,破除艰险、勇往直前。
----
以上这些例子,不知对你来说是不是也同样有趣或有帮助。
如果需要帮你镜像博客,可以在文章后面留言,本次提供5个免费服务名额,先到先得~
相比于《用GitHub创建Steem文章镜像 | Mirroring Your Steem Blogs on GitHub | 免费博客备份服务: steemblog》中的描述,steemblog 最近也有一些功能和性能更新,这里简要介绍:
其实 steemblog 的功能实现是相对比较简单的(可以参考上一篇文章)。但真正有困难的是在 hexo 的编译速度。
hexo 虽然号称速度极快,但实际编译的时候简直是灾难。当然,原因不能完全归罪与 hexo 框架本身,更多是 hexo theme的问题。
例如,以下是几个账户一开始的编译所有文章花费的时间:
@andrewma: 400+ posts, 80+ tags, ~20 mins
@ericet: 630+ posts, 260+ tags, > 60 mins
很多人的文章数量都接近这个量级,如果要全都同步的话,时间耗费还是很大的,每天更新就要消耗很多资源。
除了优化编译方式,另一种是采用增量更新(即只编译新增加的文章), hexo 虽然自称是支持增量更新的,但实际情况是基本不支持、且有很多潜在的问题。
----
(a) 优化编译过程
其实原先我只打算做一个简单的服务,不想在主题(theme)优化上花太多功夫,但真正用起来才发现开源世界并不是那么美好 :)
为了完成给大家的每天更新的承诺,以下是优化的方案。
策略1:修改主题:
.gitmodules
[submodule "theme"] path = themes/icarus url = https://github.com/steemblog/hexo-theme-icarus.git
code from https://github.com/steemblog/blog | MIT License
策略2:大量使用缓存(cache)
themes/icarus/layout/layout.ejs
<% if (page.__widget) { %>
<%- body %>
<% } else { %>
<!DOCTYPE html>
<html <%- has_config('language') ? ' lang="' + get_config('language').substring(0, 2) + '"' : '' %>>
<head>
<%- partial('common/head', {}, {cache: false}) %>
</head>
<body class="is--column">
<%- partial('common/navbar', { page }) %>
<% function main_column_class() {
switch (column_count()) {
case 1:
return 'is-12';
case 2:
return 'is-8-tablet is-8-desktop is-8-widescreen';
case 3:
return 'is-8-tablet is-8-desktop is-6-widescreen'
}
return '';
} %>
<section class="section">
<div class="container">
<div class="columns">
<%- partial('common/widget', { position: 'left' }) %>
<%- partial('common/widget', { position: 'right' }) %>
<%- partial('component/pjax_widget_js', {}, {cache: true}) %>
<div class="column has-order-2 column-main"><%- body %></div>
</div>
</div>
</section>
<%- partial('common/footer', {}, {cache: true}) %>
<%- partial('common/scripts', {}, {cache: true}) %>
<% if (has_config('search.type')) { %>
<%- partial('search/' + get_config('search.type'), {}, {cache: true}) %>
<% } %>
</body>
</html>
<% } %>
code from https://github.com/steemblog/hexo-theme-icarus | MIT License
对于重复的模块,使用 partial('component', {param}, {cache: true}) 来替换原来的实现
策略3:控件页面分离
将原来的 profile, recent_posts, category, tag, tagcloud, archive 等组件,生成独立的 html 页面,减少冗余的 html。此外,组件采用独立的页面也是实现增量编译所必须完成的,所以这一步必须要做。
themes/icarus/layout/layout.ejs 需要修改 generators,生成新的 widgets 页面。
/**
* Widget page generator
*/
module.exports = function (hexo) {
hexo.extend.generator.register('widget', function (locals) {
const widgets = hexo.extend.helper.get('get_config').bind(this)('widgets');
const component_widgets = widgets.filter((w) => (w.component))
return component_widgets.map(function(widget){
return {
path: `widgets/${widget.type}.html`,
layout: 'component/pjax_widget_src',
data: {
widget: widget,
__widget: true
}
};
});
});
}
code from https://github.com/steemblog/hexo-theme-icarus | MIT License
themes/icarus/layout/component/pjax_widget_ref.ejs 将原来的组件用对页面的引用替换。
<div class="card widget">
<div class="card-content">
<div id="widget-" data-pjax="<%= `${get_config("root")}widgets/${widget.type}` %>.html" style="position: relative; width: 100%; display: block;"></div>
</div>
</div>
code from https://github.com/steemblog/hexo-theme-icarus | MIT License
themes/icarus/layout/component/pjax_widget_src.ejs 对不同的组件,获取对应的内容
<%- partial(`widget/content/${page.widget.type}`, {widget: page.widget}) %>
code from https://github.com/steemblog/hexo-theme-icarus | MIT License
themes/icarus/source/js/pjax_widget.js 在页面加载时通过 ajax 来通过url获取组件的 html,替换控件的内容为实际内容。
if (typeof(jQuery) !== 'undefined') {
(function($){
function load_pjax(element) {
// $.pjax({url: element.attr("data-pjax"), container: "#"+element.attr("id")})
$.get(element.attr("data-pjax"), function(data) {
element.html(data);
});
}
$("div[data-pjax]").each(function(){
load_pjax($(this));
})
})(jQuery);
} else {
(function(){
window.$ = document.querySelectorAll.bind(document);
function load_pjax(element) {
const url = element.getAttribute("data-pjax")
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = function() {
element.innerHTML = xhr.responseText;
};
xhr.send();
}
for (const e of $("div[data-pjax]")) {
load_pjax(e);
}
})();
}
code from https://github.com/steemblog/hexo-theme-icarus | MIT License
themes/icarus/layout/common/widget.ejs 对控件,也要添加 cache 模式,减少冗余。
<% function render_widget(widget) {
const component = widget.component || false;
const cache = widget.cache || false;
if (component) {
const cahced_prefix = cache ? 'cached_' : '';
const template = `component/${cahced_prefix}pjax_widget_ref`;
return partial(template, { widget })
} else {
return partial('widget/' + widget.type, { widget, post: page }, {cache: cache});
}
} %>
<div class="column column- ">
<% get_widgets(position).forEach(widget => {%>
<%- render_widget(widget) %>
<% }) %>
<% if (position === 'left') { %>
<div class="column-right-shadow is-hidden-widescreen ">
<% get_widgets('right').forEach(widget => {%>
<%- render_widget(widget) %>
<% }) %>
</div>
<% } %>
</div>
<% } %>
code from https://github.com/steemblog/hexo-theme-icarus | MIT License
此外,每个组件都要重构将内容独立出来,这里不详细解释了。
策略4:采用时间线布局
将categories和tags页面的布局都显示为时间线模式,更容易浏览,且页面所占空间更小。
以 category 为例,展示为时间线 + 分页:
themes/icarus/layout/category.js
<div class="card">
<div class="card-content">
<nav class="breadcrumb" aria-label="breadcrumbs">
<ul>
<li><a href=""><%= _p('common.category', Infinity) %></a></li>
<% page.parents.forEach(category => { %>
<li><a href=""><%= category.name %></a></li>
<% }) %>
<li class="is-active"><a href="#" aria-current="page"><%= page.category %></a></li>
</ul>
</nav>
</div>
</div>
<%- partial("component/timeline", {posts: page.posts}) %>
<% if (page.total > 1) { %>
<%- partial('common/paginator') %>
<% } %>
code from https://github.com/steemblog/hexo-theme-icarus | MIT License
采用时间线以后,同时也会每个展示的 item 添加 cache:
themes/icarus/layout/component/timeline.js
<div class="card widget">
<div class="card-content">
<% if (typeof(head) !== 'undefined' && head) { %>
<h3 class="tag is-link">
<%= head %>
</h3>
<% } %>
<div class="timeline">
<% posts.each(post => { %>
<%- partial("component/cached_item", {post}) %>
<% }) %>
</div>
</div>
</div>
code from https://github.com/steemblog/hexo-theme-icarus | MIT License
策略5:增加每页的文章数量
可以有效减少页面总数,减少编译的负担。
blog/message.py
# Pagination
## Set per_page to 0 to disable pagination
per_page: 6
pagination_dir: page
index_generator:
per_page: 6
archive_generator:
per_page: 20
yearly: true
monthly: true
category_generator:
per_page: 20
tag_generator:
per_page: 20
code from https://github.com/steemblog/blog | MIT License
经过以上各类方案,编译所有文章的速度已经得到的极大的提高,大约已经提到了10~50倍了。
@andrewma: 400+ posts, 80+ tags, 1~2 mins
@ericet: 630+ posts, 260+ tags, 1~2 mins
(b) 实现增量编译
但我们的目标是增量更新,为了实现这个目的,我们需要做两方面修改:
步骤1:增量生成 markdown
为了不重复生成过去的posts的 markdown 文件,我们用 git repo 来管理过去已经生成的 markdown 文件,并只更新最近1天左右的文章。
同时,我们需要和之前的 source 对比,从而知道哪些文章需要更新。
blog/builder.py
def setup_source_repo(self):
git_clone_cmd = "git clone --depth 1 --branch {} --single-branch https://{}github.com/{}.git {}".format(SOURCE_BRANCH, self._get_github_pat(), self._get_repo(prefix=False), SOURCE_REPO_FOLDER)
os.system(git_clone_cmd)
# on `source` branch after clone
logger.info("Cloned source repo into workspace: {}".format(SOURCE_REPO_FOLDER))
def _commit_source(self):
os.chdir(SOURCE_REPO_FOLDER)
# commit the files into source repo
os.system("git add --all *")
res = os.system('git commit -m "Source updated: {}"'.format(get_uct_time_str()))
os.chdir("..")
if res == 0:
logger.info("Commited source into [{}] folder".format(SOURCE_REPO_FOLDER))
return True
else:
logger.info("Failed to add new source into [{}] folder".format(SOURCE_REPO_FOLDER))
return False
def _diff_files(self):
os.chdir(SOURCE_REPO_FOLDER)
res = subprocess.run(['git', 'diff', 'HEAD', 'HEAD~1', '--name-only'], stdout=subprocess.PIPE).stdout.decode('utf-8')
os.chdir("..")
files = [f for f in res.split("\n") if len(f) > 0]
logger.info("{} different files:\n{}".format(len(files), res))
return files
code from https://github.com/steemblog/blog | MIT License
为了保证每个用户运行时,文章的内容不会混乱,需要多source的workspace也做一些管理,这里不详细叙述了。
步骤2:只编译受影响的 html 文件
在用主题进行编译时,也需要知道哪些 markdown 文件需要更新,以及由此判断更新哪些 html。
themes/icarus/includes/helpers/diff.js
hexo.extend.helper.register('list_updated_posts', function (link) {
if (_posts.length == 0) {
process.chdir("source")
const res = execSync("git diff --name-only --cached").toString('utf8');
process.chdir("..")
const paths = res.split("\n").filter((p)=>(p.length > 0))
_posts = paths
}
return _posts
});
hexo.extend.helper.register('list_updated_categories', function (link) {
if (_categoreis.length == 0) {
const _list_updated_posts = hexo.extend.helper.get('list_updated_posts').bind(this)
const updated_posts = _list_updated_posts();
if (updated_posts.length > 0) {
for (const path of updated_posts) {
let post = Post.findOne({source: path})
if (post) {
_merge_categories(post.categories);
}
}
}
}
return _categoreis
});
hexo.extend.helper.register('list_updated_tags', function (link) {
if (_tags.length == 0) {
const _list_updated_posts = hexo.extend.helper.get('list_updated_posts').bind(this)
const updated_posts = _list_updated_posts();
if (updated_posts.length > 0) {
for (const path of updated_posts) {
let post = Post.findOne({source: path})
if (post) {
_merge_tags(post.tags);
}
}
}
}
return _tags
});
code from https://github.com/steemblog/hexo-theme-icarus | MIT License
在 generators 中,只考虑那些受影响的文件。以category 为例:
themes/icarus/includes/generators/category.js
function needs_update(category) {
if (config.incremental) {
// in incremental mode, update the affected category pages only
const updated_categories = list_updated_categories();
if (updated_categories && updated_categories.length > 0 &&
updated_categories.indexOf(category['name']) != -1) {
return true;
}
return false;
}
return true;
}
return locals.categories.reduce(function(result, category){
if (! needs_update(category)) {
return result;
}
const posts = category.posts.sort('-date');
const data = pagination(category.path, posts, {
perPage: perPage,
layout: ['category', 'archive', 'index'],
format: paginationDir + '/%d/',
data: {
category: category.name,
parents: findParent(category)
}
});
return result.concat(data);
}, []);
code from https://github.com/steemblog/hexo-theme-icarus | MIT License
需要注意的是,这里需要找到 hexo 对应的几个 generator 的源码,并将其进行替换,例如 hexo-generator-tag、hexo-generator-category、hexo-generator-archive
---
实现增量编译以后,经过测试,大概每天更新时,每个账户消耗的时间大约在几秒钟 到1分钟之间。12个账户更新时,总的耗时大约在2分钟至10分钟左右,包括环境部署的时间。
@andrewma: 400+ posts, 80+ tags, ~17s
@ericet: 630+ posts, 260+ tags, < 1min
----
至此,编译性能优化的工作告一段落,可以支持更多账户的镜像同步了。
除了 GitHub pages,现在常用的静态网站部署方式还有Netlify等。只需要按照Netlify的界面上步骤操作即可,或者参考 hexo deployment 一节的描述。
作为结果,我们可以将页面部署到不同环境:
注意:Netlify上目前只部署了 @robertyan 的文章,但也可以支持其他账户。
由于采用了组件化的 html 页面内,在上文提到过需要动态加载 widget 的页面,由于页面加载资源的阻塞等问题,我们需要对页面做一些优化才能保证 widget 的渲染不被图片等资源影响太大。
这里主要是在 widget 的占位符组件被渲染以后,立即调用 ajax 加载组件的代码,以尽早显示这些 widgets;否则,首页打开时的卡顿还是挺明显的。
themes/icarus/layout/layout.ejs
<section class="section">
<div class="container">
<div class="columns">
<%- partial('common/widget', { position: 'left' }) %>
<%- partial('common/widget', { position: 'right' }) %>
<%- partial('component/pjax_widget_js', {}, {cache: true}) %>
<div class="column has-order-2 column-main"><%- body %></div>
</div>
</div>
</section>
code from https://github.com/steemblog/hexo-theme-icarus | MIT License
主要需要优化的渲染过程就是组件的渲染,其余目前并未做更多测试。
除了以上提到的问题,由于在编译或展示时还需要一些别的问题,也一同修复了。
这些问题较容易修改:前者只需渲染前进行排序;后者需要对node运行的参数进行配置,改上限为8GB。后者的修改在:
blog/command.py
def configure():
settings.set_env_var("NODE_OPTIONS", "--max-old-space-size=8192")
os.system("cp -f _config.theme.yml themes/icarus/_config.yml")
steemblog 实现时 只是为了满足更好的阅读、搜索文章的需要。简言之,它是一个更好的博客“阅读器”(当然也是一个有趣的“时光机”),并不具有写的功能。
这是它想要解决的问题决定的,但它的未来可以有着有趣的发展方向,我们从小和大两个方面来讲。
----
小:从steemblog的角度来讲,我们可以做各类改进,包括功能、性能、运营等。
----
大:steemblog 展示了一种基于blockchain 的 app 的更多的可能性。为什么这么说呢?主要原因如下:
这只是一个大致的方向,或许随着思考的深入我们可以把它演变成新的应用。
但有三点应该是对的:(1)你在 steem 的历史不会消失;(2)更好的时光机将会不断被发明出来;(3)未来必定会来到。
基于这些前提,我们或许能不断看到更多有趣的应用吧。
----
昨天 @holger80 在 discord 和我聊到了关于 hexo theme 的问题,同时创建了 steemsites 这个项目(https://github.com/holgern/steemsites),可能也有通过 steem 构建站点,或者其他方面的考虑。
对于 steemblog 和相关的项目,我们在之后会进一步思考和探索。如有任何建议请随时留言讨论~