How to use remote.require() in Electron, using TypeScript - node.js

Currently, I'm trying to use the opencv4nodejs module within an Electron/Angular application that also uses TypeScript. I've tried several ways to do this. The following codeblock shows what I tried and what error message I get.
// This says cv is undefined:
const cv = window.require('electron').opencv4nodejs;
const img = cv.imread('../assets/poop.jpg');
// Same here:
const cv = window.require('electron').remote.opencv4nodejs;
const img = cv.imread('../assets/poop.jpg');
// Uncaught Error: The specified module could not be found. (Though the module does exist at that location)
const cv = window.require('electron').remote.require('opencv4nodejs');
const img = cv.imread('../assets/poop.jpg');
// Without window I get "Uncaught TypeError: fs.existsSync is not a function"
const remote = require('electron').remote;
const cv = remote.opencv4nodejs;
const img = cv.imread('../assets/poop.jpg');
I had the fs.existSync error before, trying to require something else. I fixed that by using the following tsconfig.app.json:
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"baseUrl": "./",
"module": "es2015",
"types": ["node"] // Included this line
},
"exclude": [
"test.ts",
"**/*.spec.ts"
]
}
As far as I understand, the remote require is needed to load modules that normally only run on the node.js server. Still I can't seem to figure out how to require the module in my application. The author of the module was very helpful with build problems and other problems, but he never used his module together with TypeScript.
How do I remote require a module in a TypeScript/Angular/Electron based application?
[edit]
I also tried the following:
import { Injectable } from '#angular/core';
// If you import a module but never use any of the imported values other than as TypeScript types,
// the resulting javascript file will look as if you never imported the module at all.
import { ipcRenderer } from 'electron';
import * as childProcess from 'child_process';
#Injectable()
export class ElectronService {
ipcRenderer: typeof ipcRenderer;
childProcess: typeof childProcess;
constructor() {
// Conditional imports
if (this.isElectron()) {
this.ipcRenderer = window.require('electron').ipcRenderer;
this.childProcess = window.require('child_process');
}
}
isElectron = () => {
return window && window.process && window.process.type;
}
require = (module: string) => {
return window.require('electron').remote.require(module);
}
}
Injecting this service into my component and calling electronService.require('opencv4nodejs') also did not work.

Since Electron v1.6.10, Electron ships with TypeScript definitions included.
In order to use remote via TypeScript, you can use the following import statement:
import {remote} from 'electron';

Related

Cannot use import after adding Typescript

So I'm trying to implement typescript to an existing project.
However, I came to a stop, where I get an error of: SyntaxError: Cannot use import statement outside a module
Here, is my helper class, which is omitted. However, you can see that I am using an import, rather than require
index.ts
// const axios = require('axios');
// const {includes, findIndex} = require('lodash');
// const fs = require('fs');
import { includes, findIndex } from "lodash";
import fs from 'fs';
type storeType = {
[key: string]: string | boolean
}
class CMS {
_store;
constructor(store: storeType) {
this._store = store;
<omitted code>
export default CMS;
}
Than, I import index.ts file to server.js file:
const { CMS, getCookie, checkLang, getLangByDomain, handleRoutes } = require('./src/utils/cms/index.ts');
Unfortunately, when I start the server, I get an error of: SyntaxError: Cannot use import statement outside a module
I am using a default tsconfig.json which has been generated after creating file and running dev environment.
Edit your tsconfig.json and change "module": "esnext" to "module": "commonjs".
This is ES type modules:
import { includes, findIndex } from "lodash";
import fs from 'fs';
But this is commonJs type:
const { CMS, getCookie, checkLang, getLangByDomain, handleRoutes } =
require('./src/utils/cms/index.ts');
I think that's the problem. You should use one type of modules.
Try to rewrite this const { CMS, getCookie, checkLang, getLangByDomain, handleRoutes } = require('./src/utils/cms/index.ts'); to this import { CMS, getCookie, checkLang, getLangByDomain, handleRoutes } from './src/utils/cms/index.ts'
Or opposite rewrite ES to commonJs, but don't forget to change type in tsconfig
You cannot explicitly import typescript files into Javascript files. Instead, you need to use the compiled typescript files(i.e. Javascript files in outDir folder).
So assume you compiled your typescript files into Javascript, then it would be converted to outDir/index.js. After that, you could directly import it into server.js
const { CMS, getCookie, checkLang, getLangByDomain, handleRoutes } =
require('./path/to/index.js'); // You cannot require a ts file.
If the typescript files and Javascript files are part of the same project, then you need to transpile the js files alongside the ts as well. In order to achieve this, you need to set allowJs to true in tsconfig.
{
"compilerOptions": {
...
"allowJs": true,
}
}
Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files.

ASP.NET 5 (Core) Website with TypeScript and Node (or ESM) modules in Visual Studio [not Code] 2019?

So I have followed this guide to setup TypeScript in a ASP.NET 5 website project (with Razor Page). I want to have TypeScript typings with Node modules instead of just importing as any.
Now I want to add a Node module, like EthersJS, so I have:
"dependencies": {
"ethers": "^5.4.1"
}
This code would not compile:
import { ethers } from "ethers";
export class EthService {
pro: ethers.providers.BaseProvider;
constructor() {
const pro = this.pro = new ethers.providers.WebSocketProvider("");
}
async getBlockNumberAsync() {
return await this.pro.getBlockNumber();
}
}
unless I add "moduleResolution": "Node" to tsconfig.json. Obviously this wouldn't actually run in a browser.
How should I set it up so the Node module get compiled somehow? I think the problem can be solved if I can do either of these:
Make TypeScript/Gulp/MSBuild change import { ethers } from "ethers"; into proper path (I can copy the compiled lib file manually into wwwroot).
Manually set import path to import { ethers } from "path/to/compiled/ethers.js";, but I need to somehow tell TypeScript that that ethers typing is from ethers Node module.
UPDATE: I think a third possibility is very nice as well if I can just have a declare import (I will just add the ESM file into the global scope) like this:
declare import { ethers } from "ethers";
Is any of the above option possible? Or is there any way? Thanks.
Note: I know there is Triple-Slash Directives and it may solve my problem but I don't really understand what they do yet.
The current gulpfile.js:
/// <binding AfterBuild='default' Clean='clean' />
/*
This file is the main entry point for defining Gulp tasks and using Gulp plugins.
Click here to learn more. http://go.microsoft.com/fwlink/?LinkId=518007
*/
var gulp = require("gulp");
var del = require("del");
var paths = {
scripts: ["Scripts/**/*.js", "Scripts/**/*.ts", "Scripts/**/*.map"],
};
gulp.task("clean", function () {
return del(["wwwroot/Scripts/**/*"]);
});
gulp.task("default", function () {
gulp.src(paths.scripts).pipe(gulp.dest("wwwroot/Scripts"));
});
tsconfig.json:
{
"compilerOptions": {
"noImplicitAny": true,
"noEmitOnError": true,
"removeComments": false,
"sourceMap": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Node"
},
"exclude": [
"wwwroot",
"node_modules"
],
"compileOnSave": true
}
I am able to solve the problem by using a standalone ethers.js script file to expose ethers namespace to the global scope:
<script defer src="/libs/ethers.umd.min.js" asp-append-version="true"></script>
Now you need to tell TypeScript that there is an ethers "thing" (namespace/module) in the global scope. I found a solution thanks to this article. Create a .d.ts file (any will work, I name it globals.d.ts):
import { ethers as eth } from "ethers";
declare global {
// #ts-ignore: export typing
export { eth as ethers };
}
Now you can use ethers anywhere without needing any declaration. For example my whole EthService.ts file:
export class EthService {
pro: ethers.providers.WebSocketProvider;
init(server: string) {
this.pro = new ethers.providers.WebSocketProvider(server);
}
async getBlockNoAsync() {
return await this.pro.getBlockNumber();
}
}

Cannot redeclare exported variable ''

I have a npm module in another directory that I'm using in another project using npm link, however when I import the module and try to use the function I get the following error a bunch of errors even though the typescript compiled successfully. Here is my tsconfig for the npm module:
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"declaration": true,
"outDir": "./lib",
"strict": true,
},
"include": ["src"],
"exclude": ["node_modules"]
}
Here is the index.ts of the module:
import * as APIServices from "./API/API"
import APIPage from "./API/APIPage"
export { APIServices, APIPage }
And here is how I'm trying to use the package:
import APIServices from 'common-backend'
console.log(APIServices)
But when I run the file I get the following errors:
TSError: тип Unable to compile TypeScript:
../../common-backend/lib/index.js:3:1 - error TS2323: Cannot redeclare exported variable 'APIPage'.
3 exports.APIPage = exports.APIServices = void 0;
~~~~~~~~~~~~~~~
../../common-backend/lib/index.js:3:19 - error TS2323: Cannot redeclare exported variable 'APIServices'.
3 exports.APIPage = exports.APIServices = void 0;
~~~~~~~~~~~~~~~~~~~
../../common-backend/lib/index.js:5:1 - error TS2323: Cannot redeclare exported variable 'APIServices'.
5 exports.APIServices = APIServices;
~~~~~~~~~~~~~~~~~~~
../../common-backend/lib/index.js:7:1 - error TS2323: Cannot redeclare exported variable 'APIPage'.
7 exports.APIPage = APIPage_1.default;
Finally this is the index.js that the errors are being thrown on:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.APIPage = exports.APIServices = void 0;
var APIServices = require("./API/API");
exports.APIServices = APIServices;
var APIPage_1 = require("./API/APIPage");
exports.APIPage = APIPage_1.default;
Is there something that I forgot to add in the tsconfig? Is this the fault of the compiler transpiling the typescript incorrectly? Or does it have to do something with the way I'm import and exporting the files? I've been scratching my head for a while on this one and nobody else seems to have had the same issue.
Edit:
Forgot to include the API class I'm trying to import:
import { Validators } from "./Validators"
import { APIRoute, Config } from "./helpers/Interface"
import { Router } from "express"
const router = Router()
class API {
private config: Config
private _routes: APIRoute[]
constructor(config: Config) {
this.config = config
this._routes = []
}
get routes() : APIRoute[] {
return this._routes
}
set routes(routes: APIRoute[]) {
this._routes = [...routes]
}
/**
* Add route to routes array
* #param route
*/
addRoute(route: APIRoute) : void {
this.routes.push(route)
}
/**
* Instatiates existing routes in the routes array
*/
loadRoutes() : void {
for(const route of this._routes) {
try {
new route.module(router, route, this.config.authFunction)
}
catch(error) {
console.error(route)
console.error(error)
}
}
}
}
export { API, Validators, router }
I don't think will solve all your problems but your imports seem off. There are two ways of handling imports.
export default APIServices
And then you can import like you have
import APIServices from 'common-backend'
Whereas if you want to export multiple named things like you have
export { APIServices, APIPage }
You need to import them one of two ways
import { APIServices } from 'common-backend'
Or I think you can do something like this but I am less familiar with it because I personally don't like how it looks (I much rather just import what I need)
import * as CommonBackend from 'common-backend'
// then use CommonBackend.APIServices
So it turns out the issue was that I was using ts-node to compile and run the code in the project using the package. I changed node --inspect -r ts-node/register src/app.ts" to simply node ./dist/src/app.js and that solved everything. Although not sure what underlying elements were causing the javascript not to be run properly.

Prerendering causes a SyntaxError: Cannot use import statement outside a module

I'm trying to execute prerender.ts as seen here to prerender my Angular code, but when I try and execute it using ts-node prerender.ts, I get the error:
import 'zone.js/dist/zone-node';
^^^^^^
SyntaxError: Cannot use import statement outside a module
at Module._compile (internal/modules/cjs/loader.js:892:18)
What is the proper way to execute this from NodeJS? Here is what prerender.ts looks like:
import 'zone.js/dist/zone-node';
import * as path from 'path';
import * as fs from 'fs';
import { enableProdMode } from '#angular/core';
import { renderModuleFactory } from '#angular/platform-server';
import { AppPrerenderModuleNgFactory } from './dist-prerender/main.bundle';
const distFolder = './dist';
const index = fs
.readFileSync(path.resolve(__dirname, `${distFolder}/index.html`), 'utf8')
.toString();
// we could automate this based on the app.routes.ts file but
// to keep it simple let's just create an array with the routes we want
// to prerender
const paths = [
'/about',
'/brews',
'/consultancy'];
enableProdMode();
// for every route render the html and save it in the correct folder
paths.forEach(p => renderToHtml(p, distFolder + p));
// don't forget to overwrite the index.html as well
renderToHtml('/index.html', distFolder);
function renderToHtml(url: string, folderPath: string): void {
// Render the module with the correct url just
// as the server would do
renderModuleFactory(AppPrerenderModuleNgFactory, {
url,
document: index
}).then(html => {
// create the route directory
if (url !== '/index.html') {
fs.mkdirSync(folderPath);
}
fs.writeFile(folderPath + '/index.html', html, (err => {
if (err) {
throw err;
}
console.log(`success`);
});
});
}
Update: I found that if I used tsc to transpile prerender.ts to JavaScript first and then executed that with node, I could get past this error. However, I started getting an error which I think is indicative of this code not running within the context of ngZone. So the code is still not right.
As stated here:
Current node.js stable releases do not support ES modules. Additionally, ts-node does not have the required hooks into node.js to support ES modules. You will need to set "module": "commonjs" in your tsconfig.json for your code to work.
Thus, pass below compiler option:
ts-node --compiler-options '{"module": "commonjs"}' prerender.ts
Of course, you can just include "module": "commonjs" in your (root) tsconfig.json file under "compilerOptions". This way you only have to execute:
ts-node prerender.ts

Writing a Node module in TypeScript for consumption by both TS and JS projects

I have an Express middleware project written in TypeScript. I'd like to consume it in both JS & TS based Node projects.
I'm having trouble configuring my projects to ensure that
the upstream project is outputting modules that can be consumed by Node
my module can be consumed in JS projects in the format myGreatFunction = require('myGreatFunction') // typeof = function
my module can be consumed in the format import myGreatFunction from 'myGreatFunction' // typeof = function
my module is not being either output as an object with a .default when that is not expected, or, vice-versa, not being done so when that is indeed expected.
It feels as though I can only achieve some of these aims but not others.
What is the correct incantation of TSConfig properties (upstream & downstream) to ensure this is so?
In the end I settled on a compromise - see below.
Library
tsconfig.json:
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"noEmitHelpers": true
},
}
module.ts:
export class MyClass {
static Version: string = "1.0";
}
When we compile this module we'll get:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var MyClass = /** #class */ (function () {
function MyClass() {
}
MyClass.Version = "1.0";
return MyClass;
}());
exports.MyClass = MyClass;
TS Client
client.ts:
import {MyClass} from "./../src/module";
console.log(MyClass.Version);
compile and run node client.js - see "1.0"
JS Client
Just grad the same code from compiled ts client :
var module_1 = require("./../src/module");
console.log(module_1.MyClass.Version);
same output obviously
Using a typescript file in typescript.
Assuming B.ts is the typescript file that you want to use in A.ts, then:
import { B's module that are exported } from 'path/to/B'; // notice there is no extension mentioned here.
Also B must have a export module in it.
Using a ts file in a js file.
B.ts = file which you want to use in your main file.
A.js = your main file.
In your A.js:
var external = require('path/to/B'); // notice there is no extension mentioned here.
In the end I settled on a compromise:
Library TSConfig:
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"declaration": true
}
}
Library:
export = function myGroovyFunction() {...}
Downstream project, TypeScript
import * as myGroovyFunction from 'mygroovyfunction';
Downstream project, JavaScript
const myGroovyFunction = require('mygroovyfunction');
The TypeScript import isn't quite as concise as I'd like, but I can deal.

Resources