发布于

Electron桌面应用开发

Authors
  • avatar
    Name
    田中原
    Twitter

Electron桌面应用开发

目录

认识Election

Electron的由来

2011 node-webkit (node.js module)

2012 node-webkit (framework)

2013 atom-shell started

2014 atom-shell open sourced

2015 renamed to Electron

2016 Electron 1.0

如果想开发一个桌面GUI应用软件,希望其能同时在Windows、Linux和Mac平台上运行,可选的技术框架并不多,在早期人们主要用wxWidgets(https://www.wxwidgets.org)、GTK(https://www.gtk.org)或Qt(https://www.qt.io)来做这类工作。这三个框架都是用C/C++语言开发的,受语言开发效率的限制,开发者想通过它们快速地完成桌面应用的开发工作十分困难。

随着前端领域的蓬勃发展,NW.js(https://nwjs.io)和Electron(https://electronjs.org)横空出世,给前端开发人员打开了这个桌面应用领域的大门。

这两个框架都与中国人有极深的渊源

09 王文睿 所在的团队当时的工作是给这些移动操作系统开发 Web Runtime,即让这些 Web 应用跑起来的核心引擎。其中一项主要的任务就是拓展它的本地 API,比如操作本地文件、使用 GPS 传感器等,以实现和原生应用同样的功能。

英特尔开源部门开展了创新计划活动,工程师可以花 10% 的时间投入在自己的项目上,王文睿(RogerWang)09年有一个拓展web功能的需求,但是那时候并没有特别好的方案。 ,那时候 Roger 正好了解到 node.js 这个项目,它正好也处于起步阶段,很受欢迎。Roger 想到了将两个项目集成在一起,这样 web 开发者就可以在 web 页面中直接调用 Node.js 平台的众多库。

第一个实现的版本是基于 webkit 和 Node.js,命名为 node-webkit,经过了公司内部的开源流程后于 2011 年底在 GitHub 上 发布

并在 Node.js 社区的邮件列表里 宣布。由于它实现了一种新的编程方式,并且开发者不需要学习新知识就可以直接开始使用,在社区里得到了很好的反响。

公司的其他部门也对这个项目产生了兴趣,甚至有些人开始用它开发产品,后来公司也表示了进一步的支持,同意他用 50% 的时间来开发这个项目,过了一段时间以后又同意他招收一个实习生来一起工作

2012年,故事的另一个主角赵成(ChengZhao)加入王文睿的小组,并对nodewebkit项目的做出了大量的改进

为node-webkit工作半年之后,2012年底,这个项目开始吸引越来越多的注意力,也开始引起Intel一定程度的注意。rogerwang得到了机会放下其他的工作,开始全职维护node-webkit。

这里有一个比较有趣的事情,根据赵成的说法,Intel内部node-webkit关注度不高。所以他作为一个实习生能如此随意地修改代码,发布新版本。

2012年底,我结束了在Intel的实习,开始为GitHub作为外包工作。着手开发另一个类似nodewebkit的项目——AtomShell,这个项目就是Electron的前身。

赵成在这个项目上倾注了大量的心血,这也是这个项目后来广受欢迎的关键因素之一。再后来GitHub把这个项目开源出来,最终更名为Electron。

赵成后来入职github,全职维护electron

基于Electron的应用

这些应用是我电脑里的,基于electron的应用。

vscode:杀手级应用。

Electron的架构

Electron = Chromium + NodeJS + Native Api

我们知道,Node.js是基于libuv的,Chromium也有一套自己的事件循环模式。

Chromium和Node.js的应用场景完全不同,它们的底层虽有一部分是相同的,但要想把它们两个整合起来并非易事。

NW.js通过修改源码合并了Node.js和Chromium的事件循环机制,但是这样也造成了耦合。在浏览器里是直接可以调用node.js的api的。

Electron则是通过新起了线程轮询libuv中来监听Node.js,一旦发现新的事件,就会立即上报到Chromium的事件循环中,唤醒主线程来处理这个事件。

Electron的优势和不足

优势

上手难度低

兼容性好

Node.JS的能力

Web生态繁荣

不足

执行效率低(vscode除外)

应用体积大

安全性差

额外原生功能受限

内存大户、能耗高

Web技术入门门槛低,生态繁荣。相较于基于C++库开发桌面软件来说,基于Electron开发更容易上手且开发效率更高。

由于JavaScript语言是一门解释执行的语言,所以C++语言固有的各种问题都不再是问题,比如:C++没有垃圾回收机制,开发人员要小心翼翼地控制内存,以免造成内存泄漏;C++语言特性繁多且复杂,学习难度曲线陡峭,需要针对不同平台进行编。使用Electron开发桌面应用就不用担心这些问题。

在执行效率上,如果前端代码写得足够优秀(基本上不可能),Electron应用完全可以做出与C++应用相媲美的用户体验,VSCode是例子,但是也是基于微软的实力。

另外,Node.js本身也可以很方便地调用C++扩展,Electron应用内又包含Node.js环境,对于一些音视频编解码或图形图像处理需求,可以使用Node.js的C++扩展来完成。

Electron可以使用几乎所有的Web前端生态领域及Node.js生态领域的组件和技术方案。非常简单方便。在完成Web前端开发工作时,开发者需要考虑很多浏览器兼容的问题,electron追Chromium版本非常快,甚至支持一些尚未通过的标准,所以基于Electron开发应用不会遇到兼容问题。

开发者的自由度得到了最大化保护,你可以在Electron中使用几乎所有HTML5、CSS3、ES6标准中定义的API。

另外,Web前端受限访问的文件系统、系统托盘、系统通知等,在Electron技术体系下均有API供开发者自由使用。额外原生功能受限:Mac小组件,访问系统服务,注册表。

安全:容易二次打包,容易下毒。很多有些带ui的勒索病毒都是用electron包装的,还有就是有些npm包会解压执行一些脚本,都有可能被误杀。杀毒软件肯定是同一特征一起杀,但是要进白名单,得一个个进来,你不去主动申请是不行的,electron有一个专门的issue来讨论这个问题,你可以翻翻。当然并不是所有都被杀,但是和你用原生的相比,概率就高很多了。

比较受诟病的几点: 内存大户、能耗高,打包体积大。安装包80m,安装体积200m+。和我们chrome浏览器的体验一样,都是吃内存,和耗电的大户。

开发入门

进程模型

浏览器进程模型

Chromium把管理页面、管理选项卡和插件的进程称为主进程。把特定于页面的进程称为渲染进程。

Chromium它把每个页面约束在单独的进程中,以保护整个浏览器不受单个页面中的故障影响。它甚至还限制了每个页面进程对其他进程和系统其他部分的访问,这极大地缓解了浏览器容易崩溃的问题。

渲染进程使用Blink开源布局引擎来解释和渲染HTML。渲染进程与主进程通过IPC管道进行通信。

每个渲染进程都对应一个全局的RenderProcess对象,都有一个或多个RenderView对象。

RenderProcess对象负责与主进程通信并管理这些RenderView对象,通常每个新窗口或选项卡都会在新进程中打开。主进程负责创建这些新的进程,并指示它们创建各自的RenderView对象。

主进程负责监视追踪这些渲染进程,一旦渲染进程崩溃或挂起,则主进程控制着界面,提示用户需要重新加载页面。

当用户点击“重新加载”按钮后,主进程则创建一个新的渲染进程来为用户服务。

有时候需要在选项卡或窗口之间共享渲染进程。比如开发者使用window.open方法打开新窗口时,就希望这个窗口复用当前的渲染进程,因为两个窗口之间往往需要同步的数据交互。另外还有一些情况需要复用渲染进程,比如打开一个新的渲染进程时,发现系统中已经有一个同样的渲染进程可以复用的情况。

Electron也提供了类似的API供开发者复用渲染进程。

由于渲染进程运行在一个单独的进程中,所有页面脚本都在此进程中运行,当页面脚本尝试访问网络或本地资源时,当前渲染进程并不提供这样的服务,而是发消息给主进程,由主进程完成相应的工作,此时主进程会判断这些操作是否合法,比如跨越同源策略的请求、突破限制访问Cookie、https页面内内嵌http的页面等,这些行为都是不合法的行为,主进程可以拒绝提供服务,这就是浏览器的沙箱模式。

多进程模式还带来了性能上的提升,对于那些不可见的渲染进程,操作系统会在用户可用内存较低时,把它们占用内存的部分或全部交换到磁盘上,以保证用户可见的进程更具响应性。相比之下,单进程浏览器架构将所有页面的数据随机分布在内存中,不可能如此干净地分离使用和未使用的数据,性能表现不佳。也正是因为这些优势,以及对更高安全性的追求,Chromium团队在分离进程的道路上越走越远,举个例子,比如一个页面内包含多个不同域下的iframe,这些iframe也会被分离到不同的渲染进程中。

Electron进程模型

Electron也继承了Chromium的多进程模式,每个BrowserWindow、BrowserView、WebView都对应一个渲染进程(并不是很准确,但基本可以这么理解),进程间通信也是通过IPC管道来实现的。多进程模式带来的缺点在Electron应用中也都存在。

多进程模式并不是没有缺点,比如每个进程都会包含公共基础结构的副本(例如V8引擎的执行环境)、更复杂的通信模型等,这都意味着浏览器会消耗更多的内存、CPU甚至电能。虽然Chromium团队在这方面做了很多优化工作,但由于底层架构所限,优化工作并没有太大的成效(这也是为什么Electron应用资源消耗较大的底层原因)

入门工具

electron Fiddle工具介绍与使用展示

Electron Fiddle 可让您创建和玩。打开后它会以一个快速启动模板来迎接你——改变一些东西,选择你想要运行它的 Electron 版本,然后到处玩。然后,将您的 Fiddle 保存为 GitHub Gist 或本地文件夹。一旦在 GitHub 上发布,任何人都可以通过在地址栏中输入 Fiddle 来快速试用您的 Fiddle。

里面也包含一些接口的示例代码

进程间通讯

IPC通讯

IPC是什么:进程通信就是 ipc(Inter-Process Communication)

渲染进程 => 主进程 (单向)

URL: https://fiddle.electronjs.org/launch?target=electron/v18.2.4/docs/fiddles/ipc/pattern-1

这份代码示例主要演示:渲染进程通过ipc的api单向向主进程发送消息。修改当前窗口的标题。

  1. 在主进程通过 ipcMain.on 监听

    在主进程中,使用 ipcMain.on API 在 set-title 通道上设置一个 IPC 监听器:

    上面的 handleSetTitle 回调函数有两个参数:一个 IpcMainEvent 结构和一个 title 字符串。 每当消息通过 set-title 通道传入时,此函数找到附加到消息发送方的 BrowserWindow 实例,并在该实例上使用 win.setTitle API。

  2. 通过预加载脚本暴露 ipcRenderer.send

    要将消息发送到上面创建的监听器,您可以使用 ipcRenderer.send API。

    默认情况下,渲染器进程没有权限访问 Node.js 和 Electron 模块。 作为应用开发者,您需要使用 contextBridge API 来选择要从预加载脚本中暴露哪些 API。

    在您的预加载脚本中添加以下代码,向渲染器进程暴露一个全局的 window.electronAPI 变量。

    此时,您将能够在渲染器进程中使用 window.electronAPI.setTitle() 函数。

使用场景:改变窗口名称、大小、位置...不关心调用结果的

双向通讯:

https://fiddle.electronjs.org/launch?target=electron/v18.2.4/docs/fiddles/ipc/pattern-2

  1. 在主进程监听

    在主进程中,我们将创建一个 handleFileOpen() 函数,它调用 dialog.showOpenDialog 并返回用户选择的文件路径值。 每当渲染器进程通过 dialog:openFile 通道发送 ipcRender.invoke 消息时,此函数被用作一个回调。 然后,返回值将作为一个 Promise 返回到最初的 invoke 调用。

    IPC 通道名称上的 dialog: 前缀对代码没有影响。 它仅用作命名空间以帮助提高代码的可读性。

  2. 通过预加载脚本暴露 ipcRenderer.invoke

    在预加载脚本中,我们暴露了一个单行的 openFile 函数,它调用并返回 ipcRenderer.invoke('dialog:openFile') 的值。 我们将在下一步中使用此 API 从渲染器的用户界面调用原生对话框。

  3. 渲染进程调用

使用场景:渲染进程需要调用结果的

主进程=>渲染进程

https://gist.github.com/807170c742751e9cac3519caa615bfb7

1. 使用 webContents 模块发送消息

2. 通过预加载脚本暴露 ipcRenderer.on

使用场景:主进程子线程任务完成、窗口preload脚本注册完成...

IPC通讯序列化问题

双向 IPC 的一个常见应用是从渲染器进程代码调用主进程模块并等待结果。 这可以通过将 ipcRenderer.invokeipcMain.handle 搭配使用来完成。

在下面的示例中,我们将从渲染器进程打开一个原生的文件对话框,并返回所选文件的路径。

在主进程中通过 handle 引发的错误是不透明的,因为它们被序列化了,并且只有原始错误的 message 属性会提供给渲染器进程。

划重点:无论是渲染进程给主进程发送消息,还是主进程给渲染进程发送消息,其背后的原理都是进程间通信。此通信过程中随消息发送的json对象会被序列化和反序列化,所以json对象中包含的方法和原型链上的数据不会被传送。

不推荐的IPC通讯方式

不推荐,但是在网上的教程,或者旧项目的示例中很常见的几种通讯方式。

前提:设置上下文隔离与Node集成

contextIsolationhttps://github.com/electron/electron/issues/23506

设置为false会导致contextBridge接口失效

运行在渲染进程的任何代码都可以轻而易举地访问到electron实例,或者访问到你在preload.js并执行特权操作(或是越权操作),而且你不希望这些特权操作可以被任意的网站执行。 你可以查看Context Isolation的文档,从中了解如何开启上下文隔离和关于它对安全性的益处 我们这么改变是为了提升electron采用默认设置时的安全性,这样你的electron应用就会更加安全,除非你故意把它设置为false

nodeIntegration: true

Electron API在主进程和渲染进程的可用性

上下文隔离是 Electron 中的一项安全措施,可确保 预加载脚本不会将拥有优先权的 Electron 或 Node.js API 泄漏到 Web 渲染器进程中的内容。 启用上下文隔离后,从预加载脚本公开 API 的唯 方法是通过 contextBridge API。

开启renderer进程Node集成的开发体验和NW.js几乎没有区别,除了原生GUI操作

我们的应用如果是本地应用,那么请求一定面临着跨域问题。

我们访问本地页面是file:///,这时候请求可以通过通过Node.js发送请求,避免跨域问题。同时读取文件内容等方式也就可以转为浏览器api选择,通过node.js的fs文件模块读取内容。

https://gist.github.com/60fc26b3d1fe8fd92921a6a8f4aaabcb

IPC双向通讯的第二种方式

https://gist.github.com/056a91fb1001c0275fd73d4e6d0a7ab4

这种方法有几个缺点:

  • 您需要设置第二个 ipcRenderer.on 监听器来处理渲染器进程中的响应。 使用 invoke,您将获得作为 Promise 返回到原始 API 调用的响应值。
  • 没有显而易见的方法可以将 dialog:openFile 消息与原始的dialog:returnVal 消息配对。 如果您通过这些通道非常频繁地来回传递消息,则需要添加其他应用代码来单独跟踪每个调用和响应。对于开发者心智模型是一种折磨

IPC双向通讯的第三种方式

https://gist.github.com/03564d46548ba5dd51729bfec116f2ca

这份代码的结构与 invoke 模型非常相似,但出于性能原因,我们建议避免使用此 API。 它的同步特性意味着它将阻塞渲染器进程,直到收到回复为止。

remote

remote 模块在 Electron 12 废弃,并将在 Electron 14 被移除. 由@electronic/remote 模块替代。

Atom源码中存在大量未迁移的remote的方式调用在主进程,electron版本目前停在了11.5.0。(9个月前11.x的最后一个版本)。

目前网络上很多教程和开源项目都存在remote的调用。此种方式写起来非常爽,但是对于性能是有巨大影响的。

Electron团队提供remote模块给开发者,

主要目的是为了简化渲染进程和主进程互访的难度,这个目的却是达到了。但也带来了很多问题,

归纳起来主要分为以下四点:

第一:它很慢

通过remote模块可以访问主进程的对象、类型、方法,

但这些操作都是跨进程的,

跨进程操作性能上的损耗可能是进程内操作的几百倍甚至上千倍。

假设你在渲染进程通过remote模块创建了一个BrowserWindow对象,

不但你创建这个对象的过程很慢,后面你使用这个对象的过程也很慢。

小到更新这个对象的属性,大到使用这个对象的方法,

都是跨进程的,这种累积性的性能损耗,可想而知影响有多大。

第二:它会制造混乱

假设我们在渲染进程中通过remote模块使用了主进程的某个对象,

此对象在某个时刻会触发一个事件(BrowserWindow对象中就有很多这样的事件),

事件处理程序是在渲染进程中注册的,

当事件发生时,实际上是主进程的原始对象先接到这个事件,

再异步的通知渲染进程要执行事件处理程序,

类似event.preventDefault()这样的操作可能毫无意义。

在一个业务复杂的应用中这类错误非常难排查。

第三:它会制造假象

我们在渲染进程中通过remote模块使用了主进程的某个对象,

得到的是这个对象的映射,是一个代理对象,

它看起来像是真正的对象,但实际上不是。

首先这个对象原型链上的属性不会被映射到渲染进程的代理对象上。

其次类似NaN、Infinity这样的值不会被正确的映射给渲染进程,

如果一个主进程方法返回一个NaN值

那么渲染进程通过remote模块访问这个方法将会得到undefined。

第四:它存在安全问题

因为remote模块底层还是通过IPC管道与主进程通信的,

那么假设你的应用需要加载第三方网页,

即使你让这些网页运行在安全沙箱内,

恶意代码仍可能通过原型污染攻击来模拟remote模块的远程消息

以获取访问主进程模块的权力,逃离沙箱的控制。

反思

Electron进程间通讯确实非常复杂,不但增加了开发人员的劳动,还增加了开发人员的心智负担

没有remote模块开发人员该怎么办呢,实际上remote模块并非被干掉了,而是从核心模块变成了可供开发者选择的模块

但开发者再使用remote模块时,一定要考虑上面提到的那四个问题

remote对象的属性和方法(包括类型的构造函数)都是主进程的属性和方法的映射。在通过remote访问它们时,Electron内部会帮你构造一个消息,这个消息从渲染进程传递给主进程,主进程完成相应的操作,得到操作结果,再把操作结果以远程对象的形式返回给渲染进程(如果返回的结果是字符串或数字,Electron会直接复制一份结果,返回给渲染进程)。

在渲染进程中持有的远程对象被回收后,主进程中相应的对象也将被回收。渲染进程不应超量全局存储远程对象,避免造成内存泄漏。

知乎上有人问,什么应用是electron开发的?下边有一个回答:“你用哪个软件卡,哪个就是”

在这里我想说一句。哪个卡,哪个用remote模块多。

ipc通讯只传递序列化的对象,remote则是copy

最佳实践

禁用node.js集成

如果攻击者跳过渲染进程并在用户电脑上执行恶意代码,那么这种跨站脚本(XSS) 攻击的危害是非常大的。 跨站脚本攻击很常见,通常情况下,威力仅限于执行代码的网站。 禁用Node.js集成有助于防止XSS攻击升级为“远程代码执行” (RCE) 攻击。

当禁用Node.js集成时,你依然可以暴露API给你的站点以使用Node.js的模块功能或特性

开启上下文隔离

上下文隔离是Electron的一个特性,它允许开发者在预加载脚本里运行代码,里面包含Electron API和专用的JavaScript上下文。 实际上,这意味全局对象如 Array.prototype.pushJSON.parse等无法被渲染进程里的运行脚本修改。

Electron使用了和Chromium相同的Content Scripts技术来开启这个行为。

即便使用了 nodeIntegration: false, 要实现真正的强隔离并且防止使用 Node.js 的功能, contextIsolation必须 开启.

remote

不要禁用同源策略

加载安全的内容:

禁用或限制网页跳转

列举一下常见的安全措施:

使用IPC:结合ts可以做类型推断。web端代码可以种模式运行。

禁用remote。性能,入侵的可能性

总结拓展

桌面应用的几种架构

(看情况,时间超长就删除此模块,有图最好)

完全离线应用: 工具

半离线应用:好牙医

完全在线应用:wolai

完整桌面应用工程应具备的点

安全问题

可以下载各使用electron开发的应用,看安装包内前端源码需要费多少功夫
  • BAD: master go
  • JUST SOSO: figma
  • NORMAL: 有道云笔记
  • FINE: wolai,百度网盘,POSTMAN

误区:ASAR不是加密只是压缩

ASAR 表示 Atom Shell Archive Format。 一个 asar 档案就是一个简单的 tar 文件 - 比如将那些有关联的文件放至一个单独的文件格式中。 Electron 能够任意读取其中的文件并且不需要解压整个文件。

ASAR格式是为了在Windows系统读取大量的小文件时 (比如像从node_modules加载应用的JavaScript依赖关系树) 提高性能。

方案对比

主流方案

  • Uglify / Obfuscator

  • 介绍:通过对 JS 代码进行丑化和混淆,尽可能降低其可读性。

  • 特征:容易解包容易阅读容易篡改容易二次打包

  • 优势:接入简单。

  • 劣势:代码格式化工具和混淆反解工具都能对代码进行一定程度的复原。丑化通过修改变量名,可能会引起代码无法运行。混淆通过调整代码结构,对代码性能有较大的影响,也可能引起代码无法执行。

  • Native 加解密

  • 介绍:将 的构建产物 Bundle 通过 XOR 或者 AES 等方案进行加密,封装进 Node Addon,然后在运行时通过 JS 进行解密。

    XOR 异或加密

  • 特征:解包有成本容易阅读容易篡改容易二次打包

  • 优势:有一定的保护作用,可以阻拦小白。

  • 劣势:对于熟悉 Node 和 Electron 的黑客来说,解包非常容易。但是如果应用支持 DevTools,则可以直接通过 DevTools 看到源代码然后再分发。如果应用不支持 DevTools,只要把 Node Addon 拷贝到一个支持 DevTools 的 Electron 下执行,还是能看到源代码。

  • ASAR 加密

  • 介绍:将 Electron ASAR 文件进行加密,并修改 Electron 源代码,在读取 ASAR 文件之前对其解密后再运行。

  • 特征:难以解包容易阅读容易篡改容易二次打包

  • 优势:有较强的保护作用,可以阻拦不少黑客。

  • 劣势:需要重新构建 Electron,初期成本高昂。但是黑客可以通过强制开启 Inspect 端口或者应用内 DevTools 读取到源代码、或者通过 Dump 内存等方式解析出源代码,并且将源代码重新打包分发。

  • v8 字节码

  • 介绍:通过 Node 标准库里的 vm 模块,可以从 Script 对象中生成其缓存数据(参考)。该缓存数据可以理解为 v8 的字节码,该方案通过分发字节码的形式来达到源代码保护的目的。

  • 特征:容易解包难以阅读难以篡改容易二次打包

  • 优势:生成的字节码,不仅几乎不可读,而且难以篡改。且不保存源代码。

  • 劣势:对构建流程具有较大侵入性,没有便捷的解决方案。字节码里还是可以读到字符串等数据,可以进行篡改。

方案介绍

关于 v8 字节码

官方的几句话介绍:https://v8.dev/blog/code-caching

扩展阅读:

我们可以理解,v8 字节码是 v8 引擎在解析和编译 JavaScript 后产物的序列化形式,它通常用于浏览器内的性能优化。所以如果我们通过 v8 字节码运行代码,不仅能够起到代码保护作用,还对性能有一定的提升。

我们在此不对 v8 字节码作为过多的阐述,可以通过阅读上述两篇文章去了解通过 v8 字节码进行代码保护的技术背景和实现方案。

v8 字节码的局限性

在代码保护上的局限

v8 字节码不保护字符串,如果我们在 JS 代码中写死了一些数据库的密钥等信息,只要将 v8 字节码作为字符串阅读,还是能直接看到这些字符串内容的。当然,简单一点的方法就是使用 Binary 形式的非字符串密钥。

另外,如果直接将上面技术方案中生成的二进制文件进行略微修改,还是可以非常容易地再分发。比如把 isVip 对应的值写死为 true,或者是把自动更新 URL 改成一个虚假的地址来禁用自动更新。为了避免这些情况,我们希望在这一层之上做更多的保护,让破解成本更加高。

对构建的影响

v8 字节码格式的和 v8 版本和环境有关,不同版本或者不同环境的 v8,其字节码产物不一样。Electron 存在两种进程,Browser 进程和 Renderer 进程。两种进程虽然 v8 版本一样,但是由于注入的方法不同,运行环境不同,因此字节码产物也有区别。在 Browser 进程中生成的 v8 字节码不能在 Renderer 进程中运行,反之也不行。当然,在 Node.js 中生成的字节码也是无法在 Electron 上运行的。因此,我们需要在 Browser 进程中构建用于 Browser 进程的代码,在 Renderer 进程中构建用于 Renderer 进程的代码。

对调试的影响以及支持 Sourcemap

由于我们将构造 vm.Script 所使用的代码都替换成了 dummyCode 进行占位,所以对 sourcemap 会有影响,并且 filename 也不再起作用。所以对调试时定位代码存在一定影响。

对代码大小的影响

对于只有几行的 JS 代码来说,编译为字节码会大大增加文件体积。如果项目中存在大量小体积的 JavaScript 文件,项目体积会有非常大幅度的增长。当然对于几 M 的 JS Bundle 来说,其体积的增量基本可以忽略不计。