Cannot redeclare exported variable '' - node.js

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.

Related

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();
}
}

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

Why converting ESM to CommonJS is not simple find and replace?

I've been working on a TypeScript project (for NodeJs environment) so I've been using ES module syntax for imports and exports. Using TSC with "module": "commonjs", "esModuleInterop": true, there is a lot of boilerplate code created such as:
var __importStar = (this && this.__importStar) || function (mod) {
// omitted
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = require("path"); // renamed identifier
const pug_1 = __importDefault(require("./template/pug"));
pug_1.default(...) // use of .default
So my question is, why we cannot simply convert ESM import/exports to plain require calls for NodeJs and why this boilerplate code and identifier renaming are needed?
For example, why following conversions cannot be done by a simple find-and-replace (with regex or some parsing):
import * as path from "path";
// const path = require("path");
import { resolve } from "path";
// const { resolve } = require("path");
export default class MyClass {...}
// module.exports = class MyClass {...}
export class MyClass {...}
// module.exports.MyClass = class MyClass {...}
It's not a 1:1 mapping. For example, using the ES6 import/export syntax, you can export multiple symbols and a default symbol. To do the same thing in CommonJS, you'd need to start nesting the exported objects into other exported objects which could cause problems.

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.

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

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';

Resources