0

I would like to make use of a function called executeJavaScript() from the Electron webContents API. Since it is very close to eval() I will use this in the example.

The problem:

  • I have a decent sized script but it is contained in a template string.
  • Expanding this app, the script could grow a lot as a string.
  • I am not sure what the best practices are for this.

I also understand that eval() is dangerous, but I am interested in the principal of my question.

Basic eval example for my question:

// Modules
const fs = require('fs');

// CONSTANTS
const EXAMPLE_1 = 'EXAMPLE_1';
const EXAMPLE_2 = 'EXAMPLE_2';
const EXAMPLE_3 = 'EXAMPLE_3';

const exampleScriptFunction = require('./exampleScriptFunction');
const exampleScriptFile = fs.readFileSync('./exampleScriptFile.js');

// using direct template string
eval(`console.log(${EXAMPLE_1})`);

// using a method from but this doesnt solve the neatness issue.
eval(exampleScriptFunction(EXAMPLE_2));

// What I want is to just use a JS file because it is neater.
eval(`${exampleScriptFile}`);

exampleScriptFunction.js

module.exports = function(fetchType) {
  return `console.log(${fetchType});`;
}
  • This will allow me to separate the script to a new file
  • what if I have many more then 1 variable???

exampleScriptFile.js:

console.log(${EXAMPLE_3});
  • This clearly does not work, but I am just trying to show my thinking.
  • back ticks are not present, fs loads as string, main file has back ticks.
  • This does not work. I do not know how else to show what I mean.
  • Because I am loading this will readFileSync, I figured the es6 template string would work.
  • This allows me to write a plain js file with proper syntax highlighting
  • The issue is the variables are on the page running the eval().

Perhaps I am completely wrong here and looking at this the wrong way. I am open to suggestions. Please do not mark me minus 1 because of my infancy in programming. I really do not know how else to ask this question. Thank you.

Michael Bruce
  • 10,567
  • 2
  • 23
  • 31
  • Possible duplicate of [Convert a string to a template string](https://stackoverflow.com/questions/29182244/convert-a-string-to-a-template-string) – tavnab Jun 22 '17 at 14:58
  • Well, in your last example, you don't have the backticks to denote a template string. It should be ``console.log(`${EXAMPLE_3}`);`` – Patrick Roberts Jun 22 '17 at 14:58
  • This is because the script file does not have the variable. I used the template strings in the main file. – Michael Bruce Jun 22 '17 at 14:59
  • `eval()` executes the script in the current context, which does have the variable, so that should work. – Patrick Roberts Jun 22 '17 at 15:00
  • 1
    What about just `eval(exampleScriptFile)`, where, as @PatrickRoberts said, your exampleScriptFile.js includes the backticks? – tavnab Jun 22 '17 at 15:02
  • tavnab I am very sure I tried this with executeJavaSctipt(), let me go back and make sure. I believe that it acts different from eval in this way. Otherwise, yes it would work. – Michael Bruce Jun 22 '17 at 15:09
  • Just a general comment (and not challenging the value of this question for the sake of learning) but this smells like an anti-pattern. There's likely a cleaner way to do this by passing in the parameters you need to a function your script exports, rather than using `eval` and template strings. – tavnab Jun 22 '17 at 15:10
  • tavnab, I HOPE that this is and I can learn something here. Just so you know, in my app is not using eval. I just wanted something that people can try out easily. – Michael Bruce Jun 22 '17 at 15:13
  • A quick scan of the electron source code suggests that `executeJavaScript` operates _very_ differently from `eval`. The former triggers an IPC message into another process, where the code is executed, presumably outside any context that the caller would've had. I haven't looked deeply beyond the IPC call, but I think you're right in that what works for `eval` might not extend to `executeJavaScript` here. – tavnab Jun 22 '17 at 15:19
  • Yes, I think it is different, but perhaps I can use eval inside the executeJavaScript(). Someone flagged this as a duplicate and there is a eval('`' + variable + '`') type of answer. I am going to figure this out and come back. – Michael Bruce Jun 22 '17 at 15:21

2 Answers2

1

Assuming your source is stored in exampleScriptFile:

// polyfill
const fs = { readFileSync() { return 'console.log(`${EXAMPLE_3}`);'; } };

// CONSTANTS
const EXAMPLE_1 = 'EXAMPLE_1';
const EXAMPLE_2 = 'EXAMPLE_2';
const EXAMPLE_3 = 'EXAMPLE_3';

const exampleScriptFile = fs.readFileSync('./exampleScriptFile.js');

// What I want is to just use a JS file because it is neater.
eval(exampleScriptFile);

Update

Perhaps I wasn't clear. The ./exampleScriptFile.js should be:

console.log(`${EXAMPLE_3}`);
Patrick Roberts
  • 49,224
  • 10
  • 102
  • 153
  • Patrick, yes, this works, but it does not solve my main issue. I would like a file that has actual Javascript (not a string) and then perhaps load this file as a string into the function. However, the file needs to evaluate variables. This is where I am open to ideas. – Michael Bruce Jun 22 '17 at 15:16
  • I think @PatrickRoberts is suggesting you populate `exampleScriptFile` with the output of `fs.readFileSync('./exampleScriptFile.js')` which would have the contents he explicitly set it to in his answer. – tavnab Jun 22 '17 at 15:21
  • See update. You're misunderstanding what's going on. – Patrick Roberts Jun 22 '17 at 15:21
  • @MichaelBruce if this suggestion doesn't work for you, you should ask yourself what is the expected behavior if you call `require('./exampleScriptFile');` In my opinion, it should be valid syntax that throws a reference error due to the attempt to use an undefined variable. – Patrick Roberts Jun 22 '17 at 15:28
  • Hmmm.. I will check this out today. But like I mentioned before, I am using executeJavaScript, not eval(). I will get back with what I find. – Michael Bruce Jun 22 '17 at 15:30
0

While what you're describing can be done with eval as @PatrickRoberts demonstrates, that doesn't extend to executeJavaScript.

The former runs in the caller's context, while the latter triggers an IPC call to another process with the contents of the code. Presumably this process doesn't have any information on the caller's context, and therefore, the template strings can't be populated with variables defined in this context.

Relevant snippets from electron/lib/browsers/api/web-contents.js:

WebContents.prototype.send = function (channel, ...args) {
  // ...
  return this._send(false, channel, args)
}

// ...

WebContents.prototype.executeJavaScript = function (code, hasUserGesture, callback) {
  // ...
  return asyncWebFrameMethods.call(this, requestId, 'executeJavaScript',
  // ...
}

// ...

const asyncWebFrameMethods = function (requestId, method, callback, ...args) {
  return new Promise((resolve, reject) => {
    this.send('ELECTRON_INTERNAL_RENDERER_ASYNC_WEB_FRAME_METHOD', requestId, method, args)
    // ...
  })
}

Relevant snippets from electron/atom/browser/api/atom_api_web_contents.cc

//...
void WebContents::BuildPrototype(v8::Isolate* isolate,
                                 v8::Local<v8::FunctionTemplate> prototype) {
  prototype->SetClassName(mate::StringToV8(isolate, "WebContents"));
  mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
      // ...
      .SetMethod("_send", &WebContents::SendIPCMessage)
      // ...
}
tavnab
  • 2,594
  • 1
  • 19
  • 26