likes
comments
collection
share

使用umi+electron实现web端和桌面端同步开发

作者站长头像
站长
· 阅读数 21

首先确定我们的目的是实现以下内容

在umi项目中,以安装electron的形式,实现web端及桌面端同步开发,实现一套代码多端使用的目的。 需实现支持打包成win64位及win32位及mac的安装包。

确定目的后我们开撸代码

一 使用umi初始化项目

1. 工程创建

1. 初始化项目

 yarn create @umijs/umi-app

2. 安装依赖

 yarn

3. 启动项目

 yarn start:dev

2.配置项目

1. 配置多环境

使用umi+electron实现web端和桌面端同步开发

config.local.ts // 本地临时配置文件 (后期看是否需要)

config.dev.ts     //  开发环境配置文件

config.prod.ts    // 正式环境配置文件 (后期看是否需要)

config.ts         // 项目公共配置文件

// config.ts 
import { defineConfig } from 'umi';
import routes from './routes';
import proxy from './proxy';

const { REACT_APP_ENV } = process.env;

console.log(REACT_APP_ENV);

export default defineConfig({
  routes,
  proxy: proxy[REACT_APP_ENV || 'dev'],
  favicon: '/logo.svg',
  locale: { // 配置国际化
    default: 'zh-CN',
    antd: true,
    // default true, when it is true, will use `navigator.language` overwrite default
    baseNavigator: true,
    baseSeparator: '-',
  },
  history: {
    type: 'hash',
  },
  publicPath: './',
  outputPath: 'app/build', // 为了打包后electron可使用
});

2.路由配置

route.ts  // 路由文件 

const route = [
  {
    path: '/',
    redirect: '/login',
  },
  {
    name: '登录',
    path: '/login',
    component: '@/pages/Login/Login',
    icon: 'home',
  },
  {
    name: '主页',
    path: '/designTool',
    component: '@/pages/DesignTool/DesiginToolIndex/DesiginToolIndex',
    icon: 'home',
    // hideChildrenInMenu: true,
    // routes: [
    //   {
    //     name: '详情',
    //     path: '/designTool/detail',
    //     component: '@/pages/DesignTool/DesiginToolDetail/DesiginToolDetail',
    //   },
    //   {
    //     name: '编辑',
    //     path: '/designTool/edit',
    //     component: '@/pages/DesignTool/DesignToolEdit/DesignToolEdit',
    //   },
    //   {
    //     redirect: '/404',
    //   },
    // ],
  },
  {
    name: 'electron操作页',
    path: '/electronPage',
    component: '@/pages/ElectronPage/ElectronHandel',
  },
];

export default route;

3.配置代理

proxy.ts  // 代理文件

// 地址
const devUrl = 'http://192.168.1.1:8080'; 

export default {
  dev: {
    '/api/': {
      target: devUrl,
      changeOrigin: true,
      pathRewrite: { '^': '' },
    },
  },
};

4.封装请求

使用umi+electron实现web端和桌面端同步开发

5.统一处理请求

使用umi+electron实现web端和桌面端同步开发

6.umi项目目录

使用umi+electron实现web端和桌面端同步开发

二 接入electron

首先根目录下创建app文件夹,用于管理electron文件

使用umi+electron实现web端和桌面端同步开发

1.根目录下配置

1.安装electron依赖

yarn add electron -S

2.package.json下配置

  • scripts 下新增electron启动命令
"electron": "electron app/main.js", 
"electron-dev": "concurrently \"cross-env BROWSER=none yarn start:dev\" \"wait-on http://localhost:8001 && cross-env NODE_ENV=development electron app/main.js\"", 
"electron-start": "yarn build && cross-env NODE_ENV=production electron app/main.js"

其中 electron-dev 内的concurrently cross-env wait-on 需要使用yarn安装对应包,过程不再赘述。

  • 配置主文件入口

"main": "app/main.js",

以下是我的package.json 配置

// package.json
{
  "name": "electron_project",
  "version": "0.0.1",
  "description": "description",
  "author": "huangjinhui",
  "private": true,
  "main": "app/main.js",
  "scripts": {
    "build": "umi build",
    "start": "cross-env PORT=8000 umi dev",
    "start:dev": "cross-env REACT_APP_ENV=dev PORT=8001 UMI_ENV=dev umi dev",
    "start:prod": "cross-env REACT_APP_ENV=prod PORT=8002 UMI_ENV=prod umi dev",
    "build:dev": "cross-env UMI_ENV=dev umi build",
    "build:prod": "cross-env REACT_APP_ENV=prod UMI_ENV=prod umi build",
    "serve": "serve ./dist",
    "postinstall": "umi generate tmp",
    "prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'",
    "test": "umi-test",
    "test:coverage": "umi-test --coverage",
    "electron": "electron app/main.js",
    "electron-dev": "concurrently \"cross-env BROWSER=none yarn start:dev\" \"wait-on http://localhost:8001 && cross-env NODE_ENV=development electron app/main.js\"",
    "electron-start": "yarn build && cross-env NODE_ENV=production electron app/main.js"
  },
  "gitHooks": {
    "pre-commit": "lint-staged"
  },
  "lint-staged": {
    "*.{js,jsx,less,md,json}": [
      "prettier --write"
    ],
    "*.ts?(x)": [
      "prettier --parser=typescript --write"
    ]
  },
  "dependencies": {
    "@ant-design/pro-layout": "^6.5.0",
    "@monaco-editor/react": "^4.5.1",
    "@types/md5": "^2.3.2",
    "ahooks": "^3.7.7",
    "antd": "5.x",
    "lodash": "^4.17.21",
    "md5": "^2.3.0",
    "react": "17.x",
    "react-dom": "17.x",
    "umi": "^3.5.41"
  },
  "devDependencies": {
    "@types/lodash": "^4.14.195",
    "@types/react": "^17.0.0",
    "@types/react-dom": "^17.0.0",
    "@umijs/fabric": "^4.0.1",
    "@umijs/preset-react": "1.x",
    "@umijs/test": "^3.5.41",
    "concurrently": "^8.1.0",
    "cross-env": "^7.0.3",
    "electron": "^25.1.0",
    "lint-staged": "^10.0.7",
    "prettier": "^2.2.0",
    "typescript": "^4.1.2",
    "wait-on": "^7.0.1",
    "yorkie": "^2.0.0"
  }
}

2.app文件夹下配置

1.创建main.js主文件

// electron打包配置
const {
  app,
  BrowserWindow,
  globalShortcut,
  // dialog
} = require('electron');
const path = require('path');

const isPro = process.env.NODE_ENV !== 'development';
const remote = require('@electron/remote/main');
remote.initialize();

// window对象的全局引用
let mainWindow;

function createWindow() {
  mainWindow = new BrowserWindow({
    minWidth: 800, // 最小宽度
    minHeight: 600, // 最小高度
    width: 1000,
    heigth: 800,
    title: '标题',
    // autoHideMenuBar: true, // 关闭工具栏
    // frame: false, // 设置为false后为无边框窗口,即无法拖拽,拉伸窗体大小,没有菜单项
    icon: path.join(__dirname, './assets/logo.ico'),
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'), // 预加载文件
      // 是否启用Node integration
      nodeIntegration: true, // Electron 5.0.0 版本之后它将被默认false
      // 是否在独立 JavaScript 环境中运行 Electron API和指定的preload 脚本.默认为 true
      contextIsolation: false, // Electron 12 版本之后它将被默认true
    },
  });
  remote.enable(mainWindow.webContents);

  // 注册快捷键

  globalShortcut.register('CommandOrControl+M', () => {
    mainWindow.maximize();
  });

  globalShortcut.register('CommandOrControl+T', () => {
    mainWindow.unmaximize();
  });

  globalShortcut.register('CommandOrControl+H', () => {
    mainWindow.close();
  });
  // 引入目录
  require(path.join(__dirname, 'menu.js'));

  if (isPro) {
    // 生产环境
    mainWindow.loadFile(`${__dirname}/build/index.html`);
    // mainWindow.loadURL(`http://192.168.10.15:30102`);
  } else {
    mainWindow.loadURL('http://localhost:8001/');
    // 打开开发者工具
    mainWindow.webContents.openDevTools();
  }

  // 解决应用启动白屏问题
  mainWindow.on('ready-to-show', () => {
    mainWindow.show();
    mainWindow.focus();
  });

  // // 关闭窗口弹框确认
  // mainWindow.on("close", (e) => {
  //   e.preventDefault();
  //   dialog.showMessageBox(mainWindow, {
  //     type: "warning",
  //     title: "关闭",
  //     message: "是否要关闭窗口",
  //     buttons: ["取消", "确定"],
  //   }).then((index) => {
  //     if (index.response === 1) {
  //       app.exit();
  //     }
  //   })
  // })

  // 关闭时触发下列事件
  mainWindow.on('closed', () => {
    mainWindow = null;
  });
}
// 是否允许打开多个窗口
// const gotTheLock = app.requestSingleInstanceLock()
// if (!gotTheLock) {
//   // 检测到本次未取得锁,即有已存在的实例在运行,则本次启动立即退出,不重复启动。
//   app.quit()
// } else {
//   app.on('second-instance', (event, commandLine, workingDirectory) =>   {
//     // 监听到第二个实例被启动时,检测当前实例的主窗口,并显示出来取得焦点
//     if (mainWindow) {
//       if (mainWindow.isMinimized()) mainWindow.restore()
//       mainWindow.focus()
//     }
//   })
// }

app.on('ready', createWindow);

// 热加载
try {
  require('electron-reloader')(module, {});
} catch (_) {}

// 所有窗口关闭时退出应用
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

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

// app.on('before-quit', (event) => {
//   dialog.showOpenDialog({

//   })
//     // 询问用户是否退出
//   event.preventDefault() // 阻止本次退出
// })

2.创建目录文件menu.js

const { app, Menu } = require('electron');

const isMac = process.platform === 'darwin';

const template = [
  // { role: 'appMenu' }  // 如果是mac系统才有
  ...(isMac
    ? [
        {
          label: app.name,
          submenu: [
            {
              role: 'about',
            },
            {
              type: 'separator',
            },
            {
              role: 'services',
            },
            {
              type: 'separator',
            },
            {
              role: 'hide',
            },
            {
              role: 'hideOthers',
            },
            {
              role: 'unhide',
            },
            {
              type: 'separator',
            },
            {
              role: 'quit',
            },
          ],
        },
      ]
    : []),
  // { role: 'fileMenu' }
  {
    label: '文件',
    submenu: [
      {
        label: '新建',
        accelerator: 'CmdOrCtrl+N',
        click: () => {},
      },
      {
        label: '打开',
        accelerator: 'CmdOrCtrl+O',
        click: () => {},
      },
      {
        label: '保存',
        accelerator: 'CmdOrCtrl+S',
        click: () => {},
      },
      {
        type: 'separator',
      }, // 分割线
      ,
      isMac
        ? {
            role: 'close',
          }
        : {
            role: 'quit',
            label: '退出',
          },
    ],
  },
  // { role: 'editMenu' }
  {
    label: '编辑',
    submenu: [
      {
        role: 'undo',
        label: '撤消',
      },
      {
        role: 'redo',
        label: '恢复',
      },
      {
        type: 'separator',
      },
      {
        role: 'cut',
        label: '剪切',
      },
      {
        role: 'copy',
        label: '复制',
      },
      {
        role: 'paste',
        label: '粘贴',
      },
      ...(isMac
        ? [
            {
              role: 'pasteAndMatchStyle',
            },
            {
              role: 'delete',
            },
            {
              role: 'selectAll',
            },
            {
              type: 'separator',
            },
            {
              label: 'Speech',
              submenu: [
                {
                  role: 'startSpeaking',
                },
                {
                  role: 'stopSpeaking',
                },
              ],
            },
          ]
        : [
            {
              role: 'delete',
              label: '删除',
            },
            {
              type: 'separator',
            },
            {
              role: 'selectAll',
              label: '全选',
            },
          ]),
    ],
  },
  // { role: 'viewMenu' }
  {
    label: '查看',
    submenu: [
      {
        role: 'reload',
        label: '重新加载',
      },
      {
        role: 'forceReload',
        label: '强制重新加载',
      },
      {
        role: 'toggleDevTools',
        label: '切换开发工具栏',
      },
      {
        type: 'separator',
      },
      {
        role: 'resetZoom',
        label: '原始开发工具栏窗口大小',
      },
      {
        role: 'zoomIn',
        label: '放大开发工具栏窗口',
      },
      {
        role: 'zoomOut',
        label: '缩小开发工具栏窗口',
      },
      {
        type: 'separator',
      },
      {
        role: 'togglefullscreen',
        label: '切换开发工具栏全屏',
      },
    ],
  },
  // { role: 'windowMenu' }
  {
    label: '窗口',
    submenu: [
      {
        role: 'minimize',
        label: '最小化',
      },
      ...(isMac
        ? [
            {
              type: 'separator',
            },
            {
              role: 'front',
            },
            {
              type: 'separator',
            },
            {
              role: 'window',
            },
          ]
        : [
            {
              role: 'close',
              label: '关闭',
            },
          ]),
    ],
  },
];

const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);


3.app文件夹下创建package.json

涉及到三个安装包

electron

electron-builder 用于打包electron项目

electron-reloader 用于编写electron时,代码保存可以热更新

scripts下配置打包命令 dist-win32 dist-win64 dist-mac

以下为package.json详细内容,可以直接复制到你的项目中,然后再app文件夹下执行yarn安装

// package.json
{
  "name": "electron_project",
  "version": "0.0.1",
  "description": "description",
  "author": "huangjinhui",
  "main": "main.js",
  "scripts": {
    "dist-win32": "electron-builder --win --ia32", 
    "dist-win64": "electron-builder --win --x64",
    "dist-mac": " electron-builder --mac"
  },
  "dependencies": {
    "@electron/remote": "^2.0.10"
  },
  "devDependencies": {
    "electron": "^25.1.0",
    "electron-builder": "^23.6.0", // 用于打包
    "electron-reloader": "^1.2.3" // 用于编写electron时,代码保存可以热更新
  },
  "build": { // electron打包配置
    "productName": "electron_project",
    "appId": "com.example.app",
    "directories": {
      "output": "dist"
    },
    "files": [ // 打包时需要保存的文件
      "build/**/*",
      "main.js",
      "menu.js",
      "preload.js",
    ],
    "extraMetadata": {
      "main": "./main.js"
    },
    "win": {
      "icon": "./assets/logo.ico"
    },
    "mac": {
      "icon": "./assets/logo.ico"
    },
    "linux":{
      "icon": "./assets/logo.ico"
    }
  }
}

最终项目目录

使用umi+electron实现web端和桌面端同步开发

三 运行及打包

  1. app/main.js为electron主配置文件
  2. app/package.json为electron项目打包所需相关依赖。
  3. 打包electron容器需在app文件夹下打包
  4. 打包web应用在根目录下打包

1.运行流程

  • 根目录下执行yarn electron-dev 即可实现先运行web端,再运行electron桌面端应用

2.打包流程 

打包需要分两步:1.打包web端文件;2打包electron容器

  • 使用webpack打包web应用。在./package.json中执行 yarn build:prod 命令。
  • 使用electron-builder打包桌面应用。在./app/package.json中执行 dist-win32/dist-win64 命令。

最终输出到app/dist 文件夹

以上为使用umi+electron实现web端和桌面端同步开发整个流程,过程中有什么问题,可以留言沟通。