Electron: 從零開始寫一個記事本app

Electron介紹

簡單來說个束,Electron就是可以讓你用Javascript慕购、HTML、CSS來編寫運行于Windows茬底、macOS沪悲、Linux系統(tǒng)之上的桌面應(yīng)用的庫。本文的目的是通過使用Electron開發(fā)一個完整但簡單的小應(yīng)用:記事本阱表,來體驗一下這個神器的開發(fā)過程殿如。本文猶如Hello World一樣的存在,是個入門級筆記最爬,但如果你之前從未接觸過Electron涉馁,而又對它有興趣,某想信這會是一篇值得一看的入門教程爱致。
  PS:這篇文章是基于Windows的開發(fā)過程烤送,未對macOS、Linux作測試糠悯。

開發(fā)環(huán)境安裝

安裝Node.js

點擊 這里 進(jìn)入官網(wǎng)下載胯努、安裝。

安裝cnpm

由于眾所周知的原因逢防,你需要一個cnpm代替npm叶沛,這里 是官網(wǎng)。安裝命令(打開系統(tǒng)的cmd.exe來執(zhí)行命令):

npm install -g cnpm --registry=https://registry.npm.taobao.org

安裝Electron

cnpm install -g electron

安裝Electron-forge

這是一個類似于傻瓜開發(fā)包的Electron工具整合項目忘朝。具體介紹點擊 這里灰署。

cnpm install -g electron-forge

新建項目

  1. 假設(shè)項目要放到H:\Electron目錄下,項目名為notepad(字母全部小寫,多個單詞之間可以用“-”連接)溉箕。
  2. 打開cmd.exe晦墙,一路cd到H:\Electron。(也可以在Electron文件夾下肴茄,按住Shift鍵并右鍵單擊空白處晌畅,選擇在此處打開命令窗口來啟動cmd.exe。)
  3. 執(zhí)行下面的命令來生成名為notepad的項目文件夾寡痰,同時安裝項目所需要的模塊抗楔、依賴項等。
electron-forge init notepad
  1. cd到notepad目錄下拦坠,執(zhí)行下面的命令來啟動app(也可以簡單的用npm start來運行)连躏。
electron-forge start
cmd.exe
  1. 這樣就可以看到基本的app界面了。


    app界面

模板文件

  1. 這里某使用Visual Studio Code來開發(fā)app贞滨。
  2. notepad文件夾整個拖到VS Code中打開(或者點菜單文件-打開文件夾選擇notepad文件夾打開項目)入热,可以看一下項目的目錄結(jié)構(gòu):node_modules文件夾下是各種模塊、類庫晓铆,src下是app的源代碼文件勺良,package.json是描述包的文件。
    Catalog
  3. 看一下package.json骄噪,注意這里默認(rèn)已經(jīng)將主進(jìn)程入口文件配置為index.js(而不是main.js)尚困。
    main

    為避免后面混亂,某還是將這里的src/index.js改成src/main.js腰池,同時也要將文件index.js改名為main.js尾组。
    main.js
  4. 看一下main.js,這是app主進(jìn)程的入口示弓,在這里創(chuàng)建了mainWindow瀏覽器窗口讳侨,使用mainWindow.loadURL("file://${__dirname}/index.html")來加載index.html主頁;使用mainWindow.webContents.openDevTools()來打開開發(fā)者工具用于調(diào)試(這個操作通常在發(fā)布app時刪除)奏属。然后是app的事件處理:
  • ready: 當(dāng)Electron完成初始化后觸發(fā)跨跨,這里初始化后就會去創(chuàng)建瀏覽器窗口并加載主頁面。
  • window-all-closed: 當(dāng)所有瀏覽器窗口被關(guān)閉后觸發(fā)囱皿,一般此時就退出應(yīng)用了勇婴。
  • activate: 當(dāng)app激活時觸發(fā),一般針對macOS要需要處理嘱腥。
  1. 看一眼index.html耕渴,這是主頁面,除了顯示Well hey there!!!的信息外齿兔,沒什么具體內(nèi)容橱脸。
  2. 于是础米,現(xiàn)在整個app只有二個源碼文件:main.jsindex.htmlmain.js是主進(jìn)程入口添诉,index.html是一個web頁面屁桑,它需要使用一個瀏覽器窗口(BrowserWindow)來加載和顯示,作為應(yīng)用的UI栏赴,它處在一個獨立的渲染進(jìn)程中蘑斧。app啟動時執(zhí)行main.js中的代碼創(chuàng)建窗口,加載頁面等须眷。主進(jìn)程與渲染進(jìn)程之間不能直接互相訪問竖瘾,需要通過ipcMainipcRenderer進(jìn)行IPC通信(Inter-process communication),或者使用remote模塊在渲染進(jìn)程中使用主進(jìn)程中的資源(反過來柒爸,在主進(jìn)程中使用webContents.executeJavascript方法可以訪問渲染進(jìn)程)准浴。

Notepad App功能設(shè)計

這里將實現(xiàn)一個類似于Windows的記事本的App事扭。這個App具備以下功能:

  1. 主菜單:包括File, Edit, View, Help四個主菜單捎稚。重點是File菜單下的三個子菜單:New(新建文件)、Open(打開文件)求橄、Save(保存文件)今野,這三個菜單需要自定義點擊事件,其它的菜單基本使用內(nèi)建的方法處理罐农,所以沒什么難度条霜。
  2. 文本框:用于文本編輯。這也是這個App上的唯一一個組件涵亏,它的寬和高自動平鋪滿整個窗口大小宰睡。當(dāng)修改了文本框中的文字后,會在App標(biāo)題欄上最右側(cè)添加一個*號以表示文檔尚未保存气筋。
  3. 加載和保存文本:可以打開本地文本文件拆内,支持.txt, .js, .html, .md等文本文件;可以將文本內(nèi)容保存為本地文本文件宠默。在打開新建文件前麸恍,如果當(dāng)前文檔尚未保存,會提示用戶先保存文檔搀矫。
  4. 退出程序:退出窗口或程序時抹沪,會檢測當(dāng)前文檔是否需要保存,如果尚未保存瓤球,提示用戶保存融欧。
  5. 右鍵菜單:支持右鍵菜單,可以通過菜單右鍵執(zhí)行一些基本的操作卦羡,如:復(fù)制噪馏、粘貼等权她。
    下面是這個記事本App的演示效果,源碼下載點擊 這里逝薪。
    Demo

Notepad App功能細(xì)節(jié)

由于主進(jìn)程與渲染進(jìn)程不能直接互相訪問隅要,所以部分細(xì)節(jié)有必要先考慮清楚。

  1. 主菜單:因為菜單只存在于主進(jìn)程中董济,所以在執(zhí)行某些涉及頁面(渲染進(jìn)程)的菜單命令時步清,比如Open(打開文件)命令,就需要與渲染進(jìn)程進(jìn)行通信虏肾,這可以使用ipcMainipcRenderer來實現(xiàn)廓啊。
  2. 右鍵菜單、對話框:所謂右鍵菜單其實和主菜單并無分別封豪,只是顯示方式不同谴轮。由于菜單、對話框等都只存在于主進(jìn)程中吹埠,要在渲染進(jìn)程中使用它們第步,就需要向主進(jìn)程發(fā)送進(jìn)程間消息,為簡化操作缘琅,Electron提供了一個remote模塊粘都,可以在渲染進(jìn)程中調(diào)用主進(jìn)程的對象和方法,而無需顯式地發(fā)送進(jìn)程間消息刷袍,所以這一部分可以由它來實現(xiàn)翩隧。PS:對于從主進(jìn)程訪問渲染進(jìn)程(反向操作),可以使用webContents.executeJavascript方法呻纹。
  3. 退出時保存檢測:用戶點擊窗口的關(guān)閉按鈕堆生,或者點擊Exit菜單就會關(guān)閉窗口退出程序。在退出時雷酪,有必要檢查文檔是否需要保存淑仆,如果尚未保存就提示用戶保存。要實現(xiàn)這一效果太闺,首先糯景,在主進(jìn)程監(jiān)測到用戶關(guān)閉窗口時,向渲染進(jìn)程發(fā)送一個特定的消息表明窗口準(zhǔn)備關(guān)閉省骂,渲染進(jìn)程獲得該消息后查看文檔是否需要保存蟀淮,如果需要就彈窗提示用戶保存,用戶保存或取消保存后钞澳,渲染進(jìn)程再向主進(jìn)程發(fā)送一個消息表明可以關(guān)閉程序了怠惶,主進(jìn)程獲得該消息后關(guān)閉窗口退出程序。這個過程也由ipcMainipcRenderer來實現(xiàn)轧粟。

Notepad App的實現(xiàn)

整個App功能比較簡單策治,最終實現(xiàn)后也只用到了三個主要文件脓魏,包括:main.jsindex.html通惫,index.js茂翔。

main.js

這是主進(jìn)程的入口,在這里創(chuàng)建App窗口履腋,生成菜單珊燎,載入頁面等。下面是該文件的完整源碼遵湖,二個//-------之間是某根據(jù)功能需要添加的代碼悔政,其余是模板自動生成的代碼。

import { app, BrowserWindow } from 'electron';
//-----------------------------------------------------------------
import { Menu, MenuItem, dialog, ipcMain } from 'electron';
import { appMenuTemplate } from './appmenu.js';
//是否可以安全退出
let safeExit = false;
//-----------------------------------------------------------------

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow;

const createWindow = () => {
  // Create the browser window.
  mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
  });

  // and load the index.html of the app.
  mainWindow.loadURL(`file://${__dirname}/index.html`);

  // Open the DevTools.
  //mainWindow.webContents.openDevTools();

  //-----------------------------------------------------------------
  //增加主菜單(在開發(fā)測試時會有一個默認(rèn)菜單延旧,但打包后這個菜單是沒有的谋国,需要自己增加)
  const menu=Menu.buildFromTemplate(appMenuTemplate); //從模板創(chuàng)建主菜單
  //在File菜單下添加名為New的子菜單
  menu.items[0].submenu.append(new MenuItem({ //menu.items獲取是的主菜單一級菜單的菜單數(shù)組,menu.items[0]在這里就是第1個File菜單對象迁沫,在其子菜單submenu中添加新的子菜單
    label: "New",
    click(){
      mainWindow.webContents.send('action', 'new'); //點擊后向主頁渲染進(jìn)程發(fā)送“新建文件”的命令
    },
    accelerator: 'CmdOrCtrl+N' //快捷鍵:Ctrl+N
  }));
  //在New菜單后面添加名為Open的同級菜單
  menu.items[0].submenu.append(new MenuItem({
    label: "Open",
    click(){
      mainWindow.webContents.send('action', 'open'); //點擊后向主頁渲染進(jìn)程發(fā)送“打開文件”的命令
    },
    accelerator: 'CmdOrCtrl+O' //快捷鍵:Ctrl+O
  })); 
  //再添加一個名為Save的同級菜單
  menu.items[0].submenu.append(new MenuItem({
    label: "Save",
    click(){
      mainWindow.webContents.send('action', 'save'); //點擊后向主頁渲染進(jìn)程發(fā)送“保存文件”的命令
    },
    accelerator: 'CmdOrCtrl+S' //快捷鍵:Ctrl+S
  }));
  //添加一個分隔符
  menu.items[0].submenu.append(new MenuItem({
    type: 'separator'
  }));
  //再添加一個名為Exit的同級菜單
  menu.items[0].submenu.append(new MenuItem({
    role: 'quit'
  }));
  Menu.setApplicationMenu(menu); //注意:這個代碼要放到菜單添加完成之后芦瘾,否則會造成新增菜單的快捷鍵無效

  mainWindow.on('close', (e) => {
    if(!safeExit){
      e.preventDefault();
      mainWindow.webContents.send('action', 'exiting');
    }
  });
  //-----------------------------------------------------------------

  // Emitted when the window is closed.
  mainWindow.on('closed', () => {
    // Dereference the window object, usually you would store windows
    // in an array if your app supports multi windows, this is the time
    // when you should delete the corresponding element.
    mainWindow = null;
  });
};

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow);

// Quit when all windows are closed.
app.on('window-all-closed', () => {
  // On OS X it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

app.on('activate', () => {
  // On OS X it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (mainWindow === null) {
    createWindow();
  }
});

// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and import them here.

//-----------------------------------------------------------------
//監(jiān)聽與渲染進(jìn)程的通信
ipcMain.on('reqaction', (event, arg) => {
  switch(arg){
    case 'exit':
      //做點其它操作:比如記錄窗口大小、位置等弯洗,下次啟動時自動使用這些設(shè)置旅急;不過因為這里(主進(jìn)程)無法訪問localStorage逢勾,這些數(shù)據(jù)需要使用其它的方式來保存和加載牡整,這里就不作演示了。這里推薦一個相關(guān)的工具類庫溺拱,可以使用它在主進(jìn)程中保存加載配置數(shù)據(jù):https://github.com/sindresorhus/electron-store
      //...
      safeExit=true;
      app.quit();//退出程序
      break;
  }
});
//-----------------------------------------------------------------

首先逃贝,app.on('ready', createWindow)也就是當(dāng)Electron完成初始化后,就調(diào)用createWindow方法來創(chuàng)建瀏覽器窗口mainWindow(與主進(jìn)程只能有1個不同迫摔,可以根據(jù)需要適時創(chuàng)建更多個瀏覽器窗口沐扳,這些窗口由主進(jìn)程負(fù)責(zé)創(chuàng)建和管理,每個瀏覽器窗口使用一個獨立的渲染進(jìn)程句占;本文只需使用一個瀏覽器窗口沪摄,即mainWindow)。同時纱烘,使用Menu.buildFromTemplate(appMenuTemplate)通過一個菜單模板來創(chuàng)建app應(yīng)用主菜單杨拐,模板代碼存放在appmenu.js文件中(這個文件包含在本文的源碼中,也可以點擊這里查看)擂啥,這個模板的寫法可以參考官方的 Electron API Demos
Customize Menus的例子哄陶。模板的第一個菜單是File菜單,它的子菜單被設(shè)計成空的哺壶,在這里使用menu.items[0].submenu.append方法向這個File菜單添加四個子菜單屋吨,分別是:New(新建文檔)蜒谤,Open(打開文檔),Save(保存文檔)至扰,Exit(退出程序)鳍徽。其中,前三個菜單在點擊后都會向渲染進(jìn)程發(fā)送信息敢课,通知渲染進(jìn)程執(zhí)行相關(guān)處理旬盯。如對于New菜單,使用mainWindow.webContents.send('action', 'new')的方式翎猛,通知渲染進(jìn)程要新建一個文檔胖翰。渲染進(jìn)程會使用ipcRenderer.on方法來執(zhí)行監(jiān)聽,監(jiān)聽到消息后就會執(zhí)行相應(yīng)處理(這部分在index.js中實現(xiàn))切厘。最后使用Menu.setApplicationMenu(menu)將主菜單安裝到瀏覽器窗體中(所有窗體會共享主菜單)萨咳。

index.html

這是App的文本編輯頁面。這個頁面很簡單疫稿,整個頁面就只有一個TextArea控件(id為txtEditor)培他,平鋪滿整個窗口。該頁面使用require('./index.js')載入index.js遗座。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Notepad</title>
  <style type="text/css">
    body,html{
        margin:0px;
        height:100%;
    }
    
    #txtEditor{
        width:100%;
        height:99.535%;
        padding:0px;
        margin:0px;
        border:0px;
        font-size: 18px;
    }
  </style>
  </head>
  <body>
  <textarea id="txtEditor"></textarea>
</body>
  <script>
    require('./index.js');
  </script>
</html>

index.js

所有主頁面index.html涉及到的頁面處理舀凛、與主進(jìn)程交互等的操作都會放到該js文件中。該文件完整代碼:

import { ipcRenderer, remote } from 'electron';
const { Menu, MenuItem, dialog } = remote;

let currentFile = null; //當(dāng)前文檔保存的路徑
let isSaved = true;     //當(dāng)前文檔是否已保存
let txtEditor = document.getElementById('txtEditor'); //獲得TextArea文本框的引用

document.title = "Notepad - Untitled"; //設(shè)置文檔標(biāo)題途蒋,影響窗口標(biāo)題欄名稱

//給文本框增加右鍵菜單
const contextMenuTemplate=[
    { role: 'undo' },       //Undo菜單項
    { role: 'redo' },       //Redo菜單項
    { type: 'separator' },  //分隔線
    { role: 'cut' },        //Cut菜單項
    { role: 'copy' },       //Copy菜單項
    { role: 'paste' },      //Paste菜單項
    { role: 'delete' },     //Delete菜單項
    { type: 'separator' },  //分隔線
    { role: 'selectall' }   //Select All菜單項
];
const contextMenu=Menu.buildFromTemplate(contextMenuTemplate);
txtEditor.addEventListener('contextmenu', (e)=>{
    e.preventDefault();
    contextMenu.popup(remote.getCurrentWindow());
});

//監(jiān)控文本框內(nèi)容是否改變
txtEditor.oninput=(e)=>{
    if(isSaved) document.title += " *";
    isSaved=false;
};

//監(jiān)聽與主進(jìn)程的通信
ipcRenderer.on('action', (event, arg) => {
    switch(arg){        
    case 'new': //新建文件
        askSaveIfNeed();
        currentFile=null;
        txtEditor.value='';   
        document.title = "Notepad - Untitled";
        //remote.getCurrentWindow().setTitle("Notepad - Untitled *");
        isSaved=true;
        break;
    case 'open': //打開文件
        askSaveIfNeed();
        const files = remote.dialog.showOpenDialog(remote.getCurrentWindow(), {
            filters: [
                { name: "Text Files", extensions: ['txt', 'js', 'html', 'md'] }, 
                { name: 'All Files', extensions: ['*'] } ],
            properties: ['openFile']
        });
        if(files){
            currentFile=files[0];
            const txtRead=readText(currentFile);
            txtEditor.value=txtRead;
            document.title = "Notepad - " + currentFile;
            isSaved=true;
        }
        break;
    case 'save': //保存文件
        saveCurrentDoc();
        break;
    case 'exiting':
        askSaveIfNeed();
        ipcRenderer.sendSync('reqaction', 'exit');
        break;
    }
});

//讀取文本文件
function readText(file){
    const fs = require('fs');
    return fs.readFileSync(file, 'utf8');
}
//保存文本內(nèi)容到文件
function saveText(text, file){
    const fs = require('fs');
    fs.writeFileSync(file, text);
}

//保存當(dāng)前文檔
function saveCurrentDoc(){
    if(!currentFile){
        const file = remote.dialog.showSaveDialog(remote.getCurrentWindow(), {
            filters: [
                { name: "Text Files", extensions: ['txt', 'js', 'html', 'md'] }, 
                { name: 'All Files', extensions: ['*'] } ]
        });
        if(file) currentFile=file;
    }
    if(currentFile){
        const txtSave=txtEditor.value;
        saveText(txtSave, currentFile);
        isSaved=true;
        document.title = "Notepad - " + currentFile;
    }
}

//如果需要保存猛遍,彈出保存對話框詢問用戶是否保存當(dāng)前文檔
function askSaveIfNeed(){
    if(isSaved) return;
    const response=dialog.showMessageBox(remote.getCurrentWindow(), {
        message: 'Do you want to save the current document?',
        type: 'question',
        buttons: [ 'Yes', 'No' ]
    });
    if(response==0) saveCurrentDoc(); //點擊Yes按鈕后保存當(dāng)前文檔
}

首先,前面說了号坡,在渲染進(jìn)程中不能直接訪問菜單懊烤,對話框等,它們只存在于主進(jìn)程中宽堆,但可以通過remote來使用這些資源腌紧。

import { remote } from 'electron';
const { Menu, MenuItem, dialog } = remote;

然后,const contextMenu=Menu.buildFromTemplate(contextMenuTemplate)即使用contextMenuTemplate模板來創(chuàng)建編輯器的右鍵菜單(雖然創(chuàng)建過程在渲染進(jìn)程中進(jìn)行畜隶,但實際上使用remote來創(chuàng)建的菜單壁肋、對話框等,仍然只存在于主進(jìn)程內(nèi))籽慢,由于這里涉及到的菜單都只需要使用系統(tǒng)的內(nèi)建功能浸遗,不需要自定義,所以這里比較簡單嗡综。使用txtEditor.addEventListener('contextmenu')來監(jiān)聽右鍵菜單請求乙帮,使用contextMenu.popup(remote.getCurrentWindow())來彈出右鍵菜單。
  txtEditor.oninput用于監(jiān)控文本框內(nèi)容變化极景,如果有改變察净,則將文檔標(biāo)記為尚未保存驾茴,并在標(biāo)題欄最右側(cè)顯示一個*號作為提示。
  PS:在Win7上如果沒有啟用Aero效果氢卡,使用document.title = xxxremote.getCurrentWindow().setTitle(xxx)都看不到程序標(biāo)題欄的標(biāo)題變化锈至,只當(dāng)你比如縮放一下窗口后這個修改才會被刷新。
  ipcRenderer.on用于監(jiān)聽由主進(jìn)程發(fā)來的消息译秦。前面說過峡捡,主進(jìn)程使用mainWindow.webContents.send('action', 'new')的方式向渲染進(jìn)程發(fā)送特定消息,渲染進(jìn)程監(jiān)聽到消息后筑悴,根據(jù)消息內(nèi)容做出相應(yīng)處理们拙。比如,這里阁吝,當(dāng)主進(jìn)程發(fā)來new的消息后砚婆,渲染進(jìn)程就開始著手新建一個文檔,在新建前會使用askSaveIfNeed方法檢測文檔是否需要保存突勇,并提示用戶保存装盯;對于open的消息就會調(diào)用remote.dialog.showOpenDialog來顯示一個文件打開對話框,由用戶選擇要打開的文檔然后加載文本數(shù)據(jù)甲馋;而對于save消息就會對當(dāng)前文檔進(jìn)行保存操作埂奈。

退出時保存檢測的實現(xiàn)過程

正如前面在App功能細(xì)節(jié)中討論的一樣,在關(guān)閉程序前定躏,友好的做法是檢測文檔是否需要保存账磺,如果尚未保存,通知用戶保存共屈。要實現(xiàn)這一功能绑谣,需要在主進(jìn)程和渲染進(jìn)程間進(jìn)行相互通信,以獲得窗體關(guān)閉和文檔保存的確認(rèn)拗引,實現(xiàn)安全退出。

主進(jìn)程端

首先在main.js中幌衣,使用mainWindow.on('close')來監(jiān)控mainWindow窗口的關(guān)閉矾削。

mainWindow.on('close', (e) => {
    if(!safeExit){
      e.preventDefault();
      mainWindow.webContents.send('action', 'exiting');
    }
  });

這里safeExit開關(guān)用于標(biāo)記渲染進(jìn)程是否已經(jīng)向主進(jìn)程反饋它已經(jīng)完成所有操作了。如果尚未反饋豁护,則使用e.preventDefault()阻止窗口關(guān)閉哼凯,并使用mainWindow.webContents.send('action', 'exiting')向渲染進(jìn)程發(fā)送一個exiting消息,告訴渲染進(jìn)程:嘿楚里,我要關(guān)掉窗口了断部,你趕緊看看還要什么沒做完的,做完后通知我班缎。
  既然主進(jìn)程要等渲染進(jìn)程的反饋蝴光,就需要監(jiān)聽渲染進(jìn)程發(fā)回的消息她渴,所以主進(jìn)程使用ipcMain.on來執(zhí)行監(jiān)聽。如果渲染進(jìn)程發(fā)送一個exit消息過來蔑祟,就表示可以安全退出了趁耗。

ipcMain.on('reqaction', (event, arg) => {
  switch(arg){
    case 'exit':
      safeExit=true;
      app.quit();
      break;
  }
});

渲染進(jìn)程端

在渲染進(jìn)程這邊的index.js中,在ipcRenderer.on監(jiān)聽方法中疆虚,相應(yīng)的有一個消息處理是針對主進(jìn)程發(fā)來的exiting消息的苛败,當(dāng)獲知主進(jìn)程準(zhǔn)備關(guān)閉窗口,渲染進(jìn)程就先去檢查文檔是否保存過了径簿,如果尚未保存就通知用戶保存罢屈,用戶保存或取消保存后,使用ipcRenderer.sendSync('reqaction', 'exit')來向主進(jìn)程發(fā)送一個exit消息篇亭,表示:我要做的都做完了儡遮,你想退就退吧。

case 'exiting':
        askSaveIfNeed();
        ipcRenderer.sendSync('reqaction', 'exit');
        break;

主進(jìn)程監(jiān)聽到這個消息后暗赶,將safeExit標(biāo)記為true鄙币,表示已經(jīng)得到渲染進(jìn)程的確認(rèn),然后就可以使用app.quit()安全退出了蹂随。當(dāng)然十嘿,在退出前,可以再執(zhí)行一些其它操作(比如保存參數(shù)配置等)岳锁。

編譯打包

  1. 鍵入以下命令進(jìn)行編譯打包:
npm run make

該命令會將文件打包到當(dāng)前項目目錄下的out文件夾下绩衷。打包后發(fā)現(xiàn),源碼直接暴露在[app項目目錄]\out\notepad-win32-x64\resources\app\src目錄下激率。

  1. 修改package.json咳燕,在electronPackagerConfig部分添加"asar": true
"electronPackagerConfig": {
        "asar": true
      }

重新打包后源碼文件會被打包進(jìn)app.asar文件中(該文件仍然在src目錄下)乒躺。

  1. 可以直接運行打包后的notepad.exe啟動程序招盲。

by Mandarava(鰻駝螺) 2017.07.12

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末芬膝,一起剝皮案震驚了整個濱河市粉楚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌然眼,老刑警劉巖讳推,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件顶籽,死亡現(xiàn)場離奇詭異,居然都是意外死亡银觅,警方通過查閱死者的電腦和手機礼饱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人镊绪,你說我怎么就攤上這事匀伏。” “怎么了帘撰?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵万皿,是天一觀的道長。 經(jīng)常有香客問我牢硅,道長蹬耘,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任综苔,我火速辦了婚禮位岔,結(jié)果婚禮上如筛,老公的妹妹穿的比我還像新娘。我一直安慰自己抒抬,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布妖胀。 她就那樣靜靜地躺著惠勒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪纠屋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天肉康,我揣著相機與錄音灼舍,去河邊找鬼涨薪。 笑死刚夺,一個胖子當(dāng)著我的面吹牛末捣,可吹牛的內(nèi)容都是我干的创橄。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼邦邦,長吁一口氣:“原來是場噩夢啊……” “哼醉蚁!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起黔龟,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤滥玷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蛋欣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體桨菜,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年泻红,在試婚紗的時候發(fā)現(xiàn)自己被綠了谊路。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片菩彬。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖惨恭,靈堂內(nèi)的尸體忽然破棺而出耙旦,到底是詐尸還是另有隱情,我是刑警寧澤锉罐,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站栽连,受9級特大地震影響侨舆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜噩茄,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一复颈、第九天 我趴在偏房一處隱蔽的房頂上張望耗啦。 院中可真熱鬧,春花似錦帜讲、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽腋舌。三九已至,卻和暖如春块饺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背辨嗽。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工召庞, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留来破,地道東北人。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓诅诱,卻偏偏與公主長得像送朱,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子炮沐,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容