Electron 從零到一搭建--編寫基礎框架(Electron + React + Mobx + Webpack4 + Typescript)

介紹

這篇文章,主要的是講述如何搭建Electron項目,這裏會用到上篇文章Webpack4+node+typescript+hotReload 搭建的框架,這裏,你們能夠先clone下來:javascript

git clone git@github.com:spcBackToLife/node-webpack4-ts-demo.git
複製代碼

基礎框架的搭建主要有如下部份內容,基本每部分都是單獨的一篇文章:css

  • 基礎node環境搭建: webpack4 + node + typescript
  • Electron集成: 單窗口 + 主進程、渲染進程在dev、prod環境的webpack處理。
  • React 集成
  • Mobx 集成 (因爲redux比較重,dva比較麻煩,最後選擇Mobx作react狀態管理會更合適)
  • Electron集成: 多窗口 + 窗口渲染進程webpack處理。
  • 數據緩存、存儲服務管理
  • 應用基礎功能服務設計:錄屏、截屏
  • 應用集成三方環境設計:Python環境
  • 應用webview注入js設計
  • 運行日誌集成

Electron 集成 - 單窗口 + 主進程、渲染進程在dev、prod環境的webpack處理

關於Electron基礎集成請看:Electron 入門簡介,此處再也不累贅,照作就行。html

  1. 添加Electron依賴java

    ELECTRON_MIRROR=https://npm.taobao.org/mirrors/electron/ yarn add electron@6.0.0 -D
    複製代碼
  2. src下新建:main-processrender-process文件夾,將main.ts放置到main-process中。node

    • main-process #主進程文件夾
    • render-process #渲染進程文件夾

關於主進程、渲染進程相關內容,請看文章:Electron-主進程、渲染進程 強烈建議先讀完這個概念再來看這篇文章下面內容,除非你很是熟悉Electron,已經瞭解主進程、渲染進程。react

  1. src下新建:index.htmlwebpack

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8">
            <title>electron-sprout</title>
            <script> ( function() { if (!process.env.HOT) { // production環境下,加載打包後的style.css,css處理在webpack裏。 const link = document.createElement('link'); link.ref = 'stylesheet'; link.href = '../dist/style.css'; } } ) </script>
        </head>
        <body>
            <div id="root">
              先寫個hellowold在這,集成react的時候清掉
            </div>
        </body>
        <script> { const scripts = []; const port = process.env.PORT || 1212; // 開發環境下,加載webpack-dev-server下的js。 // 生產環境下,加載打包後的js scripts.push( (process.env.HOT) ? 'http://localhost:' + port + '/dist/renderer.dev.js' : '../dist/renderer.prod.js' ); // 將須要加載的js寫入頁面,進行加載。 document.write( scripts .map(script => `<script defer src="${script}"><\/script>`) .join('') ); } </script>
    </html>
    複製代碼
  2. main-process目錄下新建main-window.ts:git

    import { BrowserWindow } from 'electron';
    
    export const createWindow = () => {
      const win = new BrowserWindow({
        show: true,
        width: 890,
        height: 556,
        webgl: true,
        frame: true,
        transparent: false,
        webPreferences: {
          webSecurity: true,
          allowRunningInsecureContent: false,
          nativeWindowOpen: false,
          nodeIntegration: true,
        },
      });
      win.webContents.openDevTools();
      win.loadURL(`file://${__dirname}/../index.html`);
    };
    
    複製代碼

如上代碼所見:咱們使用electron提供的BrowserWindow打開了一個系統窗口,這個窗口相似於瀏覽器的一個tab,能夠採用loadURL加載一個網頁或者本地文件index.html。api具體信息能夠查看Electron 官網github

  1. 將main.ts的測試代碼都刪除,換上以下:web

    import { app } from 'electron';
    import { createWindow } from './main-window';
    
    app.on('ready', async () => {
      createWindow(); // 建立一個系統應用窗口
    });
    
    app.on('window-all-closed', () => app.quit()); // 全部窗口關閉的時候退出應用
    
    複製代碼
  2. 配置webpack處理主進程、渲染進程js。

關於主進程、渲染進程相關內容,請看文章:Electron-主進程、渲染進程

首先咱來配置個基礎的webpack,用於處理,ts\js\css的: webpack.config.base.js,關於webpack相關內容,本身去學哈,沒寫文章,不學就照我這個配也行,我配的也不專業,哈哈,能用。

新建文件夾config/webpack,在下面新建文件:webpack.config.base.js

const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const webpack = require('webpack');

const isProd = process.env.NODE_ENV === 'production';
const cssLoader = {
  loader: 'css-loader',
  options: {
    modules: true,
    importLoaders: 1,
    localIdentName: '[local]__[hash:6]',
  },
};
// const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
  target: 'electron-main', // 會在主進程和渲染進程文章中說起這個是什麼,請看第6點開頭的那個文章指向。
  resolve: {
    // changed from extensions: [".js", ".jsx"]
    extensions: ['.ts', '.tsx', '.js', '.jsx'], // 須要處理的文件類型
  },
  module: {
    rules: [
      {
        test: /\.scss$/, // css loader配置
        use: [
          isProd ? MiniCssExtractPlugin.loader : 'style-loader',
          cssLoader,
          'sass-loader',
        ],
      },
      {
        test: /\.tsx?$/, //tsx處理
        exclude: /node_modules/,
        use: [

          {
            loader: 'babel-loader',
            options: {
              cacheDirectory: true,
            },
          },
          // {
          // loader: 'awesome-typescript-loader',
          // },
        ],
      },
      // Common Image Formats
      {
        test: /\.(?:ico|gif|png|jpg|jpeg|webp)$/, // 圖片路徑處理
        use: 'url-loader',
      },
      // addition - add source-map support
      { enforce: 'pre', test: /\.js$/, loader: 'source-map-loader' }, // 報錯的source-map
    ],
  },
  node: {
    __dirname: false, // 處理打包後dirname filename變量路徑不對問題
    __filename: false,
  },
  devtool: 'source-map',
  plugins: [
    new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /zh-cn/), // 使用moment組件的話,用這個減小moment庫體積,這個做爲基礎設施的一部分吧
    new webpack.NamedModulesPlugin(), // 用於啓動HMR(hot module reload)時能夠顯示模塊的相對路徑
  ],
};
複製代碼

再來處理主進程js。新建文件config/webpack/webpack.config.main.prod.babel.js(關於爲何要區分主進程、渲染進程分別處理js, 第6點提到的那個文章裏有哈。),因爲主進程開發環境無需作處理,直接能夠執行,因此只有production的環境下須要處理js。

const path = require('path');
const webpack = require('webpack');
const merge = require('webpack-merge');
const TerserPlugin = require('terser-webpack-plugin');
const baseConfig = require('./webpack.config.base');

module.exports = merge.smart(baseConfig, {
  devtool: 'source-map',
  mode: 'production',
  entry: './src/main.ts',
  output: {
    path: path.join(__dirname, '../../'),
    filename: './dist/main.prod.js',
  },
  optimization: {
    minimizer: process.env.E2E_BUILD
      ? []
      : [
        new TerserPlugin({  // 壓縮Javascript
          parallel: true,
          sourceMap: true,
          cache: true,
        }),
      ],
  },
  plugins: [
    new webpack.EnvironmentPlugin({
      NODE_ENV: 'production',
    }),
  ],
});

複製代碼

而後,咱們再來處理渲染進程的js,在開發環境下,爲了改了代碼可以自動刷新頁面,熱更新,咱們用了webpack-dev-server作處理,所以處理渲染進程js的時候會分開發環境和生成環境,有不一樣的配置文件。首先來生成開發環境的吧。 新建文件config/webpack/webpack.config.renderer.dev.babel.js

const path = require('path');
const merge = require('webpack-merge');
const { spawn } = require('child_process');
const webpack = require('webpack');
const baseConfig = require('./webpack.config.base');

const port = process.env.PORT || 1212;
const publicPath = `http://localhost:${port}/dist`;
module.exports = merge.smart(baseConfig, {
  devtool: 'inline-source-map',
  mode: 'development',
  target: 'electron-renderer',
  entry: {
    renderer: [
      require.resolve('../../src/render-process/index.tsx'),
      // 'react-hot-loader/patch', // 集成react的時候再開
      `webpack-dev-server/client?http://localhost:${port}/`,
      'webpack/hot/only-dev-server',
    ],
  },
  output: {
    publicPath: `http://localhost:${port}/dist`,
    filename: '[name].dev.js',
  },
  plugins: [
    new webpack.NamedModulesPlugin(), // 用於啓動HMR時能夠顯示模塊的相對路徑
    new webpack.HotModuleReplacementPlugin(), // hot module replacement 啓動模塊熱替換的插件
    new webpack.NoEmitOnErrorsPlugin(),
    new webpack.EnvironmentPlugin({
      NODE_ENV: 'development',
    }),
    new webpack.LoaderOptionsPlugin({
      debug: true,
    }),
  ],
  devServer: {
    port,
    publicPath,
    compress: true,
    noInfo: true,
    stats: 'errors-only',
    inline: true,
    lazy: false,
    hot: true,
    headers: { 'Access-Control-Allow-Origin': '*' },
    contentBase: path.join(__dirname, 'dist'),
    watchOptions: {
      aggregateTimeout: 300,
      ignored: /node_modules/,
      poll: 100,
    },
    historyApiFallback: {
      verbose: true,
      disableDotRule: false,
    },
    before() {
      if (process.env.START_HOT) {
        console.log('Starting Main Process...');
        spawn('npm', ['run', 'start-main-dev'], { // 在運行這個server以前會運行package.json裏的`start-main-dev`指令,文章下面會配置。
          shell: true,
          env: process.env,
          stdio: 'inherit',
        })
          .on('close', code => process.exit(code))
          .on('error', spawnError => console.error(spawnError));
      }
    },
  },
});

複製代碼

最後貼一下渲染進程的production環境的webpack吧: 新建文件config/webpack/webpack.config.renderer.prod.babel.js

const path = require('path');
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const merge = require('webpack-merge');
const TerserPlugin = require('terser-webpack-plugin');
const baseConfig = require('./webpack.config.base');

module.exports = merge.smart(baseConfig, {
  devtool: 'source-map',
  mode: 'production',
  target: 'electron-renderer',
  entry: {
    renderer: require.resolve('../../src/render-process/index.tsx'),
  },
  output: {
    path: path.join(__dirname, '..', '../dist'),
    publicPath: './dist/',
    filename: '[name].prod.js',
  },
  optimization: {
    minimizer: process.env.E2E_BUILD
      ? []
      : [
        new TerserPlugin({
          parallel: true,
          sourceMap: true,
          cache: true,
        }),
        new OptimizeCSSAssetsPlugin({
          cssProcessorOptions: {
            map: {
              inline: false,
              annotation: true,
            },
          },
        }),
      ],
  },
  plugins: [
    new webpack.EnvironmentPlugin({
      NODE_ENV: 'production',
    }),
    new MiniCssExtractPlugin({
      filename: 'style.css',
    }),
    new BundleAnalyzerPlugin({
      analyzerMode: process.env.OPEN_ANALYZER === 'true' ? 'server' : 'disabled',
      openAnalyzer: process.env.OPEN_ANALYZER === 'true',
    }),
  ],
});

複製代碼
  1. 還記得咱再運行那個demo的時候使用了ts-node./src/main-process/main.ts運行可讓咱們直接用node環境ts。但是目前尚未ts-electron讓咱們直接用electron運行ts。所以咱們須要用babel把咱們的運行入口處理下。咱們只須要config目錄下新建:babel-entry-config.js
module.exports = {
  extensions: ['.js', '.ts', '.tsx'],
  presets: [
    [
      '@babel/preset-env',
      {
        targets: { electron: require('electron/package.json').version },
        useBuiltIns: 'usage',
        modules: 'commonjs',
      },
    ],
    '@babel/preset-typescript',
  ],
  plugins: [
    ['@babel/plugin-proposal-decorators', { legacy: true }],
  ],
};

複製代碼

而後src下新建文件entry.dev.js

require('@babel/register')(require('../config/babel-entry-config'));
require('./main-process/main');
複製代碼

最後咱修改下package.json的運行命令:

...
  "start-main-dev": "cross-env HOT=1 NODE_ENV=development electron -r @babel/register ./src/entry.dev.js",
  "dev": "cross-env START_HOT=1 webpack-dev-server --config ./config/webpack/webpack.config.renderer.dev.babel.js"
  ...
複製代碼
  1. 咱還差最後一步就能夠運行了,在render-process下新建一個index.tsx。關於render-process新建的tsx是什麼意思,請看上面說到的主進程和渲染進程文章:Electron-主進程、渲染進程
console.log('hello 秋澤雨');
複製代碼

搞了半天,我們終於能夠yarn dev 啓動一下了,若是猜的沒錯,咱確定運行散架,由於還有些上面用到的依賴沒裝,哈哈:

yarn add cross-env webpack-dev-server webpack-merge mini-css-extract-plugin @babel/register @babel/plugin-proposal-decorators @babel/preset-typescript -D
複製代碼

最後終於能夠看到窗口以及窗口加載的html,以及html加載的js輸出的console.log: 'hello 秋澤雨'。

總體從yarn dev以後的運行流程全解圖以下:

''

此篇demo: github.com/spcBackToLi…

下一篇會講:react+mobx集成

有問題歡迎加羣溝通哦:

''
相關文章
相關標籤/搜索