Is there a way to implement fs in html script? - electron

I am trying to start a music player. For that I created a json-file that describes the path where the songs are located. Also the amount of songs that I have and the author.
{
"songs":[
{
"author": "Guns and Roses",
"name": "Welcome to the Jungle",
"url": "./Resources/welcome-to-the-jungle.mp3"
}
]
}
I am building this application in electron. In the html-file I have added one script with the name 'functions.js'.
<script type="module" src="functions.js"></script>
In that function I wanted to import the module fs and read my json-file to start working. But I am missing something. I get a message error that says I can't import the fs-module.
import fs from 'fs'
const file = fs.readFileSync('./Resources/metadata.json')
I tried also,
import fs from '../node_modules/fs'
const file = fs.readFileSync('./Resources/metadata.json')
and also add a script to the html file index.js
<script src="fs.js"></script>
But nothing works... what is the correct way to import the library into my script?

Import it with require
const fs = require('fs')
If it throws the error saying require is not defined, enable node integration in your main process, this will allow your window to use Node's require function:
mainWindow = new BrowserWindow({
...
webPreferences: {
nodeIntegration: true
}
})

Related

How to call a function is renderer.js/main.js from web page in electron

Newbie to electron here, I have built a simple web application with React JS and able to view it in a window by calling
window.loadFile('./build/index.html');
Now i would want to call a function located in say renderer.js/main.js which should read file system and return data to the web application.
I have already tried this in renderer.js
const { ipcRenderer } = require('electron');
document.getElementById('#button').addEventListener('click', function (event) {
//read file contents
console.log('file contents');
});
But there are 2 issues around here
The control is from the renderer.js, instead i would want the
control to be on the web page of React.
The data that is read should be returned back to web page, so that it can be displayed in the web page.
You should be able to import/require ipcRenderer directly on your react component scripts and maybe load the file on a lifecycle hook. The 'renderer.js' is just one way to execute client side javascript on the (electron-)web page but any other way also does the trick.
If you can't import or require electron from the webapp (I didn't play with the electron-react-boilerplate yet), then you could write a so called preload script, load that when you create the browser window (see that documentation) and put the ipcRenderer on window like so:
const {ipcRenderer} = require('electron')
window.ipcRenderer = ipcRenderer
Then you can access it from the react app.
You could directly use fs inside the event listener
const { ipcRenderer } = require('electron');
const fs = require("fs");
document.getElementById('#button').addEventListener('click', function (event) {
console.log(fs.readFileSync("some/file/path", "utf8"));
});
Electron exposes full access to Node.js both on main and renderer process
https://www.electronjs.org/docs/tutorial/application-architecture#using-nodejs-apis
You are building your electron renderer using React.
Please check this to clear what main process is and renderer is.
how to communicate between react and electron
This is the answer I posted before. Feel free to check this.
And here is the pre-requirement.
let mainWindow = new BrowserWindow(
{
width: 800,
height: 600,
webPreferences:{
nodeIntegration:true
}
});
You should enable the nodeIntegration flag when you are creating the BrowserWindow. So that you can use any Nodejs API at your renderer

nodeIntegration true ignored?

I'm trying to use a require directive in my js file attached to an html file. However, I am getting a require not defined error message with that file. My understanding is in newer versions of Electron after 5.0 that nodeIntegration was disabled by default. However, even after enabling nodeIntegration in web preferences I still am getting the require error message. My understanding was this nodeIntegration should solve that issue. Why am I still running into not defined issues with require? Here's the relevant section of the main.js file.
EDIT: I was missing a comma between preload and nodeIntegration, so thanks to the individual who pointed that out! However, I am still experiencing the issue. Still getting "Uncaught ReferenceError: require is not defined".
Second EDIT: Here's a minimal reproduction of the issue. The require function is undefined even when nodeIntegration is set to override it. In the end I'm just trying to read from a local json file but every instance I can find to do so in a simplistic manner when working with electron uses a require in some way, whether it is requiring fs or requiring the file. If the require statement won't function at all I can't make either work.
main.js:
// Modules to control application life and create native browser window
const {app, BrowserWindow} = require('electron')
const path = require('path')
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow
function createWindow () {
// Create the browser window.
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration:true
}
})
// and load the index.html of the app.
mainWindow.loadFile('index.html')
// Open the DevTools.
// mainWindow.webContents.openDevTools()
// Emitted when the window is closed.
mainWindow.on('closed', function () {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null
})
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow)
// Quit when all windows are closed.
app.on('window-all-closed', function () {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') app.quit()
})
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) createWindow()
})
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<link href="https://fonts.googleapis.com/css?family=Roboto&display=swap" rel="stylesheet">
<link href="main.css" rel="stylesheet">
<title>Destiny Guide</title>
<script src="problem.js"></script>
</head>
<body>
<h1>Hello World</h1>
Hello World
<br>
<h1>helloWorld<h1>
<!-- You can also require other files to run in this process -->
<script src="./renderer.js"></script>
</body>
</html>
problem.js:
var dataArc = require("./ZeroHourArc.json");
Third, and Last, EDIT: see answer below.
Just try to add contextIsolation: false like this:
mainWindow = new BrowserWindow({
width: 500,
height: 500,
icon: './public/logo.png',
backgroundColor: 'white',
webPreferences: {nodeIntegration: true, contextIsolation: false}
});
In Electron 12, contextIsolation will be enabled by default, so require() cannot be used in the renderer process unless nodeIntegration = true and contextIsolation = false.
For more details see: https://github.com/electron/electron/issues/23506
I'm a bit oblivious. I was editing a random file that had the same name and layout as my main.js but it wasn't in the same place. Editing the correct file in the right place, same name, allowed me to make nodeIntegration work as expected.

Need help importing Electron in Aurelia app

I'm using one of the skeleton-navigation, skeleton-typescript.
I'm trying to import Electron.remote so I can close the electron window from within the JS. This is what I have in config.js:
paths: {
"*": "dist/*",
"github:*": "jspm_packages/github/*",
"npm:*": "jspm_packages/npm/*",
"node_modules:*": "node_modules/*"
},
map: {
"electron": "node_modules:electron/index.js",
}
and in my JS file I import like this:
import * as electron from 'electron';
but I get error regarding fs.js not found in path:
Error: (SystemJS) XHR error (404 Not Found) loading http://localhost:9000/dist/fs.js
Can someone help on how I can fix this issue?
depends on the loader/bundler strategy you picked
electron has nodes require() defined.
you want to redefine that before booting up your app that relies on AMD require
https://github.com/electron/electron/issues/303
TL;DR
you want to assign nodes require to another variable
window.node_require = require
and then delete the original
delete require
only after this you reference a script with your app
and inside your app you use node_require() to load node modules
here is the relevant comment on: supporting electron modules in aurelia
This is how I fixed my issue for Aurelia Skeleton Typescript with JSPM & SystemJS: I put in index.html head which is my entry:
<script type="text/javascript">
window.node_require = require;
delete window.require;
delete window.exports;
delete window.module;
</script>
Then I set nodeIntegration: true for BrowserWindow.
And in my TS file:
declare global {
interface Window {
node_require: any;
}
}
var remote: any;
if (typeof window.node_require == "function") {
remote = window.node_require('electron').remote;
}
closeApp() {
var window = remote.getCurrentWindow();
window.close();
}

Yeoman: how to avoid file overwrite conflict message when modifying file created by subgenerator?

Overview
I am creating a yeoman generator that will generate a customized angular app. It will be generating custom angular controllers and services.
While I could simply take a current angular template, as generated by the angular generator, add my code, and then save this as a template in my generator, I prefer to have the generator call the "official" angular generator (previously installed by the user) as a subgenerator at runtime, so I don't have to synchronize my templates every time the angular generator template changes.
Problem
I have this working, but the only problem is when, from my generator, I add my code to the file generated by the subgenerator (e.g a controller), I get the message:
conflict app/scripts/controllers/mycontroller.js
? Overwrite app/scripts/controllers/mycontroller.js? overwrite
force app/scripts/controllers/mycontroller.js
Is there a way I can get the file generated by the subgenerator before it's written to disk, so I can edit it, without generating the overwrite prompt?
I think it's confusing to be prompted about overwriting a file that is being created for the first time.
Data
In reading the yeoman doc, it makes it sound like generators and subgenerators share the same "soft" in-memory filesystem:
"As asynchronous APIs are harder to use, Yeoman provide a synchronous file-system API where every file gets written to an in-memory file system and are only written to disk once when Yeoman is done running. This memory file system is shared between all composed generators."
But it appears that the subgenerator writes the files to disk and not the in-memory file system. Unfortunately, I do not have control over the subgenerator, as it's the standard angular yeoman generator.
Here is my generator (scroll down to portion marked "Relevant Portion"):
'use strict';
var yeoman = require('yeoman-generator');
var chalk = require('chalk');
var yosay = require('yosay');
module.exports = yeoman.generators.Base.extend({
prompting: function () {
var done = this.async();
// Have Yeoman greet the user.
this.log(yosay(
'Welcome to the epic ' + chalk.red('AngularVr') + ' generator!'
));
var prompts = [{
type: 'confirm',
name: 'someOption',
message: 'Would you like to enable this option?',
default: true
}];
this.prompt(prompts, function (props) {
this.props = props;
done();
}.bind(this));
},
writing: {
app: function () {
this.fs.copy(
this.templatePath('_vt_marker.json'),
this.destinationPath('vt_marker.json')
);
},
====================== Relevant Portion ===============
subgenerators: function () {
this.log("now executing subgenerators");
this.composeWith('angular:controller', {args: ['mycontroller']} );
this.log("now done with subgeneration");
},
subgenerators_read: function () {
// add a new line to generated file..this generates overwrite prompt
var fp = this.destinationPath('app/scripts/controllers/mycontroller.js');
var fc = this.fs.read(fp);
fc += '\nhello there\n';
this.fs.write(fp, fc);
},
});
This is my first attempt at writing a yeoman generator.
Many Thanks.
The issue is probably just that either your generator or the generator-angular is running on an old version of yeoman-generator.
Latest version at this time is 0.20.3 https://www.npmjs.com/package/yeoman-generator

Using console.log() in Electron app

How can I log data or messages to the console in my Electron app?
This really basic hello world opens the dev tools by default, by I am unable to use console.log('hi'). Is there an alternative for Electron?
main.js
var app = require('app');
var BrowserWindow = require('browser-window');
require('crash-reporter').start();
var mainWindow = null;
app.on('window-all-closed', function() {
// Mac OS X - close is done explicitly with Cmd + Q, not just closing windows
if (process.platform != 'darwin') {
app.quit();
}
});
app.on('ready', function(){
mainWindow = new BrowserWindow({ width: 800, height: 600});
mainWindow.loadUrl('file://' + __dirname + '/index.html');
mainWindow.openDevTools();
mainWindow.on('closed', function(){
mainWindow = null;
});
});
console.log works, but where it logs to depends on whether you call it from the main process or the renderer process.
If you call it from the renderer process (i.e. JavaScript that is included from your index.html file) it will be logged to the dev tools window.
If you call it from the main process (i.e. in main.js) it will work the same way as it does in Node - it will log to the terminal window. If you're starting your Electron process from the Terminal using electron . you can see your console.log calls from the main process there.
You can also add an environment variable in windows:
ELECTRON_ENABLE_LOGGING=1
This will output console messages to your terminal.
There is another way of logging to the console from inside the renderer process. Given this is Electron, you can access Node's native modules. This includes the console module.
var nodeConsole = require('console');
var myConsole = new nodeConsole.Console(process.stdout, process.stderr);
myConsole.log('Hello World!');
When this code is run from inside the renderer process, you will get Hello World! in the terminal you ran Electron from.
See https://nodejs.org/api/console.html for further documentation on the console module.
Yet another possibility is accessing the main process console using remote.getGlobal(name):
const con = require('electron').remote.getGlobal('console')
con.log('This will be output to the main process console.')
Adding to M. Damian's answer, here's how I set it up so I could access the main process's console from any renderer.
In your main app, add:
const electron = require('electron');
const app = electron.app;
const console = require('console');
...
app.console = new console.Console(process.stdout, process.stderr);
In any renderer, you can add:
const remote = require('electron').remote;
const app = remote.app;
...
app.console.log('This will output to the main process console.');
process.stdout.write('your output to command prompt console or node js ')
You can use the npm package electron-log https://www.npmjs.com/package/electron-log
It will log your error, warn, info, verbose, debug, silly outputs in your native os log.
var log = require('electron-log');
log.info('Hello, log');
log.error('Damn it, an error');
Sorry to raise an old thread but this is the top result for "how to output console.log to terminal" (or similar searches.
For anyone looking to gain a bit more control over what is output to the terminal you can use webContents.on('console-message') like so:
mainWindow.webContents.on('console-message', (event, level, message, line, sourceId) => {
console.log(message + " " +sourceId+" ("+line+")");
});
See:
webContents Documentation
webContents entry on BrowserWindow docs
This is a follow up to cscsandy5's answer for some addition information, info from here
process.stdout.write('your output to command prompt console or node js ')
This code works great for just outputting a simple debug message to the terminal window you launched the electron app from and is is what console.log is build on top of.
Here is an example snippet (based on tutorialspoint electon tutorial) of a jQuery script that will write hello to the terminal every time the button is pressed (warning: you need to add your own line breaks in the output strings!)
let $ = require('jquery')
var clicks = 0;
$(function() {
$('#countbtn').click(function() {
//output hello <<<<<<<<<<<<<<<<<<<<<<<
process.stdout.write('hello')
$('#click-counter').text(++clicks);
});
$('#click-counter').text(clicks);
});
This is what I use:
let mainWindow // main window reference, you should assign it below 'mainWindow = new BrowserWindow'
function log(...data) {
mainWindow.webContents.executeJavaScript("console.log('%cFROM MAIN', 'color: #800', '" + data + "');");
}
Example use (same as console.log):
log('Error', { msg: 'a common log message' })
log('hello')
Source: https://github.com/fuse-box/fuse-box-electron-seed/tree/master/src/main in the logger.js file, here you can see a real use case.
After some investigation, here my understanding:
Code
(1) main.js
const createPyProc = () => {
console.log('In createPyProc')
...
console.log('scriptStart=%s', scriptStart)
...
console.log('scriptOther=%s', scriptOther)
...
}
...
let mainWindow = null
const createWindow = () => {
mainWindow = new BrowserWindow(
{
width: 1024,
height: 768,
webPreferences: {
nodeIntegration: true,
}
}
)
mainWindow.loadURL(require('url').format({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file:',
slashes: true
}))
mainWindow.webContents.openDevTools()
mainWindow.on('closed', () => {
mainWindow = null
})
}
...
Note: which use openDevTools to opened Electron Dev Tools
(2) render.js
const zerorpc = require("zerorpc")
...
console.log("clientStart %d server is ready", PORT_START)
...
})
(3) render.js is called by: index.html
<!DOCTYPE html>
<html>
...
<script>
require('./renderer.js')
</script>
</html>
console.log
Output Logic
two process and its console.log output:
main process = NodeJS process = here Electron UI process
-> console.log in main.js will output log to here
render process
-> console.log in render.js will output log to here
Screenshot Example
DEBUG=Development mode
run ./node_modules/.bin/electron .
Production=Release mode = the xxx.app pacakged by eletron-builder
run /path_to_your_packaged_mac_app/xxx.app/Contents/MacOS/yourBinaryExecutable
added export ELECTRON_ENABLE_LOGGING=true, render.js console.log ALSO output to main process terminal
console.log() will work fine for debugging. As the electron is built on top of browser, it has DevTools support you can use devtools for debugging purpose. However, there is a hysterical behaviour of console.log() method. When you call the console.log() from main process of electron app, it will output to the terminal window from where you just launched the app and when you call it from renderer process it will output to the DevTools console.
Everything Alex Warren wrote is true. Important here is how Electron is started. If you use the standard script in the package.json file it will not work. To make console.log() work replace the old script with this new one.
Old one:
"scripts": {
"start": "electron ."
}
New one:
"scripts": {
"start": ".\\node_modules\\electron\\dist\\electron.exe ."
}
Now all console.log() calls are displayed in the terminal as well.
With this You can use developer tools of main Browser window to see logs
function logEverywhere(s) {
if (_debug === true) {
console.log(s);
// mainWindow is main browser window of your app
if (mainWindow && mainWindow.webContents) {
mainWindow.webContents.executeJavaScript(`console.log("${s}")`);
}
}
}
Example logEverywhere('test')
will output // test in console panel of main browser window's developer tools
You may need enhance this method to accept multiple args (You can done it with spread operator by es6)
Well, this is 2019 and I cant believe no one mentioned this trick in all the answers above.
Ok, so, how about logging directly to the browser console directly from the main?
I provided my answer here: https://stackoverflow.com/a/58913251/8764808
Take a look.
A project I'm working on was using electron-react-boilerplate. That has electron-devtools-installer#2.4.4, which somehow via cross-unzip causes a process to crash with Error: Exited with code 9 .
Upgrading to electron-devtools-installer#3.1.1, as proposed in electron-react-boilerplate#v2.0.0 resolved it so my console.log, console.error, etc statements worked as expected.
for log purpose, i would recommend you to use the electron-log package

Resources