顯示具有 vite 標籤的文章。 顯示所有文章
顯示具有 vite 標籤的文章。 顯示所有文章

2024年12月17日 星期二

Node.js 開發筆記 - 使用 Electron + Vite + Vue 開發一款 PC app ,可支援 mDNS 裝置搜尋 @ macOS 15.2



有點久沒用 Electron 開發 PC app ,協助同事從旁實驗一下新的架構,這次研究後,直接用 electron-vite 初始化專案,核心就是 Electron + Vite 的研發環境,前台是 Vue 前端畫面。原先在想是不是一律自行安裝套件且故意都裝最新版,想完後還是縮一下,回歸別人整理好的工具,而這次強調用 Vite 整合,就不從 electron-vue 開始。

開發環境:

% sw_vers 
ProductName: macOS
ProductVersion: 15.2
BuildVersion: 24C101
% nvm use --lts         
Now using node v22.12.0 (npm v10.9.0)

流水帳:

```
% npm create electron-vite@latest
Need to install the following packages:
create-electron-vite@0.7.1
Ok to proceed? (y) y


> npx
> create-electron-vite

✔ Project name: … my-electron-app
✔ Project template: › Vue

Scaffolding project in /private/tmp/my-electron-app...

Done. Now run:

  cd my-electron-app
  npm install
  npm run dev

npm notice
npm notice New major version of npm available! 10.9.0 -> 11.0.0
npm notice Changelog: https://github.com/npm/cli/releases/tag/v11.0.0
npm notice To update run: npm install -g npm@11.0.0
npm notice

% cd my-electron-app
my-electron-app % npm install express multicast-dns axios
my-electron-app % cat package.json 
{
  "name": "my-electron-app",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vue-tsc && vite build && electron-builder",
    "preview": "vite preview"
  },
  "dependencies": {
    "axios": "^1.7.9",
    "express": "^4.21.2",
    "multicast-dns": "^7.2.5",
    "vue": "^3.4.21"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^5.0.4",
    "electron": "^30.0.1",
    "electron-builder": "^24.13.3",
    "typescript": "^5.2.2",
    "vite": "^5.1.6",
    "vite-plugin-electron": "^0.28.6",
    "vite-plugin-electron-renderer": "^0.14.5",
    "vue-tsc": "^2.0.26"
  },
  "main": "dist-electron/main.js"
}
my-electron-app % npm run dev
```

如此就有了一個畫面,而目錄結構:

```
% tree -I 'node_modules|dist|build'
.
├── README.md
├── dist-electron
│   ├── main.js
│   └── preload.mjs
├── electron
│   ├── electron-env.d.ts
│   ├── main.ts
│   └── preload.ts
├── electron-builder.json5
├── index.html
├── package-lock.json
├── package.json
├── public
│   ├── electron-vite.animate.svg
│   ├── electron-vite.svg
│   └── vite.svg
├── src
│   ├── App.vue
│   ├── assets
│   │   └── vue.svg
│   ├── components
│   │   └── HelloWorld.vue
│   ├── main.ts
│   ├── style.css
│   └── vite-env.d.ts
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts

7 directories, 22 files
```

接著,要來擴充一下功能,讓這支程式可以使用 mDNS 協定偵測環境,這功能需實作在 Electron 架構上,在 main.ts 添加裝置搜尋:

```
$ cat electron/main.ts 
...

import mdns from 'multicast-dns'

...

// 使用一個全域變數紀錄偵測到的 mDNS 裝置,並記錄他初次出現的時間,也紀錄最後一次看到的時間
const mdnsDevices: { [key: string]: { name: string, type: string, firstSeen: number, lastSeen: number, data: any[], answers: any[] } } = {}

function startMdnsQuery() {
  const mdnsInstance = mdns()

  console.log('mDNS Query Start...')

  // 修改 name & type
  // dns-sd -B _services._dns-sd._udp local
  mdnsInstance.query({
    questions: [{
      //name: '_http._tcp.local',
      name: '_services._dns-sd._udp.local',
      type: 'PTR'
    }]
  })

  mdnsInstance.on('response', (response: { answers: string | any[] }) => {
    console.log('mDNS Response:', response)

    if (win && response.answers.length > 0) {
      for (const answer of response.answers) {
        try {
          // 建立一個 unique key 來代表一個裝置
          const key = `${answer.name}-${answer.type}`
          if (!mdnsDevices[key]) {
            mdnsDevices[key] = {
              name: answer.name,
              type: answer.type,
              data: [answer.data],
              firstSeen: Date.now(),
              lastSeen: Date.now(),
              answers: [answer]
            }
            console.log('ADD Device:')
            console.log(mdnsDevices[key])
          } else {
            mdnsDevices[key].lastSeen = Date.now()
            mdnsDevices[key].answers.push(answer)
            if (!mdnsDevices[key].data.includes(answer.data)) {
              mdnsDevices[key].data.push(answer.data)
            }
          }
          win.webContents.send('mdns-found', mdnsDevices)
        } catch (error) {
          console.error(error)
        }
      }
    }
  })
}
...

app.whenReady().then(() => {
  createWindow()
  startMdnsQuery()
})
```

在 preload.ts 增加資料回傳到前端網頁上:

```
$ cat electron/preload.ts 
...
contextBridge.exposeInMainWorld('mdnsAPI', {
  onFound: (callback: (devices: any[]) => void) => {
    ipcRenderer.on('mdns-found', (_event, devices) => {
      callback(devices)
    })
  }
})
```

App.vue:

```
<script setup lang="ts">
import HelloWorld from './components/HelloWorld.vue'

import { ref, onMounted } from 'vue'

const devices = ref<any[]>([])
onMounted(() => {
  // 若在 preload 中已暴露 mdnsAPI
  if (window.mdnsAPI && typeof window.mdnsAPI.onFound === 'function') {
    window.mdnsAPI.onFound((foundDevices) => {
      // 將接收到的裝置資料放入響應式變數
      devices.value = foundDevices
      console.log('Found devices:')
      console.log(foundDevices)
    })
  }
})
</script>

<template>
  <div>
    <a href="https://electron-vite.github.io" target="_blank">
      <img src="/electron-vite.svg" class="logo" alt="Vite logo" />
    </a>
    <a href="https://vuejs.org/" target="_blank">
      <img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
    </a>
  </div>
  <!--
  <HelloWorld msg="Vite + Vue" />
  <hr />
  -->
  <div>
    <h3>已搜尋到的裝置:</h3>
    <ul id="deviceList">
      <li v-for="(device, index) in devices" :key="index">
        [{{ new Date(device.firstSeen).toLocaleString('zh-TW', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }) }}] {{ device.name }} ({{ device.type }}) 
        <ul>
          <li v-for="(data, i) in device.data" :key="I">
            {{ data }}
          </li>
        </ul>
      </li>
    </ul>
  </div>
</template>

<style scoped>
.logo {
  height: 6em;
  padding: 1.5em;
  will-change: filter;
  transition: filter 300ms;
}
.logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
  filter: drop-shadow(0 0 2em #42b883aa);
}

#deviceList {
  text-align: left;
  list-style-type: none;
  padding: 0;
}
</style>
```

收工 ,有空再來做些什麼應用吧:github.com/changyy/study-my-electron-app

2022年9月19日 星期一

Electron 開發筆記 - 使用 Vue.js 3 與 Vite 和最新 Electron Stable Release (20.x.x)

週末繼續把玩一下 Electron framework 也複習 Vue.js ,一直在想要怎樣兜再一起比較方便,類似的整合一直都有:
等等,後來自己想了一下,乾脆練習配置吧!自己先規劃想要的架構:
  1. 盡量把高變動的 code 都擺在 src 目錄中
  2. 盡量讓 vue.js code 可以統一在某個目錄中
  3. 直接使用最新的 Vue.js 及其最新工具
  4. 使用最新的 Electron Stable Release
  5. 盡量簡單,甚至不用 TypeScript ,讓其他新手想跨入也方便
就這樣,採用了 Electron 20.0.0 (2022.08) 和 Vue.js + Vite 環境

於是乎,先摸索了一個出來:github.com/changyy/node-electron-vue-vite

% tree src 
src
├── html
│   └── mainRenderer
│       ├── index.html
│       ├── src
│       │   ├── App.vue
│       │   ├── components
│       │   │   └── HelloWorld.vue
│       │   └── main.js
│       └── vite.config.js
├── main
│   └── index.js
├── preload
│   └── mainRenderer.js
└── renderer
    ├── index.js
    └── mainRenederer.js

7 directories, 9 files

之後再來想想看有什麼題目可以把玩吧!

2022年9月13日 星期二

Javascript 開發筆記 - 使用 Vue.js 和 Vite

規劃網頁開發時,引入一個框架是不錯的選項,讓團隊有共同溝通的語言,目前打算把以前接觸 Vue.js 部分複習一下,直接來用 Vue 3 以及 Vite ,其中 Vite 是一個很像 Webpack 的工具,號稱是下一代前端開發工具且運作效率高,看了一下是可以達成打包的程式碼任務,也能提供 dev web server + hot reload (hot module replacement/HMR) 服務。

文件資料:
這次主要接觸了 Vite ,才跑去看一下 Vue.JS 作者是誰 XD 我用了好一陣子都沒去認識一下。跑去看了一下 Vue.JS 作者的 twitter 跟 WIKI,發現人在新加坡且學經歷也滿有趣的,偶爾會分享跟小孩相處的經驗,並且也分享網路受訪又被辱罵等文,在 2022.08 剛和團隊產出了 Vue.JS 3 中文文檔,年紀相仿,滿有趣的生活。

快速上手法:

% nvm use v16
Now using node v16.13.0 (npm v8.1.0)
% npm -v
8.1.0
% npm create vite@latest my-vue-app --template vue
Need to install the following packages:
  create-vite@latest
Ok to proceed? (y) y
✔ Select a framework: › Vue
✔ Select a variant: › JavaScript

Scaffolding project in /private/tmp/my-vue-app...

Done. Now run:

  cd my-vue-app
  npm install
  npm run dev
% cd my-vue-app 
% cat package.json 
{
  "name": "my-vue-app",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "vue": "^3.2.37"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^3.1.0",
    "vite": "^3.1.0"
  }
}
% tree     
.
├── README.md
├── index.html
├── package.json
├── public
│   └── vite.svg
├── src
│   ├── App.vue
│   ├── assets
│   │   └── vue.svg
│   ├── components
│   │   └── HelloWorld.vue
│   ├── main.js
│   └── style.css
└── vite.config.js

4 directories, 10 files
% npm install
% npm run dev

  VITE v3.1.0  ready in 435 ms

  ➜  Local:   http://127.0.0.1:5173/
  ➜  Network: use --host to expose

接著去編輯 src/components/HelloWorld.vue 可以看到網頁內容立即改變。如此大概就可以窺見 Vite 的基本用法,接著是編譯:

% npm run build

> my-vue-app@0.0.0 build
> vite build

vite v3.1.0 building for production...
✓ 16 modules transformed.
dist/assets/vue.5532db34.svg     0.48 KiB
dist/index.html                  0.44 KiB
dist/assets/index.43cf8108.css   1.26 KiB / gzip: 0.65 KiB
dist/assets/index.795d9409.js    52.87 KiB / gzip: 21.33 KiB

% tree dist 
dist
├── assets
│   ├── index.43cf8108.css
│   ├── index.795d9409.js
│   └── vue.5532db34.svg
├── index.html
└── vite.svg

1 directory, 5 files

% cat dist/index.html 
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + Vue</title>
    <script type="module" crossorigin src="/assets/index.795d9409.js"></script>
    <link rel="stylesheet" href="/assets/index.43cf8108.css">
  </head>
  <body>
    <div id="app"></div>
    
  </body>
</html>

以上就簡單過一下常用的東西。