Electron+Mobx+React 開(kāi)發(fā)記錄(一)

Hello World

> Contents

  1. 前言
  2. 開(kāi)發(fā)環(huán)境搭建
  3. 引入Webpack4.0前端打包工具
  4. Electron代碼結(jié)構(gòu)和代碼熱更新
  5. 前端界面React + Mobx 代碼結(jié)構(gòu)和熱更新
  6. Linux桌面客戶(hù)端開(kāi)發(fā)遇到的問(wèn)題

前言


最近桌面系統(tǒng)從Ubuntu18.04切換到了Manjaro Linux 17,之前聽(tīng)說(shuō)Manjaro的軟件豐富,倉(cāng)庫(kù)更新及時(shí),很多常用軟件都能一鍵安裝(比如QQ,微信),同時(shí)也支持主流的Linux桌面環(huán)境:Gnome、KDE、Cinnamon、Mate、Deepin等等,安裝了Gnome版本的Manjaro之后發(fā)現(xiàn)果然還不錯(cuò)。系統(tǒng)安裝好后配置比較繁瑣,就想給Manjaro寫(xiě)一個(gè)GUI客戶(hù)端工具用于安裝常用軟件和作為簡(jiǎn)單的系統(tǒng)管理工具 - electronux
作為一名正直的前端開(kāi)發(fā)人員,理所應(yīng)當(dāng)?shù)鼐蜏?zhǔn)備使用Electron + Node.js + React + Mobx + Webpack + Shell 來(lái)進(jìn)行開(kāi)發(fā)啦 ~ 目前仍然在開(kāi)發(fā)中,這篇文章用于記錄自己的環(huán)境搭建過(guò)程、一些對(duì)Electron+React開(kāi)發(fā)的理解以及談?wù)勛约河龅降囊恍㎜inux桌面軟件開(kāi)發(fā)時(shí)遇到的問(wèn)題和解決辦法。

clean_detail.png
clean_search.png
electron_pssword.png
info_total.png
install_detail.png
install_list.png
install_permission.png
startup_list.png

開(kāi)發(fā)環(huán)境搭建


代碼目錄結(jié)構(gòu)
electronux  
|---- [dir ] app ( 主代碼目錄 )
|----------- [dir ] app/configure ( 應(yīng)用配置更新 )
|----------- [dir ] app/runtime ( 運(yùn)行數(shù)據(jù)文件 )
|
|----------- [dir ] app/services ( 后臺(tái)服務(wù)存放目錄 )
|------------------------ [dir ] app/services/middleware ( 一些中間處理件 )
|------------------------ [dir ] app/services/shell ( shell腳本存放目錄 )
|------------------------ [dir ] app/services/main-serv ( 主進(jìn)程服務(wù) )
|------------------------ [dir ] app/services/render-serv ( 渲染進(jìn)程服務(wù) )
|
|----------- [dir ] app/stores ( 前端狀態(tài)管理文件目錄 )
|----------- [dir ] app/styles  ( 公用樣式表文件 )
|----------- [dir ] app/utils  ( 公用工具函數(shù) )
|
|----------- [dir ] app/views  ( UI界面代碼 )
|------------------------ [dir ] app/views/module1  ( 界面模塊1 )
|------------------------ [dir ] app/views/module2  ( 界面模塊2)
|------------------------ [dir ] app/views/module3  ( 界面模塊3 )
|
|----------- [file] app/App.js  ( 前端應(yīng)用入口文件 )
|----------- [file] app/index.js ( 前端應(yīng)用熱加載文件 )
|
|---- [dir ] dist ( 前端代碼編譯打包文件存放目錄 )
|---- [dir ] resources ( 前端靜態(tài)資源存放目錄 )
|
|---- [file] .babelrc ( babel配置文件 )
|---- [file] .editorconfig (編輯器編碼規(guī)范文件)
|---- [file] .eslintrc ( 代碼格式檢查配置文件 )
|---- [file] .gitignore ( git忽略追蹤配置文件 )
|---- [file] electron-builder.json ( electron-builder打包配置文件 )
|---- [file] index.html  ( 應(yīng)用渲染入口頁(yè)面 )
|---- [file] index.js ( 應(yīng)用主進(jìn)程入口文件 )
|---- [file] package.json (前端模塊和框架配置文件)
|---- [file] webpack.config.js (webpack開(kāi)發(fā)環(huán)境配置文件)
|---- [file] webpack.prod.config.js  ( webpack生產(chǎn)環(huán)境配置文件 )

項(xiàng)目環(huán)境依賴(lài)配置文件
{
  "name": "electronux",
  "description": "linux manager-software powered by electron & react & Mobx ",
  "version": "1.0.0",
  "author": {
    "name": "nojsja",
    "email": "yangwei020154@gmail.com"
  },
  "scripts": {
    "start": "concurrently \"npm run start-dev\" \"npm run start-electron\"",
    "start-dev": "cross-env NODE_ENV=development webpack-dev-server",
    "start-electron": "nodemon --exec 'cross-env NODE_ENV=development electron --inspect=5858 index'",
    "start-production": "cross-env NODE_ENV=production electron --inspect=5858 index",
    "build-all": "npm run dist && npm run build",
    "dist": "cross-env NODE_ENV=production webpack --config webpack.prod.config.js",
    "build": "electron-builder -l"
  },
  "keywords": [
    "electron",
    "react",
    "mobx",
    "react-router",
    "webpack4"
  ],
  "license": "",
  "nodemonConfig": {
    "ignore": [
      "resources/*",
      "node_modules/*",
      "dist/*",
      "build/*",
      "app/stores/*",
      "app/styles/*",
      "app/services/shell/*",
      "app/configure/view.conf",
      "app/views/*",
      "app/App.js",
      "app/main.js",
      "app/index.js",
      "electron-builder.yml"
    ],
    "delay": "1000"
  },
  "dependencies": {
    "semantic-ui-css": "^2.4.0",
    "semantic-ui-react": "^0.82.5",
    "mobx": "^4.4.1",
    "mobx-react": "^5.2.8",
    "prop-types": "^15.6.2",
    "react": "^16.5.1",
    "react-dom": "^16.5.1",
    "react-hot-loader": "^4.3.8",
    "react-router": "^4.3.1",
    "react-router-dom": "^4.3.1",
    "history": "^4.7.2"
  },
  "devDependencies": {
    "babel-core": "^6.26.3",
    "babel-eslint": "^10.0.1",
    "babel-loader": "^7.1.5",
    "babel-plugin-transform-decorators-legacy": "^1.3.5",
    "babel-preset-env": "^1.7.0",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-react": "^6.24.1",
    "babel-preset-stage-0": "^6.24.1",
    "clean-webpack-plugin": "^0.1.19",
    "concurrently": "^3.6.1",
    "cross-env": "^5.2.0",
    "css-loader": "^0.28.11",
    "electron": "^2.0.9",
    "electron-builder": "^20.28.4",
    "eslint": "^5.6.1",
    "eslint-config-airbnb": "^17.1.0",
    "eslint-plugin-import": "^2.14.0",
    "eslint-plugin-jsx-a11y": "^6.1.2",
    "eslint-plugin-react": "^7.11.1",
    "extract-text-webpack-plugin": "^4.0.0-beta.0",
    "file-loader": "^2.0.0",
    "html-loader": "^0.5.5",
    "html-webpack-plugin": "^3.2.0",
    "node-sass": "^4.9.4",
    "nodemon": "^1.18.4",
    "sass-loader": "^7.1.0",
    "source-map-support": "^0.5.9",
    "style-loader": "^0.21.0",
    "url-loader": "^1.1.2",
    "webpack": "^4.19.0",
    "webpack-cli": "^2.1.5",
    "webpack-dev-server": "^3.1.8"
  }
}

引入Webpack4.0前端打包工具


webpack開(kāi)發(fā)環(huán)境配置文件
const path = require('path');
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');

// 拆分樣式文件
const extractSass = new ExtractTextPlugin({
  filename: 'style.scss.css',
});

const extractCss = new ExtractTextPlugin({
  filename: 'style.css',
});

module.exports = {
  devtool: 'source-map',
  entry: [
    'react-hot-loader/patch',
    'webpack-dev-server/client?http://localhost:3000',
    'webpack/hot/only-dev-server',
    './app/index',
  ],
  mode: 'development',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
    publicPath: '/',
  },
  resolve: {
    alias: {
      resources: path.resolve(__dirname, 'resources'),
      app: path.resolve(__dirname, 'app'),
    },
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: ['babel-loader'],
      },
      {
        test: /\.css$/,
        use: extractCss.extract({
          fallback: 'style-loader',
          use: 'css-loader',
          publicPath: '/',
        }),
      },
      {
        test: /\.scss$/,
        use: extractSass.extract({
          use: [{
            loader: 'css-loader',
          }, {
            loader: 'sass-loader',
          }],
          fallback: 'style-loader', // 在開(kāi)發(fā)環(huán)境使用 style-loader
          publicPath: '/',
        }),
      },
      {
        test: /\.html$/,
        use: {
          loader: 'html-loader',
        },
      },
      {
        test: /\.(png|jpg|gif|svg|ico|woff|eot|ttf|woff2)$/,
        use: [
          {
            loader: 'file-loader',
            options: {
              name: '[path][name].[ext]',
            },
          },
        ],
      },
    ],
  },

  plugins: [
    extractSass,
    extractCss,
    new webpack.HotModuleReplacementPlugin(),
    new CleanWebpackPlugin(['dist']),
    new webpack.NamedModulesPlugin(),
    new webpack.NoEmitOnErrorsPlugin(),
  ],

  devServer: {
    host: 'localhost',
    port: 3000,
    historyApiFallback: true,
    hot: true,
  },
  target: 'electron-renderer',
};

Electron基本原理和代碼熱更新


Electron 運(yùn)行 package.json 的 main 腳本的進(jìn)程被稱(chēng)為主進(jìn)程。 在主進(jìn)程中運(yùn)行的腳本通過(guò)創(chuàng)建web頁(yè)面來(lái)展示用戶(hù)界面。 一個(gè) Electron 應(yīng)用總是有且只有一個(gè)主進(jìn)程。
由于 Electron 使用了 Chromium 來(lái)展示 web 頁(yè)面,所以 Chromium 的多進(jìn)程架構(gòu)也被使用到。 每個(gè) Electron 中的 web 頁(yè)面運(yùn)行在它自己的渲染進(jìn)程中。
在普通的瀏覽器中,web頁(yè)面通常在一個(gè)沙盒環(huán)境中運(yùn)行,不被允許去接觸原生的資源。 然而 Electron 的用戶(hù)在 Node.js 的 API 支持下可以在頁(yè)面中和操作系統(tǒng)進(jìn)行一些底層交互。
進(jìn)程使用 BrowserWindow 實(shí)例創(chuàng)建頁(yè)面。 每個(gè) BrowserWindow 實(shí)例都在自己的渲染進(jìn)程里運(yùn)行頁(yè)面。 當(dāng)一個(gè) BrowserWindow 實(shí)例被銷(xiāo)毀后,相應(yīng)的渲染進(jìn)程也會(huì)被終止。
主進(jìn)程管理所有的web頁(yè)面和它們對(duì)應(yīng)的渲染進(jìn)程。 每個(gè)渲染進(jìn)程都是獨(dú)立的,它只關(guān)心它所運(yùn)行的 web 頁(yè)面。
在頁(yè)面中調(diào)用與 GUI 相關(guān)的原生 API 是不被允許的,因?yàn)樵?web 頁(yè)面里操作原生的 GUI 資源是非常危險(xiǎn)的,而且容易造成資源泄露。 如果你想在 web 頁(yè)面里使用 GUI 操作,其對(duì)應(yīng)的渲染進(jìn)程必須與主進(jìn)程進(jìn)行通訊,請(qǐng)求主進(jìn)程進(jìn)行相關(guān)的 GUI 操作。

創(chuàng)建主進(jìn)程

在index.js文件中我們引入electron和所有的自定義模塊文件,并根據(jù)開(kāi)發(fā)環(huán)境或是生產(chǎn)環(huán)境來(lái)進(jìn)行主進(jìn)程窗口加載,開(kāi)發(fā)環(huán)境下使用http協(xié)議加載由webpack-dev-server啟動(dòng)的http服務(wù),生產(chǎn)環(huán)境下使用file協(xié)議加載本地由webpack打包好的前端bundle.js文件,所以開(kāi)發(fā)環(huán)境下npm start指令其實(shí)主要是執(zhí)行了兩步操作,一是啟動(dòng)webpack-dev-server,此時(shí)已經(jīng)可以通過(guò)外部瀏覽器訪(fǎng)問(wèn)到localhost:3000的http服務(wù),只不過(guò)我們實(shí)際是用electron之中的chromium瀏覽器來(lái)加載的,它與node.js主進(jìn)程共享同一個(gè)chrome v8引擎,所以理論上,在頁(yè)面加載后,你同樣可以在渲染進(jìn)程中使用node.js API,比如用使用fs模塊訪(fǎng)問(wèn)文件系統(tǒng)。

主進(jìn)程代碼熱更新

我用了nodemon工具實(shí)現(xiàn)了主進(jìn)程代碼熱更新,如果不用nodemon工具那么 npm start-electron命令實(shí)際是執(zhí)行cross-env NODE_ENV=development electron index,就是簡(jiǎn)單的用electron啟動(dòng)主進(jìn)程文件,使用nodemon之后npm start-electron實(shí)際上是執(zhí)行nodemon --exec 'cross-env NODE_ENV=development electron index',最后在package.json文件中增加一個(gè)nodemonConfig字段用于指定哪些文件需要納入nodemon監(jiān)聽(tīng)即可。

=> package.json中定義的啟動(dòng)腳本:

  "scripts": {
    "start": "concurrently \"npm run start-dev\" \"npm run start-electron\"",
    "start-dev": "cross-env NODE_ENV=development webpack-dev-server",
    "start-electron": "nodemon --exec 'cross-env NODE_ENV=development electron index'",
    "build": "npm run dist && npm run build-all",
    "dist": "cross-env NODE_ENV=production webpack  --config webpack.production.config.js",
    "build-all": "build -lmw"
  },

=> package.json中nodemonConfig字段

"nodemonConfig": {
    "ignore": [
      "resources/*",
      "node_modules/*",
      "dist/*",
      "app/stores/*",
      "app/styles/*",
      "app/services/shell/*",
      "app/configure/view.conf",
      "app/views/*",
      "app/App.js",
      "app/main.js",
      "app/index.js"
    ],
    "delay": "1000"
  },

=> 項(xiàng)目啟動(dòng)文件index.js:

...
// 根據(jù)運(yùn)行環(huán)境加載窗口 //
function loadWindow(window, env) {
  if (env === 'development') {
    // wait for webpack-dev-server start
    setTimeout(() => {
      window.loadURL(url.format({
        pathname: 'localhost:3000',
        protocol: 'http:',
        slashes: true,
      }));
      // window.webContents.openDevTools();
    }, 1e3);
  } else {
    window.loadURL(url.format({
      pathname: path.join(path.resolve(__dirname, './dist'), 'index.html'),
      protocol: 'file:',
      slashes: true,
    }));
  }
}

/* ------------------- main window ------------------- */

function createWindow() {
  const { width, height } = getAppConf();
  win = new BrowserWindow({
    width,
    height,
    title: 'electronux',
    autoHideMenuBar: true,
  });

  win.on('resize', () => {
    const [_width, _height] = win.getContentSize();
    viewConf.set({
      width: _width,
      height: _height,
    });
  });

  loadWindow(win, nodeEnv);
}

/* ------------------- electron event ------------------- */

app.on('ready', () => {
  if (nodeEnv === 'development') {
    sourceMapSupport.install();
  }
  createWindow();
});

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

app.on('will-quit', () => {
  viewConf.write().then(() => 0, (err) => {
    console.error(err);
    throw new Error('App quit: view-conf write error !');
  });
});

app.on('activate', () => {
  if (win === null) {
    createWindow();
  }
});

前端界面React + Mobx 代碼結(jié)構(gòu)和熱更新


代碼結(jié)構(gòu)
  1. App.js前端入口文件
    入口文件基本是整個(gè)前端應(yīng)用的關(guān)鍵點(diǎn),我們使用mobx-react包提供的Provider組件加載整個(gè)應(yīng)用,并把各個(gè)應(yīng)用模塊(按功能劃分)的mobx store示例作為props屬性傳入Provider,在各個(gè)組建中使用修飾器@inject就能直接使用store實(shí)例了,頁(yè)面層次比較多的話(huà)最好使用React Router進(jìn)行路由管理,值得注意的是React Router V4版本跟之前版本的理念和使用方式有很大區(qū)別,可以去官網(wǎng)查閱相關(guān)文檔react-router4
/* ------------------- export global history ------------------- */
export const history = createHistory();

const stores = {
  install: new InstallState(),
  startup: new StartupState(),
  info: new InfoState(),
  clean: new CleanState(),
  pub: new PublicState(),
};

function App() {
  return (
    <Provider {...stores}>
      <Router history={history}>
        <Route path="/" component={HomePage} />
      </Router>
    </Provider>
  );
}

/* ------------------- export provider ------------------- */
export default App;

  1. mobx store 存儲(chǔ)
    這是項(xiàng)目其中一個(gè)系統(tǒng)清理模塊的mobx store,在store中被mobx監(jiān)聽(tīng)的屬性最好結(jié)構(gòu)層次簡(jiǎn)單、只有單一的功能劃分,不要把一個(gè)屬性對(duì)象的嵌套寫(xiě)得太深。開(kāi)發(fā)時(shí)我們把UI界面的數(shù)據(jù)抽象成store中的數(shù)據(jù)時(shí)可能會(huì)下意識(shí)地根據(jù)頁(yè)面顯示狀態(tài)而把單個(gè)屬性對(duì)象寫(xiě)得過(guò)于復(fù)雜,但其實(shí)頁(yè)面顯示狀態(tài)只是邏輯的數(shù)據(jù)結(jié)構(gòu),我們?cè)趕tore中存儲(chǔ)的時(shí)候應(yīng)該盡量將這種邏輯數(shù)據(jù)結(jié)構(gòu)翻譯成扁平化的數(shù)據(jù)結(jié)構(gòu),然后再在各個(gè)屬性對(duì)象之間建立映射關(guān)系。
    并且使用了mobx之后請(qǐng)盡量依賴(lài)mobx的數(shù)據(jù)引用監(jiān)聽(tīng)自動(dòng)更新特性,多寫(xiě)computed、autorun來(lái)自動(dòng)生成數(shù)據(jù),使用action修飾一些需要更改store屬性的方法。
class Clean {
  constructor() { }
  /* ------------------- observable ------------------- */

  // 所有檢查項(xiàng)目 //
  @observable items = {
    appCache: false,
    appLog: false,
    trash: false,
    packageCache: false,
  };

  // 主界面加載 //
  @observable loadingMain = false;

  // 清理路徑 //
  cleanPaths = {
    appCache: [`/home/${this.userinfo.username}/.cache`],
    appLog: ['/var/log/'],
    trash: [`/home/${this.userinfo.username}/.local/share/Trash/files`],
    packageCache: ['/var/cache/pacman/pkg'],
  }

  // 路徑模塊映射 //
  @observable cleanPathMap = {
    appCache: [], // '/var/log/pacman.log'
    appLog: [],
    trash: [],
    packageCache: [],
  }

  // 清理內(nèi)容 //
  @observable cleanContents = observable.map({})

  // 清理大小 //
  cleanSizes = {
    // '/var/log//pacman.log': '10kb',
  }

  // ---- 清理選項(xiàng)細(xì)節(jié)-數(shù)據(jù)對(duì)象邏輯樹(shù)結(jié)構(gòu) ---- //
  // @observable cleanDetails = {
  //   appCache: {
  //     url: [`/home/${this.userinfo.username}/.cache`], // 指定掃描路徑多個(gè)
  //     contents: { // 絕對(duì)路徑
  //       // '/var/cache/pacman/pkg/zsh-5.6.2-1-x86_64.pkg.tar.xz': false,
  //     },
  //     size: {
  //       // '/var/cache/pacman/pkg/zsh-5.6.2-1-x86_64.pkg.tar.xz': '10kb',
  //     },
  //   },
  //   appLog: {
  //     url: ['/var/log/'],
  //     contents: {
  //       // '/var/log//pacman.log': false,
  //     },
  //     size: {
  //       // '/var/log//pacman.log': '10kb',
  //     },
  //   }
  // }

  /* ------------------- static ------------------- */


  /* ------------------- computed ------------------- */

  // 獲取所有被選中的detail item //
  @computed get allCheckedDetail() {
    const a = [];
    this.cleanContents.forEach((v, k) => {
      if (v) a.push(k);
    });
    return a;
  }

  // 清理路徑詳細(xì)信息 //
  @computed get cleanDetail() {
    const result = [];
    Object.keys(this.cleanPathMap).forEach((item) => {
      if (this.items[item]) {
        const oneResult = {
          label: item,
          contents: [],
        };
        this.cleanPathMap[item].forEach((it) => {
          oneResult.contents.push({
            content: it,
            size: this.cleanSizes[it] || 0,
          });
        });

        result.push(oneResult);
      }
    });

    return result;
  }
}

export default Clean;

  1. 頁(yè)面組件劃分
    在views目錄下創(chuàng)建的各個(gè)目錄都是一個(gè)單獨(dú)的組件目錄,組件目錄下有一個(gè)組件入口文件和css樣式表文件以及其它子組件,入口文件載入css文件和子組件,使用@inject修飾器后各個(gè)組件都可以獨(dú)立訪(fǎng)問(wèn)mobx store實(shí)例,不必在父和子組件之間通過(guò)props進(jìn)行逐級(jí)參數(shù)傳遞,但是如果一個(gè)子組件依賴(lài)父組件來(lái)加工原始數(shù)據(jù)的話(huà)也可以使用props傳遞參數(shù)。
    使用了mobx之后,并不是說(shuō)每個(gè)頁(yè)面需要使用的數(shù)據(jù)都有必要納入mobx store的管理,在我的代碼中只是把關(guān)鍵性數(shù)據(jù)以及關(guān)鍵性數(shù)據(jù)加工方法存入了store中,每個(gè)組件拿到store傳遞下來(lái)的數(shù)據(jù)后一些頁(yè)面狀態(tài)可能需要依賴(lài)組件各自的數(shù)據(jù)處理函數(shù)進(jìn)行數(shù)據(jù)二次加工,我覺(jué)得這樣應(yīng)該會(huì)減輕store實(shí)例的負(fù)載壓力,非絕對(duì)中心化。比如在一個(gè)列表菜單組件中,這個(gè)組件的列表數(shù)據(jù)可以切換顯示和隱藏,但是控制這個(gè)列表顯示/隱藏的參數(shù)狀態(tài)visible沒(méi)有必要納入store實(shí)例管理,相對(duì)的管理這個(gè)列表組件的store實(shí)例只是存儲(chǔ)了列表數(shù)據(jù)的數(shù)組,以及一些必要的數(shù)據(jù)加工方法。

  2. 渲染進(jìn)程和主進(jìn)程ipc通信的問(wèn)題
    頁(yè)面的每個(gè)渲染進(jìn)程(ipcRender),雖然說(shuō)可以直接使用node.js原生模塊和api,但是不建議在渲染進(jìn)程中過(guò)度使用原生模塊,一是因?yàn)橐恍﹏ode.js原生模塊并沒(méi)有考慮到進(jìn)程安全的問(wèn)題,第二個(gè)原因是渲染進(jìn)程應(yīng)該專(zhuān)注處理頁(yè)面交互和數(shù)據(jù)處理問(wèn)題,劃清代碼的功能區(qū)域,把和系統(tǒng)交互的問(wèn)題交由主進(jìn)程(ipcMain)處理,把網(wǎng)絡(luò)數(shù)據(jù)請(qǐng)求也交由各自的service服務(wù),減少不必要的模塊和數(shù)據(jù)耦合。渲染進(jìn)程通過(guò)ipc通信向主進(jìn)程發(fā)送處理請(qǐng)求,主進(jìn)程和service負(fù)責(zé)原始數(shù)據(jù)的獲取和網(wǎng)絡(luò)數(shù)據(jù)的傳輸,最后主進(jìn)程通過(guò)ipc通信向?qū)?yīng)的渲染進(jìn)程返回處理結(jié)果,service拿到的網(wǎng)絡(luò)數(shù)據(jù)也通過(guò)回調(diào)事件發(fā)送給渲染進(jìn)程。項(xiàng)目中我把mobx store作為和主進(jìn)程通信的橋梁,mobx store向主進(jìn)程發(fā)送信號(hào),同時(shí)也在接收到主進(jìn)程的ipc通信事件后再把主進(jìn)程發(fā)回來(lái)的數(shù)據(jù)更新到各個(gè)observer??傊鬟M(jìn)程和service服務(wù)負(fù)責(zé)系統(tǒng)交互、原始數(shù)據(jù)獲取和傳輸,渲染進(jìn)程mobx store負(fù)責(zé)響應(yīng)信號(hào)和事件進(jìn)行業(yè)務(wù)數(shù)據(jù)更新,各個(gè)view子組件只負(fù)責(zé)頁(yè)面渲染和用戶(hù)交互。

前端代碼熱更新
  1. webpack.config.js中啟動(dòng)webpack-dev-server的熱更新功能
devServer: {
    host: 'localhost',
    port: 3000,
    historyApiFallback: true,
    hot: true,
  },
  1. 使用react-hot-loader的AppContainer組件
import { AppContainer } from 'react-hot-loader';

import 'semantic-ui-css/semantic.min.css';
import './styles/public.css';

import App from './App';

render(
  <AppContainer>
    <App />
  </AppContainer>,
  document.getElementById('root')
);

Linux桌面客戶(hù)端開(kāi)發(fā)遇到的問(wèn)題


使用node.js子進(jìn)程child_process執(zhí)行shell腳本時(shí)無(wú)法取得系統(tǒng)root權(quán)限

項(xiàng)目中有的腳本需要使用root權(quán)限,比如安裝和卸載軟件、掃描系統(tǒng)關(guān)鍵路徑,node.js里執(zhí)行shell腳本可以使用child_process模塊(node.js子進(jìn)程),child_process有幾個(gè)方法,spawnexec、execFilefork,它們都能創(chuàng)建子進(jìn)程以執(zhí)行指定文件或命令,具體的使用方法見(jiàn)Node API,如果我們的腳本或指令需要使用root權(quán)限那可就麻煩了,桌面應(yīng)用又不是終端,不可能用著用著讓用戶(hù)去終端輸入密碼吧,況且只是在開(kāi)發(fā)環(huán)境下能看到終端輸出,應(yīng)用打包安裝運(yùn)行起來(lái)后就是一個(gè)獨(dú)立的應(yīng)用程序了,根本沒(méi)法輸入終端密碼,仔細(xì)查閱了Electron官網(wǎng)API發(fā)現(xiàn)electron官方并沒(méi)有集成一個(gè)什么系統(tǒng)權(quán)限調(diào)用窗口之類(lèi)的組件。沒(méi)辦法了,這種情況下手動(dòng)寫(xiě)出了兩種方法:

  1. 調(diào)用獲取系統(tǒng)權(quán)限的系統(tǒng)自帶組件來(lái)執(zhí)行自定義命令和腳本
  2. 封裝一個(gè)彈窗組件來(lái)獲取用戶(hù)首次輸入的密碼,然后手動(dòng)把密碼記錄到文件中,應(yīng)用啟動(dòng)的時(shí)候從文件中讀出密碼,在使用child_process創(chuàng)建子進(jìn)程的時(shí)候再監(jiān)聽(tīng)子進(jìn)程的輸出事件和錯(cuò)誤事件,然后把讀取到的保存在內(nèi)存中的密碼以輸入流(input stream)的形式發(fā)送給child_process創(chuàng)建的子進(jìn)程,子進(jìn)程讀取到輸入流傳入的密碼后就能繼續(xù)執(zhí)行了。
electron_pssword.png
install_permission.png

具體代碼見(jiàn)github/nojsja/electronux/app/utils/sudo-prompt.js

感謝閱讀,文章中出現(xiàn)的錯(cuò)誤之處還請(qǐng)多原諒~

未完待續(xù).....
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容