[toc]
background
Pick up where you left off
When developing the electron desktop, there will be all kinds of pits, just to summarize.
- Anti-virus software damage check
- Preventing debugging
- Client crash report
- Improve the client startup speed
- Performance monitoring analysis
- Lazy loading module
- The scrollbar style is uniform
- BrowserWindow error listening
- The browserWindow A TAB opens the default browser
- -jquery, requirejs, meteor, AngularJS cannot be used in electron-.
- electron-bridge
- The proxy Settings
- System Version Comparison (MAC)
- Multi-window management
- Similar to vscode seamless upgrade installation
Anti-virus software damage check
For some internal core files, you can use the whitelist mechanism to check whether the file exists. If not, the software is damaged and you can exit directly.
const getBinaryFileCheckList = () = >{
const dir = [];
// The network-interface package is required.
const network = require.resolve("network-interface/package"),
dir.push(network);
return dir;
}
constbinaryFileCheckList = getBinaryFileCheckList(); <! - check - >for (let e = 0; e < binaryFileCheckList.length; e++) {
const n = binaryFileCheckList[e];
if(! fs.existsSync(n)) { dialog.showErrorBox("Startup failed"."Application file is damaged, possibly caused by anti-virus software, please download and install again.");
/ / exit directly
electronApp.exit(1);
break}}Copy the code
Preventing debugging
Check argv for chrome debugging keywords such as inspect or debugging.
const runWithDebug = process.argv.find(e= > e.includes("--inspect") || e.includes("--inspect-brk") || e.includes("--remote-debugging-port"));
if(runWithDebug){
// Exit directly.
electronApp.quit()
}
Copy the code
Client crash report
Third-party plug-ins can be used to assist with client-side crash reporting
@sentry/electron
Chinese document
www.yuque.com/lizhiyao/dx…
Improve the client startup speed
There are several ways to speed up client startup.
Use V8 to cache data
Electorn uses V8 to run JS, which requires parsing and compilation before executing the code. The parsing and compilation processes are time-consuming and often lead to performance bottlenecks. The V8 cache function can cache the compiled bytecode, saving the time of next parsing and compilation.
Compile the code for the plug-in using the V8-compile-cache
V8-compile-cache is very simple to use. Add a line of code to the code that needs to be cached:
require('v8-compile-cache')
Copy the code
By default, v8-compile-cache is cached in the temporary folder
/v8-compile-cache-
. After the computer restarts, the file will be deleted.
If you want caching to be permanent, you can specify the cache folder using the environment variable process.env.v8_compile_cache_cache_dir to avoid deletion after a computer restarts. In addition, if you want different versions of your project to correspond to different caches, you can include the code version number (or other unique identifier) in the folder name to ensure that the caches and project versions correspond exactly. Of course, this also means multiple caches for multiple versions of the project. In order not to take up too much disk space, we need to remove other versions of the cache when the program exits.
Performance monitoring analysis
Main process, you can use V8-inspect-profiler for performance monitoring. The resulting.cpuProfile file can be analyzed using the Javascript Profiler on DevTools. If a child process is started with a fork, for example, it can be monitored in the same way, with different monitoring ports.
v8-inspect-profiler
Set the startup command, add the parameter –inspect=${port}, set the v8 debug port for the main process.
/ / package. Json {" name ":" test ", "version" : "1.0.0", "main" : ". The main js ", "devDependencies" : {" electron ": "9.2.1"}, "scripts" : {" start ":" electron., inspect = 5222 "}, "dependencies" : {" v8 - inspect - profiler ":" ^ 0.0.20 "}}Copy the code
Lazy loading module
Some of the project’s dependency modules are required only when a particular function is triggered. Therefore, it is not necessary to load immediately when the application is started; it can be loaded when the method is called.
Before optimization
// import module const XXX = require(' XXX '); export function share() { ... // execute dependent method XXX ()}Copy the code
The optimized
Export function share() {const XXX = require(' XXX '); . // execute dependent method XXX ()}Copy the code
The scrollbar style is uniform
For Windows and macOs, the default scroll width is different
function getScrollbarWidth() {
const div = document.createElement('div');
div.style.visibility = 'hidden';
div.style.width = '100px';
document.body.appendChild(div);
const offsetWidth = div.offsetWidth;
div.style.overflow = 'scroll';
const childDiv = document.createElement('div');
childDiv.style.width = '100%';
div.appendChild(childDiv);
const childOffsetWidth = childDiv.offsetWidth;
div.parentNode.removeChild(div);
return offsetWidth - childOffsetWidth;
}
Copy the code
Then set it differently depending on getScrollbarWidth.
Such as:
<! --> webkit-scrollbar-thumb{background-color:rgba(180.180.180.0.2);
border-radius: 8px;
}
::-webkit-scrollbar-thumb:hover{
background-color: rgba(180.180.180.0.5); } <! --> ::-webkit-scrollbar-track {border-radius:8px; } <! --> ::-webkit-scrollbar{width:8px;
height: 8px;
}
Copy the code
document.onreadystatechange=(() = >{
if("interactive"= = =document.readyState){
// Process logic}})Copy the code
BrowserWindow error listening
Listen for unhandledrejection and error events
error
window.addEventListener('error'.(error) = >{
const message = {
message: error.message,
source: error.source,
lineno: error.lineno,
colno: error.colno,
stack: error.error && error.error.stack,
href: window.location.href
};
// Send to the main process via ipcRender for logging.
ipcRenderer.send("weblog", n)
},false)
Copy the code
unhandledrejection
window.addEventListener('unhandledrejection'.(error) = >{
if(! error.reason){return;
}
const message = {
message: error.reason.message,
stack: error.reason.stack,
href: window.location.href } <! -- Sent via ipcRender to the main process for logging. --> ipcRenderer.send("weblog", n)
},false)
Copy the code
The browserWindow A TAB opens the default browser
In business, there will be an A tag on the browserWindow page. In this case, if you are in the electron container, you need to intercept it and open it through the default browser.
document.addEventListener('click'.(event) = >{
const target = event.target;
if(target.nodeName === 'A') {if(event.defaultPrevented){
return;
}
if(location.hostname){
event.preventDefault();
}
if(target.href){ shell.openExternal(target.href); }}},false);
Copy the code
Expose a global method for opening a browser
window.openExternalLink = ((r)=>{
shell.openExternal(r)
});
Copy the code
-jquery, requirejs, meteor, AngularJS cannot be used in electron-.
Because Electron introduces Node.js into the runtime environment, there are a few extra variables in the DOM, such as Module, exports, and require. This causes many libraries to fail because they also need to add variables of the same name to the runtime environment.
Two ways,
- One way is through configuration
webPreferences.nodeIntegration
为false
By disabling Node.js - Through the top of the header inside electron-bridge.js
delete window.require;
.delete window.exports;
.delete window.module;
way
Const {BrowserWindow} = require('electron') const win = new BrowserWindow(format @@webPreferences: { nodeIntegration: false } }) win.show()Copy the code
<head>
<script>
window.nodeRequire = require;
delete window.require;
delete window.exports;
delete window.module;
</script>
<script type="text/javascript" src="jquery.js"></script>
</head>
Copy the code
electron-bridge
Inject the extra ELECTRON API into browserWindow via the Bridge
const {ipcRenderer: ipcRenderer, shell: shell, remote: remote, clipboard: clipboard} = require("electron"), <! -- Parameters in process -->const processStaticValues = _.pick(process, ["arch"."argv"."argv0"."execArgv"."execPath"."helperExecPath"."platform"."type"."version"."versions"]);
module.exports = (() = > ({
ipcRenderer: ipcRenderer, // ipc renderer
shell: shell, // shell
remote: remote, //
clipboard: clipboard,
process: {
...processStaticValues,
hang: () = > {
process.hang()
},
crash: () = > {
process.crash()
},
cwd: () = > {
process.cwd()
}
}
}));
Copy the code
The proxy Settings
For proxy Settings, there are generally two modes:
- PAC
- HTTP
PAC
Enter the address Protocol://IP:Port
HTTP
For HTTP mode, yes
- HTTP
- SOCKS4
- SOCKS5
Input Protocol: / / IP: Port
System Version Comparison (MAC)
Semver is recommended.
Multi-window management
Microelectron Windows is recommended to support dynamic window creation.
address
Similar to vscode seamless upgrade installation
Run the following command to mount DMG to the /Volumes directory on the MAC. Delete the app under /Applications and copy the app under /Volumes to /Applications. To uninstall DMG; Restart the application. This method can achieve a similar effect of seamless update.
With the help of hdiutil,
There are six main steps:
- which hdiutil
- hdiutil eject [/Volumes/appDisplayName latestVersion]
- hdiutil attach [latest Dmg Path]
- mv [local App Path] [temp dir]
- cp -R [latest app path] [local app path]
- hdiutil eject [/Volumes/appDisplayName latestVersion]
which hdiutil
Check whether the hdiutil executable exists.
hdiutil eject [/Volumes/appDisplayName latestVersion]
Uninstall the files under [/Volumes/appDisplayName latestVersion].
hdiutil attach [latest Dmg Path]
Install the DMG file
mv [local App Path] [temp dir]
Move the old local app directory to the tempDir directory.
cp -R [latest app path] [local app path]
Copy all files in the latest App path file to the original app directory.
hdiutil eject [/Volumes/appDisplayName latestVersion]
Uninstall the [/Volumes/appDisplayName latestVersion] file again
At each step, if it works, it works.
Example code.
const path = require("path"); const os = require('os'); const {waitUntil, spawnAsync} = require('.. /.. /utils'); const {existsSync} = require('original-fs'); const getMacOSAppPath = () => { const sep = path.sep; const execPathList = process.execPath.split(sep); const index = execPathList.findIndex(t => 'Applications' === t); return execPathList.slice(0, index + 2).join(sep); }; module.exports = (async (app) => { const {appDisplayName} = app.config; const {latestVersion, latestDmgPath} = app.updateInfo; // const macOsAppPath = getMacOSAppPath(); // temp dir const tempDir = path.join(os.tmpdir(), String((new Date).getTime())); const appDisplayNameVolumesDir = path.join('/Volumes', `${appDisplayName} ${latestVersion}`); // const latestAppPath = path.join(appDisplayNameVolumesDir, `${appDisplayName}.app`); // step 1 which hdiutil // /usr/bin/hdiutil try { const hdiutilResult = await spawnAsync('which', ['hdiutil']); if (! hdiutilResult.includes('/bin/hdiutil')) { throw new Error('hdiutil not found'); } } catch (e) { app.logger.warn(e); return { success: false, type: 'dmg-install-failed' } } // step 2 hdiutil eject appDisplayNameVolumesDir try { await spawnAsync("hdiutil", ["eject", appDisplayNameVolumesDir]) } catch (e) { e.customMessage = '[InstallMacOSDmgError] step2 volume exists'; app.logger.warn(e); } finally { const result = await waitUntil(() => ! existsSync(latestAppPath), { ms: 300, retryTime: 5 }); if (! result) { app.logger.warn('[InstallMacOSDmgError] step2 volume exists'); return { success: false } } } //step 3 hdiutil attach latestDmgPath try { await spawnAsync('hdiutil', ['attach', latestDmgPath]) } catch (e) { e.customMessage = '[InstallMacOSDmgError] step3 hdiutil attach error'; app.logger.warn(e); } finally { const result = await waitUntil(() => ! existsSync(latestAppPath), { ms: 300, retryTime: 5 }); if (! result) { app.logger.warn('[InstallMacOSDmgError] step3 hdiutil attach fail'); return { success: false } } } // step 4 mv try { await spawnAsync('mv', [macOsAppPath, tempDir]); } catch (e) { e.customMessage = '[InstallMacOSDmgError] step4 mv to tmp path error'; app.logger.warn(e); } finally { const result = await waitUntil(() => ! existsSync(tempDir), { ms: 300, retryTime: 5 }); if (! result) { app.logger.warn('[InstallMacOSDmgError] step4 cp to tmp path fail'); return { success: false, type: "dmg-install-failed" } } } // step 5 try { await spawnAsync('cp', ['-R', latestAppPath, macOsAppPath]) } catch (e) { e.customMessage = '[InstallMacOSDmgError] step5 cp to app error'; app.logger.warn(e); } finally { const result = await waitUntil(() => ! existsSync(macOsAppPath), { ms: 300, retryTime: 5 }); if (! result) { app.logger.warn('[InstallMacOSDmgError] step5 cp to app fail'); await spawnAsync('mv', [tempDir, macOsAppPath]); return { success: false, type: "dmg-install-failed" } } } // step 6 try { await spawnAsync('hdiutil', ['eject', appDisplayNameVolumesDir]) } catch (e) { e.customMessage = '[InstallMacOSDmgError] step6 hdiutil eject fail'; app.logger.warn(e); } return { success: true } });Copy the code
The project address
To this end, I stripped out the functions required by each client used in the business system and created a new template to facilitate the development of the new business system.
Coding is not easy, welcome star
Github.com/bosscheng/e…
github