ohroy / hexo-neat

auto Minify html、js、css and make it neat
74 stars 11 forks source link

hexo g的效率问题 #13

Closed lewky closed 4 years ago

lewky commented 5 years ago

大佬,我可以厚颜无耻地提个性能方面的issue吗? 是这样的,在使用hexo-neat进行压缩的时候,我需要花费至少三分钟的时间,如果不压缩的话只要几十秒就可以了。 我看hexo-neat在压缩页面时,分别压缩了html, swig, js, css, md等文件;这些花费了大量的时间,swig和md文件只是模板和初始文件而已,应该没有必要去压缩的吧,能不能改成只对生成后的html静态页面进行压缩呢? 因为我发现一旦使用了neat_html:里的exclude来排除掉md和swig文件,会导致最后的html是没有被压缩的,而且当md文件里使用到了next主题自带的tab标签,还会有语法错误导致压缩失败。。

kainy commented 5 years ago

还有一个html首尾注释时间戳的问题,发布后会导致所有页面缓存失效。

du33169 commented 5 years ago

一个比较土的解决方法:将_config.yml中的neat_enable设为false,然后将其复制一份(例如neat.yml),在neat.yml中将neat_enable设为true。

这样在平时hexo g的时候不会压缩保证效率,在需要部署的时候可以用hexo g --config neat.yml

缺点是要保持两个配置文件同步,可以用脚本实现。

yansheng836 commented 5 years ago

(如果不执行hexo clean的话)好像原版的hexo g是只处理有变化的文件,但是添加插件后好像全部都处理,然后就导致速度慢,有没有办法改善啊?

inkss commented 4 years ago

我重新分析了下,可以确定两点:

重复压缩

内容不同的无法处理,因为确实是不同,所以就想办法让内容相同的,从多次压缩改成一次压缩,将压缩后的内容存储起来,下次遇见直接调用。所以这里的判断我是分两部分:1.判断当前被压缩的文件是否是模板文件,2.判断压缩的内容是否相同(这里是直接简单的判断了内容的长度),大致的代码如下:

var html_map = new Map(); // 全局变量
function logic_html(str, data) {
    //...
    var path = data.path == null ? "no_path_value.ejs" : data.path;
    var exclude = options.exclude;

    var tempMap;  // 临时变量
    var checkFlag = minimatch(path, "**/*.ejs");  // 模板文件
    if(checkFlag){  // 模板内容
        if(html_map.has(path)){ // 如果存在相同路径
            tempMap = new Map(html_map.get(path));  // 取值
            if(tempMap.has(str.length))  // 判断是相同的长度
                return tempMap.get(str.length);  // 直接返回值
        } else tempMap = new Map();
    }

    //...
    var log = hexo.log || console.log;
    var result = Htmlminifier(str, options);
    if(checkFlag){  // 只记录模板文件的内容
        tempMap.set(str.length,result);  // 存储压缩后的内容
        html_map.set(path,tempMap);  // 存放到全局变量中
    }
    var saved = ((str.length - result.length) / str.length * 100).toFixed(2);
    log.log('neat the html: %s [ %s saved]', path, saved + '%');
    return result;
};

这里,我只开启了 html 文件的压缩(jscss 可以用 jsdelivr 的 CDN),修改前生成用时:15s

image

修改后,生成用时:5.93s ,效果显著,啊哈哈。

image


好了,这次才算是完全修改,完结撒花~

yansheng836 commented 4 years ago

@inkss 你好,能请教下你,具体是改哪个文件吗?

我的重复压缩模板文件,(大概100+博客)好像耗时半个小时以上了。

ohroy commented 4 years ago

今天抽空看了下这个问题。先说结论:这里其实是有两个问题:一是重复渲染,二是重复生成。重复渲染是hexo设计缺陷,重新生成是本插件画蛇添足。而这两者导致变慢的罪魁祸首是重复渲染。

问题分析记录

从名字上来看,本插件注册的filterafter_render:html,即为渲染后执行的意思。为什么一篇文章会被重复渲染呢?本文记录调试分析。

一,旧的检测检测资源是否被修改的方式

https://github.com/hexojs/hexo/blob/850ffbcb157c51268b0a4b9c88bd46cca56bbcc2/lib/plugins/console/generate.js#L41 插件只有在下面的 writeFile 函数里才会被调用,按道理说,这里假如不传递force,并且文件已经存在,并且文件没有被修改,就一定不会传递给插件,自然就没有后续问题了。

但实情是isModfied永远为true,除非将文件类型设置为skip。因此,所有文章就必定会经过渲染,无论使用本插件与否

二,生成文章与否的判断

上面有同学也提到了,假如不实用本插件的话,就没有问题。其实这个话是不严谨的,无论使用与否,都存在问题,只是本插件放大了此问题。(hexo原有体系在渲染时不会输出,所以用户无法感知。本插件在文章比较多时,压缩会比较慢。)

但无论如何,不使用本插件时,hexo不会重新生成文件。 这个结论是正确的,但是为什么会这样呢?继续往下分析 https://github.com/hexojs/hexo/blob/850ffbcb157c51268b0a4b9c88bd46cca56bbcc2/lib/plugins/console/generate.js#L76 这里可以清楚的看到,hexo使用hash对生成后的文件进行了hash校验,假如他们一致则不重新生成。 至此,问题已经一目了然了。那就是hexo在设计时并未考虑到插件可能会修改渲染后的内容,导致此处判断形同虚设。一个完整的流程是
hexo g -> 这里有缺陷,导致无条件渲染->hexo渲染后交由插件渲染->插件渲染后交由hexo生成->hexo判断渲染后的结果,发现与缓存的不一致->重新生成 而正常的流程是 hexo g -> 这里有缺陷,导致无条件渲染->hexo渲染后交由hexo生成->hexo判断渲染后的结果,发现与缓存一致->不重新生成

这里要想和保持和hexo的结果一致,就需要保证插件每次渲染后的结果要一致,而显然可能是结果不一致的。经过分析,最终发 https://github.com/rozbo/hexo-neat/blob/5819ebed6fc2a1104675b5c488aa3e56ed8779c6/lib/filter.js#L32 此处导致的每次生成的最终文件不一致,导致每次需要重新生成,去掉即可解决每次重新生成的问题

至于重复渲染,hexo并未提供相应的接口或者方式能解决此问题,我将尝试自行造轮子解决。

inkss commented 4 years ago

没用的,hexo clean 后就全没了,我上面的观点也有不对的地方,主要是 不同页面间有可能传递参数(无法判断同一路径下的相同模板文件是否相同(接收参数的部分)

inkss commented 4 years ago

除非是改变监听方式,对渲染完成后的完整的 html 文件执行压缩

ohroy commented 4 years ago

除非是改变监听方式,对渲染完成后的完整的 html 文件执行压缩

现在就是监听的渲染完成,但问题是hexo本身是无差别渲染的。

ohroy commented 4 years ago

为了解决这个问题,我又新造了一个轮子 https://github.com/rozbo/hexo-cute, 相关问题可以在那里讨论。