Serving static files in Electron (React app)

18,331

Solution 1

I found another solution without using express or serve-static, we only need to cusomize Electron built-in interceptFileProtocol() to serve static contents.

Code:(main.js)

(I use the electron-quick-start as Electron template)

function createWindow () {
  window = new BrowserWindow({ width: 800, height: 600 })
  window.loadURL(url.format({
    pathname: 'index.html',    /* Attention here: origin is path.join(__dirname, 'index.html') */
    protocol: 'file',
    slashes: true
  }))

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

app.on('ready', () => {
  protocol.interceptFileProtocol('file', (request, callback) => {
    const url = request.url.substr(7)    /* all urls start with 'file://' */
    callback({ path: path.normalize(`${__dirname}/${url}`)})
  }, (err) => {
    if (err) console.error('Failed to register protocol')
  })
  createWindow()
})

Reference: protocol.interceptFileProtocol()

Explaination:

  • Normally, if you run React app as a normal website, all static contents should be served by HTTP [GET] method. Though they use relative paths, your HTTP server will handle the path parsing work.

  • However, when running under Electron, things change.

  • Your static contents usually use relative path like ./picture.jpg, Electron will use file protocol instead of HTTP protocol and find the file under root path like C://.//. So static contents like ./picture.jpg won't be loaded correctly.

  • By customizing interceptFileProtocol(), all static contents' requests will be pointed to your working directory instead of Windows(or other OS) root.

Finally, I'm not sure whether it's a good solution for all Electron projects, but if you already have a React project (or some other SPA) and want to wrap it with Electron, this solution would be fine to use.

Solution 2

As an addition to the great answer from @yeze322 above, here a working sample for all not so familiar with node and electron (like me). It took me some time to find out the correct require statements.

main.js (code from @yeze322 plus required imports)

const { app, BrowserWindow, protocol } = require('electron')
const path = require('path')
const url = require('url')

let mainWindow

function createWindow() {
  mainWindow = new BrowserWindow({ width: 800, height: 600 })

  mainWindow.loadURL(url.format({
    pathname: 'index.html',    /* Attention here: origin is path.join(__dirname, 'index.html') */
    protocol: 'file',
    slashes: true
  }))

  mainWindow.on('closed', function () {
    mainWindow = null
  })
}

app.on('ready', () => {
  protocol.interceptFileProtocol('file', (request, callback) => {
    const url = request.url.substr(7)    /* all urls start with 'file://' */
    callback({ path: path.normalize(`${__dirname}/${url}`) })
  }, (err) => {
    if (err) console.error('Failed to register protocol')
  })
  createWindow()
})

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

app.on('activate', function () {
  if (mainWindow === null) {
    createWindow()
  }
})

Solution 3

In your main file you have

const app = require("app")
app.on("ready", () => {
  ...

Here you can start the server like you would do in node.js

  const serveStatic = require('serve-static')
  // or
  const express = require('express')
  ...
}
Share:
18,331
Ales Maticic
Author by

Ales Maticic

I can bootstrap a startup prototype in couple of weeks all by myself. I do everything from server administration, platform development to design presentation. My blog http://www.7loops.com/

Updated on June 15, 2022

Comments

  • Ales Maticic
    Ales Maticic almost 2 years

    I am working on a project where I need to build a desktop app in Electron. The majority of functionality will be built in React, but there will be a part where we need to integrate a 3rd party static HTML magazine. I need some advice on how to do this. I am building a proof of concept app currently and I have based it on this https://github.com/chentsulin/electron-react-boilerplate

    how would I add that on /static/ I server static HTML files. I know I could do it in express, but I really don't want to include the entire express framework just for serving static files.

    I was looking at this https://www.npmjs.com/package/serve-static but have no Idea how to integrate it in my react app and bundle it into electron app.

  • Chris Dolphin
    Chris Dolphin over 6 years
    This worked for me, but then I had trouble requiring my index js file in index.html using relative paths. Ended up requiring it using an absolute, by getting app path with require('electron').remote.app.getAppPath()
  • Marcelo H. Carneiro
    Marcelo H. Carneiro almost 6 years
    Where u added it @ChrisDolphin? I with a big problem here, works fine with dev mode but after pack the app doesn't work, driving me crazy
  • Marcelo H. Carneiro
    Marcelo H. Carneiro almost 6 years
    Works here, i changed the url = path.join(require('electron').remote.app.getAppPath(), WEB_FOLDER, url), thanks, works when packing the app
  • Marcelo H. Carneiro
    Marcelo H. Carneiro almost 6 years
    Works here, i changed the url = path.join(require('electron').remote.app.getAppPath(), WEB_FOLDER, url), thanks, works when packing the app
  • shivshankar
    shivshankar almost 5 years
    On development mode, serving using webpack give me error "can't find module react", mean module resolve issue while without intercept protocol it works, any idea ?
  • davidkomer
    davidkomer over 4 years
    Nothing personal - but this should really just be a comment on @yeze322's answer. I spent a bit of time looking between the two answers trying to figure out which I should use ;)
  • davidkomer
    davidkomer over 4 years
    Couple points: 1. would be nicer to incorporate all the imports etc. like @zgue showed. 2. another problem I ran into is that it only works like this if webpack html template does not inject the hash - so make sure that's off ;)
  • Eric Burel
    Eric Burel about 4 years
    Hi, I found it easier to create a custom app protocol so it does not interfere with file://, using registerFileProtocol (syntax is identical to interceptFileProtocol). Also you may need to remove the query param of URL for URLs like app://foobar.js?version=2, you should drop the ?version=2. It was useful for me to install MathJax in an Electron app for example.
  • punjabi4life
    punjabi4life over 3 years
    Thanks, I was able to use this to run a Blazor WebAssembly app through Electron. After making the Blazor WebAssembly app into static files.