Electron: Custom protocol and require for local files

Created on 17 Feb 2016  ·  3Comments  ·  Source: electron/electron

I'm new to electron and trying to use custom protocols for my application and distributed client-side js files, that are required. However, I'm not able to load the modules, because require is ignoring my custom scheme. This is my environment:

lib/ - node scripts
  main.js - node entry script
resources/ - client side resources
  css/
  html/
    index.html
  js/ - client side scripts
    main.js
    test.js
package.json

lib/main.js is based on the example from the public homepage:

// lib/main.js
'use strict';

const electron = require('electron');
const app = electron.app;  // Module to control application life.
const BrowserWindow = electron.BrowserWindow;  // Module to create native browser window.
const path = require('path');

var mainWindow = null;

app.on('ready', function() {
  const protocol = electron.protocol;

  /* js, css, fonts, ... */
  protocol.registerFileProtocol('resource', (request, callback) => {
    var url = request.url.substr(11);
    callback({path: path.join(__dirname, '../resources', url)});
  });

  /* html, possible rendered from other files like markdown or jade */
  protocol.registerFileProtocol('view', (request, callback) => {
    var url = request.url.substr(11);
    callback({path: path.join(__dirname, '../resources/html', url)});
  });


  // Create the browser window.
  mainWindow = new BrowserWindow({width: 800, height: 600});

  // and load the index.html of the app.
  mainWindow.loadURL('view://index.html');

  // Open the DevTools.
  mainWindow.webContents.openDevTools();

  // ...
});

Note that the process behind the view:// protocol is going to compile other files into html in the future, such as markdown or jade (view://index.jade will compile the jade file and return html string).

<!-- resources/html/index.html -->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Electron Test</title>
  </head>
  <body>
    <h1>Electron Test</h1>

    <script src="resource://js/main.js"></script>
  </body>
</html>

The html file will access resources like css or js files via my custom resource:// protocol. The main file is executed correctly, however I can't require another file there:

// resources/js/main.js
const test = require('./test'); // Error: Cannot find module './test'

test.sayHello();
// resources/js/test.js
exports.sayHello = function() {
  console.log('Hello!');
};

I'm executing it with electron ., v0.36.7.

When inspecting the code behind require in the devtools (around module.js:336, line 337 in node's current master) when the error occurs, request is "./test" and paths only contains one entry for electron's core modules, and I couldn't find a way to extend that list of search paths.

I've checked other issues (#4459, #2539) that seem to be about the same issue, but they didn't help me (e.g. using a preload script manipulating module's path or globalPaths does not work.

Any ideas how to get this working? Or would you recommend using a single JavaScript file? (I am using TypeScript as a preprocessor, that can concatenate all source files).

Most helpful comment

I found this solution for whomever it may concern:
The WEB_FOLDER contains the index.html and all other relative referenced content.
You can set it to any empty string if your index file isn't located inside a sub folder (web in this example)

function createWindow() {
  const WEB_FOLDER = 'web';
  const PROTOCOL = 'file';

  electron.protocol.interceptFileProtocol(PROTOCOL, (request, callback) => {
      // // Strip protocol
      let url = request.url.substr(PROTOCOL.length + 1);

      // Build complete path for node require function
      url = path.join(__dirname, WEB_FOLDER, url);

      // Replace backslashes by forward slashes (windows)
      // url = url.replace(/\\/g, '/');
      url = path.normalize(url);

      console.log(url);
      callback({path: url});
  });

  // Create the browser window.
  mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: false
    }
  });

  // and load the index.html of the app.
  mainWindow.loadURL(url.format({
    pathname: 'index.html',
    protocol: PROTOCOL + ':',
    slashes: true
  }));

All 3 comments

You can check the search paths of Node modules in module.paths, if your resources/js is not in it, then require won't be able to find test.js.

I think it is because you didn't manipulate the module search paths correctly, you might be a bee to get some ideas in the community.

When you load a .js file through a <script> tag it's embedded in the document, so

<body>
  <h1>Electron Test</h1>
  <script src="resource://js/main.js"></script>
</body>

is functionally equivalent to

<body>
  <h1>Electron Test</h1>
  <script>
    const test = require('./test'); // Error: Cannot find module './test'
    test.sayHello();
  </script>
</body>

Which means that ./test is resolved relative to index.html, not relative to js/main.js. To fix this you can do

<body>
  <h1>Electron Test</h1>
  <script>
    require('../js/main.js');
  </script>
</body>

I found this solution for whomever it may concern:
The WEB_FOLDER contains the index.html and all other relative referenced content.
You can set it to any empty string if your index file isn't located inside a sub folder (web in this example)

function createWindow() {
  const WEB_FOLDER = 'web';
  const PROTOCOL = 'file';

  electron.protocol.interceptFileProtocol(PROTOCOL, (request, callback) => {
      // // Strip protocol
      let url = request.url.substr(PROTOCOL.length + 1);

      // Build complete path for node require function
      url = path.join(__dirname, WEB_FOLDER, url);

      // Replace backslashes by forward slashes (windows)
      // url = url.replace(/\\/g, '/');
      url = path.normalize(url);

      console.log(url);
      callback({path: url});
  });

  // Create the browser window.
  mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: false
    }
  });

  // and load the index.html of the app.
  mainWindow.loadURL(url.format({
    pathname: 'index.html',
    protocol: PROTOCOL + ':',
    slashes: true
  }));
Was this page helpful?
0 / 5 - 0 ratings

Related issues

rhnorskov picture rhnorskov  ·  3Comments

reggi picture reggi  ·  3Comments

wsangtoki picture wsangtoki  ·  3Comments

christiangenco picture christiangenco  ·  3Comments

etiktin picture etiktin  ·  3Comments