
Hello,大家好,我是松宝写代码,写宝写的不止是代码。接下来给大家带来的是关于Webpack4的性能优化的系列,今天带来的是编译阶段的性能优化。
由于优化都是在 Webpack 4 上做的,当时 Webpack 5 还未稳定,现在使用 Webpack 5 时可能有些优化方案不再需要或方案不一致,这里主要介绍优化思路,仅作为参考。
在接触一些大型项目构建速度慢的很离谱,有些项目在 编译构建上30分钟超时,有些构建到一半内存溢出。但当时一些通用的 Webpack 构建优化方案要么已经接入,要么场景不适用:
在这种情况下,只好另辟蹊径去寻找更多优化方案,这篇文章主要就是介绍这些“非主流”的优化方案,以及引发的思考。
简化Webpack 的构建流程后,Webpack 的构建流程大体上分为如下几个阶段:

而在尽可能不改变处理逻辑的情况下,常见的优化思路就是“并行”和“缓存”:
但目前“并行”和“缓存”仅覆盖模块编译阶段,能否把“并行”和“缓存”的方案扩展到整个构建流程呢?
为了让“并行”+“缓存”能够覆盖整个构建流程,需要做如下准备工作:
引用透明改造包括如下几个部分:
缓存池的核心功能:
并行调度池类似于数据库连接池,主要功能:
编译任务:使用 loader-runner 编译模块代码。
压缩任务:使用 terser/esbuild 压缩模块代码。
SourceMap 任务:生成序列化 SourceNode。


做好了这些准备工作后,就可以开始进行各个阶段的“并行”+“缓存”改造。
Webpack 内部的单个模块构建流程大致如下所示:

loader 运行类似于 Express/Koa 的中间件机制,每一个 Loader 分为 pitch 和 normal 两个阶段,cache-loader 利用这一点,在 pitch 阶段进行缓存检测,如果检测到缓存可用则直接返回。无缓存或缓存不可用则继续运行后续流程,直到 normal 阶段生成缓存写入文件系统。
thread-loader也是同理,只不过把后续的 loader 以及相关参数交给了子进程,并在子进程中模拟了 Webpack 的 loader 运行机制。

但 cache-loader 无法解决 AST Parser + 遍历生成依赖带来的消耗,开源界有 hard-source-webpack-plugin 尝试解决这个问题(但问题很多)。Webpack 团队自己也意识到了这个问题, 因此在 Webpack 5 中增加的 Persistent caching 来优化,但它的实现思路是将 Webpack 整个上下文都缓存下来,因此 Webpack 5 给几乎每个对象都增加了序列化/反序列化的方法:
// webpack@5.9.0/lib/NormalModule.js L1068 ~ L1105
serialize(context) {
const { write } = context;
// deserialize
write(this._source);
write(this._sourceSizes);
write(this.error);
write(this._lastSuccessfulBuildMeta);
write(this._forceBuild);
super.serialize(context);
}
deserialize(context) {
const { read } = context;
this._source = read();
this._sourceSizes = read();
this.error = read();
this._lastSuccessfulBuildMeta = read();
this._forceBuild = read();
super.deserialize(context);
}但由于当时无法升级 Webpack 5,且 Persistent caching 脱离了统一的缓存控制,最终选择自己实现缓存来保证可移植、可拼接、预生成,如果在 Webpack 5 上实现,理论上可以复用一部分模块、依赖的序列化/反序列化能力,并桥接到缓存池上。
方案如下图所示:


模块的序列化分为两部分:模块本体序列化、模块依赖序列化。
模块本体的序列化较为简单:
模块的依赖序列化较为复杂,因为依赖由 Webpack 解析 AST 后遍历生成,依赖内部会直接保留相关联的 AST 节点,这些 AST 节点在后续的 chunk 产物生成的 dependency template 阶段会用来生成模块引用依赖的相关代码。
但实际上,依赖内部并不会真正使用多少 AST 的节点,仅仅是从其中读取少量信息用来做代码替换的位置判断和字符串拼接,因此序列化的过程就变成了提取 AST 上依赖使用的关键信息,而反序列化则是将这些关键信息伪造成 AST 节点即可。
不过,Webpack 内部这样的依赖有数十个(webpack/lib/dependencies目录下),需要一个个处理。同时,对于一些特殊的场景,比如 Block 类型的依赖(通常是异步加载的代码)无法支持。(Webpack 5 中可以直接用这些 Dependency 上面的序列化/反序列化方法)。

'use strict';
const NullDependency = require('./NullDependency');
class HarmonyExportHeaderDependency extends NullDependency {
constructor(range, rangeStatement) {
super();
this.range = range;
this.rangeStatement = rangeStatement;
}
get type() {
return 'harmony export header';
}
}
HarmonyExportHeaderDependency.Template = class HarmonyExportDependencyTemplate {
apply(dep, source) {
const content = '';
const replaceUntil = dep.range ? dep.range[0] - 1 : dep.rangeStatement[1] - 1;
source.replace(dep.rangeStatement[0], replaceUntil, content);
}
};
module.exports = HarmonyExportHeaderDependency;如此这般,当缓存命中时,模块的依赖解析流程会被完全跳过。但这个流程并行化难度较高,主要原因是 Webpack 内 Parser Hooks 的桥接较为复杂,可以说 Hooks 的存在本身就是副作用的一种体现。
对 Webpack 的 enhance-resolver 进行缓存,降低 Webpack 在文件系统中查找的成本。由于 Resolver 较为复杂,且不同的 node_modules 组织方式、不同的依赖版本、不同的起始路径,都可能使得相同的 request 被解析到完全不同的文件,因此针对不同类型的 request,缓存的处理逻辑不同:
构建器和 Webpack 的处理流程中存在大量的 Hash 计算。而使用 md5 作为 Hash 的成本较高,可以采用如 imurmurhash 等碰撞率高一些但性能更好的 Hash 方案进行替换。同时代理的 Hash 也可用来做后续的可移植缓存。
我不知道为什么,但是当我设置这个设置时它无法编译设置:static_cache_control,[:public,:max_age=>300]这是我得到的syntaxerror,unexpectedtASSOC,expecting']'(SyntaxError)set:static_cache_control,[:public,:max_age=>300]^我只想将“过期”header设置为css、javaascript和图像文件。谢谢。 最佳答案 我猜您使用的是Ruby1.8.7。Sinatra文档中显示的语法似乎是在Ruby1.
最近因为项目需要,需要将Android手机系统自带的某个系统软件反编译并更改里面某个资源,并重新打包,签名生成新的自定义的apk,下面我来介绍一下我的实现过程。APK修改,分为以下几步:反编译解包,修改,重打包,修改签名等步骤。安卓apk修改准备工作1.系统配置好JavaJDK环境变量2.需要root权限的手机(针对系统自带apk,其他软件免root)3.Auto-Sign签名工具4.apktool工具安卓apk修改开始反编译本文拿Android系统里面的Settings.apk做demo,具体如何将apk获取出来在此就不过多介绍了,直接进入主题:按键win+R输入cmd,打开命令窗口,并将路
我正在使用Ruby解决一些ProjectEuler问题,特别是这里我要讨论的问题25(Fibonacci数列中包含1000位数字的第一项的索引是多少?)。起初,我使用的是Ruby2.2.3,我将问题编码为:number=3a=1b=2whileb.to_s.length但后来我发现2.4.2版本有一个名为digits的方法,这正是我需要的。我转换为代码:whileb.digits.length当我比较这两种方法时,digits慢得多。时间./025/problem025.rb0.13s用户0.02s系统80%cpu0.190总计./025/problem025.rb2.19s用户0.0
我正在寻找一个用ruby演示计时器的在线示例,并发现了下面的代码。它按预期工作,但这个简单的程序使用30Mo内存(如Windows任务管理器中所示)和太多CPU有意义吗?非常感谢deftime_blockstart_time=Time.nowThread.new{yield}Time.now-start_timeenddefrepeat_every(seconds)whiletruedotime_spent=time_block{yield}#Tohandle-vesleepinteravalsleep(seconds-time_spent)iftime_spent
如果用户是所有者,我有一个条件来检查说删除和文章。delete_articleifuser.owner?另一种方式是user.owner?&&delete_article选择它有什么好处还是它只是一种写作风格 最佳答案 性能不太可能成为该声明的问题。第一个要好得多-它更容易阅读。您future的自己和其他将开始编写代码的人会为此感谢您。 关于ruby-on-rails-如果条件与&&,是否有任何性能提升,我们在StackOverflow上找到一个类似的问题:
是否有适用于Ruby语言的.NETFramework编译器?我听说过DLR(动态语言运行时),这是否将使Ruby能够用于.NET开发? 最佳答案 IronRuby是Microsoft支持的项目,建立在动态语言运行时之上。 关于.net-是否有Ruby.NET编译器?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/199638/
关闭。这个问题是off-topic.它目前不接受答案。想改进这个问题吗?Updatethequestion所以它是on-topic用于堆栈溢出。关闭10年前。ImprovethisquestionLinux专家正在转向Mac(10.8)。因为我懒...我使用MacPorts安装MacVim。它似乎安装没有错误。我只需要mvim中的python、ruby和perl支持。$/opt/local/bin/mvim--version|egrep'patches|python|ruby|perl'Includedpatches:1-244,246-646+multi_lang-mzscheme+
我编写了一个Ruby应用程序,它可以解析来自不同格式html、xml和csv文件的源中的大量数据。我如何找出代码的哪些区域花费的时间最长?有没有关于如何提高Ruby应用程序性能的好资源?或者您是否有任何始终遵循的性能编码标准?例如,你总是用加入你的字符串吗?output=String.newoutput或者你会使用output="#{part_one}#{part_two}\n" 最佳答案 好吧,有一些众所周知的做法,例如字符串连接比“#{value}”慢得多,但是为了找出您的脚本在哪里消耗了大部分时间或比所需时间更多,您需要进行分
当我刚刚运行middleman时服务,all.css编译得很好,只包含对+box-shadow(none)的调用:/*line1,/home/yang/asdf/source/stylesheets/content.css.sass*/div{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}但是当我构建网站时,我得到了这个Sass/Compass错误:$middlemanbuildSlim::EmbeddedEngineisdeprecated,itiscalledSlim::EmbeddedinSlim2.0
1.回顾.TransportServicepublicclassTransportServiceextendsAbstractLifecycleComponentTransportService:方法:1publicfinalTextendsTransportResponse>voidsendRequest(finalTransport.Connectionconnection,finalStringaction,finalTransportRequestrequest,finalTransportRequestOptionsoptions,TransportResponseHandlerT>