Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Async rendering of tags within a post #4926

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 44 additions & 11 deletions lib/hexo/post.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const assert = require('assert');
const moment = require('moment');
const parse5 = require('parse5');
const Promise = require('bluebird');
const { join, extname, basename } = require('path');
const { magenta } = require('picocolors');
Expand Down Expand Up @@ -404,18 +405,50 @@ class Post {
text: data.content,
path: source,
engine: data.engine,
toString: true,
onRenderEnd(content) {
// Replace cache data with real contents
data.content = cacheObj.restoreAllSwigTags(content);

// Return content after replace the placeholders
if (disableNunjucks) return data.content;

// Render with Nunjucks
return tag.render(data.content, data);
}
toString: true
}, options);
}).then(content => {
// This function restores swig tags in `content` and render them.
if (disableNunjucks) {
// If rendering is disabled, do nothing.
return cacheObj.restoreAllSwigTags(content);
}
// Whether to allow async/concurrent rendering of tags within the post.
// Enabling it can improve performance for slow async tags, but may produce
// wrong results if tags within a post depend on each other.
const async_tags = data.async_tags || false;
if (!async_tags) {
return tag.render(cacheObj.restoreAllSwigTags(content), data);
}
// We'd like to render tags concurrently, so we split `content`
// by top-level HTML nodes that have swig tags into `split_content` array
// (nodes that don't have swig tags don't need to be split).
// Then we render items in `split_content` asynchronously.
const doc = parse5.parseFragment(content);
const split_content = [];
let current = []; // Current list of nodes to be added to split_content.
doc.childNodes.forEach(node => {
const html = parse5.serializeOuter(node);
const restored = cacheObj.restoreAllSwigTags(html);
current.push(restored);
if (html !== restored) {
// Once we encouner a node that has swig tags, merge
// all content we've seen so far and add to `split_content`.
// We don't simply add every node to `split_content`, because
// most nodes don't have swig tags and calling `tag.render` for
// all of them has significant overhead.
split_content.push(current.join(''));
current = [];
}
});
if (current.length) {
split_content.push(current.join(''));
}
// Render the tags in each top-level node asynchronously.
const results = split_content.map(async content => {
return await tag.render(content, data);
});
return Promise.all(results).then(x => x.join(''));
}).then(content => {
data.content = cacheObj.restoreCodeBlocks(content);

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"moment": "^2.29.1",
"moment-timezone": "^0.5.34",
"nunjucks": "^3.2.3",
"parse5": "^7.0.0",
"picocolors": "^1.0.0",
"pretty-hrtime": "^1.0.3",
"resolve": "^1.22.0",
Expand Down
39 changes: 39 additions & 0 deletions test/scripts/hexo/post.js
Original file line number Diff line number Diff line change
Expand Up @@ -902,6 +902,36 @@ describe('Post', () => {
].join('\n'));
});

it('render() - multiple tags with async rendering', async () => {
const content = [
'{% blockquote %}',
'test1',
'{% quote test2 %}',
'test3',
'{% endquote %}',
'test4',
'{% endblockquote %}',
'ASDF',
'{% quote test5 %}',
'test6',
'{% endquote %}'
].join('\n');

const data = await post.render(null, {
content,
async_tags: true
});
data.content.trim().should.eql([
'<blockquote><p>test1</p>',
'<blockquote><p>test3</p>',
'<footer><strong>test2</strong></footer></blockquote>',
'test4</blockquote>',
'ASDF',
'<blockquote><p>test6</p>',
'<footer><strong>test5</strong></footer></blockquote>'
].join('\n'));
});

it('render() - shouln\'t break curly brackets', async () => {
hexo.config.prismjs.enable = true;
hexo.config.highlight.enable = false;
Expand Down Expand Up @@ -1203,6 +1233,15 @@ describe('Post', () => {
});

data.content.trim().should.eql(`<p><code>${escapeSwigTag('{{ 1 + 1 }}')}</code> 2</p>`);

// Test that the async tags logic recognize the tags correctly.
const data_async = await post.render(null, {
content,
engine: 'markdown',
async_tags: true
});

data_async.content.trim().should.eql(`<p><code>${escapeSwigTag('{{ 1 + 1 }}')}</code> 2</p>`);
});

// #3543
Expand Down