From 643fcaf7828f1fbffba8095ba79f059d5f4356f3 Mon Sep 17 00:00:00 2001 From: Yuxin Wu Date: Tue, 29 Mar 2022 20:07:40 -0700 Subject: [PATCH 1/4] Async rendering of tags within a post --- lib/hexo/post.js | 26 +++++++++++++++----------- package.json | 1 + 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/lib/hexo/post.js b/lib/hexo/post.js index 117669e5ff..188ce0cc23 100644 --- a/lib/hexo/post.js +++ b/lib/hexo/post.js @@ -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'); @@ -404,18 +405,21 @@ 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 => { + const doc = parse5.parseFragment(content); + // Indepedently render the tags in each top-level node + // asynchronously. This can significantly speed up rendering of slow tags + const results = doc.childNodes.map(async node => { + // Replace cache data with real contents + const content = cacheObj.restoreAllSwigTags(parse5.serializeOuter(node)); + // If disabled, return content after replacing the placeholders + if (disableNunjucks) return content; + // Render with Nunjucks + return await tag.render(content, data); + }); + return Promise.all(results).then(x => x.join('')); }).then(content => { data.content = cacheObj.restoreCodeBlocks(content); diff --git a/package.json b/package.json index 09e0b12120..65b0b3020c 100644 --- a/package.json +++ b/package.json @@ -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", From 2720e39f5781d8e841ec0d30186b5e5e6ae05f28 Mon Sep 17 00:00:00 2001 From: Yuxin Wu Date: Sun, 18 Sep 2022 16:12:32 -0700 Subject: [PATCH 2/4] Faster rendering by less split --- lib/hexo/post.js | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/lib/hexo/post.js b/lib/hexo/post.js index 188ce0cc23..b91cbea111 100644 --- a/lib/hexo/post.js +++ b/lib/hexo/post.js @@ -408,15 +408,37 @@ class Post { 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); + } + // 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); - // Indepedently render the tags in each top-level node - // asynchronously. This can significantly speed up rendering of slow tags - const results = doc.childNodes.map(async node => { - // Replace cache data with real contents - const content = cacheObj.restoreAllSwigTags(parse5.serializeOuter(node)); - // If disabled, return content after replacing the placeholders - if (disableNunjucks) return content; - // Render with Nunjucks + 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('')); From 3b488da10744e0a874c67089af25751326f5037f Mon Sep 17 00:00:00 2001 From: Yuxin Wu Date: Mon, 28 Nov 2022 23:24:58 -0800 Subject: [PATCH 3/4] add a per-post option --- lib/hexo/post.js | 7 +++++++ test/scripts/hexo/post.js | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/lib/hexo/post.js b/lib/hexo/post.js index b91cbea111..e2ebea8b9b 100644 --- a/lib/hexo/post.js +++ b/lib/hexo/post.js @@ -413,6 +413,13 @@ class Post { // 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). diff --git a/test/scripts/hexo/post.js b/test/scripts/hexo/post.js index 8927708bf4..cadfcaee1c 100644 --- a/test/scripts/hexo/post.js +++ b/test/scripts/hexo/post.js @@ -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([ + '

test1

', + '

test3

', + '
test2
', + 'test4
', + 'ASDF', + '

test6

', + '
test5
' + ].join('\n')); + }); + it('render() - shouln\'t break curly brackets', async () => { hexo.config.prismjs.enable = true; hexo.config.highlight.enable = false; @@ -1203,6 +1233,15 @@ describe('Post', () => { }); data.content.trim().should.eql(`

${escapeSwigTag('{{ 1 + 1 }}')} 2

`); + + // 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(`

${escapeSwigTag('{{ 1 + 1 }}')} 2

`); }); // #3543 From 6d275c6f9d09f14aa620287e67e98a8091af1567 Mon Sep 17 00:00:00 2001 From: uiolee <22849383+uiolee@users.noreply.github.com> Date: Fri, 12 Jan 2024 15:44:12 +0800 Subject: [PATCH 4/4] reset to ppwwyyxx/master [skip ci]