Implementing an import statement in an Electron renderer script

After following the Electron typescript quick-start code structure by cloning the repo, everything worked fine. However, when attempting to split my code into multiple .ts files and import them into the renderer script, I encountered an issue. Upon adding

import { stuff } from "./otherfile.ts"
at the top of renderer.ts, I received an error message stating
Uncaught ReferenceError: exports is not defined
.

Further investigation revealed that the problem stemmed from the

"module": "commonjs"
setting in tsconfig. Changing this to esnext caused Electron to no longer load the preload script.

Has anyone successfully managed to make Electron and typescript work seamlessly, allowing for the use of import statements across multiple files?


Minimal reproducible example:

File structure:

/dist
/dist/main.js
/dist/preload.js
/dist/renderer.js
/dist/stuff.js
/src
/src/main.ts
/src/preload.ts
/src/renderer.ts
/src/stuff.ts
/index.html

/src/main.ts:

import { ipcMain } from "electron"; // Imports work fine in main
...

ipcMain.on(...

/src/preload.ts:

import { contextBridge, ipcRenderer} from "electron"; // Imports work fine in preload

contextBridge.exposeInMainWorld("my-api", { ....

/src/renderer.ts

import stuff from "./stuff.ts"; // Import fails in renderer (exports is not defined error)

/src/stuff.ts

const stuff = { ... };
export default stuff;

/index.html

<html>
   ...
   <script src="./dist/renderer.js"></script>
 </html>

ts.config:

  • If I change "module" to "es6", then preload.ts will not load.
  • If I leave "module" as "commonjs", then imports in renderer.ts do not work.

If I manually add var exports = {} in renderer.js after the TS compilation, I encounter a different error stating, "require is not defined".

Answer №1

Unfortunately, the configuration of the boilerplate does not allow for the use of import/require inside the renderer process.

In order to enable this functionality, you will need to either adjust the security settings in Electron or utilize a transpiler to ensure that the final code executed by the renderer does not contain import/require.

The issue has been confirmed in the electron-quick-start-typescript project, and you can refer to this file for more information.

// This file is necessary to run in the renderer process for the window
// Node.js APIs are not available unless nodeIntegration is set to true in webPreferences
// Use preload.js to enable features required in the renderer process

It's important to note that Node.js APIs, including import and require, are not accessible in this context.

Additionally, when accessing require, a polyfilled version is used if sandboxing is enabled - more details can be found here.

Your alternative, aside from modifying boilerplates, is to access necessary functionalities through the preload script like window.api.yourFunction.


If you require the use of import in the renderer process, consider using a boilerplate that supports transpilation via Webpack.

Two recommended options are:


PS - Avoid enabling nodeIntegration when distributing code to maintain Electron's security model!

PPS - Consider using

"module": "ES2020"
, but be aware that it does not address the limitation of import/require within the renderer process.

Answer №2

Summary

The error you're encountering seems to stem from mixing CommonJS (CJS) and ES modules (ESM), which are not compatible. Below is a functional example for an Election model, followed by a comparison of CJS and ESM exports and imports.

Solution

Without the code from your original post, I've outlined a potential solution for the issue. Follow these steps:

# Clone this repository
git clone https://github.com/electron/electron-quick-start-typescript
# Navigate to the repository
cd electron-quick-start-typescript
# Install dependencies
npm install

Create a new file as shown below:

// src/otherfile.ts
export const stuff = {
    fullOf: 'stuff'
}

export function shareLocation(loc: string): void {
    console.log(`I was imported by: ${loc}`);
}

In the src/main.ts file, make the following modifications:

// Add import statement at the top
import {stuff, shareLocation} from "./otherfile"

// Add this code at the end of the createWindow() function
shareLocation('main.ts (main.js)');

Run the example with npm start to see

I was imported by: main.ts (main.js)
in your terminal. If you try the same with src/preload.ts, you'll encounter an error due to sandboxing restrictions.

To resolve this, update your src/main.ts file as follows:

// Modify the webPreferences of the new BrowserWindow
const mainWindow = new BrowserWindow({
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, "preload.js"),
      sandbox: false // <=== ADD THIS LINE
    },
    width: 800,
});

Now, your imports should work correctly, displaying

I was imported by: preload.ts (preload.js)
in the electron window console, though be aware that this method is not secure. Consider using IPC instead of disabling the sandbox.

If you're still facing issues, you may be mistakenly using CJS exports with ESM imports. Refer to the next section for a demonstration of the differences between the two.

Comparison: CommonJS (CJS) vs ES modules (ESM)

CommonJS (CJS)

CommonJS was initially used for exporting and requiring modules in Node.js. Below is an example of how to export and import in a CommonJS module:

// example.js
function hello() {
    console.log('World!');
}
module.exports = hello;

And importing the module in another file:

// main.js
const hello = require('./example.js');
hello();

You can export various types using CommonJS, similar to ES modules. Here's an example exporting multiple functions:

// example.js
function hello() {
    console.log('World!');
}
function foo() {
    console.log('Bar!');
}
module.exports = {
    hello,
    foo
};

The same module can be imported as:

// main.js
const {hello, foo} = require('./example.js');
hello();
foo();

ES Module (ESM)

ES modules are the modern way of importing JavaScript code into other modules. An example using ESM is shown below:

// example.js
function hello() {
    console.log('World!');
}
export default hello;

You import the module as follows:

// main.js
import hello from "example";
hello();

If you choose to export multiple items without using default, you'll need to import them differently:

// example.js
function hello() {
    console.log('World!');
}
function foo() {
    console.log('Bar!');
}
export {
    hello,
    foo
}

And import them using object destructuring:

// main.js
import {hello, foo} from "example";
hello();
foo();

For more information on imports, refer to MDN documentation on import and this Free Code Camp Article on JavaScript modules. Additionally, JavaScript Tutorial offers a helpful guide on object destructuring.

Answer №3

While Electron isn't fully compatible with ECMAScript modules at this time, your Typescript code can still be very cutting-edge.

OPTION 1 (LOOSE FILES)

To quickly kickstart your renderer process's Typescript code, add the following to your index.html file, similar to this initial sample. Note that this method utilizes node integration, which should be disabled before deploying to production:

<body>
   <div id='root' class='container'></div>

   <script type='text/javascript'>
        require('./built/renderer');
   </script>
</body>

OPTION 2 (BUNDLED)

To eliminate the require statement and align more with Electron security guidelines for SPAs, consider utilizing webpack bundling and referencing built Typescript in the following manner, similar to this updated sample:

<body>
    <div id='root' class='container'></div>

    <script type='module' src='vendor.bundle.js'></script>
    <script type='module' src='app.bundle.js'></script>
</body>

SETTINGS

In my setup, I specify type=commonjs in package.json and module=commonjs in the tsconfig.json file specifically for Electron projects. For non-Electron projects, I opt for ECMAScript module settings for slightly improved output.

The key is to establish a modern code setup that aligns with your preferences, rather than being confined to preset starter projects.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the process for expanding types for a section element in Typescript?

When working with TypeScript, we define our component props types by extending a div like so: import { HTMLAttributes } from "react"; export interface IContainerProps extends HTMLAttributes<HTMLDivElement> { // Additional custom props for the c ...

implementing ng-grid to showcase json information within an angularjs application

Recently I started learning Angularjs and I am looking to showcase the JSON data retrieved from a webservice response in a grid format using ng-grid. Here is my code snippet: function TestController($scope, $http) { alert("test"); $http({ url: &apos ...

Could you explain the concept of implodeQuery to me?

While browsing through the source page of Facebook, I came across a function that is commonly used. The specific line of code was: input_len=URI.implodeQuery(this.data).length I am having trouble understanding what this line of code means and what exactl ...

Simplest method for defining an associative array

Looking to create an array in the format shown below: [0: 0, 1: 1, 2: 2] This is achieved using the following code snippet: var arr = []; for(i=0; i<3; i++){ arr[i] = i; } Upon execution, my array appears as follows: [0, 1, 2] The values with ...

What is the best way to input individual students' CA and exam scores into distinct fields and then calculate their total scores in a separate text field?

As a beginner in jQuery, I am looking for guidance on creating a script that calculates the Total score, Grade, Remark, and Position based on the user input of CAT score and EXAM score. The result should be displayed dynamically once both scores are entere ...

Require modification of JSON values in React Promise code

Looking to modify the data returned from a promise and wrap a link around one of the fields. Here is the React code: this.state = { medications: [], } queryMeds().then((response) => { this.setState({medications: response}); }); The response c ...

JavaScript's blank identifier

One interesting feature in Golang is the use of the _ (Blank Identifier). myValue, _, _ := myFunction() This allows you to ignore the 2nd and 3rd return values of a function. Can this same concept be applied in JavaScript? function myFunction() { re ...

Issue with child routes not functioning properly within single spa React application

{ "name": "@my-react-app/demo", "scripts": { "start": "webpack serve", "start:standalone": "webpack serve --env standalone", "build": "concurrently npm:build:*", "build:webpack": "webpack --mode=production", "analyze": "webpack --mo ...

Creating an original list by iterating through a given list

I understand that some may consider this a duplicate, but I have yet to come across a similar example that I can relate to with my limited knowledge. So, I apologize in advance :) This should be pretty simple for an experienced JS developer, I assume. My ...

Set the position of a div element to be fixed

I am currently working on a straightforward application that requires implementing a parallax effect. Here are the methods I have experimented with so far: - Inserting an image within a div with the class parallax. - Subsequently, adding an overlay div ...

JavaScript error: Undefined variable or function

I'm facing an issue with the following code. When I position the brace in different places, I encounter errors like "var not defined" or "function not defined". My goal is to convert an array into a string so that I can analyze the data and decide how ...

"Trouble with script: external JavaScript file failing to run

The issue lies in the fact that the specified external JavaScript file is not being executed: <script src="//www.google-analytics.com/cx/api.js?experiment=YOUR_EXPERIMENT_ID"></script> Even when I try to directly access the URL, it downloads ...

What is the method for selecting the desired month on a primeng calendar with multiple selection enabled?

I am looking for a solution to make my inline primeNg Calendar display the month of a specific date in the model, and when I remove dates from the model, I want it to show the current month. I have tried using defaultDate={{value}} and minDate={{value}}, a ...

Trouble with populating Ext.grid.Panel from ExtJS4 JSON data

Despite researching various posts on the topic, I am still facing issues. Even after attempting to create a Panel with minimal data, I cannot seem to make it work. This problem is really puzzling me. Below is the code snippet that I am currently working wi ...

Encountering issues with Typescript Intellisense not functioning properly with root directory imports specified with the @

I am facing a challenge where TypeScript/Intellisense is unable to determine types when importing using the @ symbol with my compilerOptions set to the root directory: https://i.sstatic.net/1PgBI.png When I use ../, types are visible clearly: https://i. ...

Using jQuery to extract a href URL from a div tag with jQuery

Is it possible to extract the href urls from anchor tags within a div tag? <div id="testing"> <a onclick="http://google.com">google</a> <a href="http://facebook.com">facebook</a> <a onclick="http://gmail.com">gmail< ...

Can I obtain a dictionary containing the connections between labels and phrases from the classifier?

I've implemented a LogisticRegressionClassifier using the natural library for node: const natural = require('natural'); const classifier = new natural.LogisticRegressionClassifier(); classifier.addDocument('category1', 'sent ...

Angular: Issue encountered while attempting to differentiate an '[object Object]'. Arrays and iterables are the only permissible types for this operation

I encountered the following error message while attempting to retrieve updated data: Error trying to diff '[object Object]'. Only arrays and iterables are allowed Snippet of Get Code: allDatas allData(data) { this.allDatas = data } Up ...

Struggling to access values from your model using EL in JavaScript? Let me provide some guidance

In my model, there is an object named "domain" with two methods: getDescriptionEn() and getDescriptionFr(). I am trying to retrieve the appropriate description based on the current locale. My issue lies in the following code snippet: var locale = "${cur ...

Quoting Properties in Javascript Objects

If we have the object below: var ObjectName = { propertyOne: 1, propertyTwo: 2 } Do we need to include quotes around the object properties like this? var ObjectName = { 'propertyOne': 1, 'propertyTwo': 2 } Is the sol ...