Electron 结合了 Chromium、Node.js 和用于调用操作系统本地功能的 API(如打开文件窗口、通知、图标等,基于 Electron 的开发,就好像开发一个网页一样,而且能够无缝地使用 Node。或者说:就好像构建一个 Node app,并通过 HTML 和 CSS 构建界面。
那么如何在页面中调用 Node API 呢?
碰到了一些坑…
先从页面加载方式说起,Electron 中加载页面的方式有两种:
一种是直接加载本地文件,另一种是通过 http 网络请求页面。
win.loadURL(url.format({
pathname: path.join(__dirname, '/dist/index.html' ),
protocol: 'file:' ,
slashes: true
}));
win.loadURL('http://localhost:3000' );
现在我想要在某个js文件中引用一个本地的 npm 包,其中包含 Node API,所以在浏览器中无法使用。
var local = window.nodeRequire('local' );
此时出现一个问题,使用方法1运行正常,但使用方法2时报错,但是如果使用方法1,每次修改完代码都需要先打包一遍,再使用 Electron 启动,耗时耗力啊。继续寻找解决方法。
can not find module xxx
调试发现在使用网络文件时,在调用 module.js 中的 Module._load 函数时参入的参数 parent 为
重点在下面两个变量,从 Http 加载页面时,由于路径是网络地址,所以 Electron 将文件名设置为 Electron 安装目录下的 init.js.
filename: "C:\Users \asus \AppData \Roaming \npm \node _modules\electron \dist \resources \electron .asar\renderer \init .js"
paths: Array[ 0]
而在使用本地 index.html 时,pathname 指向正确的路径,而且 paths 中也包含了多个 node_modules 路径,module在初始化时会将当前路径以及上一级、上上一级…直到根目录的 node_modules 作为搜索路径。
filename: "E:\WebStormWorkspace \electron _require\index .html"
从下面 module.js 源码可以看到,文件名解析的时候正式利用了这个 paths 中的路径。因为 paths 中的空的,所以找不到所需要的模块。
其实 Electron 是从安全的角度考虑,在从 Http 请求中加载网页时,如果能直接调用本地的一些模块,会比较危险。
Module._resolveFilename = function (request, parent, isMain) {
if (NativeModule.nonInternalExists(request)) {
return request;
}
var resolvedModule = Module._resolveLookupPaths(request, parent);
var id = resolvedModule[0 ];
var paths = resolvedModule[1 ];
debug('looking for %j in %j' , id, paths);
var filename = Module._findPath(request, paths, isMain);
if (!filename) {
var err = new Error ("Cannot find module '" + request + "'" );
err.code = 'MODULE_NOT_FOUND' ;
throw err;
}
return filename;
};
此时很自然地想到可以把所需要模块的路径加入到 paths 中去,但这其实是不可行的,Electron 包含主进程和渲染进程,主进程就是这里命名main.js 的文件,该文件是每个 Electron 应用的入口。它控制了应用的生命周期(从打开到关闭)。它能调用原生元素和创建新的(多个)渲染进程,而且整个 Node API 是内置其中的。
渲染进程就是一个浏览器窗口,现在我们的 js 跑在渲染进程里面,所以我们并不能直接在主进程里面修改渲染进程的数据。
Electron 提供了 IPC 接口专门用于主进程和渲染进程之间的通信,他提供了同步和异步两种方法,同步方法直接设置 event.returnValue,异步方法使用 event.sender.send(…).
const {ipcMain} = require ('electron' )
ipcMain.on('asynchronous-message' , (event, arg) => {
console.log(arg)
event.sender.send('asynchronous-reply' , 'pong' )
})
ipcMain.on('synchronous-message' , (event, arg) => {
console.log(arg)
event.returnValue = 'pong'
})
const {ipcRenderer} = require ('electron' )
console.log(ipcRenderer.sendSync('synchronous-message' , 'ping' ))
ipcRenderer.on('asynchronous-reply' , (event, arg) => {
console.log(arg)
})
ipcRenderer.send('asynchronous-message' , 'ping' )
但其实有更简单的方法,使用 remote 模块来直接调用:
const remote = window.nodeRequire('electron' ).remote;
var local = remote.require('local' );
这样子就可以直接使用外部模块了,这里为什么能引用 electron 模块,而其他的不可以呢?
继续看源码, Electron 重写了 Module._resolveFilename 函数,在 require(‘electron’) 时,就直接返回路径,所以就可以找到啦。
const electronPath = path.join(__dirname, '..' , process.type, 'api' , 'exports' , 'electron.js' )
const originalResolveFilename = Module._resolveFilename
Module._resolveFilename = function (request, parent, isMain) {
if (request === 'electron' ) {
return electronPath
} else {
return originalResolveFilename(request, parent, isMain)
}
}
}.call(this , exports, require , module, __filename, __dirname); });