UNPKG

mixone

Version:

MixOne is a Node scaffolding tool implemented based on Vite, used for compiling HTML5, JavasCript, Vue, React and other codes. It supports packaging Web applications with multiple HTML entry points (BS architecture) and desktop installation packages (CS a

309 lines (291 loc) 9.88 kB
const { spawn, exec } = require('child_process'); const path = require('path'); const fs = require('fs'); const chokidar = require('chokidar'); // 新增 FileWatcher 类 class FileWatcher { constructor(electronManager, watchDir, ignorePatterns) { this.electronManager = electronManager; this.watchDir = watchDir; this.ignorePatterns = ignorePatterns; this.watcher = null; this.reloadDebounce = null; } initialize() { this.watcher = chokidar.watch(this.watchDir, { ignored: this.ignorePatterns, ignoreInitial: true, awaitWriteFinish: { stabilityThreshold: 500, pollInterval: 100 } }); this.watcher.on('all', this.handleFileChange.bind(this)); this.watcher.on('error', this.handleError.bind(this)); } async handleFileChange(event, filePath) { if (['add', 'change', 'unlink'].includes(event)) { console.log(`The detection of ${filePath} ${event} triggered a restart...`); clearTimeout(this.reloadDebounce); this.reloadDebounce = setTimeout(async () => { await this.electronManager.restart(); }, 50); } } handleError(error) { console.error('File listening error:', error); } async close() { if (this.watcher) { await this.watcher.close(); // console.log('✅ 已关闭文件监听器'); } } } // 新增 Electron 进程管理类 class ElectronManager { constructor() { this.electronProcess = null; this.restarting = false; // 新增状态文件路径属性 this.stateFilePath = path.join(require('os').tmpdir(), 'window-states.txt'); } // 启动 Electron 进程 start(args = []) { const defaultArgs = ['electron','.', '--dev']; // console.log('start Electron process...',defaultArgs,args); this.electronProcess = spawn('npx', [...defaultArgs, ...args], { stdio: 'inherit', shell: true }); // 监听进程意外退出 this.electronProcess.on('exit', async (code) => { console.log(`The Electron process has exited, exit code: ${code}`); if (!this.restarting) { await cleanup(); process.exit(code); } }); } // 终止 Electron 进程(返回 Promise) async stop() { if (!this.electronProcess) return; return new Promise((resolve) => { this.restarting = true; // Windows 使用 taskkill 强制终止进程树 exec(`taskkill /F /T /PID ${this.electronProcess.pid}`, (error) => { this.electronProcess = null; this.restarting = false; if (error) console.error('Failed to terminate the process:', error.message); resolve(); }); }); } // 重启应用 async restart() { await this.stop(); this.start([`--window-states=${this.stateFilePath}`]); console.log('The Electron process has been restarted'); } } // 在文件顶部声明实例 const electronManager = new ElectronManager(); // 初始化文件监听 const fileWatcher = new FileWatcher( electronManager, path.join(process.cwd(), 'main'), [ /(^|[/\\])\../, '**/node_modules/**', '**/*fn.js' ] ); function projectType(packageJsonPath){ let obj = { UIlib:'', MVCframework:'' } if (fs.existsSync(packageJsonPath)) { try { const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); const dependencies = { ...packageJson.dependencies, ...packageJson.devDependencies }; if (dependencies.vue) { const vueVersion = dependencies.vue.match(/\d+/)?.[0]; obj.MVCframework = vueVersion === '2' ? 'vue27' : vueVersion === '3' ? 'vue3' : ''; if(obj.MVCframework === 'vue27'){ if (dependencies['element-ui']) { obj.UIlib = 'elementUI'; } } else if(obj.MVCframework === 'vue3'){ if (dependencies['ant-design-vue']) {//不支持ant了,因为2.7打包后运行报错 obj.UIlib = 'AntDesignVue'; } else if(dependencies['element-plus']) { obj.UIlib = 'ElementPlus'; } } } else if (dependencies.react) { const reactVersion = dependencies.react.match(/\d+/)?.[0]; obj.MVCframework = reactVersion === '16' ? 'react16' : reactVersion === '19' ? 'react19' : ''; if(obj.MVCframework == 'react16'){ if (dependencies['react-desktop']) { obj.UIlib = 'react-desktop'; } } else if(obj.MVCframework == 'react19'){ if (dependencies['antd']) { obj.UIlib = 'AntDesign'; } else if (dependencies['react-bootstrap']) { obj.UIlib = 'react-bootstrap'; } else if(dependencies['@mui/material']){ obj.UIlib = 'Material-UI'; } } } } catch (err) { console.error('read package.json fail:', err); } } return obj; } // 解析命令行参数 const args = process.argv.slice(2); const shouldLaunchElectron = args.includes('--desktop'); const shouldOpen = args.includes('--open'); const {MVCframework} = projectType(path.join(process.cwd(),'..','package.json')); let electronStarted = false; // let electronProcess = null; let viteProcess = null; function killProcessOnPort(port) { const { exec } = require('child_process'); exec(`netstat -ano | findstr :${port}`, (err, stdout, stderr) => { if (err || !stdout) { console.log(`The process occupying port ${port} was not found`); return; } // 解析 PID const lines = stdout.trim().split('\n'); const pids = new Set(); lines.forEach(line => { const parts = line.trim().split(/\s+/); const pid = parts[parts.length - 1]; if (pid && !isNaN(pid)) { pids.add(pid); } }); if (pids.size === 0) { console.log(`The process occupying port ${port} was not found`); return; } // 杀掉所有相关进程 pids.forEach(pid => { if(pid==0){ return; } exec(`taskkill /F /PID ${pid}`, (killErr) => { if (killErr) { // console.log(`杀掉进程 ${pid} 失败: ${killErr.message}`); } else { // console.log(`成功杀掉占用端口 ${port} 的进程 PID: ${pid}`); } }); }); }); } // 清理进程的函数 async function cleanup() { // console.log('开始清理进程...'); // 关闭文件监听器 if (fileWatcher) { await fileWatcher.close(); } await electronManager.stop(); // 使用管理器停止 // 如果 Electron 进程存在,杀掉它 // if (electronProcess) { // try { // process.kill(electronProcess.pid); // console.log('✅ Electron 进程已清理'); // } catch (err) { // console.log(`清理 Electron 进程失败: ${err.message}`); // } // } // 查找并杀死所有相关的 node 进程 // try { // // Windows 下使用 taskkill 命令 // exec('taskkill /F /IM node.exe', (error, stdout, stderr) => { // if (error) { // console.log(`清理 node 进程失败: ${error.message}`); // return; // } // console.log('✅ node 进程已清理'); // }); // } catch (err) { // console.log(`执行清理命令失败fail 123: ${err.message}`); // } // 尝试删除 dev-server.json const serverInfoPath = path.resolve(process.cwd(), 'main/dev-server.json'); if (fs.existsSync(serverInfoPath)) { const content = fs.readFileSync(serverInfoPath, 'utf-8'); const json = JSON.parse(content); const url = json.url; // 例如 "http://localhost:5174" const port = url.match(/:(\d+)/) ? url.match(/:(\d+)/)[1] : null; killProcessOnPort(port); } if (fs.existsSync(serverInfoPath)) { try { fs.unlinkSync(serverInfoPath); console.log('deleted dev-server.json'); } catch (err) { console.log(`delete dev-server.json fail: ${err.message}`); } } // 确保所有进程都有时间被清理 await new Promise(resolve => setTimeout(resolve, 1000)); } // 注册进程退出事件 process.on('SIGINT', async () => { await cleanup(); process.exit(0); }); process.on('SIGTERM', async () => { await cleanup(); process.exit(0); }); process.on('exit', cleanup); let viteElectronServe = ['vue27','react16'].includes(MVCframework) ? 'vite.electron.serve.config.js' : 'vite.electron.serve.config.ts'; // 启动 Vite 服务 const viteArgs = ['vite','--config', viteElectronServe]; if (shouldOpen) { viteArgs.push('--open'); } console.log('Start the Vite develope service...'); viteProcess = spawn('npx', viteArgs, { cwd: path.join(process.cwd(), 'windows'), stdio: ['inherit', 'pipe', 'pipe'], shell: true }); // 监听 Vite 服务的输出 viteProcess.stdout.on('data', (data) => { process.stdout.write(data); if(data.indexOf('Local')>-1){ // 检查 dev-server.json 是否存在 const serverInfoPath = path.resolve(process.cwd(), 'main/dev-server.json'); if (!electronStarted && shouldLaunchElectron && fs.existsSync(serverInfoPath)) { electronStarted = true; // console.log('The Vite service has been started and Electron is being started...'); electronManager.start(); // 使用管理器启动 fileWatcher.initialize(); } } }); // 监听 Vite 服务的错误 viteProcess.stderr.on('data', (data) => { console.log("vite error -- >",data.toString()) process.stdout.write(data); }); // 监听 Vite 服务退出 viteProcess.on('exit', async (code) => { console.log(`Vite Service has exited, exit code: ${code}`); await cleanup(); process.exit(code); });