从0到1搭建electron+vite+vue3模板

1,909 阅读8分钟

从0到1搭建electron+vite+vue3模板

实现目标
  • 能正常启动,打包
  • 自己搭建打包过程,使用node
实现方案
  • 网上常见的打包方案
    • 修改main.js,在里面加载vite的dist包
    • package,启动命令行先使用vite的打包,再使用electron的启动
  • gitee-SKY-Electron-Vite-Template 本文使用的方案
    • 打包思路 使用node配合vite和electron进行打包

自己创建一个模板

思路:使用vite编译vue软件打包,再使用electron引用打包后的文件

学习:

  1. 通过vite启动一个web版

  2. 通过rollup编译electron

  3. 启动electron,连接vite

1. 创建模板并能运行网页版

1. 使用npm init命令创建一个模板

  • name,version, description, main为必填项

  • main需要设置为main.js(也可不要,下面代码注意)

2. 引入基础版所需要的依赖

// electron 用于启动和编译应用程序包
npm install electron --save-dev

// vite 用于启动和编译vue程序包
npm install vite --save-dev

// vue vue语言包
npm install vue --save-dev

// @vitejs/plugin-vue 用于编译vue文件
npm install @vitejs/plugin-vue --save-dev

// 用于获取端口使用情况
npm install portfinder --save-dev

3. 创建需要的文件夹和文件

  1. 创建.electron-vite文件夹用于放置启动和编译

    • dev-runner.js 用于启动
    //* 设置环境变量
    process.env.NODE_ENV = 'development'
    
    //* 依赖引入
    const path = require('path') // node内置 用来获取路径
    const { createServer } = require('vite') //* createServer 用于启动vue项目
    const Portfinder = require("portfinder") //* 用于获取端口
    
    //* 配置项引入
    const viteConfig = require('./vite.config') //* 引入vite创建需要的配置项
    const envConfig = require('../config') //* 引入环境变量需要的配置项
    
    function startWeb() { //* 启动vite项目
        //* 通过环境变量获取端口 通过PortFinder判断端口。通过vite方法进行启动和监听端口
        return new Promise((resolve, reject) => {
            Portfinder.basePort = envConfig.dev.port || 9080 //* 配置要检查的端口
            Portfinder.getPort(async (err, port) => { //* 检查端口是否能正常使用
                if(err) {//* 有错误则返回出去
                    reject('PortError: ' + err)
                    return
                }
                //* 如果端口没问题,则通过vite进行创建
                const server = await createServer(viteConfig)
                process.env.PORT = port //* 设置环境变量
                //* 监听该端口 进行启动
                server.listen(port).then(() => {
                    console.log('port:', port)
                })
                resolve()
            })
        })
    }
    
    //* 启动方法
    function start() {
        startWeb()
    }
    
    //* 启动
    start()
    
    • vite.config.js vite启动配置
    //* 引入依赖
    const { resolve } = require('path')
    const { defineConfig } = require('vite') //* 用于解析config配置
    const vue = require("@vitejs/plugin-vue") //* vue支持
    
    const root = resolve('src/renderer') //* 入口文件 index.html所在的位置
    
    //* 生成配置
    const config = defineConfig({
        root,//* 入口文件 index.html所在的位置
        mode: process.env.NODE_ENV, //* 默认'development'(serve),'production'(build)配置环境变量
        define: { //* 设置全局变量
            'process.env': process.env.NODE_ENV === 'production' ? 			userConfig.build.env : userConfig.dev.env
        },
        build: { //* 打包配置
            outDir: resolve('dist/electron/renderer'), //* 打包文件夹
            emptyOutDir: true, //* 打包时清空目录
    
        },
        plugins: [vue()],
        publicDir: resolve('static') //* 配置静态文件夹,并且会打包到outDir下
    })
    
    module.exports = config
    
  2. 创建static文件夹用于放置资源文件

  3. 创建src文件夹用于放置代码

    • 创建renderer文件夹来放置vue代码

      • 创建index.html文件来作为vite启动页面
      <html lang="en">
      <body>
          <div id="app"></div>
          <script type="module" src="/main.js"></script>
      </body>
      </html>
      
      • 创建main.js作为入口文件 引入App.vue进行显示

        import { createApp } from 'vue';
        import App from './App.vue'
        
        const app = createApp(App)
        app.mount('#app')
        
      • 创建App.vue来作为app显示内容

      <template>
          <div class="app-container">Hello World!!!</div>
      </template>
      
    • 创建main文件夹来放置electron的方法

  4. 创建config文件夹用于放置环境变量

    • dev.env.js:dev 开发环境-变量

      module.exports = {
          NODE_ENV: 'development'
      }
      
    • prod.env.js 正式环境-变量

      module.exports = {
          NODE_ENV: 'production'
      }
      
    • index.js 通用-变量

      module.exports = {
        build: {
          env: require("./prod.env"), // 配置环境变量
        },
        dev: {
          env: require("./dev.env"),
          port: 9080,//* 开发环境下使用的端口
        },
      };
      

4. 启动网页版的vite

上面代码最主要的是src/renderer的vue代码和.electron-vite文件夹内代码的实现。通过vite-config.js提供的配置,使用vite的createServer进行网页的启动。下面进行最后的配置:

  1. package.json启动命令配置

该命令将使用咱们上面配置的.electron-vite/dev-runner.js进行启动

"scripts": {
    "start": "node .electron-vite/dev-runner.js"
},
  1. 命令行进行启动
npm start

如果成功的话根据终端打印的port可以在网页上面进行查看

2. 配置编译electron

需要使用rollup对文件进行编译

  • ollup-plugin-node-resolve: 告诉 Rollup 如何查找外部模块

  • rollup-plugin-commonjs:将CommonJS模块转换为 ES2015 供 Rollup 处理,请注意,rollup-plugin-commonjs应该用在其他插件转换你的模块之前 - 这是为了防止其他插件的改变破坏CommonJS的检测

1. 依赖引入

//* 引入rollup
npm install @rollup/plugin-commonjs --save-dev

2. 使用到的文件和文件夹

  1. .electron-vite 添加rollup.config.js

    • rollup.config.js 用于electron的编译配置项

      //* 依赖引入
      const path = require('path')
      const replace = require("@rollup/plugin-replace"); //* 变量替换插件
      const alias = require('@rollup/plugin-alias')
      const { builtinModules } = require('module') //* 不想打包的模块名
      
      const config = (env = 'production') => {
          const mainConfig = {
              input: path.join(__dirname, '../src/main/index.js'),
              output: { //* 输出
                  file: path.join(__dirname, '../dist/electron/main/main.js'),
                  format: 'cjs',
                  name: 'MainProcess',
                  sourcemap: false,
              },
              plugins: [
                  replace({ //* 替换路径
                      preventAssignment: true,
                      "process.env.NODE_ENV": JSON.stringify(env),
                  }),
                  alias({ //* 路径别名
                      entries: [
                          { find: '@main', replacement: path.join(__dirname, '..', 'src', 'main'), },
                          { find: '@config', replacement: path.join(__dirname, '..', 'config') },
                          { find: '@common', replacement: path.join(__dirname, '..', 'src', 'common') },
                      ]
                  })
              ],
              external: [ //* 需要在项目中使用的依赖需要在这里引用
                  ...builtinModules,
                  'electron'
              ],
          }
          return mainConfig
      }
      
      module.exports = config
      
    • dev-runner.js 添加编译方法

      const rollup = require('rollup')//* 构建包的工具
      const rollupConfig = require('./rollup.config')
      const rollupOption = rollupConfig(process.env.NODE_ENV)
      function startMain() { //* 构建electron文件
          return new Promise((resolve, reject) => {
              //* 创建一个监听模块处理的对象
              const watcher = rollup.watch(rollupOption)
              //* 监听构建事件 会有code https://www.rollupjs.com/guide/javascript-api
              watcher.on('event', event => {
                  //* code等于END代表完成所有文件束构建
                  if(event.code === 'END') {
                      //* 等构建完成后发送出去 通知可以打开
                      resolve()
                  } else if (event.code === 'ERROR') {
                      reject(event.error)
                  }
              })
          })
      }
      
      //* 启动方法
      async function start() {
          await startWeb() //* 启动网页版
          await startMain() //* 构建包
      }
      
  2. src/main

    • index.js electron应用程序的入口

      //* 依赖引入
      import { app } from 'electron'
      import InitWindow from './services/windowManager'
      //* 监听启动完成事件 则打开窗口
      function onAppReady() { //* App准备完成事件
          new InitWindow().initWindow()
      }
      app.whenReady().then(onAppReady)
      
      app.on('window-all-closed', () => {
          // 所有平台均为所有窗口关闭就退出软件
          app.quit()
      })
      
  3. src/main/services

    • windowManager 用来管理窗口 创建窗口等

      //* 依赖引入
      import { BrowserWindow } from 'electron'
      import { join } from 'path' //* 路径
      import { getUrl } from '../utils/URL'
      
      const winURL = getUrl("", join(__dirname, '..', 'renderer', 'index.html')); //* 获取vue的路径
      // const winURL = 'http://localhost:9080/'
      class MainInit {
          mainWindow = null
          createMainWindow() {
              //* 创建一个窗口对象
              this.mainWindow = new BrowserWindow({
                  titleBarStyle: 'default', //* 设置标题栏样式
              })
              //* 加载vue页面
              this.mainWindow.loadURL(winURL)
              this.mainWindow.openDevTools();
          }
          initWindow() { //* 初始化窗口
              console.log('<===== 要来创建窗口')
              this.createMainWindow()
          }
      }
      
      export default MainInit
      
  4. src/main/utils

    • URL.js 工具类 getUrl方法用于获取连接vue项目的链接
    const isDev = process.env.NODE_ENV === 'development';
    /**
     * 获取真正的地址
     *
     * @param {string} devPath 开发环境路径
     * @param {string} proPath 生产环境路径
     * @param {string} [hash=""] hash值
     * @param {string} [search=""] search值
     * @return {*}  {string} 地址
     */
    export function getUrl(devPath, proPath, hash = "", search = "") {
        const url = isDev ? new URL(`http://localhost:${process.env.PORT}`) : new URL('file://');
        url.pathname = isDev ? devPath : proPath;
        url.hash = hash;
        url.search = search;
        return url.href;
      }
    

3. 编译应用程序

为什么要编译应用程序?

请先编译,在启动electron会说一下大概流程

  1. 使用npm start命令进行启动
  2. 查看dist/electron/main/main.js文件是否存在,存在即为成功

3. 启动electron

回顾:

刚才的教程中第一步为启动vite项目,让其可以在网页中浏览。第二步为编译electron应用程序包。

electron简单来说只是一个壳,引用了js项目进行渲染显示。那么启动逻辑就很简单了。

  • 启动编译后的electron程序
  • electron程序会通过loadURL获取vite项目进行渲染。这就是为什么我们需要启动vite项目让其可以在网页上面访问的原因。比如loadURL('http://localhost:8090')。

1. 使用到的文件和文件夹

  1. .electron-vite

    • dev-runner.js

      const { spawn } = require('child_process') //* 创建异步线程 用于创建electron
      const electron = require('electron')
      
      function startElectron() {
          //* 构建参数
          var args = [
              '--inspect=5858',
              path.join(__dirname, '../dist/electron/main/main.js')
          ]
          //* 添加环境变量参数 为node地址和当前文件地址
          // detect yarn or npm and process commandline args accordingly
          if (process.env.npm_execpath.endsWith('yarn.js')) {
              args = args.concat(process.argv.slice(3))
          } else if (process.env.npm_execpath.endsWith('npm-cli.js')) {
              args = args.concat(process.argv.slice(2))
          }
          //* 异步添加子线程 参数1:要执行的命令 参数2:字符串参数列表
          electronProcess = spawn(electron, args)
          electronProcess.on('close', () => {
              process.exit()
          })
      }
      //* 启动方法
      async function start() {
          await startWeb() //* 启动网页版
          await startMain() //* 构建包
          await startElectron()//* 创建electron应用
      }
      

2. 启动

npm start

至此,start环节结束

编译

1. 安装依赖

//* 用于删除文件 构建之前需要先清空文件
npm install del --save-dev
//* electron打包工具
npm install electron-builder --save-dev

//* 热更新 忽略
electron-updater

2. 修改package配置

配置打包的index.js位置。并且通过该文件引用的依赖去打包需要的文件

{
    "main": "./dist/electron/main/main.js",
    "scripts": {
    "build": "node .electron-vite/build.js  && electron-builder -c build.json"
  }
}

3. 用到的文件和文件夹

思路:electron只是一个打包工具,一个壳。所以总思路还是壳引用vue页面

  1. 使用vite打包web包到dist文件夹中
  2. 使用rollup打包electron启动需要的main.js到dist文件夹
  3. 使用electron-builder打包dist文件夹中的东西到app包中
  1. .electron-vite

    • vite.config.js

      const config = defineConfig({
      	base: './', //* 打包之后的index.html里面的引用地址 因为打包之后是地址引用file://。如果使用/的话会导致查找不到文件夹的问题
      })
      
    • build.js

      //* 引入依赖
      const { sync } = require('del') //* 删除文件 构建之前需要先删除文件
      const rollup = require('rollup')//* 构建electron的工具
      const { build } = require('vite') //* 构建web包的工具
      
      //* 引入配置
      //* 配置项引入
      const viteConfig = require('./vite.config') //* 引入vite创建需要的配置项
      const rollupConfig = require('./rollup.config')
      const rollupOption = rollupConfig(process.env.NODE_ENV) //* 获取electron打包配置
      
      function buildApp() { //* 构建App包
          return new Promise((resolve, reject) => {
              //* 删除使用的包
              sync(['dist/electron/main/*'])
              rollup.rollup(rollupOption)
                  .then(build => {
                      build.write(rollupOption.output)
                      console.log('App Build Success!!!')
                      resolve()
                  })
                  .catch(error => {
                      console.log('App Build Fail!!!', error)
                      reject(error)
                  })
          })  
      }
      
      function buildWeb() { //* 构建web包
          return new Promise((resolve, reject) => {
              //* 删除包
              sync(['dist/electron/renderer/*'])
              build(viteConfig).then((res) => {
                  console.log('Web Build Success!!!')
                  resolve()
              })
              .catch(() => {
                  console.log('Web Build Faild!!!')
                  reject()
              })
          })
      }
      
      function start() { //* 启动构建方法
          Promise.all([buildApp(),buildWeb()])
          .then(()=> {
              //* 成功退出
              process.exit()
          })
          .catch(() => {
              //* 报错 退出环境 code:1 为错误
              process.exit(1)
          })
      }
      
      start()
      
  2. build.json

    {
        "asar": false,//* 是否使用asar打包
        "appId": "org.electron-demo",//* 软件id
        "productName": "electron-vite-demo",//* 打包出来的软件名
        "directories": { //* 输出路径 打包后路径
            "output": "build"
        },
        "mac": { //* icon路径
            "icon": "build/icons/icon.icns"
        },
        "win": {
            "icon": "build/icons/icon.ico",
            "target": "nsis"
        },
        "linux": {
            "target": "deb",
            "icon": "build/icons"
        }
    }
    
  3. build文件夹,可根据build.json中的icon手动添加icon。并且为编译后的文件存放地址

4. 启动

npm run build

如果正常编译 不报错即为成功

5. 编译后文件夹用途

build/xxx.exe 为安装文件 会安装到电脑进行使用

build/win-unpacked文件夹为免安装版 可以直接使用

链接

electron-vite-template: 基于vite+electron LTS的模板,借助electron next的新特性和vite以及rollup的速度,可以让您能够快速开发 (gitee.com)

electron-vite-demo地址
待更新

  1. 多彩文字
  2. 配置项
  3. devTools配置
  4. 版本自动更新
  5. preload.js使用