React项目集成Electron-APP,打包好的react项目直接使用index.html loadurl 出现js,css等资源找不到的情况解决方案

概要

最近做了一个项目,是已有的react项目,需要改造成electron-app,再加上一些客户端的功能,例如打开点击图标打开某个具体的应用,或者打开某个目录。

首先,需要新建一个electron-app, 这个目录可以新建,与原先的的React项目可以分开。关于如何新建electron-app这里不做赘述。

在你的electron-app的main.js里写下如下内容:

  const win = new BrowserWindow({
      width: 1920,
      height: 1080,
      minHeight: 632,
      minWidth: 960,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      webSecurity: false // 禁用webSecurity,避免CORS错误
    }
  });
  win.loadURL(entryPath );

其中serverurl是你的项目地址,例如,一下的dist文件是我的react项目打包后的文件

  const entryPath = path.resolve(__dirname, '../dist/index.html')
  win.loadFile(entryPath)

如果你的项目地址支持,可以直接上线,那serverURL就会是 htttp://xx.xx.xx.xx:xxxx
由于我自身的项目不支持,同时直接引入dist下index.html,将会导致你在本地运行时,页面无法直接引用index.html中的js文件。
所以我找了种可以避免使用线上react地址的方式,也就是每次打开应用则新开一个端口用作重定向,所有index.html以及相应的资源可以通过重定向的方式进行查找。
在你的main.js中先进行服务器的启动

  // 启动HTTP服务器
  console.log('Starting HTTP server...');
  serverProcess = exec('node server.mjs', { cwd: __dirname }, (error, stdout, stderr) => {
    if (error) {
      console.error('Server start error:', error);
    }
  });
    // 等待服务器启动,然后加载URL
  setTimeout(() => {
    // 加载HTTP服务器提供的文件,避免CORS错误
    let serverPort = 3002;
    const portFile = path.resolve(__dirname, 'server-port.txt');

    // 读取服务器实际使用的端口
    if (fs.existsSync(portFile)) {
      serverPort = parseInt(fs.readFileSync(portFile, 'utf8'));
      console.log('Server port from file:', serverPort);
    }

    const serverURL = `http://localhost:${serverPort}/`;
    console.log('Loading URL:', serverURL);
    win.loadURL(serverURL);
  }, 2000);

server.mjs 是实际执行启动服务器的关键文件。其中内容是

import http from 'http';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';

// 获取当前文件的目录路径
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

let port = 3002;
const outDir = path.join(__dirname, '../out');

// 创建服务器
const server = http.createServer((req, res) => {
  // 解析请求路径
  let filePath = path.join(outDir, req.url === '/' ? 'index.html' : req.url);
  
  // 检查文件是否存在
  fs.stat(filePath, (err, stats) => {
    if (err) {
      // 文件不存在,返回index.html(支持单页应用路由)
      filePath = path.join(outDir, 'index.html');
    } else if (stats.isDirectory()) {
      // 如果是目录,返回index.html
      filePath = path.join(filePath, 'index.html');
    }
    
    // 读取文件
    fs.readFile(filePath, (err, content) => {
      if (err) {
        res.writeHead(404, { 'Content-Type': 'text/plain' });
        res.end('File not found');
        return;
      }
      
      // 设置Content-Type
      const extname = path.extname(filePath);
      let contentType = 'text/html';
      
      switch (extname) {
        case '.js':
          contentType = 'text/javascript';
          break;
        case '.css':
          contentType = 'text/css';
          break;
        case '.json':
          contentType = 'application/json';
          break;
        case '.png':
          contentType = 'image/png';
          break;
        case '.jpg':
        case '.jpeg':
          contentType = 'image/jpeg';
          break;
        case '.gif':
          contentType = 'image/gif';
          break;
        case '.svg':
          contentType = 'image/svg+xml';
          break;
      }
      
      res.writeHead(200, { 'Content-Type': contentType });
      res.end(content, 'utf-8');
    });
  });
});

// 尝试启动服务器,直到找到可用端口
function startServer() {
  server.listen(port, 'localhost', () => {
    console.log(`Server running at http://localhost:${port}/`);
    console.log('Press Ctrl+C to stop the server');
    
    // 将端口信息保存到临时文件,供Electron应用读取
    fs.writeFileSync(path.join(__dirname, 'server-port.txt'), port.toString());
  });

  server.on('error', (err) => {
    if (err.code === 'EADDRINUSE') {
      console.log(`端口 ${port} 被占用,尝试使用端口 ${port + 1}`);
      port++;
      startServer();
    } else {
      console.error('服务器启动失败:', err);
    }
  });
}

// 启动服务器
startServer();

这段代码的主要逻辑是启动一个本地服务器,占用机器的端口,然后对应用内容的资源重定向,以防直接查看文件查找不到。同时,以防本地端口被占用,每次启动时都会查看端口是否被占用,如果占用的话使用新端口,server.port.text文件则用来存放端口信息,供loadUrl使用。

更多推荐