此篇专题名称可能叫Electron
的编译(虽然并没有具体的编译步骤,但实际上本文里面的各种技术都需要源码编译Electron)更为合适。因为此篇文章基本不涉及到源码的解析和修改,但是因为也是基于Electron
的学习和理解之上的,勉勉强强也叫这个名字吧。我测试编译的环境为Electron
V4.2.9,为2019年年底的版本,本文中部分的方法在最新的版本Electron
版本上不能直接适用; 那这种情况怎么办呢?自己去改改就好了。
如果对体积有强烈需求的人,可以去看Miniblink
:https://miniblink.net/,该方案几乎是重写了整个浏览器,除了V8之类的东西,所以程序体积可以非常小,压缩了之后小于10M完全妥妥的。 本文所列举出的方法改变不了Chromium
浏览器体积庞大的本质问题;V8的二进制文件达到了30M,你让我去改成10M,20M?这个不现实;体积大有体积大的优势和理由。我这里的方法最多能减省各种看起来不需要
的东西;程序的本质我是没那个能力去改变的。比如物理阉割代码什么的,欢迎大佬指教。
针对Electron
体积削减的话题讨论也是由来已久,因为Electron
的根本就是Chromium
, 也因此Chromium
的裁剪办法基本上也都适用于Electron
,Electron
是一个独立的exe, 针对Electron
也有一些特殊的办法减小一些体积。本文的目的就是介绍一些我现在用到的一些用于裁剪程序的方法,看下效果:
注意:该方法不适用于老版本的Electron
,老版本通过static_library
的方法在链接的时候太慢,我个人认为没必要在这上面花功夫。
整理的开关如下:(以下开关主要作用是关闭一些不太可能用到的功能,对体积影响不是很大, 也相当于是源码裁剪,但是力度不大,做不到把具体某个庞大的部件减去)
is_debug = false
target_cpu = "x86" #编译目标程序为32位
enable_nacl = false
is_component_build = false
is_component_ffmpeg=false
symbol_level = 2 #设置为0的时候,将不会产生PDB,少量减少体积和极大加快编译速度
blink_symbol_level=2 # 同上
enable_basic_printing = false
enable_resource_whitelist_generation=false
debug_devtools = false
#enable_plugins = false
enable_pdf = false
enable_webrtc = false
enable_plugin_installation =false
enable_spellcheck = false
use_browser_spellchecker = false
enable_print_preview = false
enable_web_speech = false
#enable_electron_extensions = false
enable_task_manager = false
enable_themes = false
win_pdf_metafile_for_printing = false
enable_service_discovery = false
enable_wifi_bootstrapping = false
enable_webvr = false
enable_notifications = false
disable_ftp_support = true
#enable_electron_extensions = false
enable_fake_location_provider = false
enable_message_center = false
enable_nacl_nonsfi = false
enable_native_notifications = false
enable_one_click_signin = false
#enable_openvr = false
enable_session_service = false
enable_tts = false
enable_vr = false
enable_windows_mr = false
#pdf_is_standalone = true
pdf_use_skia_paths = false
pdf_use_skia = false
pdf_is_complete_lib = false
pdf_enable_xfa_tiff = false
pdf_enable_xfa_png = false
pdf_enable_xfa_gif = false
pdf_enable_xfa_bmp = false
pdf_enable_xfa = false
pdf_enable_v8 = false
pdf_enable_click_logging = false
pdf_bundle_freetype = false
rtc_build_examples = false
rtc_build_tools = false
#toolkit_views = false
treat_warnings_as_errors = false
start_daemons_for_testing = false
is_unsafe_developer_build = true
angle_enable_custom_vulkan_cmd_buffers=false
angle_enable_d3d11=true
angle_enable_d3d9=true
angle_enable_vulkan=false
angle_enable_gl=false
angle_enable_gl_null=false
angle_enable_essl=false
enable_swiftshader=false
enable_reporting = flase
enable_net_mojo=false
那么这些开关放在哪里呢? 通过执行命令gn args out\Release
会打开你对应的编译目录的配置,然后将这些东西放进去。保存,然后关闭。然后这些配置会被存储到out\Release
目录下的args.gn
中。如图示:
在4.2.9版本中,绝大部分的项目的编译优化选项是/O1(最小体积优先);部分项目的编译优化选项是/O2(速度优先),如图:
我们这里来个极限优化。
把/O2
和/O1
的优化选项都改成/Ox /Os /GS- /GF /GR- /Oy
, 为了快速方便的改,这里推荐一个工具grepWin
(下载页面:https://sourceforge.net/projects/grepwin/),可以直接对目录进行搜索替换,一步直接到位。
这里可能会让人有点儿疑惑, 这个VC-LTL
是个什么东西? VC-LTL
很NB啊,VC-LTL
是一套由国内的初雨团队打造的一套用于链接Windows自带运行库的套件;
简单来说,我们在编写程序的时候,经常会用到memcpy
,strcpy
类似的函数;通常来讲,在DEBUG的模式或者非/MT
下,memcpy
,strcpy
这一类的函数会直接链接到类似于msvcp100.dll
,msvcr100.dll
(链接到具体版本的DLL,由编译器的版本决定)这一类的DLL库中,所以当程序调用memcpy
的时候,就会实际调用对应模块中的函数。
当程序选择使用/MT
编译的时候,这一类函数的就会直接以嵌入到程序中,这就会直接增加程序的体积。然而其实Windows操作系统在发行的时候,这一类的函数的包含库它自己已经携带了。意思就是,操作系统自己有一套自己的运行库。
如果能仔细看到这里,那么想必你已经明白了。VC-LTL
干的事情就是,让目标程序也能用上这个Windows自带的库。Windows自带的库的名称为msvcrt.dll
,每个版本的Windows的这个库都叫这个名字。
VC-LTL
的作用就是将以/MT
编译的程序所使用的基础库以/MD
的形式链接到msvcrt.dll
中。
VC-LTL
在Electron中的使用方法:https://zhuanlan.zhihu.com/p/64070741, 该篇中对于添加引用目录的地方不是很详细,这里再做说明。
安装VC-LTL
, 并确定当前的Electron
所使用的编译器版本,并将VC-LTL
引用到Electron
;
在toolchain.ninja
中确定SDK和库的版本,例如我这里4.2.9使用的版本是SDK,10.0.17763.0和14.16.27023。为什么要确定?引用VC-LTL
需要手动将头文件目录,LIB库目录加入到当前的编译环境中。
添加VC-LTL
的引用;假定你已经正确安装了VC-LTL
,并且已经确定了版本号。那么接下来需要将VC-LTL
的目录分别添加到toolchain.ninja
和electron_app.ninja
中。假定VC-LTL
的目录在F:\Open_Source\VC-LTL
中
处理toolchain.ninja
;
rule cc
下的/showIncludes
,将如下目录添加到/showIncludes
后面, "-imsvcF:\Open_Source\VC-LTL\config\Vista" "-imsvcF:\Open_Source\VC-LTL\VC\14.16.27023\include" "-imsvcF:\Open_Source\VC-LTL\VC\14.16.27023\atlmfc\include" "-imsvcF:\Open_Source\VC-LTL\ucrt\10.0.17763.0"
rule cxx
下的/showIncludes
,执行上一步的操作。处理electron_app.ninja
,假定你的SDK安装在C:/Program Files (x86)/Windows Kits/10/lib/10.0.17763.0/um/x86
搜索electron_app.ninja
,将/LIBPATH:"C$:/Program$ Files$ (x86)/Windows$ Kits/10/lib/10.0.17763.0/um/x86"
替换为:
/LIBPATH:"F$:/Open_Source/VC-LTL/VC/VC-LTL-4.0.1.10-Binary-VS2017/lib/x86/Vista" /LIBPATH:"F$:/Open_Source/VC-LTL/VC/VC-LTL-4.0.1.10-Binary-VS2017/lib/x86/Vista/Advanced" /LIBPATH:"F$:/Open_Source/VC-LTL/VC/VC-LTL-4.0.1.10-Binary-VS2017/VC/14.16.27023/lib/x86" /LIBPATH:"F$:/Open_Source/VC-LTL/VC/VC-LTL-4.0.1.10-Binary-VS2017/VC/14.16.27023/lib/x86/Vista" /LIBPATH:"F$:/Open_Source/VC-LTL/VC/VC-LTL-4.0.1.10-Binary-VS2017/ucrt/10.0.17763.0/lib/x86" /LIBPATH:"C$:/Program$ Files$ (x86)/Windows$ Kits/10/lib/10.0.17763.0/um/x86"
如果你的程序是64位的,那么就引用到64位对应的目录即可。
注意:因为不同的操作系统的附带的msvcrt.dll
各不相同,那么很容易存在新编译器支持的特性在老版本的操作系统上不受支持。例如:新版本的vcruntime
肯定支持类似于%zu
这样的格式控制串,但是老版本的操作系统,例如Win7,甚至老一点儿的Win10所附带的msvcrt.dll
不支持该格式控制串。但如果采用常规的方式以/MT
的方式编译的话,那么自然没有问题;因为%zu
的支持是直接嵌入到程序里面的,因此哪怕是老版本的操作系统也不受链接文件影响。但是VC-LTL
的模式天生决定了这个问题不好处理,因此读者在使用VC-LTL的时候应注意相关的问题。
熟悉PE结构的读者肯定知道重定位表是个什么东西,其实这个玩意儿就是为了让DLL能够每次都加载到不同的基址上的技术。PE结构中,DLL为了解决DLL默认基址被占用的问题,弄出了一个重定位表;一旦DLL不能被加载到预期的位置上,那么通过重定位表就能重新将变量,函数等调用重新指向对应的位置。
当然现在有一个链接选项就是动态基址的/DYNAMICBASE
,所有有时候哪怕DLL的默认基址没有被占用,也会重新定位到新的基址。
上述的东西只是对DLL有限制,Electron
是一个单独的exe程序,exe程序是不需要这个重定位表的,因为最先映射的就是exe,所以不存在抢占问题。指哪儿哪儿就是基址。再而Electron
的代码体积巨大,也因此这个重定位表的体积也是相当的吓人。看下图:
这个4.0.0版本的Electron
的重定位表体积,就已经达到了惊人的2M+。所以还是移除比较科学。
准备修改编译选项,打开 electron_app.ninja
文件:
/FIXED$:NO
并替换成/FIXED
,(V4.2.9里面有两处)/DYNAMICBASE
并替换成/DYNAMICBASE$:NO
至此编译出来的程序就没有重定位表了,且不会对原本程序有任何影响。
devtools_resources.pak
(又可以减少5M左右)devtools_resources.pak
的文件在新版本的Electron
目录中已经不能直接被看到了,这个文件被嵌入到了resources.pak
中,devtools_resources.pak
发行的版本并不需要这个文件,那么完全可以干掉。裁掉之前文件约为8.31M,移除之后文件体积约为2.42M。如图:
准备修改编译选项,打开 toolchain.ninja
文件:
repack resources.pak
, 删除该条命令中的gen/content/browser/devtools/devtools_resources.pak
,然后保存即可。ninja -C out/Release/ resources.pak
PostTask
用到的FROM_HERE
宏里面传递了函数名称和代码位置,移除可以使体积略微减小;他的反调试意义大于裁剪意义;(有关于安全保护相关的帖子,请看我的另一篇同系列帖子《Electron源码学习:Electron加密与安全》)libEGL.dll
和libGLESv2.dll
(视使用场景而定)