Electron+serialport串口通信+sqlite3集成过程

概要

提示:最近在electron项目中使用serialport+sqlite3的时候遇到的各种问题再此做个记录

准备工作

首先你要设置electron的镜像源不然你很有可能在后面的某些步骤上出错

npm config set registry https://registry.npmmirror.com
npm config set ELECTRON_MIRROR https://npmmirror.com/mirrors/electron/

然后win+R打开cmd 输入.npmrc回车
在这里插入图片描述
会出现一个这样的文件
在这里插入图片描述

在里面添加

disturl=https://registry.npmmirror.com/-/binary/node
ELECTRON_BUILDER_BINARIES_MIRROR=https://npmmirror.com/mirrors/electron-builder-binaries/
operadriver_cdnurl=https://registry.npmmirror.com/-/binary/operadriver/
phantomjs_cdnurl=https://registry.npmmirror.com/phantomjs
SASS_BINARY_SITE=https://registry.npmmirror.com/mirrors/node-sass/

这都是各种踩坑后总结出来的
PS:另外node_gyp、msvs_version、python这几项在下面会单独说。折腾我一两天

1.项目创建

网上有个比较火的脚手架electron-vue,github 上 12.2k 的 star,大家应该都听说过或者使用过,但现在我们不使用它,electron-vue是vue-cli2.0的版本,现在都已经出道 4.0 了,再者electron-vue已经很久没有更新,我们可以使用 vue 最新的脚手架加上插件vue-cli-plugin-electron-builder来搭建项目,项目结构也更加清晰明了。如果对脚手架electron-vue感兴趣可去 这里

vue create vue-electron
// 自定义创建
? Please pick a preset:
  default (babel, eslint)
❯ Manually select features
// 这里选择自定义创建,使用babel(语法编译器)、Router(路由)、CSS Pre-processors(css预处理器)、Linter / Formatter(代码风格、格式校验):
? Please pick a preset: Manually select features
? Check the features needed for your project:
 ◉ Babel
 ◯ TypeScript
 ◯ Progressive Web App (PWA) Support
 ◉ Router
 ◉ Vuex
 ◉ CSS Pre-processors
❯◉ Linter / Formatter
 ◯ Unit Testing
 ◯ E2E Testing
 
` 注意:router这里不要使用history模式,原因是electron的项目不能使用history模式 输入n,进入下一步:`
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, CSS Pre-processors, Linter
? Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n) n

// css预处理器这里选择Sass
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, CSS Pre-processors, Linter
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default):
  Sass/SCSS (with dart-sass)
❯ Sass/SCSS (with node-sass)
  Less
  Stylus

// 选择ESLint代码检查工具,我这里选择的是“ESLint + Airbnb config”,强烈建议大家一定要在项目内使用ESLint,一直用一直爽,懂的自然懂……该有的规范还是要遵循的:
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, CSS Pre-processors, Linter
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Less
? Pick a linter / formatter config:
  ESLint with error prevention only
❯ ESLint + Airbnb config
  ESLint + Standard config
  ESLint + Prettier
  
// 选择什么时候执行ESLint检查,我这里两个都选了,保存时检查“Lint on save”,向仓库提交代码时也检查“Lint and fix on commit”,不符合规范的代码我们不允许提交到仓库:
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, CSS Pre-processors, Linter
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Less
? Pick a linter / formatter config: Airbnb
? Pick additional lint features:
 ◉ Lint on save
❯◉ Lint and fix on commit

// 这里是询问 babel, postcss, eslint这些配置是单独的配置文件还是放在package.json 文件中,这里我们选择“In dedicated config files”单独放置:
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, CSS Pre-processors, Linter
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Less
? Pick a linter / formatter config: Airbnb
? Pick additional lint features: Lint on save, Lint and fix on commit
? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys)
❯ In dedicated config files
  In package.json
// 这里是最后的操作,是否保存该配置,如果你下次需要使用相同的配置,那么可以选择 yes,我这选择 no:
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, CSS Pre-processors, Linter
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Less
? Pick a linter / formatter config: Airbnb
? Pick additional lint features: Lint on save, Lint and fix on commit
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? (y/N) no

执行完以上操作,剩下的我们等待项目下载依赖包,vue项目初始化就算搞定了。

cd vue-electron-notes进入项目跑一遍npm run serve,这个时候没什么问题就已经成功运行起来了!

2.在项目内集成Electron

进入我们项目的根目录,我们执行以下命令来安装插件

vue add vue-cli-plugin-electron-builder

这里选择版本^12.0.0:

✔  Successfully installed plugin: vue-cli-plugin-electron-builder

? Choose Electron Version (Use arrow keys)
  ^10.0.0
  ^11.0.0^12.0.0

等安装完毕后会重新构建项目架构,在src目录下生成background.js文件,并且还新增了启动命令,如下图:
在这里插入图片描述
到这里其实还有一个小问题,插件安装的Electron是 12.0 版本的,但官方最新的版本其实已经到了31.x.x了,为了后续我们可以使用最新的api,我们需要执行命令更新一下版本:

npm uninstall electron

2.1安装最新的electron版本

npm install -S electron
npm install -S @electron/remote

2.2运行程序

npm run serve

在这里插入图片描述
看到这个就说明成功了

2.3 解压dist_electron/win-unpacked/resources/app.saar文件(重要)
npm install -g asar
//进入app.asar所在目录,使用以下命令
asar e app.asar ./
2.4 electron配置,background.js:
//background.js
//文件头部,引用增加ipcMain用于通信
import { app, protocol, BrowserWindow, shell, Menu } from 'electron'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
const isDevelopment = process.env.NODE_ENV !== 'production'
const winURL = process.env.NODE_ENV === 'development' ? `http://localhost:${process.env.PORT}` : `app://./index.html`
const loadingURL = process.env.NODE_ENV === 'development' ? `http://localhost:${process.env.PORT}/loader.html` : `app://./loader.html`
var loadWindow = null
var mainWindow = null
async function createWindow () {
  // Create the browser window.
   //由于渲染进程中electron.remote已废弃,需要手动引入,并在每一个browserwindow中启用remote
 require('@electron/remote/main').initialize()
  mainWindow = new BrowserWindow({
    width: 1200,
    height: 800,
    frame: true, //是否window自带的关闭最小化等按钮
    show: false,
    useContentSize: false,
    resizable: true, //禁止改变主窗口尺寸
    maximizable: true, // 是否可以最大化
    webPreferences: {
      webSecurity: false, // 安全策略
      enableRemoteModule: true, // 可以使用remote
      nodeIntegration: true, // 渲染层可以使用node
      contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
    }
  })

 require("@electron/remote/main").enable(mainWindow.webContents);
  if (process.env.NODE_ENV !== 'development') {
    createProtocol('app')
  }
  mainWindow.loadURL(winURL)
 

  mainWindow.webContents.once('dom-ready', () => {
    mainWindow.show()
    mainWindow.webContents.openDevTools({ mode: 'detach' })
    if (process.env.NODE_ENV !== 'development'){
     
    } 
    loadWindow.destroy()
  })
  mainWindow.on('closed', () => {
    mainWindow = null
  })
  createMenu()
  mainWindow.webContents.setWindowOpenHandler(({ url }) => {
    shell.openExternal(url)
    return { action: 'deny' }
  })
}
// 设置菜单栏
function createMenu () {
  // darwin表示macOS,针对macOS的设置
  if (process.platform === 'darwin') {
    const template = [
      {
        label: 'App Demo',
        submenu: [
          {
            role: 'about'
          },
          {
            role: 'quit'
          }]
      }]
    let menu = Menu.buildFromTemplate(template)
    Menu.setApplicationMenu(menu)
  } else {
    // windows及linux系统
    Menu.setApplicationMenu(null)
  }
}



function loadindWindow () {
  loadWindow = new BrowserWindow({
    width: 400,
    height: 600,
    frame: false,
    show: false,
    backgroundColor: '#222',
    transparent: true,
    resizable: false,
    webPreferences: { experimentalFeatures: true }
  })
  if (process.env.NODE_ENV !== 'development') {
    createProtocol('app')
  }
  loadWindow.loadURL(loadingURL)

  loadWindow.webContents.once('dom-ready', () => {
    loadWindow.show()
  })
  setTimeout(() => {
    createWindow()
  }, 3000)

  loadWindow.on('closed', () => {
    loadWindow = null
  })
}

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

app.on('activate', () => {
  if (BrowserWindow.getAllWindows().length === 0) loadindWindow()
})


app.on('ready', async () => {
  // if (isDevelopment && !process.env.IS_TEST) {
  // // Install Vue Devtools
  //   try {
  //     await installExtension(VUEJS_DEVTOOLS)
  //   } catch (e) {
  //     console.error('Vue Devtools failed to install:', e.toString())
  //   }
  // }
  loadindWindow()
})

// 获取单实例锁
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
  // 如果获取失败,说明已经有实例在运行了,直接退出
  app.quit();
}

// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([
  { scheme: 'app', privileges: { secure: true, standard: true } }
])

if (isDevelopment) {
  if (process.platform === 'win32') {
    process.on('message', (data) => {
      if (data === 'graceful-exit') {
        app.quit()
      }
    })
  } else {
    process.on('SIGTERM', () => {
      app.quit()
    })
  }
}

**这里我用了一个启动加载动画方法为 loadindWindow (loadingURL)为启动时要展示的路径 文件位置级代码如下 **
在这里插入图片描述

<!doctype html>
<html>

<head>
  <meta charset="utf-8">
  <title></title>

  <style>
    html {
      height: 100%;
      min-height: 100%;
      overflow: hidden;
      user-select: none;
      -webkit-app-region: no-drag;
    }

    html body {
      background-size: 163px;
      font: 14px/21px Monaco, sans-serif;
      color: #999;
      -webkit-font-smoothing: antialiased;
      -webkit-text-size-adjust: 100%;
      -moz-text-size-adjust: 100%;
      -ms-text-size-adjust: 100%;
      text-size-adjust: 100%;
      height: 100%;
      min-height: 100%;
      margin: 0px;
    }

    html body h4 {
      margin: 0;
    }

    .scene {
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100%;
      background: #222;
      position: relative;
    }

    .loader {
      position: relative;
      width: 15em;
      height: 15em;
      background: linear-gradient(-225deg, #ff3cac 0%, #562b7c 52%, #2b86c5 100%);
      border-radius: 50%;
      animation: spin 0.5s linear infinite;
    }

    span {
      position: absolute;
      width: 100%;
      height: 100%;
      border-radius: inherit;
      background: inherit;
    }

    span:nth-child(1) {
      filter: blur(5px);
    }

    span:nth-child(2) {
      filter: blur(10px);
    }

    span:nth-child(3) {
      filter: blur(25px);
    }

    span:nth-child(4) {
      filter: blur(50px);
    }

    span::after {
      position: absolute;
      content: "";
      top: 10px;
      left: 10px;
      right: 10px;
      bottom: 10px;
      background: #222;
      border-radius: inherit;
    }

    .text {
      position: absolute;
      opacity: 0;
      animation: breath 3s ease-in-out infinite;
    }

    @keyframes breath {
      from {
        opacity: 0.05;
      }

      50% {
        opacity: 1;
      }

      to {
        opacity: 0.05;
      }
    }

    @keyframes spin {
      to {
        transform: rotate(1turn);
      }
    }
  </style>
</head>

<body>
  <div class="scene">
    <div class="loader">
      <span></span>
      <span></span>
      <span></span>
      <span></span>
    </div>
    <div class="text">正在准备资源中...</div>
  </div>

</body>

</html>
2.4 vue.config.js:
const path = require("path");
function resolve(dir) {
  return path.join(__dirname, dir);
}
// const BASE_URL = process.env.NODE_EVN === 'production' ? './' : './'
const port = process.env.PORT || 8080
const { dependencies } = require('./package.json')
module.exports = {
  transpileDependencies: true,
  // publicPath: BASE_URL,
  lintOnSave: process.env.NODE_ENV === 'development',
  // 这里写你调用接口的基础路径,来解决跨域,如果设置了代理,那你本地开发环境的axios的baseUrl要写为 '' ,即空字符串
  devServer: {
    // proxy: 'localhost:3000'
    port,
    open: false
  },
  chainWebpack(config) {
    config.module
      .rule('svg')
      .exclude.add(resolve('src/icons'))
      .end()
    config.module
      .rule('icons')
      .test(/\.svg$/)
      .include.add(resolve('src/icons'))
      .end()
      .use('svg-sprite-loader')
      .loader('svg-sprite-loader')
      .options({
        symbolId: 'icon-[name]'
      })
      .end()
  },
  pluginOptions: {
    electronBuilder: {
      chainWebpackMainProcess: (config) => {
        config.output.filename('background.js');
    },
    customFileProtocol: "./",
    nodeIntegration: true,
    enableRemoteModule: true,
    externals: [
      ...Object.keys(dependencies || {})
    ],
      builderOptions: {
        // "appId": "com.example.app",
        // "productName": "xxxxxx",//项目名,也是生成的安装文件名
        copyright: "Copyright © 2021",//版权信息
        // directories: {
        //   output: "build"//输出文件路径
        // },
        win: {//win相关配置
          // "icon": "./src/assets/icon.ico",//图标,当前图标在根目录下,
          target: [
            {
              target: "nsis",//利用nsis制作安装程序
              arch: [
                "x64",//64位
                // "ia32"//32位
              ]
            }
          ]
        },
        nsis: {
          oneClick: false, // 是否一键安装
          allowElevation: true, // 允许请求提升。 如果为false,则用户必须使用提升的权限重新启动安装程序。
          allowToChangeInstallationDirectory: true, // 允许修改安装目录
          // "installerIcon": "./src/assets/icon.ico",// 安装图标
          // "uninstallerIcon": "./src/assets/icon.ico",//卸载图标
          // "installerHeaderIcon": "./src/assets/icon.ico", // 安装时头部图标
          createDesktopShortcut: true, // 创建桌面图标
          createStartMenuShortcut: true,// 创建开始菜单图标
          // "shortcutName": "mine", // 图标名称
        },
      }
    },
  },
  // 设为false打包时不生成.map文件
  productionSourceMap: true
}

3.安装Serialport

npm i serialport

安装electron-rebuild(用来重新编译serialport包)
sqlite3也需要它来重新编译

 npm install --save-dev electron-rebuild

node-serialport是一个让node可以访问电脑串口设备的原生模块,需要编译,也是出问题最多的一个环节。编译需要做一些准备工作。

1. 安装gyp命令 npm install -g node-gyp (如果有可以跳过);
2. 安装Visual C ++构建环境 Visual Studio构建工具网上很多说要安装2015或者2017,其实没有影响安装最新的2022也可以
3. 安装Python 2.7~3.10.0之间的任意版本也有人说必须2.7其实也没有影响,我这里安装的3.10.0
4. 在第一节准备工作的时候.npmrc文件中的node_gypmsvs_versionpython这三个参数就是为了解决这里安装gyp、Visual C ++和Python 后的问题

执行.\node_modules\.bin\electron-rebuild.cmd 提示“Rebuild Complete”表示执行成功

.\node_modules\.bin\electron-rebuild.cmd
√ Rebuild Complete

4.安装sqlite3

如果你按照前面的步骤一步一步到这里了的话到了,接下来按照下面的步骤做就应该不会有问题

1. npm i sqlite3@5.1.6
2. npm install -g node-pre-gyp

安装完成后一定要看自己的sqlite3依赖包里面是否含有

\node_modules\sqlite3\lib\binding\napi-xxx-xxx-unknown-x64的文件,如果没有请重新安装低版本的sqlite3
安装完成后尝试执行以下命令

// 进入依赖根目录
1. cd ./node_modules/sqlite3
// 重新构建运行文件
2. node-pre-gyp install --fallback-to-build
3. node-pre-gyp rebuild //或者 node-pre-gyp rebuild --disturl=https://npmmirror.com/mirrors/node

完成后回到项目的根目录依次执行以下命令

1. npm i 
2. npm run postinstall
3. .\node_modules\.bin\electron-rebuild.cmd

完成后就可以使用serialport以及sqlite3了

打包问题

具体参考:打包采坑问题汇总

无法使用require引用问题

当你在项目中使用require引用一些模块(比如jQuery),会导致Uncaught TypeError: $ is not a function
这个问题是的直接原因是require无法引用,根本原因node中的require覆盖了webpack的require,这就导致了打包失败
解决办法使用window.require代替
例如:

const { SerialPort } = window.require(“serialport”);

总结

路漫漫其修远兮 多看文档 多研究

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值